performing-purple-team-atomic-testing について
Atomic Red Team テストを実行し、SIEM/EDR の検知と MITRE ATT&CK のカバレッジを評価する、紫チームと検知エンジニア向けのセキュリティスキルです。反復検証や定期テストの手順も扱います。
主な機能
- 01ATT&CK 技術別のテスト選択
- 02ログ付きテスト実行
- 03SIEM でのアラート検証
- 04カバレッジギャップ分析
- 05Navigator ヒートマップ作成
- 06定期テストのスケジュール
活用シーン
- 01認証情報アクセスのテストに対する EDR 検知確認
- 02月次 ATT&CK カバレッジレポート作成
- 03検知ルール調整後の再テスト
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ mukul975/ anthropic-cybersecurity-skills --skill performing-purple-team-atomic-testingインストール後、Claude Code で/performing-purple-team-atomic-testingを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
LICENSEreferences/api-reference.mdscripts/agent.py詳細
パープルチームのアトミックテストの実施
使用する場面
- 特定の MITRE ATT&CK テクニックに対する検知範囲を検証する
- Atomic Red Team のテストライブラリを使用してパープルチーム演習を実施する
- ATT&CK のカバレッジギャップ分析を実施し、SIEM/EDR の死角を特定する
- 検知検証のサイクルを構築する:アトミックテストの実行、SIEM の確認、ルールの調整、再テスト
- 経営層への報告用に ATT&CK Navigator のヒートマップレイヤーを生成する
- CI/CD またはスケジュールされたパイプラインで継続的なアトミックテストを自動化する
- 脅威インテリジェンスレポートを実行可能なアトミックテストにマッピングする
使用しないでください:カスタムインプラントやアトミックテストを超える実際の敵対者シミュレーションを必要とする、全範囲のレッドチーム演習には使用しないでください。高度な敵対者エミュレーションには Caldera、SCYTHE、または Cobalt Strike を使用してください。
免責事項:Atomic Red Team のテストは実際の攻撃テクニックを実行します。テストを実行できるのは、所有するシステム、またはテストの明示的な書面による許可を得たシステムに限られます。多くのテストはシステムの状態を変更し、アーティファクトを作成したり、セキュリティアラートを発生させたりします。テスト後は必ずクリーンアップコマンドを実行してください。ステークホルダーからリスクの受容を得ずに、本番環境でアトミックテストを実行しないでください。
前提条件
- PowerShell 5.1 以降または PowerShell Core 7 以降を備えた Windows ホスト(クロスプラットフォームのアトミックテストは Linux/macOS にも対応)
- PSGallery から Invoke-AtomicRedTeam PowerShell モジュールをインストール済みであること
- Atomic Red Team のアトミックテストリポジトリをローカルにクローン済みであること
- テスト対象エンドポイントからログを取り込む SIEM/EDR(Splunk、Elastic、Microsoft Sentinel、CrowdStrike)
- レイヤーの可視化に MITRE ATT&CK Navigator(ウェブベースまたはローカルインスタンス)
- 自動化スクリプト用の
mitreattack-python、pyyaml、requestsを備えた Python 3.9 以降 - 検知の相関分析用の Sigma ルールリポジトリ
- テスト対象エンドポイントでの管理者/root アクセス
- 隔離されたテスト環境(ラボ、サンドボックス、または専用のテスト環境)
ワークフロー
ステップ 1:Invoke-AtomicRedTeam のインストールと設定
実行フレームワークをセットアップし、アトミックテストライブラリをダウンロードします。
# Install the PowerShell execution module
Install-Module -Name invoke-atomicredteam -Scope CurrentUser -Force
Install-Module -Name powershell-yaml -Scope CurrentUser -Force
# Import the module
Import-Module invoke-atomicredteam
# Install atomics to default location (C:\AtomicRedTeam\atomics)
IEX (IEX (New-Object System.Net.WebClient).DownloadString(
'https://raw.githubusercontent.com/redcanaryco/invoke-atomicredteam/master/install-atomicredteam.ps1'
)); Install-AtomicRedTeam -getAtomics -Force
# Verify installation - list available techniques
$atomicsPath = "C:\AtomicRedTeam\atomics"
$techniques = Get-ChildItem $atomicsPath -Directory | Where-Object { $_.Name -match '^T\d{4}' }
Write-Host "Available techniques: $($techniques.Count)"
# Configure execution logging
$env:ARTLOG = "C:\AtomicRedTeam\logs"
if (-not (Test-Path $env:ARTLOG)) { New-Item -Path $env:ARTLOG -ItemType Directory }
ステップ 2:アトミックテストの列挙と選択
利用可能なテストを一覧化し、脅威インテリジェンスまたはギャップ分析に基づいて対象を選択します。
# List all tests for a specific technique
Invoke-AtomicTest T1059.001 -ShowDetailsBrief
# Show full details including attack commands and cleanup
Invoke-AtomicTest T1059.001 -ShowDetails
# List tests for a tactic (e.g., Persistence)
$persistenceTechniques = @(
"T1547.001", # Boot or Logon Autostart - Registry Run Keys
"T1053.005", # Scheduled Task
"T1136.001", # Create Account - Local Account
"T1543.003", # Create or Modify System Process - Windows Service
"T1546.001", # Event Triggered Execution - Change Default File Association
"T1574.001", # Hijack Execution Flow - DLL Search Order Hijacking
"T1197" # BITS Jobs
)
foreach ($tech in $persistenceTechniques) {
Write-Host "`n=== $tech ===" -ForegroundColor Cyan
try {
Invoke-AtomicTest $tech -ShowDetailsBrief
} catch {
Write-Host " No tests available" -ForegroundColor Yellow
}
}
# Get all atomic techniques from YAML files programmatically
$allAtomics = Get-ChildItem "$atomicsPath\T*\T*.yaml" -Recurse |
ForEach-Object {
$yaml = Get-Content $_.FullName -Raw | ConvertFrom-Yaml
[PSCustomObject]@{
TechniqueId = $yaml.attack_technique
TechniqueName = $yaml.display_name
TestCount = $yaml.atomic_tests.Count
Platforms = ($yaml.atomic_tests.supported_platforms | Sort-Object -Unique) -join ", "
}
}
$allAtomics | Sort-Object TechniqueId | Format-Table -AutoSize
Write-Host "Total techniques with tests: $($allAtomics.Count)"
Write-Host "Total individual tests: $(($allAtomics | Measure-Object -Property TestCount -Sum).Sum)"
ステップ 3:ログを記録しながらアトミックテストを実行
検知検証のため、実行前後のログを記録してテストを実行します。
# Execute a single test by technique ID (runs all tests for that technique)
Invoke-AtomicTest T1059.001
# Execute a specific test by number
Invoke-AtomicTest T1059.001 -TestNumbers 1
# Execute by test name
Invoke-AtomicTest T1059.001 -TestNames "Mimikatz - Cradled Invoke Expression"
# Execute by GUID
Invoke-AtomicTest T1059.001 -TestGuids "2e803f96-4e33-4c2c-b0c8-1c10cbb3945f"
# Execute with prerequisite check and installation
Invoke-AtomicTest T1059.001 -TestNumbers 1 -CheckPrereqs
Invoke-AtomicTest T1059.001 -TestNumbers 1 -GetPrereqs
Invoke-AtomicTest T1059.001 -TestNumbers 1
# Execute with timeout (seconds)
Invoke-AtomicTest T1003.001 -TimeoutSeconds 120
# Cleanup after testing
Invoke-AtomicTest T1059.001 -TestNumbers 1 -Cleanup
# Execute with full logging wrapper
function Invoke-AtomicWithLogging {
param(
[string]$TechniqueId,
[int[]]$TestNumbers,
[string]$LogPath = "C:\AtomicRedTeam\logs"
)
$timestamp = Get-Date -Format "yyyyMMdd_HHmmss"
$logFile = Join-Path $LogPath "${TechniqueId}_${timestamp}.json"
$result = @{
technique_id = $TechniqueId
test_numbers = $TestNumbers
start_time = (Get-Date).ToString("o")
hostname = $env:COMPUTERNAME
username = $env:USERNAME
results = @()
}
foreach ($testNum in $TestNumbers) {
$testResult = @{
test_number = $testNum
status = "unknown"
start_time = (Get-Date).ToString("o")
}
try {
# Show what will execute
$details = Invoke-AtomicTest $TechniqueId -TestNumbers $testNum -ShowDetails 2>&1
$testResult["details"] = $details | Out-String
# Execute the test
Invoke-AtomicTest $TechniqueId -TestNumbers $testNum -Confirm:$false
$testResult["status"] = "executed"
} catch {
$testResult["status"] = "failed"
$testResult["error"] = $_.Exception.Message
}
$testResult["end_time"] = (Get-Date).ToString("o")
$result.results += $testResult
# Wait for SIEM ingestion
Start-Sleep -Seconds 30
}
$result["end_time"] = (Get-Date).ToString("o")
$result | ConvertTo-Json -Depth 10 | Set-Content $logFile
Write-Host "Log written to: $logFile" -ForegroundColor Green
return $result
}
# Usage
Invoke-AtomicWithLogging -TechniqueId "T1059.001" -TestNumbers @(1, 2, 3)
ステップ 4:SIEM で検知を検証
SIEM にクエリを実行し、アトミックテストによってアラートが生成されたかを確認します。
Splunk SPL Queries for Detection Validation:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-- T1059.001: PowerShell Execution
index=windows sourcetype="WinEventLog:Microsoft-Windows-PowerShell/Operational"
EventCode=4104
| eval script_block=ScriptBlockText
| where len(script_block) > 500
| stats count by host, script_block
| sort -count
-- T1003.001: LSASS Memory Credential Dumping
index=windows sourcetype="WinEventLog:Security" EventCode=4663
ObjectName="*lsass*"
| stats count by host, SubjectUserName, ProcessName
| where count > 0
-- T1547.001: Registry Run Key Persistence
index=windows sourcetype="WinEventLog:Microsoft-Windows-Sysmon/Operational"
EventCode=13
TargetObject="*\\CurrentVersion\\Run*"
| stats count by host, Image, TargetObject, Details
-- T1053.005: Scheduled Task Creation
index=windows sourcetype="WinEventLog:Security" EventCode=4698
| stats count by host, SubjectUserName, TaskName, TaskContent
| sort -count
-- Generic: Hunt for Atomic Red Team artifacts
index=windows (sourcetype="WinEventLog:Microsoft-Windows-Sysmon/Operational"
OR sourcetype="WinEventLog:Security")
| search "*AtomicRedTeam*" OR "*atomic*" OR "*Invoke-AtomicTest*"
| stats count by sourcetype, EventCode, host
Elastic / KQL Queries:
━━━━━━━━━━━━━━━━━━━━━
-- PowerShell script block logging
event.code: "4104" and powershell.file.script_block_text: *
-- Sysmon process creation from AtomicRedTeam paths
event.code: "1" and process.executable: *AtomicRedTeam*
-- Registry modification (persistence)
event.code: "13" and registry.path: *CurrentVersion\\Run*
-- Credential access indicators
event.code: "10" and winlog.event_data.TargetImage: *lsass.exe*
ステップ 5:ATT&CK カバレッジギャップ分析
テスト済みのテクニックと検知済みのテクニックを比較するカバレッジマトリックスを生成します。
#!/usr/bin/env python3
"""ATT&CK coverage gap analysis - compares atomic test results against SIEM detections."""
import json
import os
import yaml
from pathlib import Path
from datetime import datetime
def load_atomics_inventory(atomics_path):
"""Parse all atomic test YAML files to build technique inventory."""
inventory = {}
atomics_dir = Path(atomics_path)
for yaml_file in atomics_dir.glob("T*/T*.yaml"):
try:
with open(yaml_file, "r", encoding="utf-8") as f:
data = yaml.safe_load(f)
tech_id = data.get("attack_technique", "")
if not tech_id:
continue
tests = data.get("atomic_tests", [])
inventory[tech_id] = {
"name": data.get("display_name", "Unknown"),
"test_count": len(tests),
"platforms": list(set(
p for t in tests
for p in t.get("supported_platforms", [])
)),
"tests": [
{
"name": t.get("name", "Unnamed"),
"description": t.get("description", ""),
"platforms": t.get("supported_platforms", []),
"executor": t.get("executor", {}).get("name", "unknown"),
}
for t in tests
],
}
except Exception as e:
print(f"[WARN] Failed to parse {yaml_file}: {e}")
return inventory
def load_execution_logs(log_dir):
"""Load atomic test execution logs."""
executed = {}
log_path = Path(log_dir)
if not log_path.exists():
return executed
for log_file in log_path.glob("T*_*.json"):
try:
with open(log_file, "r") as f:
data = json.load(f)
tech_id = data.get("technique_id", "")
if tech_id:
if tech_id not in executed:
executed[tech_id] = {
"executions": [],
"last_executed": data.get("end_time", ""),
}
executed[tech_id]["executions"].append({
"timestamp": data.get("start_time", ""),
"results": data.get("results", []),
})
except Exception as e:
print(f"[WARN] Failed to parse {log_file}: {e}")
return executed
def load_detection_results(detection_file):
"""Load SIEM detection validation results (JSON export from SIEM queries)."""
if not os.path.exists(detection_file):
return {}
with open(detection_file, "r") as f:
data = json.load(f)
detections = {}
for entry in data:
tech_id = entry.get("technique_id", "")
if tech_id:
detections[tech_id] = {
"detected": entry.get("detected", False),
"alert_count": entry.get("alert_count", 0),
"rule_name": entry.get("rule_name", ""),
"confidence": entry.get("confidence", "unknown"),
"data_sources": entry.get("data_sources", []),
}
return detections
# MITRE ATT&CK tactic ordering for structured output
TACTIC_ORDER = [
"reconnaissance", "resource-development", "initial-access",
"execution", "persistence", "privilege-escalation",
"defense-evasion", "credential-access", "discovery",
"lateral-movement", "collection", "command-and-control",
"exfiltration", "impact",
]
# Tactic-to-technique mapping for common techniques (subset for illustration)
TACTIC_TECHNIQUE_MAP = {
"execution": [
"T1059", "T1059.001", "T1059.003", "T1059.004", "T1059.005",
"T1059.006", "T1059.007", "T1047", "T1053", "T1053.005",
"T1129", "T1203", "T1569", "T1569.002",
],
"persistence": [
"T1547", "T1547.001", "T1547.004", "T1547.009",
"T1053.005", "T1136", "T1136.001", "T1543", "T1543.003",
"T1546", "T1546.001", "T1546.003", "T1574", "T1574.001",
"T1197", "T1505", "T1505.003",
],
"credential-access": [
"T1003", "T1003.001", "T1003.002", "T1003.003",
"T1003.004", "T1003.005", "T1003.006",
"T1110", "T1110.001", "T1110.003",
"T1555", "T1555.003", "T1552", "T1552.001",
"T1558", "T1558.003",
],
"defense-evasion": [
"T1070", "T1070.001", "T1070.004",
"T1218", "T1218.001", "T1218.003", "T1218.005",
"T1218.010", "T1218.011",
"T1027", "T1140", "T1562", "T1562.001",
"T1036", "T1036.005",
],
"discovery": [
"T1082", "T1083", "T1087", "T1087.001", "T1087.002",
"T1016", "T1049", "T1057", "T1069", "T1069.001",
"T1069.002", "T1518", "T1518.001",
],
"lateral-movement": [
"T1021", "T1021.001", "T1021.002", "T1021.003",
"T1021.004", "T1021.006", "T1570",
],
"command-and-control": [
"T1071", "T1071.001", "T1071.004",
"T1105", "T1132", "T1573", "T1573.001",
"T1219", "T1090",
],
"exfiltration": [
"T1041", "T1048", "T1048.003", "T1567",
],
"impact": [
"T1485", "T1486", "T1489", "T1490", "T1491",
],
}
def generate_coverage_report(atomics_inventory, execution_logs, detection_results):
"""Generate comprehensive coverage gap analysis."""
report = {
"generated_at": datetime.utcnow().isoformat() + "Z",
"summary": {},
"tactics": {},
"gaps": [],
"recommendations": [],
}
total_available = len(atomics_inventory)
total_executed = len(execution_logs)
total_detected = sum(1 for d in detection_results.values() if d.get("detected"))
report["summary"] = {
"total_techniques_with_atomics": total_available,
"total_techniques_executed": total_executed,
"total_techniques_detected": total_detected,
"execution_coverage_pct": round(
(total_executed / total_available * 100) if total_available else 0, 1
),
"detection_coverage_pct": round(
(total_detected / total_executed * 100) if total_executed else 0, 1
),
}
# Per-tactic analysis
for tactic, technique_ids in TACTIC_TECHNIQUE_MAP.items():
tactic_data = {
"techniques_available": 0,
"techniques_executed": 0,
"techniques_detected": 0,
"gaps": [],
}
for tech_id in technique_ids:
if tech_id in atomics_inventory:
tactic_data["techniques_available"] += 1
executed = tech_id in execution_logs
detected = detection_results.get(tech_id, {}).get("detected", False)
if executed:
tactic_data["techniques_executed"] += 1
if detected:
tactic_data["techniques_detected"] += 1
if executed and not detected:
gap = {
"technique_id": tech_id,
"technique_name": atomics_inventory[tech_id]["name"],
"tactic": tactic,
"status": "BLIND_SPOT",
"detail": "Test executed but no detection triggered",
}
tactic_data["gaps"].append(gap)
report["gaps"].append(gap)
elif not executed and tech_id in atomics_inventory:
gap = {
"technique_id": tech_id,
"technique_name": atomics_inventory[tech_id]["name"],
"tactic": tactic,
"status": "NOT_TESTED",
"detail": "Atomic test available but not yet executed",
}
tactic_data["gaps"].append(gap)
avail = tactic_data["techniques_available"]
tactic_data["coverage_pct"] = round(
(tactic_data["techniques_detected"] / avail * 100) if avail else 0, 1
)
report["tactics"][tactic] = tactic_data
# Generate prioritized recommendations
blind_spots = [g for g in report["gaps"] if g["status"] == "BLIND_SPOT"]
if blind_spots:
report["recommendations"].append({
"priority": "CRITICAL",
"action": f"Write detection rules for {len(blind_spots)} blind spot techniques",
"techniques": [g["technique_id"] for g in blind_spots],
})
low_coverage_tactics = [
t for t, d in report["tactics"].items() if d["coverage_pct"] < 30
]
if low_coverage_tactics:
report["recommendations"].append({
"priority": "HIGH",
"action": f"Expand testing in low-coverage tactics: {', '.join(low_coverage_tactics)}",
"detail": "These tactics have less than 30% detection coverage",
})
return report
def generate_navigator_layer(atomics_inventory, execution_logs, detection_results,
layer_name="Purple Team Coverage"):
"""Generate ATT&CK Navigator layer JSON for heatmap visualization."""
layer = {
"name": layer_name,
"versions": {
"attack": "15",
"navigator": "5.1",
"layer": "4.5",
},
"domain": "enterprise-attack",
"description": f"Purple team atomic testing coverage - Generated {datetime.utcnow().isoformat()}Z",
"filters": {"platforms": ["Windows", "Linux", "macOS"]},
"sorting": 0,
"layout": {
"layout": "side",
"aggregateFunction": "average",
"showID": True,
"showName": True,
},
"hideDisabled": False,
"techniques": [],
"gradient": {
"colors": ["#ff6666", "#ffeb3b", "#66bb6a"],
"minValue": 0,
"maxValue": 100,
},
"legendItems": [
{"label": "No Coverage (Blind Spot)", "color": "#ff6666"},
{"label": "Logged Only (Partial)", "color": "#ffeb3b"},
{"label": "Alert/Detection Active", "color": "#66bb6a"},
{"label": "Not Tested", "color": "#d3d3d3"},
],
"metadata": [],
"links": [],
"showTacticRowBackground": True,
"tacticRowBackground": "#dddddd",
"selectTechniquesAcrossTactics": True,
"selectSubtechniquesWithParent": False,
}
for tech_id, tech_data in atomics_inventory.items():
executed = tech_id in execution_logs
detection = detection_results.get(tech_id, {})
detected = detection.get("detected", False)
confidence = detection.get("confidence", "none")
if detected and confidence in ("high", "medium"):
score = 100
color = "#66bb6a" # Green - high confidence detection
comment = f"DETECTED - {detection.get('rule_name', 'Alert active')}"
elif detected:
score = 50
color = "#ffeb3b" # Yellow - logged/partial
comment = "PARTIAL - Detection exists but low confidence"
elif executed:
score = 0
color = "#ff6666" # Red - blind spot
comment = "BLIND SPOT - Test executed, no detection"
else:
score = 0
color = "#d3d3d3" # Gray - not tested
comment = f"NOT TESTED - {tech_data['test_count']} atomic tests available"
technique_entry = {
"techniqueID": tech_id,
"tactic": "",
"color": color,
"comment": comment,
"score": score,
"enabled": True,
"metadata": [
{"name": "tests_available", "value": str(tech_data["test_count"])},
{"name": "executed", "value": str(executed)},
{"name": "detected", "value": str(detected)},
],
"links": [],
"showSubtechniques": False,
}
layer["techniques"].append(technique_entry)
return layer
def print_coverage_report(report):
"""Print formatted coverage report to console."""
print("=" * 72)
print("PURPLE TEAM ATOMIC TESTING - COVERAGE GAP ANALYSIS")
print("=" * 72)
print(f"Generated: {report['generated_at']}")
print()
s = report["summary"]
print("EXECUTIVE SUMMARY")
print("-" * 40)
print(f" Techniques with atomics: {s['total_techniques_with_atomics']}")
print(f" Techniques executed: {s['total_techniques_executed']}")
print(f" Techniques detected: {s['total_techniques_detected']}")
print(f" Execution coverage: {s['execution_coverage_pct']}%")
print(f" Detection coverage: {s['detection_coverage_pct']}%")
print()
print("PER-TACTIC COVERAGE")
print("-" * 72)
print(f"{'Tactic':<25} {'Available':>9} {'Executed':>9} {'Detected':>9} {'Coverage':>9}")
print("-" * 72)
for tactic in TACTIC_ORDER:
if tactic in report["tactics"]:
t = report["tactics"][tactic]
bar = "#" * int(t["coverage_pct"] / 5) + "." * (20 - int(t["coverage_pct"] / 5))
print(
f" {tactic:<23} {t['techniques_available']:>9} "
f"{t['techniques_executed']:>9} {t['techniques_detected']:>9} "
f"{t['coverage_pct']:>8.1f}%"
)
print()
blind_spots = [g for g in report["gaps"] if g["status"] == "BLIND_SPOT"]
if blind_spots:
print("CRITICAL BLIND SPOTS (executed but not detected)")
print("-" * 72)
for gap in blind_spots:
print(f" [!] {gap['technique_id']} - {gap['technique_name']}")
print(f" Tactic: {gap['tactic']}")
print()
if report["recommendations"]:
print("RECOMMENDATIONS")
print("-" * 72)
for rec in report["recommendations"]:
print(f" [{rec['priority']}] {rec['action']}")
if "techniques" in rec:
print(f" Techniques: {', '.join(rec['techniques'][:10])}")
if "detail" in rec:
print(f" {rec['detail']}")
print()
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(description="ATT&CK coverage gap analysis for purple team testing")
parser.add_argument("--atomics-path", default=r"C:\AtomicRedTeam\atomics",
help="Path to Atomic Red Team atomics directory")
parser.add_argument("--log-dir", default=r"C:\AtomicRedTeam\logs",
help="Path to atomic test execution logs")
parser.add_argument("--detections-file", default="detection_results.json",
help="Path to SIEM detection validation export (JSON)")
parser.add_argument("--output-layer", default="navigator_layer.json",
help="Output path for ATT&CK Navigator layer JSON")
parser.add_argument("--output-report", default="coverage_report.json",
help="Output path for coverage report JSON")
args = parser.parse_args()
print("[*] Loading atomics inventory...")
inventory = load_atomics_inventory(args.atomics_path)
print(f" Found {len(inventory)} techniques with atomic tests")
print("[*] Loading execution logs...")
exec_logs = load_execution_logs(args.log_dir)
print(f" Found logs for {len(exec_logs)} techniques")
print("[*] Loading detection results...")
det_results = load_detection_results(args.detections_file)
print(f" Found detection data for {len(det_results)} techniques")
print("[*] Generating coverage report...")
report = generate_coverage_report(inventory, exec_logs, det_results)
print_coverage_report(report)
# Save report JSON
with open(args.output_report, "w") as f:
json.dump(report, f, indent=2)
print(f"[+] Report saved to {args.output_report}")
# Generate and save Navigator layer
print("[*] Generating ATT&CK Navigator layer...")
layer = generate_navigator_layer(inventory, exec_logs, det_results)
with open(args.output_layer, "w") as f:
json.dump(layer, f, indent=2)
print(f"[+] Navigator layer saved to {args.output_layer}")
print(f" Import at: https://mitre-attack.github.io/attack-navigator/")
ステップ 6:継続的なアトミックテストのパイプラインを実行
優先度の高いテクニックに対して、定期的なテストをスケジュールします。
# Continuous Atomic Testing - scheduled execution with validation
# Run as a scheduled task or via CI/CD pipeline
$PriorityTechniques = @(
# Top MITRE ATT&CK techniques by prevalence (Red Canary Threat Detection Report)
@{ Id = "T1059.001"; Name = "PowerShell" },
@{ Id = "T1059.003"; Name = "Windows Command Shell" },
@{ Id = "T1547.001"; Name = "Registry Run Keys" },
@{ Id = "T1053.005"; Name = "Scheduled Task" },
@{ Id = "T1003.001"; Name = "LSASS Memory" },
@{ Id = "T1003.003"; Name = "NTDS" },
@{ Id = "T1070.004"; Name = "File Deletion" },
@{ Id = "T1218.011"; Name = "Rundll32" },
@{ Id = "T1082"; Name = "System Information Discovery" },
@{ Id = "T1105"; Name = "Ingress Tool Transfer" }
)
$ResultsLog = @()
$RunTimestamp = Get-Date -Format "yyyyMMdd_HHmmss"
foreach ($technique in $PriorityTechniques) {
Write-Host "`n[*] Testing $($technique.Id) - $($technique.Name)" -ForegroundColor Cyan
# Check prerequisites
try {
$prereqs = Invoke-AtomicTest $technique.Id -TestNumbers 1 -CheckPrereqs 2>&1
$prereqsMet = $prereqs -notmatch "not met"
} catch {
$prereqsMet = $false
}
if (-not $prereqsMet) {
Write-Host " [!] Prerequisites not met, attempting install..." -ForegroundColor Yellow
try {
Invoke-AtomicTest $technique.Id -TestNumbers 1 -GetPrereqs 2>&1 | Out-Null
} catch {
Write-Host " [!] Prereq install failed, skipping" -ForegroundColor Red
$ResultsLog += [PSCustomObject]@{
TechniqueId = $technique.Id
Name = $technique.Name
Status = "PREREQS_FAILED"
Timestamp = (Get-Date).ToString("o")
}
continue
}
}
# Execute the test
try {
$startTime = Get-Date
Invoke-AtomicTest $technique.Id -TestNumbers 1 -Confirm:$false
$endTime = Get-Date
$duration = ($endTime - $startTime).TotalSeconds
Write-Host " [+] Executed successfully (${duration}s)" -ForegroundColor Green
$ResultsLog += [PSCustomObject]@{
TechniqueId = $technique.Id
Name = $technique.Name
Status = "EXECUTED"
Duration = $duration
Timestamp = $startTime.ToString("o")
}
} catch {
Write-Host " [-] Execution failed: $($_.Exception.Message)" -ForegroundColor Red
$ResultsLog += [PSCustomObject]@{
TechniqueId = $technique.Id
Name = $technique.Name
Status = "FAILED"
Error = $_.Exception.Message
Timestamp = (Get-Date).ToString("o")
}
}
# Cleanup
try {
Invoke-AtomicTest $technique.Id -TestNumbers 1 -Cleanup 2>&1 | Out-Null
Write-Host " [+] Cleanup completed" -ForegroundColor DarkGray
} catch {
Write-Host " [!] Cleanup failed" -ForegroundColor Yellow
}
# Brief pause between tests for log ingestion
Start-Sleep -Seconds 10
}
# Export results
$ResultsLog | Export-Csv "C:\AtomicRedTeam\logs\continuous_test_$RunTimestamp.csv" -NoTypeInformation
$ResultsLog | Format-Table -AutoSize
# Summary
$executed = ($ResultsLog | Where-Object Status -eq "EXECUTED").Count
$failed = ($ResultsLog | Where-Object Status -ne "EXECUTED").Count
Write-Host "`n=== CONTINUOUS TEST SUMMARY ===" -ForegroundColor Cyan
Write-Host " Executed: $executed / $($PriorityTechniques.Count)" -ForegroundColor Green
Write-Host " Failed: $failed / $($PriorityTechniques.Count)" -ForegroundColor Red
ステップ 7:検知検証のサイクル
テストと検知を相関分析し、改善を繰り返してパープルチームのサイクルを完了します。
Detection Validation Loop Workflow:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
┌──────────────────┐
│ 1. SELECT │ Choose technique from threat intel or gap report
│ TECHNIQUE │ Map to Atomic Red Team test ID
└───────┬──────────┘
│
┌───────▼──────────┐
│ 2. EXECUTE │ Run Invoke-AtomicTest with logging
│ ATOMIC TEST │ Record timestamp, hostname, test details
└───────┬──────────┘
│
┌───────▼──────────┐
│ 3. WAIT FOR │ Allow 30-60 seconds for log ingestion
│ INGESTION │ Ensure Sysmon, WinEventLog, EDR agents forward data
└───────┬──────────┘
│
┌───────▼──────────┐
│ 4. QUERY SIEM │ Search for correlated alerts matching technique
│ FOR ALERTS │ Check: Did our detection rules fire?
└───────┬──────────┘
│
┌────┴────┐
│DETECTED?│
└────┬────┘
YES │ NO
┌───────▼──────────┐ ┌──────────────────┐
│ 5a. MARK GREEN │ │ 5b. WRITE NEW │
│ Update Navigator│ │ Sigma/SIEM Rule │
│ layer, log pass │ │ for technique │
└───────┬──────────┘ └───────┬──────────┘
│ │
│ ┌───────▼──────────┐
│ │ 5c. DEPLOY RULE │
│ │ Push to SIEM │
│ └───────┬──────────┘
│ │
│ ┌───────▼──────────┐
│ │ 5d. RE-EXECUTE │
│ │ Re-run atomic │
│ │ test, validate │
│ └───────┬──────────┘
│ │
┌───────▼───────────────────────▼──┐
│ 6. CLEANUP │
│ Run Invoke-AtomicTest -Cleanup│
│ Document results │
└───────┬──────────────────────────┘
│
┌───────▼──────────┐
│ 7. NEXT │ Select next technique
│ TECHNIQUE │ Repeat from step 1
└──────────────────┘
主要な概念
| 用語 | 定義 |
|---|---|
| Atomic Red Team | Red Canary が提供するオープンソースライブラリです。MITRE ATT&CK の手法に対応付けられた、小規模で目的を絞ったテストが含まれており、検知機能の検証に使用します |
| Invoke-AtomicRedTeam | 前提条件の確認とクリーンアップ機能を備え、アトミックテストをローカルまたはリモートで実行する PowerShell 実行フレームワークです |
| ATT&CK Navigator | MITRE ATT&CK マトリクスに注釈を付け、検知範囲を示すレイヤー形式のヒートマップとして可視化するウェブベースのツールです |
| Navigator Layer | 各 ATT&CK 手法の色、スコア、コメントを定義する JSON ファイルです。検知範囲のヒートマップ生成に使用します |
| Detection Validation Loop | 攻撃の実行、検知の確認、検知されなかった場合のルール作成または調整、そして確認のための再実行を繰り返す、パープルチームのプロセスです |
| Coverage Gap | 検知ルールが存在しない、または手法を実行してもルールが発火しない ATT&CK 手法です |
| Blind Spot | アトミック実行でテストしたものの、SIEM アラートがまったく生成されなかった手法です。重大な検知の失敗を示します |
| Sigma Rule | ベンダーに依存しない検知ルール形式です。Splunk SPL、Elastic KQL、Microsoft KQL、その他の SIEM クエリに変換できます |
| Purple Team | レッドチームが既知の手法を実行し、ブルーチームがリアルタイムで検知を検証、改善する共同のセキュリティ演習です |
| Continuous Atomic Testing | 環境の変化に伴っても検知ルールが機能し続けることを検証するため、アトミックテストを定期的かつ自動で実行することです |
ツールとシステム
- Invoke-AtomicRedTeam:前提条件の管理とクリーンアップ機能を備えた、Atomic Red Team テスト実行用の PowerShell モジュールです
- Atomic Red Team Atomics:200 以上の MITRE ATT&CK 手法にわたる 700 件以上のテストライブラリです。手法 ID ごとに整理されています
- ATT&CK Navigator:手法の検知範囲を示すヒートマップを作成する、mitre-attack.github.io/attack-navigator にあるウェブツールです
- VECTR:テストキャンペーン、結果、検知範囲の推移を追跡する、SCYTHE のパープルチーム向けレポートプラットフォームです
- Sigma:SIEM システム向けの汎用シグネチャ形式です。sigmac または pySigma で対象の SIEM プラットフォーム向けに変換できます
- Splunk / Elastic / Microsoft Sentinel:アトミックテストの実行後に検知結果をクエリするための SIEM プラットフォームです
- Sysmon:プロセス、ネットワーク、レジストリ、ファイルの詳細なテレメトリを提供する、Windows システム監視ドライバーです
- Caldera:単一のアトミックテストを超える、自動化された複数ステップの攻撃チェーン向けに MITRE が開発した、アドバーサリエミュレーションプラットフォームです
よくあるシナリオ
シナリオ:EDR 導入後の認証情報アクセス検知の検証
背景:組織が新しい EDR エージェントを導入し、認証情報のダンプ手法(LSASS へのアクセス、NTDS 抽出、SAM ダンプ)を検知することを確認する必要があります。導入チケットを完了する前に、パープルチームが検知の動作を確認しなければなりません。
手順:
- 新しい EDR に登録されたテスト用エンドポイントに Invoke-AtomicRedTeam をインストールします
- 認証情報アクセスのアトミックテストを実行します:T1003.001(LSASS Memory)、T1003.002(SAM)、T1003.003(NTDS)
- 60 秒待ってから、テスト対象のホスト名に関するアラートを EDR コンソールと SIEM でクエリします
- 各手法について、Navigator レイヤーで検知済み(緑)または検知漏れ(赤)として記録します
- 検知漏れについては、Sysmon EventCode 10(lsass.exe への ProcessAccess)と EventCode 1(既知のダンプツール)を対象とする Sigma ルールを作成します
- ルールをデプロイし、アトミックテストを再実行して検知を確認します
- 導入チケット用に Navigator レイヤーと検知範囲レポートをエクスポートします
注意点:
- ローカル管理者権限なしで認証情報ダンプのアトミックテストを実行する(テストが失敗しても通知されません)
- 既定では無効になっている Sysmon の ProcessAccess ロギング(EventCode 10)を有効にし忘れる
- クリーンアップを実行せず、テストの痕跡(作成したアカウント、レジストリキー、スケジュールされたタスク)を残してしまう
- T1003 のサブ手法を 1 つだけテストし、認証情報アクセス全体を網羅したと思い込む
- PowerShell や AtomicRedTeam ディレクトリを許可リストに登録している可能性のある、EDR エージェントの除外設定を考慮しない
シナリオ:経営陣向けの月次 ATT&CK カバレッジレポートの作成
背景:CISO は、MITRE ATT&CK にマッピングされた検知カバレッジの月次指標を求めています。セキュリティチームは、定期的にテストを実施し、改善状況を追跡して、時間の経過に伴うカバレッジの拡大を示す視覚的なヒートマップを作成する必要があります。
アプローチ:
- Red Canary Threat Detection Report の上位 50 件の ATT&CK テクニックを網羅するテスト計画を定義する
- PowerShell パイプラインを毎週実行する Windows Task Scheduler を使用して、継続的なアトミックテストをスケジュールする
- 各実行後に、実行ログと SIEM の検知クエリ結果を JSON にエクスポートする
- Python のギャップ分析スクリプトを実行して、Navigator レイヤーとカバレッジレポートを作成する
- Navigator レイヤーを ATT&CK Navigator にインポートし、経営陣向けスライド資料用にスクリーンショットを撮る
- 月ごとに、緑色(検知済み)と赤色(死角)のテクニック数を追跡する
- 影響度の高い死角を優先して、検知エンジニアリングのスプリント計画を立てる
注意点:
- 「ログに記録された」ことを「検知された」こととして数えること(ログでイベントを確認できることと、アラートルールがあることは同じではありません)
- atomics リポジトリを更新しないこと(新しいテストは頻繁に追加されます)
- 検知品質ではなく、テクニック数だけに注目すること(誤検知率が 90%のノイズの多いルールは、ルールがない状態よりも悪影響を及ぼします)
- アラートをトリアージする SOC アナリストを検証プロセスに参加させないこと
出力形式
PURPLE TEAM ATOMIC TESTING REPORT
====================================
Campaign: Q1 2026 Detection Validation
Date Range: 2026-01-15 to 2026-03-15
Test Environment: YOURLAB-WIN10-01, YOURLAB-SRV-01
SIEM: Splunk Enterprise 9.x
EXECUTION SUMMARY
Techniques Tested: 47 / 200+ available
Individual Tests Run: 112
Tests Succeeded: 104
Tests Failed (prereqs): 8
Cleanup Completed: 104 / 104
DETECTION COVERAGE
Tested Detected Coverage
Execution: 12 10 83.3%
Persistence: 8 5 62.5%
Credential Access: 6 4 66.7%
Defense Evasion: 10 3 30.0%
Discovery: 5 5 100.0%
Lateral Movement: 3 1 33.3%
Command and Control: 2 1 50.0%
Exfiltration: 1 0 0.0%
CRITICAL BLIND SPOTS
[!] T1218.011 - Rundll32 (defense-evasion)
[!] T1218.005 - Mshta (defense-evasion)
[!] T1070.004 - File Deletion (defense-evasion)
[!] T1574.001 - DLL Search Order (persistence)
[!] T1021.002 - SMB/Admin Shares (lateral-movement)
[!] T1048.003 - Exfil Over HTTP (exfiltration)
NAVIGATOR LAYER: coverage_layer_20260315.json
Import at: https://mitre-attack.github.io/attack-navigator/
RECOMMENDATIONS
[CRITICAL] Write Sigma rules for 6 blind spot techniques
[HIGH] Expand defense-evasion testing (30% coverage)
[HIGH] Enable exfiltration monitoring data sources
[MEDIUM] Increase lateral movement test coverage (3 of 7 tested)