Skip to main content

attack/domain/
technique.rs

1use serde::{Deserialize, Serialize};
2use stix_rs::{CommonProperties, StixObject, KillChainPhase};
3use crate::domain::AttackObject;
4
5/// Represents a MITRE ATT&CK Technique (attack-pattern).
6#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
7pub struct Technique {
8    #[serde(flatten)]
9    pub common: CommonProperties,
10
11    pub name: String,
12    pub description: Option<String>,
13
14    #[serde(default, rename = "x_mitre_is_subtechnique")]
15    pub is_subtechnique: bool,
16
17    #[serde(default, rename = "x_mitre_platforms")]
18    pub platforms: Vec<String>,
19
20    #[serde(default)]
21    pub kill_chain_phases: Vec<KillChainPhase>,
22
23    // Extended ATT&CK fields
24    #[serde(default, rename = "x_mitre_version")]
25    pub version: Option<String>,
26
27    #[serde(default, rename = "x_mitre_detection")]
28    pub detection: Option<String>,
29
30    #[serde(default, rename = "x_mitre_permissions_required")]
31    pub permissions_required: Vec<String>,
32
33    #[serde(default, rename = "x_mitre_effective_permissions")]
34    pub effective_permissions: Vec<String>,
35
36    #[serde(default, rename = "x_mitre_defense_bypassed")]
37    pub defense_bypassed: Vec<String>,
38
39    #[serde(default, rename = "x_mitre_system_requirements")]
40    pub system_requirements: Vec<String>,
41
42    #[serde(default, rename = "x_mitre_network_requirements")]
43    pub network_requirements: Option<bool>,
44
45    #[serde(default, rename = "x_mitre_remote_support")]
46    pub remote_support: Option<bool>,
47
48    #[serde(default, rename = "x_mitre_data_sources")]
49    pub data_sources_legacy: Vec<String>,
50
51    #[serde(default, rename = "x_mitre_impact_type")]
52    pub impact_type: Vec<String>,
53
54    #[serde(default, rename = "x_mitre_contributors")]
55    pub contributors: Vec<String>,
56
57    #[serde(default, rename = "x_mitre_domains")]
58    pub domains: Vec<String>,
59}
60
61impl Technique {
62    /// Extracts the ATT&CK T-Code (e.g., "T1566" or "T1566.001") from external references.
63    pub fn tcode(&self) -> Option<&str> {
64        self.common.external_references.as_ref()?
65            .iter()
66            .find(|r| r.source_name == "mitre-attack" || r.source_name == "mitre-mobile-attack" || r.source_name == "mitre-ics-attack")?
67            .external_id.as_deref()
68    }
69
70    /// Gets the tactics this technique belongs to from kill chain phases.
71    pub fn tactics(&self) -> Vec<&str> {
72        self.kill_chain_phases.iter()
73            .filter(|phase| phase.name == "mitre-attack" || phase.name == "mitre-mobile-attack" || phase.name == "mitre-ics-attack")
74            .map(|phase| phase.phase_name.as_str())
75            .collect()
76    }
77}
78
79impl StixObject for Technique {
80    fn id(&self) -> &str {
81        &self.common.id
82    }
83
84    fn type_(&self) -> &str {
85        &self.common.r#type
86    }
87
88    fn created(&self) -> chrono::DateTime<chrono::Utc> {
89        self.common.created
90    }
91}
92
93impl AttackObject for Technique {
94    fn name(&self) -> &str {
95        &self.name
96    }
97
98    fn description(&self) -> Option<&str> {
99        self.description.as_deref()
100    }
101
102    fn revoked(&self) -> bool {
103        self.common.revoked.unwrap_or(false)
104    }
105
106    fn deprecated(&self) -> bool {
107        self.common.custom_properties.get("x_mitre_deprecated").and_then(|v| v.as_bool()).unwrap_or(false)
108    }
109}