mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-07-17 05:05:16 +03:00
8cae0648ec
Demand-driven expansion targeting the fastest-growing 2025-2026 threat and
skills categories (ISC2/WEF/CrowdStrike/Mandiant signals):
- AI Security (NEW domain, 12 skills): LLM red-teaming with garak/PyRIT,
prompt injection (direct/indirect/RAG), MCP tool-poisoning, agentic tool
invocation, guardrails, model/data poisoning, system-prompt leakage,
embedding/vector weaknesses, model extraction, continuous red-teaming
- Supply Chain Security (NEW domain, 5 skills): SBOMs, dependency confusion,
malicious-npm triage, typosquatting, SLSA/Sigstore provenance
- Hardware & Firmware Security (NEW domain, 4 skills): CHIPSEC/UEFI audit,
Secure Boot bypass, TPM measured-boot attestation, ESP bootkit hunting
- Identity (10): Entra ID/ROADtools, GraphRunner, AADInternals, ADCS/Certipy,
shadow credentials, coercion, BloodHound CE, device-code phishing, SSO abuse
- Cloud-native (8): Stratus, Pacu, CloudFox, container escape, K8s RBAC,
Falco, Trivy, kube-bench
- Offensive C2 (6): Sliver, Havoc, NetExec, DPAPI, NTLM relay ESC8, redirectors
- DFIR (6): Hayabusa, Chainsaw, KAPE, Velociraptor, EZ Tools, Plaso
- Backfill (4): OpenCTI, MISP, honeytokens, post-quantum crypto migration
Each skill follows the repo taxonomy (SKILL.md + references/{standards,api-reference}.md
+ scripts/agent.py + LICENSE), with researched real tool commands (no placeholders),
complete frontmatter, and ATT&CK/ATLAS + NIST CSF mappings. Updates README domain
table, skill count, and index.json.
128 lines
4.7 KiB
Python
128 lines
4.7 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
agent.py - BloodHound CE ingestion + Cypher helper.
|
|
|
|
Authenticates to a BloodHound Community Edition server's REST API, uploads a
|
|
SharpHound ZIP or AzureHound JSON file via the file-upload job workflow, and runs
|
|
Cypher queries (e.g. shortest path from owned principals to Domain Admins).
|
|
|
|
AUTHORIZED USE ONLY. BloodHound reveals privilege-escalation paths to full domain
|
|
/ tenant compromise. Use only against environments you are authorized to assess.
|
|
|
|
References:
|
|
- BloodHound CE https://github.com/SpecterOps/BloodHound
|
|
- CE API docs https://bloodhound.specterops.io/
|
|
"""
|
|
import argparse
|
|
import json
|
|
import os
|
|
import sys
|
|
import time
|
|
import urllib.request
|
|
import urllib.error
|
|
|
|
DA_SHORTEST_PATH = (
|
|
'MATCH p=shortestPath((n {owned:true})-[*1..]->(g:Group)) '
|
|
'WHERE g.objectid ENDS WITH "-512" RETURN p'
|
|
)
|
|
|
|
|
|
def _req(method, url, token=None, body=None, content_type="application/json", raw=False):
|
|
headers = {}
|
|
if token:
|
|
headers["Authorization"] = f"Bearer {token}"
|
|
data = None
|
|
if body is not None:
|
|
if raw:
|
|
data = body
|
|
headers["Content-Type"] = content_type
|
|
else:
|
|
data = json.dumps(body).encode()
|
|
headers["Content-Type"] = "application/json"
|
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
|
try:
|
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
|
text = resp.read().decode()
|
|
return json.loads(text) if text.strip() else {}
|
|
except urllib.error.HTTPError as e:
|
|
msg = e.read().decode(errors="replace")
|
|
raise SystemExit(f"[!] API {method} {url} -> HTTP {e.code}: {msg}")
|
|
except urllib.error.URLError as e:
|
|
raise SystemExit(f"[!] cannot reach {url}: {e.reason}")
|
|
|
|
|
|
def login(base, username, secret):
|
|
r = _req("POST", f"{base}/api/v2/login", body={
|
|
"login_method": "secret", "username": username, "secret": secret})
|
|
token = r.get("data", {}).get("session_token")
|
|
if not token:
|
|
raise SystemExit("[!] login failed: no session_token returned")
|
|
print("[+] authenticated to BloodHound CE")
|
|
return token
|
|
|
|
|
|
def upload(base, token, path):
|
|
if not os.path.isfile(path):
|
|
raise SystemExit(f"[!] file not found: {path}")
|
|
job = _req("POST", f"{base}/api/v2/file-upload/start", token=token)
|
|
job_id = job.get("data", {}).get("id")
|
|
if job_id is None:
|
|
raise SystemExit("[!] could not start upload job")
|
|
ctype = "application/zip" if path.lower().endswith(".zip") else "application/json"
|
|
with open(path, "rb") as fh:
|
|
_req("PUT", f"{base}/api/v2/file-upload/{job_id}", token=token,
|
|
body=fh.read(), content_type=ctype, raw=True)
|
|
_req("POST", f"{base}/api/v2/file-upload/{job_id}/end", token=token)
|
|
print(f"[+] uploaded {os.path.basename(path)} (job {job_id}); ingestion queued")
|
|
return job_id
|
|
|
|
|
|
def run_cypher(base, token, query):
|
|
r = _req("POST", f"{base}/api/v2/graphs/cypher", token=token,
|
|
body={"query": query, "include_properties": True})
|
|
nodes = r.get("data", {}).get("nodes", {})
|
|
edges = r.get("data", {}).get("edges", [])
|
|
print(f"[+] query returned {len(nodes)} nodes / {len(edges)} edges")
|
|
for nid, node in list(nodes.items())[:50]:
|
|
label = node.get("label") or node.get("objectId") or nid
|
|
print(f" - {node.get('kind','?')}: {label}")
|
|
return r
|
|
|
|
|
|
def main():
|
|
p = argparse.ArgumentParser(description="BloodHound CE ingest + Cypher helper.")
|
|
p.add_argument("--url", default="http://localhost:8080", help="BloodHound CE base URL")
|
|
p.add_argument("--user", default="admin", help="Admin username")
|
|
p.add_argument("--secret", default=os.environ.get("BHE_SECRET"),
|
|
help="Admin password (or set BHE_SECRET)")
|
|
p.add_argument("--upload", action="append", default=[],
|
|
help="Collector file to ingest (ZIP or JSON); repeatable")
|
|
p.add_argument("--cypher", help="Cypher query to run")
|
|
p.add_argument("--da-path", action="store_true",
|
|
help="Run the built-in owned->Domain Admins shortest-path query")
|
|
args = p.parse_args()
|
|
|
|
if not args.secret:
|
|
raise SystemExit("[!] provide --secret or set BHE_SECRET")
|
|
|
|
base = args.url.rstrip("/")
|
|
token = login(base, args.user, args.secret)
|
|
|
|
for f in args.upload:
|
|
upload(base, token, f)
|
|
if args.upload:
|
|
print("[i] waiting 10s for ingestion to begin...")
|
|
time.sleep(10)
|
|
|
|
if args.da_path:
|
|
run_cypher(base, token, DA_SHORTEST_PATH)
|
|
if args.cypher:
|
|
run_cypher(base, token, args.cypher)
|
|
if not (args.upload or args.cypher or args.da_path):
|
|
print("[i] nothing to do; pass --upload, --cypher, or --da-path")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|