1use std::collections::HashMap;
2use std::path::Path;
3use serde::Deserialize;
4use crate::domain::{*, software};
5use crate::error::Result;
6use stix_rs::{Relationship, StixObject};
7
8#[derive(Debug, Default)]
9pub struct AttackStore {
10 tactics: HashMap<String, Tactic>,
11 techniques: HashMap<String, Technique>,
12 groups: HashMap<String, Group>,
13 software: HashMap<String, Software>,
14 mitigations: HashMap<String, Mitigation>,
15 data_sources: HashMap<String, DataSource>,
16 data_components: HashMap<String, DataComponent>,
17 campaigns: HashMap<String, Campaign>,
18 matrices: HashMap<String, Matrix>,
19 analytics: HashMap<String, Analytic>,
20 detection_strategies: HashMap<String, DetectionStrategy>,
21
22 tcode_index: HashMap<String, String>, technique_to_group: HashMap<String, Vec<String>>,
25 software_to_group: HashMap<String, Vec<String>>,
26 technique_to_mitigation: HashMap<String, Vec<String>>,
27 technique_to_datasource: HashMap<String, Vec<String>>,
28 technique_to_datacomponent: HashMap<String, Vec<String>>,
29 subtechnique_to_technique: HashMap<String, String>,
30 campaign_to_group: HashMap<String, Vec<String>>,
31 campaign_to_technique: HashMap<String, Vec<String>>,
32 campaign_to_software: HashMap<String, Vec<String>>,
33 datacomponent_to_datasource: HashMap<String, String>,
34
35 replacements: HashMap<String, String>,
37}
38
39#[derive(Deserialize)]
40struct StixBundle {
41 objects: Vec<serde_json::Value>,
42}
43
44impl AttackStore {
45 pub fn new() -> Self {
46 Self::default()
47 }
48
49 pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
50 let file = std::fs::File::open(path)?;
51 let bundle: StixBundle = serde_json::from_reader(file)?;
52 Ok(Self::from_bundle(bundle))
53 }
54
55 pub fn from_url(url: &str) -> Result<Self> {
56 let resp = reqwest::blocking::get(url)?;
57 let bundle: StixBundle = resp.json()?;
58 Ok(Self::from_bundle(bundle))
59 }
60
61 fn from_bundle(bundle: StixBundle) -> Self {
62 let mut store = Self::new();
63 let mut relationships = Vec::new();
64
65 for obj in bundle.objects {
66 let type_ = obj.get("type").and_then(|v| v.as_str()).unwrap_or("");
67 match type_ {
68 "x-mitre-tactic" => {
69 if let Ok(tactic) = serde_json::from_value::<Tactic>(obj) {
70 store.tactics.insert(tactic.id().to_string(), tactic);
71 }
72 }
73 "attack-pattern" => {
74 if let Ok(technique) = serde_json::from_value::<Technique>(obj) {
75 if let Some(tcode) = technique.tcode() {
76 store.tcode_index.insert(tcode.to_string(), technique.id().to_string());
77 }
78 store.techniques.insert(technique.id().to_string(), technique);
79 }
80 }
81 "intrusion-set" => {
82 if let Ok(group) = serde_json::from_value::<Group>(obj) {
83 store.groups.insert(group.id().to_string(), group);
84 }
85 }
86 "malware" => {
87 if let Ok(malware) = serde_json::from_value::<software::Malware>(obj) {
88 let id = malware.id().to_string();
89 store.software.insert(id, Software::Malware(malware));
90 }
91 }
92 "tool" => {
93 if let Ok(tool) = serde_json::from_value::<software::Tool>(obj) {
94 let id = tool.id().to_string();
95 store.software.insert(id, Software::Tool(tool));
96 }
97 }
98 "course-of-action" => {
99 if let Ok(mitigation) = serde_json::from_value::<Mitigation>(obj) {
100 store.mitigations.insert(mitigation.id().to_string(), mitigation);
101 }
102 }
103 "x-mitre-data-source" => {
104 if let Ok(ds) = serde_json::from_value::<DataSource>(obj) {
105 store.data_sources.insert(ds.id().to_string(), ds);
106 }
107 }
108 "x-mitre-data-component" => {
109 if let Ok(dc) = serde_json::from_value::<DataComponent>(obj.clone()) {
110 if let Some(ds_ref) = &dc.data_source_ref {
112 store.datacomponent_to_datasource.insert(dc.id().to_string(), ds_ref.clone());
113 }
114 store.data_components.insert(dc.id().to_string(), dc);
115 }
116 }
117 "campaign" => {
118 if let Ok(campaign) = serde_json::from_value::<Campaign>(obj) {
119 store.campaigns.insert(campaign.id().to_string(), campaign);
120 }
121 }
122 "x-mitre-matrix" => {
123 if let Ok(matrix) = serde_json::from_value::<Matrix>(obj) {
124 store.matrices.insert(matrix.id().to_string(), matrix);
125 }
126 }
127 "x-mitre-analytic" => {
128 if let Ok(analytic) = serde_json::from_value::<Analytic>(obj) {
129 store.analytics.insert(analytic.id().to_string(), analytic);
130 }
131 }
132 "x-mitre-detection-strategy" => {
133 if let Ok(strategy) = serde_json::from_value::<DetectionStrategy>(obj) {
134 store.detection_strategies.insert(strategy.id().to_string(), strategy);
135 }
136 }
137 "relationship" => {
138 if let Ok(rel) = serde_json::from_value::<Relationship>(obj) {
139 relationships.push(rel);
140 }
141 }
142 _ => {}
143 }
144 }
145
146 for tech in store.techniques.values() {
148 if tech.is_subtechnique {
149 if let Some(tcode) = tech.tcode()
151 && let Some(parent_tcode) = tcode.split('.').next()
152 && let Some(parent_id) = store.tcode_index.get(parent_tcode)
153 {
154 store.subtechnique_to_technique.insert(tech.id().to_string(), parent_id.clone());
155 }
156 }
157 }
158
159 for rel in relationships {
161 match rel.relationship_type.as_str() {
162 "uses" => {
163 if rel.target_ref.starts_with("attack-pattern--") {
165 if rel.source_ref.starts_with("intrusion-set--") {
166 store.technique_to_group.entry(rel.target_ref.clone()).or_default().push(rel.source_ref.clone());
167 } else if rel.source_ref.starts_with("campaign--") {
168 store.campaign_to_technique.entry(rel.source_ref.clone()).or_default().push(rel.target_ref.clone());
169 }
170 }
171 else if rel.target_ref.starts_with("malware--") || rel.target_ref.starts_with("tool--") {
173 if rel.source_ref.starts_with("intrusion-set--") {
174 store.software_to_group.entry(rel.target_ref.clone()).or_default().push(rel.source_ref.clone());
175 } else if rel.source_ref.starts_with("campaign--") {
176 store.campaign_to_software.entry(rel.source_ref.clone()).or_default().push(rel.target_ref.clone());
177 }
178 }
179 }
180 "mitigates" => {
181 if rel.target_ref.starts_with("attack-pattern--") && rel.source_ref.starts_with("course-of-action--") {
182 store.technique_to_mitigation.entry(rel.target_ref.clone()).or_default().push(rel.source_ref.clone());
183 }
184 }
185 "detects" => {
186 if rel.target_ref.starts_with("attack-pattern--") {
187 if rel.source_ref.starts_with("x-mitre-data-source--") {
188 store.technique_to_datasource.entry(rel.target_ref.clone()).or_default().push(rel.source_ref.clone());
189 } else if rel.source_ref.starts_with("x-mitre-data-component--") {
190 store.technique_to_datacomponent.entry(rel.target_ref.clone()).or_default().push(rel.source_ref.clone());
191 }
192 }
193 }
194 "attributed-to" => {
195 if rel.source_ref.starts_with("campaign--") && rel.target_ref.starts_with("intrusion-set--") {
196 store.campaign_to_group.entry(rel.source_ref.clone()).or_default().push(rel.target_ref.clone());
197 }
198 }
199 "subtechnique-of" => {
200 if rel.source_ref.starts_with("attack-pattern--") && rel.target_ref.starts_with("attack-pattern--") {
201 store.subtechnique_to_technique.insert(rel.source_ref.clone(), rel.target_ref.clone());
202 }
203 }
204 "revoked-by" => {
205 store.replacements.insert(rel.source_ref.clone(), rel.target_ref.clone());
206 }
207 _ => {}
208 }
209 }
210
211 store
212 }
213
214 pub fn get_tactic(&self, id: &str) -> Option<&Tactic> { self.tactics.get(id) }
215 pub fn get_technique(&self, id: &str) -> Option<&Technique> { self.techniques.get(id) }
216 pub fn get_group(&self, id: &str) -> Option<&Group> { self.groups.get(id) }
217 pub fn get_software(&self, id: &str) -> Option<&Software> { self.software.get(id) }
218 pub fn get_mitigation(&self, id: &str) -> Option<&Mitigation> { self.mitigations.get(id) }
219 pub fn get_data_source(&self, id: &str) -> Option<&DataSource> { self.data_sources.get(id) }
220 pub fn get_data_component(&self, id: &str) -> Option<&DataComponent> { self.data_components.get(id) }
221 pub fn get_campaign(&self, id: &str) -> Option<&Campaign> { self.campaigns.get(id) }
222 pub fn get_matrix(&self, id: &str) -> Option<&Matrix> { self.matrices.get(id) }
223 pub fn get_analytic(&self, id: &str) -> Option<&Analytic> { self.analytics.get(id) }
224 pub fn get_detection_strategy(&self, id: &str) -> Option<&DetectionStrategy> { self.detection_strategies.get(id) }
225
226 pub fn get_replacement(&self, id: &str) -> Option<&str> {
227 self.replacements.get(id).map(|s| s.as_str())
228 }
229
230 pub fn get_technique_by_tcode(&self, tcode: &str) -> Option<&Technique> {
231 let id = self.tcode_index.get(tcode)?;
232 self.techniques.get(id)
233 }
234
235 pub fn get_groups_using_technique(&self, technique_id: &str) -> Vec<&Group> {
236 self.technique_to_group.get(technique_id)
237 .map(|ids| ids.iter().filter_map(|id| self.get_group(id)).collect())
238 .unwrap_or_default()
239 }
240
241 pub fn get_mitigations_for_technique(&self, technique_id: &str) -> Vec<&Mitigation> {
242 self.technique_to_mitigation.get(technique_id)
243 .map(|ids| ids.iter().filter_map(|id| self.get_mitigation(id)).collect())
244 .unwrap_or_default()
245 }
246
247 pub fn get_datasources_for_technique(&self, technique_id: &str) -> Vec<&DataSource> {
248 self.technique_to_datasource.get(technique_id)
249 .map(|ids| ids.iter().filter_map(|id| self.get_data_source(id)).collect())
250 .unwrap_or_default()
251 }
252
253 pub fn get_datacomponents_for_technique(&self, technique_id: &str) -> Vec<&DataComponent> {
254 self.technique_to_datacomponent.get(technique_id)
255 .map(|ids| ids.iter().filter_map(|id| self.get_data_component(id)).collect())
256 .unwrap_or_default()
257 }
258
259 pub fn get_datasource_for_component(&self, component_id: &str) -> Option<&DataSource> {
260 let ds_id = self.datacomponent_to_datasource.get(component_id)?;
261 self.data_sources.get(ds_id)
262 }
263
264 pub fn get_components_for_datasource(&self, datasource_id: &str) -> Vec<&DataComponent> {
265 self.datacomponent_to_datasource.iter()
266 .filter(|(_, ds_id)| ds_id.as_str() == datasource_id)
267 .filter_map(|(dc_id, _)| self.data_components.get(dc_id))
268 .collect()
269 }
270
271 pub fn get_parent_technique(&self, subtechnique_id: &str) -> Option<&Technique> {
272 let parent_id = self.subtechnique_to_technique.get(subtechnique_id)?;
273 self.techniques.get(parent_id)
274 }
275
276 pub fn get_subtechniques(&self, technique_id: &str) -> Vec<&Technique> {
277 self.subtechnique_to_technique.iter()
278 .filter(|(_, parent_id)| parent_id.as_str() == technique_id)
279 .filter_map(|(sub_id, _)| self.techniques.get(sub_id))
280 .collect()
281 }
282
283 pub fn get_groups_for_campaign(&self, campaign_id: &str) -> Vec<&Group> {
284 self.campaign_to_group.get(campaign_id)
285 .map(|ids| ids.iter().filter_map(|id| self.get_group(id)).collect())
286 .unwrap_or_default()
287 }
288
289 pub fn get_techniques_for_campaign(&self, campaign_id: &str) -> Vec<&Technique> {
290 self.campaign_to_technique.get(campaign_id)
291 .map(|ids| ids.iter().filter_map(|id| self.get_technique(id)).collect())
292 .unwrap_or_default()
293 }
294
295 pub fn get_software_for_campaign(&self, campaign_id: &str) -> Vec<&Software> {
296 self.campaign_to_software.get(campaign_id)
297 .map(|ids| ids.iter().filter_map(|id| self.get_software(id)).collect())
298 .unwrap_or_default()
299 }
300
301 pub fn find_technique_by_name(&self, name: &str) -> Option<&Technique> {
302 let name_lower = name.to_lowercase();
303
304 if let Some(t) = self.techniques.values()
306 .find(|t| t.name.to_lowercase() == name_lower) {
307 return Some(t);
308 }
309
310 self.techniques.values()
312 .find(|t| t.name.to_lowercase().contains(&name_lower))
313 }
314
315 pub fn tactics(&self) -> impl Iterator<Item = &Tactic> { self.tactics.values() }
316 pub fn techniques(&self) -> impl Iterator<Item = &Technique> { self.techniques.values() }
317 pub fn groups(&self) -> impl Iterator<Item = &Group> { self.groups.values() }
318 pub fn software(&self) -> impl Iterator<Item = &Software> { self.software.values() }
319 pub fn mitigations(&self) -> impl Iterator<Item = &Mitigation> { self.mitigations.values() }
320 pub fn data_sources(&self) -> impl Iterator<Item = &DataSource> { self.data_sources.values() }
321 pub fn data_components(&self) -> impl Iterator<Item = &DataComponent> { self.data_components.values() }
322 pub fn campaigns(&self) -> impl Iterator<Item = &Campaign> { self.campaigns.values() }
323 pub fn matrices(&self) -> impl Iterator<Item = &Matrix> { self.matrices.values() }
324 pub fn analytics(&self) -> impl Iterator<Item = &Analytic> { self.analytics.values() }
325 pub fn detection_strategies(&self) -> impl Iterator<Item = &DetectionStrategy> { self.detection_strategies.values() }
326}