npx skills add mukul975/Anthropic-Cybersecurity-SkillsMITRE ATT&CK
NIST CSF 2.0
Authorized Use Only: Deception assets described here are defensive controls deployed inside your own environment. Deploying tokens, decoy credentials, or honeypots on infrastructure you do not own or administer, or using them to entrap third parties, may violate computer-misuse and privacy law. Deploy only on assets you own or are explicitly authorized to instrument, and route all alert data through approved monitoring channels.
Overview
Honeytokens (a.k.a. canarytokens) are decoy artifacts — credentials, files, URLs, API keys, DNS names, database connection strings, documents — that have no legitimate operational use. Because no authorized user or process should ever touch them, any interaction is a high-fidelity signal of an intrusion, insider misuse, or reconnaissance. Unlike signature- or anomaly-based detection, honeytokens generate near-zero false positives: the alert is the compromise.
Thinkst's open-source Canarytokens project (https://canarytokens.org and the self-hostable thinkst/canarytokens-docker) generates dozens of token types that "phone home" when triggered: an HTTP/web-bug URL that fires on GET, an AWS API key that fires when used against AWS, an MS Word/PDF document that fires on open, a DNS token that fires on resolution, a Slack API token, a Kubernetes kubeconfig, an Azure login certificate, a log4shell payload, and more. Each token is bound to a unique memo (so you know where it was planted) plus an alert channel (email and/or webhook).
This skill maps to MITRE D3FEND's Decoy File (D3-DF), Decoy User Credential (D3-DUC), and Honeytoken techniques. From an ATT&CK perspective, a triggered honey credential most commonly evidences adversary attempts to abuse or modify authentication material (T1556 – Modify Authentication Process and related credential-access activity), giving the SOC an early, unambiguous tripwire deep inside the kill chain — typically after initial access but before lateral movement completes.
When to Use
- When you need high-fidelity intrusion detection in segments where traditional telemetry is sparse (file shares, password vaults, code repos, cloud accounts).
- When validating that an attacker who reaches a "crown-jewel" host or document store is detected, not just blocked at the perimeter.
- When seeding decoy credentials into LSASS-reachable memory, browser stores,
.aws/credentials, or password managers to catch credential dumping and reuse. - When instrumenting documents, repos, or wikis to catch data theft and ransomware staging.
- When building a MITRE D3FEND-aligned deception layer as part of a defense-in-depth or zero-trust program.
Prerequisites
- Docker Engine and Docker Compose v2 for self-hosting (
docker compose version). - A registered domain you control plus DNS delegation for DNS-based tokens (NS records pointing at your switchboard host).
- A public IPv4 address reachable on 80/443 (HTTP tokens) and 53/udp (DNS tokens).
- An SMTP relay or Mailgun account, and/or a Slack/Teams/generic webhook URL for alert delivery.
- Python 3.8+ for the helper script:
python3 -m pip install requests - For quick use with no hosting, an account-free token from the public service at https://canarytokens.org.
Objectives
- Stand up a self-hosted Canarytokens instance (or use the public service) with working alerting.
- Generate the major token types (HTTP, DNS, AWS key, MS Word/PDF, Slack, kubeconfig) with descriptive memos.
- Plant decoy credentials and decoy files in realistic, monitored locations.
- Validate that each token fires and that alerts reach the SOC channel.
- Catalogue deployed tokens and map them to MITRE D3FEND/ATT&CK for coverage tracking.
MITRE ATT&CK Mapping
| ID | Official Technique Name | Relevance |
|---|---|---|
| T1556 | Modify Authentication Process | A triggered honey credential reveals an adversary harvesting/abusing authentication material; the decoy provides a detection tripwire for credential abuse activity. |
Related MITRE D3FEND defensive techniques (the offensive counter-mapping for this control):
| D3FEND ID | Technique | Role |
|---|---|---|
| D3-DF | Decoy File | Canary documents, fake configs, decoy archives placed and monitored. |
| D3-DUC | Decoy User Credential | Honey credentials (AWS keys, AD accounts, kubeconfig) integrated with a monitored decoy asset. |
| D3-DO | Decoy Object | Umbrella for honeytokens/canarytokens as monitored decoy artifacts. |
Workflow
1. Deploy a self-hosted Canarytokens switchboard
Clone the official Docker repo and create the two environment files from their distributed templates:
git clone https://github.com/thinkst/canarytokens-docker
cd canarytokens-docker
cp switchboard.env.dist switchboard.env
cp frontend.env.dist frontend.envSet the core variables. In frontend.env:
CANARY_DOMAINS=canary.example.com # general-purpose token domains (comma-separated)
CANARY_NXDOMAINS=nx.example.com # domains reserved for PDF/DNS tokens
CANARY_PUBLIC_IP=203.0.113.10 # public IP of this hostIn switchboard.env:
CANARY_PUBLIC_DOMAIN=canary.example.com
CANARY_MAILGUN_DOMAIN_NAME=mg.example.com
CANARY_MAILGUN_API_KEY=key-xxxxxxxxxxxxxxxx
CANARY_ALERT_EMAIL_FROM_ADDRESS=canary@example.com
CANARY_ALERT_EMAIL_FROM_DISPLAY=Canarytokens
CANARY_ALERT_EMAIL_SUBJECT=Canarytoken Triggered
# WireGuard token seed:
# dd bs=32 count=1 if=/dev/urandom 2>/dev/null | base64
CANARY_WG_PRIVATE_KEY_SEED=<base64-seed>2. Bring the stack up
docker compose up -d
# HTTPS with automatic Let's Encrypt certs (after editing certbot.env):
# docker compose -f docker-compose-letsencrypt.yml up -d
docker compose ps
docker compose logs -f frontendThe frontend (token generator UI) is now served on your CANARY_PUBLIC_DOMAIN; the switchboard listens on 80/443 and 53/udp to receive triggers.
3. Generate an HTTP (web-bug) token via the API
Both the public service and a self-hosted frontend expose a POST /generate endpoint. Create an HTTP token that fires on any GET:
curl -s https://canarytokens.org/generate \
-F 'type=http' \
-F 'email=soc@example.com' \
-F 'memo=Internal wiki - IT admin passwords page' \
-F 'webhook_url=https://hooks.slack.com/services/T000/B000/XXXX'
# Response JSON includes: token, auth, hostname, url, url_componentsThe returned url is the trip-wire link; place it where only an intruder would find it (a fake bookmark, a hidden link in a wiki page, an email signature).
4. Generate a cloud credential (AWS API key) honeytoken
curl -s https://canarytokens.org/generate \
-F 'type=aws_keys' \
-F 'email=soc@example.com' \
-F 'memo=Decoy AWS keys - jenkins build host /root/.aws/credentials'
# Response contains access_key_id and secret_access_key plus a downloadable
# credentials file via /download?token=<token>&auth=<auth>&fmt=aws_keysDrop the keys into a plausible ~/.aws/credentials on a monitored host. Any sts:GetCallerIdentity or other AWS call using them triggers an alert with the source IP and user agent.
5. Generate a document token (MS Word / PDF) for data-theft detection
# MS Word
curl -s https://canarytokens.org/generate \
-F 'type=msword' \
-F 'email=soc@example.com' \
-F 'memo=Q4-Layoffs-DRAFT.docx on FILESERVER01\\HR$' \
-o token-meta.json
# Download the weaponized document:
curl -s "https://canarytokens.org/download?fmt=msword&token=<token>&auth=<auth>" -o Q4-Layoffs-DRAFT.docxFor PDFs use type=adobe_pdf. The document phones home when opened (DNS/HTTP callback), exposing the reader's IP.
6. Plant a DNS token for resolver-level tripwires
curl -s https://canarytokens.org/generate \
-F 'type=dns' \
-F 'email=soc@example.com' \
-F 'memo=DNS canary referenced in backup script comments'
# Response 'hostname' is a unique FQDN; any resolution of it fires an alert.Embed the hostname in scripts, configs, or /etc/hosts comments. Because resolution alone triggers it, DNS tokens catch reconnaissance even when egress HTTP is blocked.
7. Generate infrastructure tokens (Slack, kubeconfig, Azure)
# Slack API token canary (fires when the fake token is used against Slack)
curl -s https://canarytokens.org/generate -F 'type=slack_api' \
-F 'email=soc@example.com' -F 'memo=Decoy Slack bot token in repo .env'
# Kubeconfig canary (fires when used against the kube API)
curl -s https://canarytokens.org/generate -F 'type=kubeconfig' \
-F 'email=soc@example.com' -F 'memo=Decoy kubeconfig in /home/deploy/.kube/config'Commit decoy .env / kubeconfig files only to repos and hosts you instrument, never to public repos.
8. Plant Active Directory honey credentials (decoy user)
Create a non-privileged-looking but never-used AD account whose authentication is alerted on. Set a SPN so it appears Kerberoastable bait, and forward Event ID 4768/4769/4625 for it to your SIEM:
New-ADUser -Name "svc_backup_legacy" -SamAccountName "svc_backup_legacy" `
-AccountPassword (ConvertTo-SecureString 'C0mpl3xDecoy!2026' -AsPlainText -Force) `
-Enabled $true -Description "Legacy backup service (do not use)"
Set-ADUser svc_backup_legacy -ServicePrincipalNames @{Add="MSSQLSvc/decoy.example.com:1433"}Add a Windows audit ACL/SACL or a SIEM correlation rule so any 4768/4769 for svc_backup_legacy pages the SOC — no legitimate logon should ever occur.
9. Validate and catalogue
Trigger each token from a controlled host and confirm the alert lands:
# HTTP token
curl -s "https://canary.example.com/<token-url>" >/dev/null
# DNS token
dig +short <unique-hostname>
# AWS key token
AWS_ACCESS_KEY_ID=<id> AWS_SECRET_ACCESS_KEY=<secret> aws sts get-caller-identityRecord each deployed token (type, memo, location, alert channel, D3FEND mapping) in an inventory. Use the helper script below to bulk-generate and export this inventory as JSON.
Tools and Resources
| Tool / Resource | Purpose | Link |
|---|---|---|
| Canarytokens (public) | Free hosted token generation | https://canarytokens.org |
| canarytokens-docker | Self-hosted switchboard + frontend | https://github.com/thinkst/canarytokens-docker |
| canarytokens (core) | Source for the token engine | https://github.com/thinkst/canarytokens |
| Canarytokens docs | Per-token-type usage guides | https://docs.canarytokens.org |
| MITRE D3FEND | Defensive technique taxonomy (D3-DF, D3-DUC) | https://d3fend.mitre.org |
| MITRE ATT&CK T1556 | Modify Authentication Process | https://attack.mitre.org/techniques/T1556/ |
Token Type Reference
type value |
Token | Fires when |
|---|---|---|
http |
Web-bug URL | URL is requested (GET) |
dns |
DNS name | Hostname is resolved |
aws_keys |
AWS API key | Keys used against AWS |
msword / adobe_pdf |
Office/PDF document | Document is opened |
slack_api |
Slack API token | Token used against Slack |
kubeconfig |
Kubernetes config | Used against the kube API |
azure_id |
Azure login certificate | Cert used to authenticate to Azure |
qr_code |
QR code | Encoded URL is requested |
web_image |
Image web-bug | Image is loaded |
log4shell |
Log4j JNDI string | Vulnerable logger evaluates it |
cmd |
Sensitive command (Windows) | Process/command is executed |
Validation Criteria
- Self-hosted switchboard (or public service) deployed and reachable on 80/443 and 53/udp.
- Alert channel (email and/or webhook) configured and test alert received.
- At least one each of HTTP, DNS, cloud-credential, and document tokens generated with descriptive memos.
- Decoy credentials planted in realistic, monitored locations (no production secrets co-located).
- AD honey account created with SACL/SIEM rule firing on any authentication.
- Each token validated by a controlled trigger; alert confirmed end-to-end.
- Token inventory exported (type, memo, location, alert channel, D3-DF/D3-DUC mapping).
- No tokens committed to public repositories or planted on out-of-scope systems.
References and resources
Everything below is rendered for inspection. Script files are read-only and never run.
References 2
api-reference.md3.5 KB
Canarytokens API and Deployment Reference
Self-hosting (canarytokens-docker)
| Step | Command |
|---|---|
| Clone | git clone https://github.com/thinkst/canarytokens-docker |
| Config (switchboard) | cp switchboard.env.dist switchboard.env |
| Config (frontend) | cp frontend.env.dist frontend.env |
| Start (HTTP) | docker compose up -d |
| Start (Let's Encrypt) | docker compose -f docker-compose-letsencrypt.yml up -d |
| Status / logs | docker compose ps / docker compose logs -f frontend |
Key environment variables
| Variable | File | Purpose |
|---|---|---|
CANARY_DOMAINS |
frontend.env | Comma-separated domains for general-purpose tokens |
CANARY_NXDOMAINS |
frontend.env | Domains reserved for PDF/DNS tokens |
CANARY_PUBLIC_IP |
frontend.env | Public IPv4 of the host |
CANARY_PUBLIC_DOMAIN |
switchboard.env | Domain serving the frontend |
CANARY_MAILGUN_DOMAIN_NAME |
switchboard.env | Mailgun domain for email alerts |
CANARY_MAILGUN_API_KEY |
switchboard.env | Mailgun API key |
CANARY_ALERT_EMAIL_FROM_ADDRESS |
switchboard.env | Alert sender address |
CANARY_ALERT_EMAIL_FROM_DISPLAY |
switchboard.env | Alert sender display name |
CANARY_ALERT_EMAIL_SUBJECT |
switchboard.env | Alert email subject |
CANARY_WG_PRIVATE_KEY_SEED |
switchboard.env | Base64 seed for WireGuard tokens (`dd bs=32 count=1 if=/dev/urandom |
Public / frontend HTTP API
POST /generate
Create a token. Form fields:
| Field | Required | Description |
|---|---|---|
type |
yes | Token type string (see table below) |
email |
one of email/webhook | Alert email address |
webhook_url |
one of email/webhook | Webhook (Slack/Teams/generic) |
memo |
yes | Free-text reminder of where the token is planted |
Response (JSON) includes: token, auth, hostname, url, url_components; for aws_keys it adds access_key_id and secret_access_key.
GET /download
Download the artifact for document/credential tokens.
| Param | Description |
|---|---|
fmt |
Output format, e.g. msword, aws_keys, adobe_pdf |
token |
Token id from /generate |
auth |
Auth value from /generate |
GET /history
View triggers for a token (params token, auth).
Token type strings
type |
Token | Trigger |
|---|---|---|
http |
Web-bug URL | HTTP GET on the URL |
dns |
DNS name | DNS resolution of the hostname |
aws_keys |
AWS API key | Use of the key against AWS |
msword |
MS Word doc | Document opened |
adobe_pdf |
PDF doc | Document opened |
slack_api |
Slack API token | Use against Slack API |
kubeconfig |
Kubernetes config | Use against kube API |
azure_id |
Azure login cert | Azure authentication |
qr_code |
QR code | Encoded URL requested |
web_image |
Image web-bug | Image loaded |
log4shell |
Log4j JNDI string | Vulnerable logger evaluates string |
cmd |
Sensitive command (Windows) | Command/process executed |
cloned_web |
Cloned website | JS detects clone load |
sql_server |
SQL Server | DB connection/trigger |
Active Directory honey credential (PowerShell)
| Action | Command |
|---|---|
| Create decoy user | New-ADUser -Name svc_backup_legacy -AccountPassword (ConvertTo-SecureString 'C0mpl3xDecoy!2026' -AsPlainText -Force) -Enabled $true |
| Add SPN (Kerberoast bait) | Set-ADUser svc_backup_legacy -ServicePrincipalNames @{Add="MSSQLSvc/decoy.example.com:1433"} |
| Alerting | SIEM rule on Event ID 4768/4769/4625 for the decoy SAM account |
standards.md1.5 KB
Standards and Framework Mapping
NIST Cybersecurity Framework 2.0
| ID | Name | Rationale |
|---|---|---|
| DE.CM-01 | Networks and network services are monitored to find potentially adverse events | Honeytokens and canarytokens are monitored decoy assets; their interaction events are the adverse-event signal this control requires. |
MITRE ATT&CK
| ID | Name | Rationale |
|---|---|---|
| T1556 | Modify Authentication Process | A triggered honey credential is high-fidelity evidence of adversary attempts to harvest, replay, or otherwise abuse authentication material; the decoy provides the detection tripwire. |
MITRE D3FEND (defensive counter-mapping)
| ID | Name | Rationale |
|---|---|---|
| D3-DF | Decoy File | Canary documents (Word/PDF), fake configs, and decoy archives are decoy files placed in monitored locations. |
| D3-DUC | Decoy User Credential | Honey credentials (AWS keys, AD service accounts, kubeconfig, Slack tokens) are decoy credentials integrated with a monitored decoy asset. |
| D3-DO | Decoy Object | Umbrella technique covering honeytokens/canarytokens as monitored decoy artifacts. |
OWASP / Industry References
- Thinkst Canarytokens — open-source canarytoken engine and hosted service (https://canarytokens.org).
- Picus Security: "Defending Against Credential Access Attacks: Harnessing MITRE D3FEND Decoy Objects."
- The Canarytokens approach aligns with the deception layer recommended in zero-trust and defense-in-depth architectures.
Scripts 1
agent.py6.6 KB
#!/usr/bin/env python3
"""
honeytoken_agent.py — Generate, validate, and inventory Canarytokens.
Talks to a Canarytokens frontend (the public service at https://canarytokens.org
or a self-hosted thinkst/canarytokens-docker instance) via its POST /generate
and GET /history HTTP API. Maintains a local JSON inventory of every token
deployed, with its memo, planted location, and MITRE D3FEND mapping, so a blue
team can track deception coverage.
This is a defensive tool. Only generate tokens for assets you own or are
authorized to instrument, and never commit decoy artifacts to public repos.
Examples:
# Generate an HTTP web-bug token on the public service
python3 honeytoken_agent.py generate --type http \
--email soc@example.com --memo "wiki admin-passwords page" \
--location "https://wiki.internal/it/admin" --d3fend D3-DF
# Generate against a self-hosted frontend with a webhook
python3 honeytoken_agent.py generate --base-url https://canary.example.com \
--type aws_keys --webhook https://hooks.slack.com/services/T/B/X \
--memo "decoy keys jenkins host" --location "/root/.aws/credentials"
# Show triggers (history) for a stored token
python3 honeytoken_agent.py history --token-id <token> --auth <auth>
# List the local inventory
python3 honeytoken_agent.py inventory
"""
import argparse
import json
import os
import sys
from datetime import datetime, timezone
try:
import requests
except ImportError:
sys.stderr.write("ERROR: install dependency with: python3 -m pip install requests\n")
sys.exit(2)
DEFAULT_BASE = "https://canarytokens.org"
INVENTORY = os.environ.get("CANARY_INVENTORY", "canarytoken_inventory.json")
VALID_TYPES = {
"http", "dns", "aws_keys", "msword", "adobe_pdf", "slack_api",
"kubeconfig", "azure_id", "qr_code", "web_image", "log4shell",
"cmd", "cloned_web", "sql_server",
}
def _load_inventory():
if not os.path.exists(INVENTORY):
return []
try:
with open(INVENTORY, "r", encoding="utf-8") as fh:
return json.load(fh)
except (json.JSONDecodeError, OSError) as exc:
sys.stderr.write(f"WARN: could not read inventory {INVENTORY}: {exc}\n")
return []
def _save_inventory(items):
try:
with open(INVENTORY, "w", encoding="utf-8") as fh:
json.dump(items, fh, indent=2)
except OSError as exc:
sys.stderr.write(f"ERROR: could not write inventory {INVENTORY}: {exc}\n")
sys.exit(1)
def generate(args):
if args.type not in VALID_TYPES:
sys.stderr.write(f"ERROR: unknown type '{args.type}'. Valid: {sorted(VALID_TYPES)}\n")
sys.exit(1)
if not args.email and not args.webhook:
sys.stderr.write("ERROR: provide --email and/or --webhook for alerting.\n")
sys.exit(1)
data = {"type": args.type, "memo": args.memo}
if args.email:
data["email"] = args.email
if args.webhook:
data["webhook_url"] = args.webhook
url = args.base_url.rstrip("/") + "/generate"
try:
resp = requests.post(url, data=data, timeout=args.timeout)
resp.raise_for_status()
except requests.RequestException as exc:
sys.stderr.write(f"ERROR: generate request failed: {exc}\n")
sys.exit(1)
try:
body = resp.json()
except ValueError:
sys.stderr.write("ERROR: non-JSON response from server:\n" + resp.text[:500] + "\n")
sys.exit(1)
token_id = body.get("token") or body.get("canarytoken")
record = {
"type": args.type,
"memo": args.memo,
"location": args.location or "",
"d3fend": args.d3fend or "",
"token": token_id,
"auth": body.get("auth"),
"hostname": body.get("hostname"),
"url": body.get("url"),
"access_key_id": body.get("access_key_id"),
"created": datetime.now(timezone.utc).isoformat(),
"base_url": args.base_url.rstrip("/"),
}
inv = _load_inventory()
inv.append(record)
_save_inventory(inv)
print(json.dumps({k: v for k, v in record.items() if v is not None}, indent=2))
if args.type in ("msword", "adobe_pdf", "aws_keys") and token_id and record["auth"]:
dl = (f"{record['base_url']}/download?fmt={args.type}"
f"&token={token_id}&auth={record['auth']}")
print(f"\nDownload artifact:\n curl -s '{dl}' -o token_artifact")
return 0
def history(args):
url = args.base_url.rstrip("/") + "/history"
try:
resp = requests.get(url, params={"token": args.token_id, "auth": args.auth},
timeout=args.timeout)
resp.raise_for_status()
except requests.RequestException as exc:
sys.stderr.write(f"ERROR: history request failed: {exc}\n")
sys.exit(1)
try:
print(json.dumps(resp.json(), indent=2))
except ValueError:
print(resp.text)
return 0
def inventory(_args):
inv = _load_inventory()
if not inv:
print("(inventory empty)")
return 0
print(f"{'TYPE':<12} {'D3FEND':<8} {'MEMO':<40} LOCATION")
print("-" * 90)
for rec in inv:
print(f"{rec.get('type',''):<12} {rec.get('d3fend',''):<8} "
f"{(rec.get('memo','') or '')[:40]:<40} {rec.get('location','')}")
print(f"\nTotal tokens deployed: {len(inv)}")
return 0
def build_parser():
p = argparse.ArgumentParser(description="Canarytoken generation, validation and inventory helper.")
p.add_argument("--base-url", default=DEFAULT_BASE,
help=f"Canarytokens frontend base URL (default {DEFAULT_BASE})")
p.add_argument("--timeout", type=int, default=20, help="HTTP timeout seconds")
sub = p.add_subparsers(dest="cmd", required=True)
g = sub.add_parser("generate", help="Create a new canarytoken")
g.add_argument("--type", required=True, help="Token type (e.g. http, dns, aws_keys, msword)")
g.add_argument("--email", help="Alert email address")
g.add_argument("--webhook", help="Alert webhook URL")
g.add_argument("--memo", required=True, help="Reminder of where the token is planted")
g.add_argument("--location", help="Where the token will be planted (for inventory)")
g.add_argument("--d3fend", help="MITRE D3FEND mapping, e.g. D3-DF or D3-DUC")
g.set_defaults(func=generate)
h = sub.add_parser("history", help="Show triggers for a token")
h.add_argument("--token-id", required=True)
h.add_argument("--auth", required=True)
h.set_defaults(func=history)
i = sub.add_parser("inventory", help="List the local token inventory")
i.set_defaults(func=inventory)
return p
def main():
args = build_parser().parse_args()
return args.func(args)
if __name__ == "__main__":
sys.exit(main())