feat(ttp): E.1.8 UKC bridge contract — ATTACK_TACTIC_TO_UKC + tactic_to_ukc_phase + inverse
This commit is contained in:
@@ -106,3 +106,71 @@ def stage_of(phase: UKCPhase) -> str:
|
|||||||
if phase in STAGE_THROUGH:
|
if phase in STAGE_THROUGH:
|
||||||
return "through"
|
return "through"
|
||||||
return "out"
|
return "out"
|
||||||
|
|
||||||
|
|
||||||
|
# MITRE ATT&CK tactic ID -> UKC phase. Covers the 14 enterprise tactics
|
||||||
|
# plus the four ICS tactics referenced by Appendix A.7 (Conpot, MQTT).
|
||||||
|
# Adding additional ICS tactics is a one-line addition. See
|
||||||
|
# TTP_TAGGING.md "UKC bridge".
|
||||||
|
ATTACK_TACTIC_TO_UKC: dict[str, UKCPhase] = {
|
||||||
|
# Enterprise
|
||||||
|
"TA0043": UKCPhase.RECONNAISSANCE, # Reconnaissance
|
||||||
|
"TA0042": UKCPhase.RESOURCE_DEVELOPMENT, # Resource Development
|
||||||
|
"TA0001": UKCPhase.DELIVERY, # Initial Access
|
||||||
|
"TA0002": UKCPhase.EXECUTION, # Execution
|
||||||
|
"TA0003": UKCPhase.PERSISTENCE, # Persistence
|
||||||
|
"TA0004": UKCPhase.PRIVILEGE_ESCALATION, # Privilege Escalation
|
||||||
|
"TA0005": UKCPhase.DEFENSE_EVASION, # Defense Evasion
|
||||||
|
"TA0006": UKCPhase.CREDENTIAL_ACCESS, # Credential Access
|
||||||
|
"TA0007": UKCPhase.DISCOVERY, # Discovery
|
||||||
|
"TA0008": UKCPhase.LATERAL_MOVEMENT, # Lateral Movement
|
||||||
|
"TA0009": UKCPhase.COLLECTION, # Collection
|
||||||
|
"TA0011": UKCPhase.COMMAND_AND_CONTROL, # Command and Control
|
||||||
|
"TA0010": UKCPhase.EXFILTRATION, # Exfiltration
|
||||||
|
"TA0040": UKCPhase.IMPACT, # Impact
|
||||||
|
# ICS — first-class projection so MQTT / Conpot / Modbus tags
|
||||||
|
# don't drop out of campaign rollups when the clusterer projects
|
||||||
|
# tactic to phase. ICS uses an independent tactic-ID range.
|
||||||
|
"TA0100": UKCPhase.COLLECTION, # ICS: Collection
|
||||||
|
"TA0102": UKCPhase.DISCOVERY, # ICS: Discovery
|
||||||
|
"TA0105": UKCPhase.IMPACT, # ICS: Impact
|
||||||
|
"TA0106": UKCPhase.IMPACT, # ICS: Impair Process Control
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def tactic_to_ukc_phase(tactic: str) -> UKCPhase | None:
|
||||||
|
"""Map an ATT&CK tactic ID (e.g. ``"TA0001"``) to a :class:`UKCPhase`.
|
||||||
|
|
||||||
|
Returns ``None`` for unknown tactics. The map is closed-over the
|
||||||
|
enterprise + ICS tactics referenced by the rule pack; a tactic
|
||||||
|
outside that set is a contributor bug, not a runtime miss.
|
||||||
|
"""
|
||||||
|
return ATTACK_TACTIC_TO_UKC.get(tactic)
|
||||||
|
|
||||||
|
|
||||||
|
# Inverse map, built once at import time. Several enterprise tactics
|
||||||
|
# would collide (e.g. both TA0009 and TA0100 map to COLLECTION); the
|
||||||
|
# enterprise tactic wins because it's listed first in
|
||||||
|
# ATTACK_TACTIC_TO_UKC, which dict comprehension preserves via
|
||||||
|
# last-write semantics — so we iterate in reverse to keep the FIRST
|
||||||
|
# occurrence per phase. Pre-target phases (RECONNAISSANCE,
|
||||||
|
# RESOURCE_DEVELOPMENT, WEAPONIZATION, SOCIAL_ENGINEERING) that are
|
||||||
|
# not in OBSERVABLE_PHASES are deliberately lossy on the inverse —
|
||||||
|
# TTP tags must never assign them, so projecting back to a tactic
|
||||||
|
# is undefined. See TTP_TAGGING.md §UKC bridge.
|
||||||
|
_UKC_TO_TACTIC: dict[UKCPhase, str] = {
|
||||||
|
phase: tactic
|
||||||
|
for tactic, phase in reversed(list(ATTACK_TACTIC_TO_UKC.items()))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def ukc_phase_to_tactic(phase: UKCPhase) -> str | None:
|
||||||
|
"""Map a :class:`UKCPhase` back to an ATT&CK tactic ID.
|
||||||
|
|
||||||
|
Lossy on phases outside :data:`OBSERVABLE_PHASES` — pre-target
|
||||||
|
phases (e.g. ``RECONNAISSANCE``, ``WEAPONIZATION``) return
|
||||||
|
``None`` because no rule emits them, so the inverse is
|
||||||
|
undefined by design. The CDD test in E.2.9 pins which phases
|
||||||
|
are lossy.
|
||||||
|
"""
|
||||||
|
return _UKC_TO_TACTIC.get(phase)
|
||||||
|
|||||||
@@ -2334,6 +2334,8 @@ unrelated events.
|
|||||||
|
|
||||||
**E.1.8 — UKC bridge contract** (`decnet/clustering/ukc.py`)
|
**E.1.8 — UKC bridge contract** (`decnet/clustering/ukc.py`)
|
||||||
|
|
||||||
|
**Status:** ✅ done.
|
||||||
|
|
||||||
- `ATTACK_TACTIC_TO_UKC: dict[str, UKCPhase]` — the static map
|
- `ATTACK_TACTIC_TO_UKC: dict[str, UKCPhase]` — the static map
|
||||||
from the body of this doc.
|
from the body of this doc.
|
||||||
- `def tactic_to_ukc_phase(tactic: str) -> UKCPhase | None`.
|
- `def tactic_to_ukc_phase(tactic: str) -> UKCPhase | None`.
|
||||||
|
|||||||
Reference in New Issue
Block a user