@blamejs/exceptd-skills 0.9.4 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +45 -0
- package/CHANGELOG.md +131 -0
- package/README.md +30 -5
- package/bin/exceptd.js +403 -1
- package/data/_indexes/_meta.json +3 -3
- package/data/_indexes/currency.json +138 -138
- package/data/playbooks/ai-api.json +763 -0
- package/data/playbooks/containers.json +766 -0
- package/data/playbooks/cred-stores.json +715 -0
- package/data/playbooks/crypto.json +726 -0
- package/data/playbooks/framework.json +725 -0
- package/data/playbooks/hardening.json +672 -0
- package/data/playbooks/kernel.json +549 -0
- package/data/playbooks/mcp.json +727 -0
- package/data/playbooks/runtime.json +649 -0
- package/data/playbooks/sbom.json +893 -0
- package/data/playbooks/secrets.json +690 -0
- package/lib/cross-ref-api.js +224 -0
- package/lib/playbook-runner.js +826 -0
- package/lib/schemas/playbook.schema.json +652 -0
- package/lib/verify.js +45 -0
- package/manifest-snapshot.json +1 -1
- package/manifest.json +39 -39
- package/orchestrator/dispatcher.js +13 -1
- package/orchestrator/index.js +119 -5
- package/orchestrator/pipeline.js +8 -2
- package/orchestrator/scanner.js +191 -4
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
- package/scripts/builders/currency.js +5 -3
|
@@ -0,0 +1,649 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_meta": {
|
|
3
|
+
"id": "runtime",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"last_threat_review": "2026-05-11",
|
|
6
|
+
"threat_currency_score": 95,
|
|
7
|
+
"changelog": [
|
|
8
|
+
{
|
|
9
|
+
"version": "1.0.0",
|
|
10
|
+
"date": "2026-05-11",
|
|
11
|
+
"summary": "Initial seven-phase runtime inventory playbook. Walks listening sockets, sudo rules, SUID/SGID binaries, cron/systemd timers, and process tree on a live Linux host. Cross-references findings against ATLAS T1078/T1543/T1546/T1574 lineage and the catalogued kernel LPE chain. Designed as the live-host corroborator of the kernel playbook — kernel.json answers 'is the running kernel vulnerable?', runtime.json answers 'and what is the attacker actually going to find when they land here?'.",
|
|
12
|
+
"framework_gaps_updated": ["nist-800-53-CM-7", "nist-800-53-AC-6", "iso-27001-2022-A.8.2", "nis2-art21-2d"]
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
"owner": "@blamejs/platform-security",
|
|
16
|
+
"air_gap_mode": false,
|
|
17
|
+
"preconditions": [
|
|
18
|
+
{
|
|
19
|
+
"id": "linux-platform",
|
|
20
|
+
"description": "Runtime inventory is Linux-shaped (ss/sudo/find/systemctl). macOS/Windows hosts halt rather than emit misleading evidence.",
|
|
21
|
+
"check": "host.platform == 'linux'",
|
|
22
|
+
"on_fail": "halt"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"id": "exec-allowed",
|
|
26
|
+
"description": "Host AI must be permitted to execute read-only inventory commands. Read-only scans (no writes, no kill) are required.",
|
|
27
|
+
"check": "agent_has_command_exec == true",
|
|
28
|
+
"on_fail": "halt"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"id": "non-destructive",
|
|
32
|
+
"description": "All commands in this playbook are read-only. If the agent's policy forbids any command in the chain, skip that artifact and downgrade confidence rather than abort.",
|
|
33
|
+
"check": "agent_policy.allows_read_only_scan == true",
|
|
34
|
+
"on_fail": "warn"
|
|
35
|
+
}
|
|
36
|
+
],
|
|
37
|
+
"mutex": [],
|
|
38
|
+
"feeds_into": [
|
|
39
|
+
{
|
|
40
|
+
"playbook_id": "kernel",
|
|
41
|
+
"condition": "always"
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"playbook_id": "hardening",
|
|
45
|
+
"condition": "always"
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"playbook_id": "cred-stores",
|
|
49
|
+
"condition": "finding.severity == 'critical' OR analyze.blast_radius_score >= 3"
|
|
50
|
+
}
|
|
51
|
+
]
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
"domain": {
|
|
55
|
+
"name": "Linux runtime attack-surface inventory",
|
|
56
|
+
"attack_class": "kernel-lpe",
|
|
57
|
+
"atlas_refs": [],
|
|
58
|
+
"attack_refs": ["T1078", "T1543", "T1546", "T1574", "T1068", "T1548.003"],
|
|
59
|
+
"cve_refs": [],
|
|
60
|
+
"cwe_refs": ["CWE-269", "CWE-732", "CWE-250", "CWE-426"],
|
|
61
|
+
"d3fend_refs": ["D3-PA", "D3-SCA", "D3-SFA"],
|
|
62
|
+
"frameworks_in_scope": [
|
|
63
|
+
"nist-800-53", "nist-csf-2", "iso-27001-2022",
|
|
64
|
+
"soc2", "pci-dss-4", "nis2", "dora",
|
|
65
|
+
"uk-caf", "au-ism", "au-essential-8"
|
|
66
|
+
]
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
"phases": {
|
|
70
|
+
|
|
71
|
+
"govern": {
|
|
72
|
+
"jurisdiction_obligations": [
|
|
73
|
+
{
|
|
74
|
+
"jurisdiction": "EU",
|
|
75
|
+
"regulation": "NIS2 Art.23",
|
|
76
|
+
"obligation": "notify_regulator",
|
|
77
|
+
"window_hours": 24,
|
|
78
|
+
"clock_starts": "detect_confirmed",
|
|
79
|
+
"evidence_required": ["affected_host_inventory", "lateral_movement_path_assessment", "interim_mitigation_record"]
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"jurisdiction": "EU",
|
|
83
|
+
"regulation": "DORA Art.19",
|
|
84
|
+
"obligation": "notify_regulator",
|
|
85
|
+
"window_hours": 4,
|
|
86
|
+
"clock_starts": "detect_confirmed",
|
|
87
|
+
"evidence_required": ["initial_notification", "ict_third_party_dependencies"]
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
"jurisdiction": "AU",
|
|
91
|
+
"regulation": "APRA CPS 234",
|
|
92
|
+
"obligation": "notify_regulator",
|
|
93
|
+
"window_hours": 72,
|
|
94
|
+
"clock_starts": "validate_complete",
|
|
95
|
+
"evidence_required": ["materiality_assessment", "remediation_completed_evidence"]
|
|
96
|
+
}
|
|
97
|
+
],
|
|
98
|
+
"theater_fingerprints": [
|
|
99
|
+
{
|
|
100
|
+
"pattern_id": "least-privilege-as-paper-policy",
|
|
101
|
+
"claim": "Org has a documented least-privilege policy → AC-6 / A.8.2 satisfied.",
|
|
102
|
+
"fast_detection_test": "Run `sudo -l -n` on a sample of service accounts and `find / -perm -4000 -user root` on representative production hosts. Count SUID binaries that exist beyond the distro baseline AND count sudoers entries with NOPASSWD wildcards. Any non-zero result with a 'least privilege' attestation is theater.",
|
|
103
|
+
"implicated_controls": ["nist-800-53-AC-6", "iso-27001-2022-A.8.2", "nis2-art21-2i"]
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
"pattern_id": "edr-as-runtime-visibility",
|
|
107
|
+
"claim": "We have EDR deployed → runtime attack surface is monitored (CM-7, A.8.2).",
|
|
108
|
+
"fast_detection_test": "Ask the EDR vendor for a single SUID-creation alert from the last 90 days. Ask for a single sudoers-modification alert. Most EDRs do not alert on the static attack surface — they alert on execution. Static-surface theater.",
|
|
109
|
+
"implicated_controls": ["nist-800-53-CM-7", "iso-27001-2022-A.8.7"]
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
"pattern_id": "cron-as-someone-elses-problem",
|
|
113
|
+
"claim": "Scheduled jobs are reviewed quarterly per change-management policy.",
|
|
114
|
+
"fast_detection_test": "Diff the current /etc/cron.d + /etc/systemd/system/*.timer inventory against the last quarterly review record. Any new entry not in the review = drift. If the org cannot produce the quarterly review file, that is itself the theater verdict.",
|
|
115
|
+
"implicated_controls": ["nist-800-53-CM-3", "iso-27001-2022-A.8.32"]
|
|
116
|
+
}
|
|
117
|
+
],
|
|
118
|
+
"framework_context": {
|
|
119
|
+
"gap_summary": "Runtime attack-surface inventory sits in the gap between CM-7 (least functionality), AC-6 (least privilege), and the vulnerability-handling controls. Frameworks treat 'least privilege' as a policy artifact reviewable annually; they do not require a programmatic enumeration of SUID/SGID/sudoers/cron-timer/listening-socket state on production hosts. NIS2 Art.21(2)(d) names supply-chain security but does not name persistence-vector inventory. ISO 27001:2022 A.8.2 names privileged-access rights but accepts a documented policy as evidence. The actual attacker is enumerating exactly these primitives on landing — the post-foothold reconnaissance phase. Framework lag here is structural: there is no control that names 'enumerate persistence + lateral primitives quarterly'.",
|
|
120
|
+
"lag_score": 21,
|
|
121
|
+
"per_framework_gaps": [
|
|
122
|
+
{
|
|
123
|
+
"framework": "nist-800-53",
|
|
124
|
+
"control_id": "CM-7",
|
|
125
|
+
"designed_for": "Least functionality — services and applications limited to required only.",
|
|
126
|
+
"insufficient_because": "Specifies a policy outcome, not an enumeration cadence. A host with 200 SUID binaries and 40 listening sockets can pass CM-7 attestation if the org documents 'we run only required services'. The auditor cannot disprove that without running this playbook themselves."
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
"framework": "nist-800-53",
|
|
130
|
+
"control_id": "AC-6(7)",
|
|
131
|
+
"designed_for": "Review of user privileges — at least annually.",
|
|
132
|
+
"insufficient_because": "Annual cadence permits 364-day drift in sudoers/SUID state. AC-6(7) does not name the specific enumeration tests that distinguish drift from compliant state, so 'we reviewed user privileges' attestation is accepted without programmatic evidence."
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
"framework": "iso-27001-2022",
|
|
136
|
+
"control_id": "A.8.2",
|
|
137
|
+
"designed_for": "Privileged access rights — managed and reviewed.",
|
|
138
|
+
"insufficient_because": "Accepts policy + review-record evidence. Does not require live enumeration of sudoers + SUID + cron/timer state. Most certified orgs can produce policy without producing inventory."
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
"framework": "nis2",
|
|
142
|
+
"control_id": "Art.21(2)(i)",
|
|
143
|
+
"designed_for": "Access control policies, including privileged accounts.",
|
|
144
|
+
"insufficient_because": "Policy-shaped, not state-shaped. Permits policy compliance without state-of-host evidence."
|
|
145
|
+
}
|
|
146
|
+
]
|
|
147
|
+
},
|
|
148
|
+
"skill_preload": ["kernel-lpe-triage", "attack-surface-pentest", "incident-response-playbook", "framework-gap-analysis", "compliance-theater", "policy-exception-gen"]
|
|
149
|
+
},
|
|
150
|
+
|
|
151
|
+
"direct": {
|
|
152
|
+
"threat_context": "Post-foothold reconnaissance is the highest-signal attacker behavior of 2025-2026. Confirmed in IR engagements: attackers who establish initial access (phishing, vuln chain, supply chain) execute a standard enumeration sweep within 60-180 seconds — listening sockets, sudo rules, SUID binaries, cron/systemd timers, process tree — to identify the privilege-escalation path. CVE-2026-31431 'Copy Fail' (KEV, 2026-03-15) and the unprivileged-userns LPE class are not exploitable in isolation; they are chained to runtime primitives this playbook inventories. ATLAS T1078 (Valid Accounts) and ATT&CK T1543 (Create or Modify System Process), T1546 (Event Triggered Execution), T1574 (Hijack Execution Flow) are the named persistence primitives an attacker plants once they have a foothold. Defenders who only patch CVEs without inventorying these primitives are patching the door while leaving the windows open.",
|
|
153
|
+
"rwep_threshold": {
|
|
154
|
+
"escalate": 85,
|
|
155
|
+
"monitor": 65,
|
|
156
|
+
"close": 35
|
|
157
|
+
},
|
|
158
|
+
"framework_lag_declaration": "NIST CM-7 + AC-6, ISO A.8.2 + A.8.18, and NIS2 Art.21(2)(i) treat least-privilege and least-functionality as policy outcomes reviewable annually. They do not require programmatic enumeration of the specific primitives (sudoers NOPASSWD, non-baseline SUID, world-writable cron, host-mounted systemd services, listening sockets on non-loopback) that attackers use to convert foothold into root. A host can be CM-7/AC-6 compliant on paper while presenting dozens of LPE primitives to a landed attacker. Gap = ~21 days between drift and policy review at typical orgs; for fast-moving environments (containerized + GitOps-deployed), drift accumulates faster than annual review can catch.",
|
|
159
|
+
"skill_chain": [
|
|
160
|
+
{ "skill": "attack-surface-pentest", "purpose": "Enumerate listening sockets, sudo rules, SUID/SGID binaries, cron/timer entries, process tree. Map each to ATT&CK persistence/PrivEsc TTPs.", "required": true },
|
|
161
|
+
{ "skill": "kernel-lpe-triage", "purpose": "Cross-reference enumerated primitives with the kernel LPE catalog to identify which primitives the running kernel could weaponize.", "required": true },
|
|
162
|
+
{ "skill": "incident-response-playbook", "purpose": "If any deterministic finding (rogue cron, attacker-shaped SUID, unknown systemd timer) fires, hand off to IR-playbook for containment + chain-of-custody.", "skip_if": "detect.indicators.fired.deterministic == false", "required": false },
|
|
163
|
+
{ "skill": "framework-gap-analysis", "purpose": "Map each finding to which framework control claims to cover it and where the gap is.", "required": true },
|
|
164
|
+
{ "skill": "compliance-theater", "purpose": "Run the theater test on the org's least-privilege/least-functionality attestation.", "required": true },
|
|
165
|
+
{ "skill": "policy-exception-gen", "purpose": "Generate auditor-ready exception language for primitives that cannot be removed within the compliance window.", "skip_if": "close.exception_generation.trigger_condition == false", "required": false }
|
|
166
|
+
],
|
|
167
|
+
"token_budget": {
|
|
168
|
+
"estimated_total": 20000,
|
|
169
|
+
"breakdown": {
|
|
170
|
+
"govern": 2400,
|
|
171
|
+
"direct": 1600,
|
|
172
|
+
"look": 2400,
|
|
173
|
+
"detect": 3200,
|
|
174
|
+
"analyze": 4400,
|
|
175
|
+
"validate": 3600,
|
|
176
|
+
"close": 2400
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
|
|
181
|
+
"look": {
|
|
182
|
+
"artifacts": [
|
|
183
|
+
{
|
|
184
|
+
"id": "listening-sockets",
|
|
185
|
+
"type": "process_list",
|
|
186
|
+
"source": "ss -tlnp 2>/dev/null || netstat -tlnp 2>/dev/null",
|
|
187
|
+
"description": "Listening TCP sockets with owning PID/program. Required for T1133 (External Remote Services) and T1021 (Remote Services) surface mapping.",
|
|
188
|
+
"required": true,
|
|
189
|
+
"air_gap_alternative": "Read /proc/net/tcp and /proc/net/tcp6 directly and resolve socket inodes via /proc/*/fd/*; mark process-name resolution as best-effort."
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
"id": "listening-sockets-udp",
|
|
193
|
+
"type": "process_list",
|
|
194
|
+
"source": "ss -ulnp 2>/dev/null || netstat -ulnp 2>/dev/null",
|
|
195
|
+
"description": "Listening UDP sockets. Required to spot rogue services (DNS rebinding, cobalt-strike-style C2 listeners).",
|
|
196
|
+
"required": false
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
"id": "sudo-rules",
|
|
200
|
+
"type": "config_file",
|
|
201
|
+
"source": "sudo -l -n 2>/dev/null; cat /etc/sudoers; ls /etc/sudoers.d/",
|
|
202
|
+
"description": "Sudo rules for current user + visible sudoers files. NOPASSWD entries and wildcards are the primary LPE drivers.",
|
|
203
|
+
"required": true,
|
|
204
|
+
"air_gap_alternative": "Read /etc/sudoers + /etc/sudoers.d/* directly if sudo binary unavailable; mark per-user resolution as best-effort."
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
"id": "suid-binaries",
|
|
208
|
+
"type": "process_list",
|
|
209
|
+
"source": "find / -xdev \\( -perm -4000 -o -perm -2000 \\) -type f 2>/dev/null",
|
|
210
|
+
"description": "SUID/SGID binaries across local filesystems. Non-baseline entries are LPE primitives (gtfobins-shaped).",
|
|
211
|
+
"required": true,
|
|
212
|
+
"air_gap_alternative": "Compare against a snapshotted baseline from /var/lib/exceptd/suid-baseline.txt if present; otherwise emit full inventory as evidence."
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
"id": "cron-entries",
|
|
216
|
+
"type": "config_file",
|
|
217
|
+
"source": "ls -la /etc/cron.{hourly,daily,weekly,monthly,d}/ /var/spool/cron/ 2>/dev/null; cat /etc/crontab",
|
|
218
|
+
"description": "System crontab + drop-in cron directories. T1053.003 (Scheduled Task — Cron) primary persistence vector.",
|
|
219
|
+
"required": true
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
"id": "user-cron",
|
|
223
|
+
"type": "config_file",
|
|
224
|
+
"source": "for u in $(getent passwd | awk -F: '{print $1}'); do crontab -u $u -l 2>/dev/null && echo \"---$u---\"; done",
|
|
225
|
+
"description": "Per-user crontabs. Often missed in policy reviews because they're not in /etc.",
|
|
226
|
+
"required": false
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
"id": "systemd-timers",
|
|
230
|
+
"type": "config_file",
|
|
231
|
+
"source": "systemctl list-timers --all --no-pager 2>/dev/null; systemctl list-unit-files --type=timer --no-pager 2>/dev/null",
|
|
232
|
+
"description": "Systemd timers — modern scheduled-task primitive. T1053.006. Often outside legacy cron review scope.",
|
|
233
|
+
"required": true,
|
|
234
|
+
"air_gap_alternative": "ls /etc/systemd/system/*.timer /usr/lib/systemd/system/*.timer if systemctl unavailable."
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
"id": "systemd-services",
|
|
238
|
+
"type": "config_file",
|
|
239
|
+
"source": "systemctl list-unit-files --type=service --state=enabled --no-pager 2>/dev/null",
|
|
240
|
+
"description": "Enabled systemd services. T1543.002 (Create or Modify System Process — Systemd Service) persistence target.",
|
|
241
|
+
"required": true
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
"id": "process-tree",
|
|
245
|
+
"type": "process_list",
|
|
246
|
+
"source": "ps -eo pid,ppid,user,cmd --forest 2>/dev/null || ps auxf",
|
|
247
|
+
"description": "Process tree with parent linkage. Required to spot rogue PPID 1 (orphan reparented to init = classic implant signal) and unexpected privileged children.",
|
|
248
|
+
"required": true
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
"id": "passwd-shadow-baseline",
|
|
252
|
+
"type": "config_file",
|
|
253
|
+
"source": "getent passwd; awk -F: '$3 == 0' /etc/passwd",
|
|
254
|
+
"description": "Local users + any UID=0 accounts other than root. UID=0 duplicates are T1136.001 (Create Account — Local) persistence.",
|
|
255
|
+
"required": true
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
"id": "world-writable-paths",
|
|
259
|
+
"type": "config_file",
|
|
260
|
+
"source": "find /etc /usr/local /opt -xdev -type f -perm -o+w 2>/dev/null | head -200",
|
|
261
|
+
"description": "World-writable files in trusted path locations. T1574.005 (Hijack Execution Flow — Executable Installer File Permissions Weakness).",
|
|
262
|
+
"required": false
|
|
263
|
+
}
|
|
264
|
+
],
|
|
265
|
+
"collection_scope": {
|
|
266
|
+
"time_window": "current",
|
|
267
|
+
"asset_scope": "local_host",
|
|
268
|
+
"depth": "standard",
|
|
269
|
+
"sampling": "single-host point-in-time snapshot; re-collect on regression_trigger events (post-deploy, monthly cadence, new-CVE-in-class)"
|
|
270
|
+
},
|
|
271
|
+
"environment_assumptions": [
|
|
272
|
+
{
|
|
273
|
+
"assumption": "host.platform == 'linux'",
|
|
274
|
+
"if_false": "Skip playbook with visibility_gap=platform_unsupported. macOS launchd and Windows scheduled-task inventory require different artifact shapes."
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
"assumption": "agent runs with read access to /etc, /proc, /var/spool/cron, and can list /home/*/.crontab",
|
|
278
|
+
"if_false": "Downgrade affected artifacts to inconclusive; emit a privilege-scoped visibility gap rather than a clean negative."
|
|
279
|
+
},
|
|
280
|
+
{
|
|
281
|
+
"assumption": "host has a SUID baseline at /var/lib/exceptd/suid-baseline.txt (from prior run or vendor install)",
|
|
282
|
+
"if_false": "First run on this host. Emit current SUID inventory as the baseline + flag as 'baseline-establishing run'; do not call non-baseline entries findings."
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
"assumption": "host is not running inside a hardened container with /proc masked",
|
|
286
|
+
"if_false": "Many inventory artifacts will be empty by design (gVisor, kata-containers, hardened systemd-nspawn). Mark as container-restricted and refer to container playbook for the meaningful surface."
|
|
287
|
+
}
|
|
288
|
+
],
|
|
289
|
+
"fallback_if_unavailable": [
|
|
290
|
+
{ "artifact_id": "listening-sockets", "fallback_action": "use_compensating_artifact", "confidence_impact": "low" },
|
|
291
|
+
{ "artifact_id": "sudo-rules", "fallback_action": "use_compensating_artifact", "confidence_impact": "medium" },
|
|
292
|
+
{ "artifact_id": "suid-binaries", "fallback_action": "use_compensating_artifact", "confidence_impact": "low" },
|
|
293
|
+
{ "artifact_id": "cron-entries", "fallback_action": "mark_inconclusive", "confidence_impact": "medium" },
|
|
294
|
+
{ "artifact_id": "systemd-timers", "fallback_action": "mark_inconclusive", "confidence_impact": "medium" },
|
|
295
|
+
{ "artifact_id": "process-tree", "fallback_action": "escalate_to_human", "confidence_impact": "high" },
|
|
296
|
+
{ "artifact_id": "passwd-shadow-baseline", "fallback_action": "escalate_to_human", "confidence_impact": "high" }
|
|
297
|
+
]
|
|
298
|
+
},
|
|
299
|
+
|
|
300
|
+
"detect": {
|
|
301
|
+
"indicators": [
|
|
302
|
+
{
|
|
303
|
+
"id": "non-baseline-suid",
|
|
304
|
+
"type": "file_path",
|
|
305
|
+
"value": "Any SUID/SGID binary not in /var/lib/exceptd/suid-baseline.txt AND not in the distro's official SUID list for the running version",
|
|
306
|
+
"description": "Non-baseline SUID binary. Could be (a) post-install drift, (b) attacker-planted persistence, or (c) third-party install. All three need a finding.",
|
|
307
|
+
"confidence": "high",
|
|
308
|
+
"deterministic": false,
|
|
309
|
+
"attack_ref": "T1548.001"
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
"id": "sudoers-nopasswd-wildcard",
|
|
313
|
+
"type": "log_pattern",
|
|
314
|
+
"value": "sudoers entry matching regex `NOPASSWD:\\s*(ALL|/[^,]*\\*)` for non-root user",
|
|
315
|
+
"description": "Wildcard NOPASSWD entry for a non-root user is an LPE primitive. Indistinguishable on the host from an attacker-planted backdoor.",
|
|
316
|
+
"confidence": "deterministic",
|
|
317
|
+
"deterministic": true,
|
|
318
|
+
"attack_ref": "T1548.003"
|
|
319
|
+
},
|
|
320
|
+
{
|
|
321
|
+
"id": "duplicate-uid-zero",
|
|
322
|
+
"type": "log_pattern",
|
|
323
|
+
"value": "/etc/passwd contains more than one entry with UID 0",
|
|
324
|
+
"description": "Multiple UID=0 accounts. T1136.001 persistence pattern. Outside legitimate installs.",
|
|
325
|
+
"confidence": "deterministic",
|
|
326
|
+
"deterministic": true,
|
|
327
|
+
"attack_ref": "T1136.001"
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
"id": "listening-socket-unknown-bind",
|
|
331
|
+
"type": "network_pattern",
|
|
332
|
+
"value": "Listening TCP/UDP socket bound to 0.0.0.0 or :: on a port not in the host's documented service inventory",
|
|
333
|
+
"description": "Unknown external-bound listener. Could be a forgotten test service, a rogue agent, or a C2 listener.",
|
|
334
|
+
"confidence": "high",
|
|
335
|
+
"deterministic": false,
|
|
336
|
+
"attack_ref": "T1133"
|
|
337
|
+
},
|
|
338
|
+
{
|
|
339
|
+
"id": "cron-or-timer-outside-policy",
|
|
340
|
+
"type": "file_path",
|
|
341
|
+
"value": "Cron entry in /etc/cron.{d,hourly,daily,weekly,monthly} OR systemd timer in /etc/systemd/system/ NOT in the org's scheduled-task inventory",
|
|
342
|
+
"description": "Scheduled task not in change-management record. T1053.003 / T1053.006 persistence.",
|
|
343
|
+
"confidence": "high",
|
|
344
|
+
"deterministic": false,
|
|
345
|
+
"attack_ref": "T1053.003"
|
|
346
|
+
},
|
|
347
|
+
{
|
|
348
|
+
"id": "world-writable-in-trusted-path",
|
|
349
|
+
"type": "file_path",
|
|
350
|
+
"value": "World-writable file under /etc, /usr/local/bin, /usr/local/sbin, /opt",
|
|
351
|
+
"description": "Hijack-execution-flow primitive. Any non-root process can replace the file.",
|
|
352
|
+
"confidence": "deterministic",
|
|
353
|
+
"deterministic": true,
|
|
354
|
+
"attack_ref": "T1574.005"
|
|
355
|
+
},
|
|
356
|
+
{
|
|
357
|
+
"id": "orphan-privileged-process",
|
|
358
|
+
"type": "process_name",
|
|
359
|
+
"value": "Process running as UID 0 with PPID 1 AND parent != systemd/init AND executable path under /tmp, /dev/shm, /var/tmp, /home",
|
|
360
|
+
"description": "Privileged orphan in a writable temp path. Common implant shape.",
|
|
361
|
+
"confidence": "deterministic",
|
|
362
|
+
"deterministic": true,
|
|
363
|
+
"attack_ref": "T1055"
|
|
364
|
+
}
|
|
365
|
+
],
|
|
366
|
+
"false_positive_profile": [
|
|
367
|
+
{
|
|
368
|
+
"indicator_id": "non-baseline-suid",
|
|
369
|
+
"benign_pattern": "Third-party package installed via vendor RPM/DEB that ships its own SUID helper (e.g. some KVM/Docker tools, /opt/-installed enterprise software).",
|
|
370
|
+
"distinguishing_test": "For each non-baseline SUID, run `rpm -qf <path>` or `dpkg -S <path>`. If the file is owned by an installed package and the package's vendor public key signed it, downgrade to medium confidence + emit a 'package-owned non-baseline SUID' finding rather than 'unknown SUID'. If the file is owned by no package OR by a package whose signature does not verify, escalate to high."
|
|
371
|
+
},
|
|
372
|
+
{
|
|
373
|
+
"indicator_id": "sudoers-nopasswd-wildcard",
|
|
374
|
+
"benign_pattern": "Ops automation user (Ansible, Puppet, Chef bootstrap account) with NOPASSWD entry, intended and documented in change management.",
|
|
375
|
+
"distinguishing_test": "Check whether the user account appears in the org's automation-accounts inventory AND whether the wildcard scope is bounded (e.g. NOPASSWD: /usr/bin/systemctl, not NOPASSWD: ALL). NOPASSWD: ALL with no scope is theater regardless of intent."
|
|
376
|
+
},
|
|
377
|
+
{
|
|
378
|
+
"indicator_id": "listening-socket-unknown-bind",
|
|
379
|
+
"benign_pattern": "Service discovery / observability agent (Prometheus node-exporter, Datadog agent, Consul) bound to 0.0.0.0 because operator deployed default config.",
|
|
380
|
+
"distinguishing_test": "Resolve the PID owning the socket back to a package via /proc/<pid>/exe → `rpm -qf` or `dpkg -S`. If owned by a known observability vendor's package AND port matches the vendor's documented default, downgrade to medium and emit a 'wide-bind on observability service' hardening finding instead of a rogue-listener finding."
|
|
381
|
+
},
|
|
382
|
+
{
|
|
383
|
+
"indicator_id": "cron-or-timer-outside-policy",
|
|
384
|
+
"benign_pattern": "Distro-shipped cron entries (logrotate, mlocate, apt-daily, fwupd-refresh) that are not in the org's user-managed inventory because they were never reviewed.",
|
|
385
|
+
"distinguishing_test": "Check whether the cron/timer unit file is owned by a distro package (`dpkg -S` / `rpm -qf`). If yes, classify as 'distro-baseline' and only flag if the unit calls an executable under /tmp, /var/tmp, /dev/shm, /home, or a world-writable path."
|
|
386
|
+
}
|
|
387
|
+
],
|
|
388
|
+
"minimum_signal": {
|
|
389
|
+
"detected": "At least one deterministic indicator fires (sudoers-nopasswd-wildcard, duplicate-uid-zero, world-writable-in-trusted-path, orphan-privileged-process) OR two or more high-confidence indicators fire on the same host.",
|
|
390
|
+
"inconclusive": "Multiple artifacts marked inconclusive due to access restrictions (containerized host with masked /proc, agent without read access to /etc/sudoers); cannot distinguish clean state from undetectable state.",
|
|
391
|
+
"not_detected": "All required artifacts captured AND no deterministic indicators fired AND no more than one high-confidence indicator with a confirmed benign distinguishing test."
|
|
392
|
+
}
|
|
393
|
+
},
|
|
394
|
+
|
|
395
|
+
"analyze": {
|
|
396
|
+
"rwep_inputs": [
|
|
397
|
+
{ "signal_id": "non-baseline-suid", "rwep_factor": "active_exploitation", "weight": 15, "notes": "If the SUID maps to a gtfobins-listed primitive, weaponization is one shell command away — full weight." },
|
|
398
|
+
{ "signal_id": "non-baseline-suid", "rwep_factor": "public_poc", "weight": 10, "notes": "Gtfobins itself is the public PoC corpus for the named SUID primitive." },
|
|
399
|
+
{ "signal_id": "sudoers-nopasswd-wildcard", "rwep_factor": "active_exploitation", "weight": 25, "notes": "NOPASSWD wildcard is direct privilege escalation; treat as exploitation-already-confirmed." },
|
|
400
|
+
{ "signal_id": "duplicate-uid-zero", "rwep_factor": "active_exploitation", "weight": 30, "notes": "Duplicate root account is post-compromise persistence — treat as incident-already-occurred." },
|
|
401
|
+
{ "signal_id": "listening-socket-unknown-bind", "rwep_factor": "blast_radius", "weight": 10, "notes": "Each externally-bound unknown listener adds to blast radius." },
|
|
402
|
+
{ "signal_id": "cron-or-timer-outside-policy", "rwep_factor": "active_exploitation", "weight": 20, "notes": "Unrecognized scheduled task with attacker-shape path (/tmp, /dev/shm) = persistence indicator." },
|
|
403
|
+
{ "signal_id": "world-writable-in-trusted-path", "rwep_factor": "ai_weaponization", "weight": 10, "notes": "AI-assisted attackers exploit these reliably; the primitive is trivial to find with grep." },
|
|
404
|
+
{ "signal_id": "orphan-privileged-process", "rwep_factor": "active_exploitation", "weight": 25, "notes": "Implant-shaped process running. Treat as IR trigger." }
|
|
405
|
+
],
|
|
406
|
+
"blast_radius_model": {
|
|
407
|
+
"scope_question": "Given the inventoried runtime primitives, what is the realistic post-foothold pivot capability from this host?",
|
|
408
|
+
"scoring_rubric": [
|
|
409
|
+
{ "condition": "No deterministic indicators fired AND no non-baseline SUID AND no NOPASSWD entries beyond bounded automation account", "blast_radius_score": 1, "description": "Foothold buys the attacker a non-privileged shell with no obvious LPE path. Must chain a kernel CVE or 0-day." },
|
|
410
|
+
{ "condition": "One non-baseline SUID OR one unknown external listener fired, on a single-tenant host", "blast_radius_score": 2, "description": "One credible LPE primitive present; lateral pivot still requires separate exploit chain." },
|
|
411
|
+
{ "condition": "NOPASSWD wildcard or non-baseline SUID present AND host mounts shared filesystems OR has SSH key agent forwarding", "blast_radius_score": 3, "description": "Root → credential/keystore theft → adjacent hosts." },
|
|
412
|
+
{ "condition": "Deterministic indicator fired AND host is k8s node OR runs Vault/SSM agent OR has cloud-IMDS access with privileged IAM", "blast_radius_score": 4, "description": "Root → cluster admin or cloud account compromise via inventoried primitives alone, no kernel CVE chain needed." },
|
|
413
|
+
{ "condition": "Multiple deterministic indicators OR orphan-privileged-process fired AND host has cross-account trust OR runs identity-broker / bastion role", "blast_radius_score": 5, "description": "Active compromise already shaped on the host. Identity boundary collapse imminent or in progress." }
|
|
414
|
+
]
|
|
415
|
+
},
|
|
416
|
+
"compliance_theater_check": {
|
|
417
|
+
"claim": "Org enforces least privilege and least functionality per AC-6 / CM-7 / A.8.2 / A.8.18 / NIS2 Art.21(2)(i).",
|
|
418
|
+
"audit_evidence": "Annual privileged-access review record + change-management-approved service inventory + EDR alerting policy document.",
|
|
419
|
+
"reality_test": "On a sample of 3-5 production hosts, count: (a) sudoers NOPASSWD entries not bounded to a single command path, (b) SUID binaries that are not owned by a vendor-signed package, (c) listening sockets on 0.0.0.0 with PIDs not in the documented service inventory, (d) cron/timer units calling executables under /tmp, /var/tmp, /dev/shm, /home. If the sum across the sample is > 5, the attestation describes policy that the runtime does not implement.",
|
|
420
|
+
"theater_verdict_if_gap": "Least-privilege/least-functionality attestation is paper-only. The host inventory diverges materially from the policy. Either (a) shrink the runtime surface to match the attested policy, (b) update the policy + change-management record to match the inventoried reality, OR (c) generate a policy exception via policy-exception-gen documenting the divergence + compensating controls + time-bound remediation plan."
|
|
421
|
+
},
|
|
422
|
+
"framework_gap_mapping": [
|
|
423
|
+
{
|
|
424
|
+
"finding_id": "runtime-attack-surface-inventory",
|
|
425
|
+
"framework": "nist-800-53",
|
|
426
|
+
"claimed_control": "CM-7 — Least Functionality",
|
|
427
|
+
"actual_gap": "Specifies a policy outcome. No required programmatic enumeration of SUID/sudoers/cron/listener state. Accepts attestation as evidence.",
|
|
428
|
+
"required_control": "Add a CM-7 sub-control requiring quarterly programmatic enumeration of the runtime persistence surface (SUID, sudoers, cron, systemd timers, listening sockets) with diff against documented baseline. Drift requires change-management review."
|
|
429
|
+
},
|
|
430
|
+
{
|
|
431
|
+
"finding_id": "runtime-attack-surface-inventory",
|
|
432
|
+
"framework": "nist-800-53",
|
|
433
|
+
"claimed_control": "AC-6(7) — Review of User Privileges",
|
|
434
|
+
"actual_gap": "Annual cadence permits 364-day drift. No required enumeration tests; review evidence is attestation-shaped.",
|
|
435
|
+
"required_control": "Require quarterly enumeration of sudoers (NOPASSWD wildcards), SUID/SGID (non-baseline binaries), UID 0 duplicates. Annual policy review remains; quarterly state inventory becomes mandatory."
|
|
436
|
+
},
|
|
437
|
+
{
|
|
438
|
+
"finding_id": "runtime-attack-surface-inventory",
|
|
439
|
+
"framework": "iso-27001-2022",
|
|
440
|
+
"claimed_control": "A.8.2 — Privileged Access Rights",
|
|
441
|
+
"actual_gap": "Permits policy-shaped evidence. Does not require live enumeration of NOPASSWD entries or SUID inventory.",
|
|
442
|
+
"required_control": "Add a Statement of Applicability footnote requiring quarterly programmatic inventory of the privileged-access surface alongside the policy attestation. ISO does not need to mandate the tooling, but it should mandate the evidence shape."
|
|
443
|
+
},
|
|
444
|
+
{
|
|
445
|
+
"finding_id": "runtime-attack-surface-inventory",
|
|
446
|
+
"framework": "nis2",
|
|
447
|
+
"claimed_control": "Art.21(2)(i) — Access Control Policies",
|
|
448
|
+
"actual_gap": "Policy-shaped. Permits 'access control policy in place' as evidence without requiring host-state corroboration.",
|
|
449
|
+
"required_control": "Require essential/important entities to produce host-state corroboration alongside policy documentation when responding to competent-authority requests."
|
|
450
|
+
}
|
|
451
|
+
],
|
|
452
|
+
"escalation_criteria": [
|
|
453
|
+
{ "condition": "rwep >= 90 AND deterministic_indicator_fired == true", "action": "page_on_call" },
|
|
454
|
+
{ "condition": "blast_radius_score >= 4", "action": "trigger_playbook", "target_playbook": "cred-stores" },
|
|
455
|
+
{ "condition": "orphan-privileged-process fired OR duplicate-uid-zero fired", "action": "trigger_playbook", "target_playbook": "kernel" },
|
|
456
|
+
{ "condition": "compliance_theater_check.verdict == 'theater' AND jurisdiction_obligations contains 'EU'", "action": "notify_legal" }
|
|
457
|
+
]
|
|
458
|
+
},
|
|
459
|
+
|
|
460
|
+
"validate": {
|
|
461
|
+
"remediation_paths": [
|
|
462
|
+
{
|
|
463
|
+
"id": "remove-non-baseline-suid",
|
|
464
|
+
"description": "Strip SUID bit from non-baseline binaries that are not vendor-owned. Document the change; update /var/lib/exceptd/suid-baseline.txt.",
|
|
465
|
+
"preconditions": ["non_baseline_suid_count > 0", "ops_authorization_for_chmod == true"],
|
|
466
|
+
"priority": 1,
|
|
467
|
+
"compensating_controls": ["suid-baseline-tracked", "edr-alerts-on-suid-creation"],
|
|
468
|
+
"estimated_time_hours": 1
|
|
469
|
+
},
|
|
470
|
+
{
|
|
471
|
+
"id": "bound-sudo-nopasswd",
|
|
472
|
+
"description": "Replace NOPASSWD: ALL or NOPASSWD: /path/* entries with bounded command-specific entries (NOPASSWD: /usr/bin/systemctl restart $servicename pattern).",
|
|
473
|
+
"preconditions": ["sudoers_nopasswd_wildcard_count > 0", "automation_account_owner_authorization == true"],
|
|
474
|
+
"priority": 1,
|
|
475
|
+
"compensating_controls": ["sudoers-change-recorded", "automation-account-inventory-updated"],
|
|
476
|
+
"estimated_time_hours": 2
|
|
477
|
+
},
|
|
478
|
+
{
|
|
479
|
+
"id": "investigate-implant",
|
|
480
|
+
"description": "Hand off to IR playbook for any finding matching implant shape (duplicate-uid-zero, orphan-privileged-process, cron-or-timer with attacker-path executable). Do not delete on the live host — preserve for forensics.",
|
|
481
|
+
"preconditions": ["deterministic_implant_indicator_fired == true"],
|
|
482
|
+
"priority": 1,
|
|
483
|
+
"compensating_controls": ["host-isolated-from-network", "memory-snapshot-captured-pre-removal"],
|
|
484
|
+
"estimated_time_hours": 8
|
|
485
|
+
},
|
|
486
|
+
{
|
|
487
|
+
"id": "scope-listening-services",
|
|
488
|
+
"description": "Re-bind unknown 0.0.0.0 listeners to loopback OR to a documented internal-only interface. Add to documented service inventory if intentional; uninstall if not.",
|
|
489
|
+
"preconditions": ["unknown_external_listener_count > 0", "service_owner_identified == true"],
|
|
490
|
+
"priority": 2,
|
|
491
|
+
"compensating_controls": ["host-firewall-rule-added", "service-inventory-updated"],
|
|
492
|
+
"estimated_time_hours": 2
|
|
493
|
+
},
|
|
494
|
+
{
|
|
495
|
+
"id": "policy-exception",
|
|
496
|
+
"description": "If a finding cannot be remediated within the compliance window, generate an auditor-ready exception with bounded compensating controls.",
|
|
497
|
+
"preconditions": ["remediation_paths[1..4] blocked OR time_to_remediate > obligation_window"],
|
|
498
|
+
"priority": 4,
|
|
499
|
+
"compensating_controls": ["enhanced-edr-monitoring-on-affected-paths", "monthly-residual-risk-review"],
|
|
500
|
+
"estimated_time_hours": 8
|
|
501
|
+
}
|
|
502
|
+
],
|
|
503
|
+
"validation_tests": [
|
|
504
|
+
{
|
|
505
|
+
"id": "suid-diff-clean",
|
|
506
|
+
"test": "Re-run `find / -xdev -perm -4000 -type f 2>/dev/null` and diff against /var/lib/exceptd/suid-baseline.txt. Expect zero non-baseline entries.",
|
|
507
|
+
"expected_result": "Diff is empty OR only contains entries explicitly added to baseline via change management.",
|
|
508
|
+
"test_type": "functional"
|
|
509
|
+
},
|
|
510
|
+
{
|
|
511
|
+
"id": "sudo-nopasswd-bounded",
|
|
512
|
+
"test": "Grep /etc/sudoers + /etc/sudoers.d/* for `NOPASSWD:\\s*(ALL|/[^,]*\\*)` patterns. Expect zero matches.",
|
|
513
|
+
"expected_result": "No unbounded NOPASSWD entries remain; bounded entries (single command path, no wildcards) acceptable.",
|
|
514
|
+
"test_type": "negative"
|
|
515
|
+
},
|
|
516
|
+
{
|
|
517
|
+
"id": "listener-inventory-aligned",
|
|
518
|
+
"test": "Diff ss -tlnp output against the documented service inventory. Expect every listener to map to a documented service.",
|
|
519
|
+
"expected_result": "Every listening socket maps to a documented service OR is bound only to loopback.",
|
|
520
|
+
"test_type": "functional"
|
|
521
|
+
},
|
|
522
|
+
{
|
|
523
|
+
"id": "cron-timer-aligned",
|
|
524
|
+
"test": "Diff cron/timer inventory against the org's scheduled-task register. Expect no unrecognized entries.",
|
|
525
|
+
"expected_result": "Every cron/timer entry is in the scheduled-task register OR is a distro-baseline entry owned by a signed package.",
|
|
526
|
+
"test_type": "functional"
|
|
527
|
+
},
|
|
528
|
+
{
|
|
529
|
+
"id": "no-uid-zero-duplicates",
|
|
530
|
+
"test": "Run `awk -F: '$3 == 0' /etc/passwd | wc -l`. Expect exactly 1 (root).",
|
|
531
|
+
"expected_result": "Single UID=0 account.",
|
|
532
|
+
"test_type": "negative"
|
|
533
|
+
},
|
|
534
|
+
{
|
|
535
|
+
"id": "implant-removed-forensics-complete",
|
|
536
|
+
"test": "For any implant-shape finding handed to IR: confirm memory + disk forensics complete, implant removed, host re-imaged or formally re-attested.",
|
|
537
|
+
"expected_result": "IR ticket closed with evidence package; host re-attested.",
|
|
538
|
+
"test_type": "exploit_replay"
|
|
539
|
+
}
|
|
540
|
+
],
|
|
541
|
+
"residual_risk_statement": {
|
|
542
|
+
"risk": "Even after remediation, the runtime attack surface accumulates drift faster than scheduled review. Any non-routine deploy can introduce a non-baseline SUID, an unintended NOPASSWD entry, or a new listener that the inventory misses until the next cadence run.",
|
|
543
|
+
"why_remains": "Drift is structural: every package install, ops automation change, or developer ad-hoc fix can change the surface. The compensating control is cadence + EDR + change-management discipline, not absence of drift.",
|
|
544
|
+
"acceptance_level": "manager",
|
|
545
|
+
"compensating_controls_in_place": ["quarterly-runtime-inventory", "edr-suid-creation-alerting", "change-management-on-sudoers", "documented-service-inventory"]
|
|
546
|
+
},
|
|
547
|
+
"evidence_requirements": [
|
|
548
|
+
{
|
|
549
|
+
"evidence_type": "scan_report",
|
|
550
|
+
"description": "Full inventory output (listeners, sudo rules, SUID list, cron/timer list, process tree, world-writable list) with timestamps and host identity.",
|
|
551
|
+
"retention_period": "1_year",
|
|
552
|
+
"framework_satisfied": ["nist-800-53-CM-7", "nist-800-53-AC-6", "iso-27001-2022-A.8.2"]
|
|
553
|
+
},
|
|
554
|
+
{
|
|
555
|
+
"evidence_type": "config_diff",
|
|
556
|
+
"description": "Diff of /etc/sudoers + /etc/sudoers.d/* before and after remediation; SUID baseline before and after.",
|
|
557
|
+
"retention_period": "7_years",
|
|
558
|
+
"framework_satisfied": ["nist-800-53-CM-3", "iso-27001-2022-A.8.32"]
|
|
559
|
+
},
|
|
560
|
+
{
|
|
561
|
+
"evidence_type": "ticket_reference",
|
|
562
|
+
"description": "Change-management ticket references for each sudoers / SUID / listener modification, with approver and approval timestamp.",
|
|
563
|
+
"retention_period": "7_years",
|
|
564
|
+
"framework_satisfied": ["soc2-cc8.1", "iso-27001-2022-A.8.32"]
|
|
565
|
+
},
|
|
566
|
+
{
|
|
567
|
+
"evidence_type": "attestation",
|
|
568
|
+
"description": "Signed exceptd attestation: host identity, scan timestamp, indicator firing counts, RWEP at detection, RWEP post-remediation.",
|
|
569
|
+
"retention_period": "7_years",
|
|
570
|
+
"framework_satisfied": ["nist-800-53-CA-7", "iso-27001-2022-A.5.36", "nis2-art21-2c"]
|
|
571
|
+
}
|
|
572
|
+
],
|
|
573
|
+
"regression_trigger": [
|
|
574
|
+
{ "condition": "post_major_deploy", "interval": "on_event" },
|
|
575
|
+
{ "condition": "monthly", "interval": "30d" },
|
|
576
|
+
{ "condition": "new_cve_in_class == true", "interval": "on_event" },
|
|
577
|
+
{ "condition": "ir_incident_on_host == true", "interval": "on_event" }
|
|
578
|
+
]
|
|
579
|
+
},
|
|
580
|
+
|
|
581
|
+
"close": {
|
|
582
|
+
"evidence_package": {
|
|
583
|
+
"bundle_format": "json",
|
|
584
|
+
"contents": ["scan_report", "config_diff", "ticket_reference", "attestation", "framework_gap_mapping", "compliance_theater_verdict", "residual_risk_statement"],
|
|
585
|
+
"destination": "local_only",
|
|
586
|
+
"signed": true
|
|
587
|
+
},
|
|
588
|
+
"learning_loop": {
|
|
589
|
+
"enabled": true,
|
|
590
|
+
"lesson_template": {
|
|
591
|
+
"attack_vector": "Post-foothold reconnaissance via $primitive_class (NOPASSWD wildcard / non-baseline SUID / unknown listener / unsupervised cron/timer / orphan privileged process).",
|
|
592
|
+
"control_gap": "Least-privilege/least-functionality controls (AC-6, CM-7, A.8.2) are policy-shaped, not state-shaped. They accept attestation evidence and do not require enumeration of the specific runtime primitives an attacker uses post-foothold.",
|
|
593
|
+
"framework_gap": "NIST + ISO + NIS2 vulnerability + access-control frameworks do not name the persistence-vector inventory as required evidence. Frameworks lag the attacker's standard 60-180 second post-foothold enumeration by an entire annual review cycle.",
|
|
594
|
+
"new_control_requirement": "Mandate quarterly programmatic enumeration of the runtime persistence surface (sudoers NOPASSWD, SUID baseline diff, listener vs service inventory, cron/timer vs scheduled-task register, UID 0 duplicates, world-writable trusted paths) with documented baseline + drift review."
|
|
595
|
+
},
|
|
596
|
+
"feeds_back_to_skills": ["attack-surface-pentest", "framework-gap-analysis", "compliance-theater", "incident-response-playbook"]
|
|
597
|
+
},
|
|
598
|
+
"notification_actions": [
|
|
599
|
+
{
|
|
600
|
+
"obligation_ref": "EU/NIS2 Art.23 24h",
|
|
601
|
+
"deadline": "computed_at_runtime",
|
|
602
|
+
"recipient": "internal_legal",
|
|
603
|
+
"evidence_attached": ["affected_host_inventory", "lateral_movement_path_assessment", "interim_mitigation_record"],
|
|
604
|
+
"draft_notification": "NIS2 Art.23 24-hour early-warning notification: Runtime attack-surface inventory on ${affected_host_count} host(s) revealed deterministic compromise indicators including ${indicator_summary}. Affected host(s) isolated pending IR. Interim mitigation: ${interim_mitigation}. Full assessment to follow within 72 hours per Art.23(4)."
|
|
605
|
+
},
|
|
606
|
+
{
|
|
607
|
+
"obligation_ref": "EU/DORA Art.19 4h",
|
|
608
|
+
"deadline": "computed_at_runtime",
|
|
609
|
+
"recipient": "internal_legal",
|
|
610
|
+
"evidence_attached": ["initial_notification", "ict_third_party_dependencies"],
|
|
611
|
+
"draft_notification": "DORA Art.19 initial notification: Major ICT-related incident — runtime compromise indicators detected on financial-entity host(s). Indicators: ${indicator_summary}. ICT third-party dependencies affected: ${ict_dependencies}. Full classification + impact assessment to follow within statutory windows."
|
|
612
|
+
}
|
|
613
|
+
],
|
|
614
|
+
"exception_generation": {
|
|
615
|
+
"trigger_condition": "remediation_blocked == true OR (sudoers_change_requires_vendor_coordination == true AND vendor_eta > obligation_window)",
|
|
616
|
+
"exception_template": {
|
|
617
|
+
"scope": "Runtime primitives ${primitive_inventory} on ${affected_host_count} host(s); remediation paths 1-3 blocked or pending vendor coordination.",
|
|
618
|
+
"duration": "until_vendor_patch",
|
|
619
|
+
"compensating_controls": ["edr-monitoring-on-affected-primitive", "enhanced-audit-logging-on-affected-binary", "host-firewall-rule-added", "change-management-freeze-on-affected-paths"],
|
|
620
|
+
"risk_acceptance_owner": "manager",
|
|
621
|
+
"auditor_ready_language": "Pursuant to ${framework_id} ${control_id}, the organization documents a time-bound risk acceptance for runtime attack-surface primitive(s) ${primitive_inventory} on ${affected_host_count} host(s). Primitive class: ${primitive_class}. Compensating controls in place: ${compensating_controls}. Residual RWEP post-compensation: ${rwep_post_compensation}. Risk accepted by ${manager_name} on ${acceptance_date}. Time-bound until ${duration_expiry}. Detection coverage during the exception window is provided by ${detection_controls}. The exception will be re-evaluated on (a) vendor patch publication, (b) the listed expiry date, OR (c) a new exploitation indicator firing on the affected primitive — whichever is first."
|
|
622
|
+
}
|
|
623
|
+
},
|
|
624
|
+
"regression_schedule": {
|
|
625
|
+
"next_run": "computed_at_runtime",
|
|
626
|
+
"trigger": "both",
|
|
627
|
+
"notify_on_skip": true
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
},
|
|
631
|
+
|
|
632
|
+
"directives": [
|
|
633
|
+
{
|
|
634
|
+
"id": "full-runtime-inventory",
|
|
635
|
+
"title": "Full Linux runtime attack-surface inventory — listeners, sudo, SUID, cron/timers, process tree",
|
|
636
|
+
"applies_to": { "always": true }
|
|
637
|
+
},
|
|
638
|
+
{
|
|
639
|
+
"id": "post-foothold-imitation",
|
|
640
|
+
"title": "Targeted directive — run the attacker's standard 60-180s post-foothold enumeration sweep",
|
|
641
|
+
"applies_to": { "attack_technique": "T1078" }
|
|
642
|
+
},
|
|
643
|
+
{
|
|
644
|
+
"id": "persistence-surface",
|
|
645
|
+
"title": "Persistence-vector inventory — cron/systemd timer + sudoers + UID 0 duplicates",
|
|
646
|
+
"applies_to": { "attack_technique": "T1543" }
|
|
647
|
+
}
|
|
648
|
+
]
|
|
649
|
+
}
|