1use crate::CapecDisplayType::{
2 All, Alternates, Attacks, Consequences, Description, Flow, Indicators, Instances, Likelihood,
3 Mitigations, Name, Prerequisites, Resources, Severity, Skills, Taxonomy, Weaknesses,
4};
5use fenir::capec::{extract_mitre_attack_ids, CapecRecord};
6use fenir::cve::cve_from_cwe;
7use fenir::cwe::show_weaknesses;
8use fenir::database::{
9 criteria_analysis, db_mitre_path, execute_query, find_capec_by_id, find_cwe_by_id,
10 find_domain_and_criteria, parse_id_to_u32, prepare_query, search, text_or_null, MitreDatabase,
11};
12use fenir::facilities::{
13 configure_csv_reader, hierarchical_show, ProgressText, Standardize, Transform, Uppercase,
14};
15use fenir::package::{
16 concepts::simplify_cve_list_content,
17 errors::{write_error_message, write_error_message_and_exit},
18};
19use fenir::query::{build_query, QueryType::ByCwe};
20use fenir::section;
21use termint::widgets::ToSpan;
22use termint::{enums::Color};
23use treelog::builder::TreeBuilder;
24use treelog::Tree;
25
26use csv::Reader;
27
28use fenir::db_mitre;
29use fenir::os::parse_id;
30use rusqlite::{params, Connection, Rows};
31use std::error::Error;
32use std::fs::File;
33use std::path::Path;
34
35fn parse_to_id_format(elements: String, criteria: &str) -> String {
36 let mut result = String::new();
37 let prefix = criteria.split(" ").collect::<Vec<&str>>()[0];
38
39 let mut previous = 0;
40 elements.match_indices(criteria).for_each(|part| {
41 let start_at = part.0 + criteria.len();
42 result.push_str(&elements[previous..start_at]);
43 let next = &elements[start_at..];
44 let next_separator = next.find(':').unwrap();
45 let str_id = &next[0..next_separator];
46 let tmp_id = parse_id(
47 format!("{}-{}", prefix.to_lowercase(), str_id).as_str(),
48 prefix.to_lowercase().as_str(),
49 );
50 if let Some(id) = tmp_id {
51 let opt_capec = find_capec_by_id(id);
52 if let Some(capec) = opt_capec {
53 result
54 .push_str(format!("{}-{} - {}", prefix, str_id, capec.name.as_str()).as_str());
55 }
56 }
57
58 previous = start_at + next_separator;
59 });
60
61 result
62}
63
64pub fn inject_csv_into_capec_table(csv_file: &Path) -> Result<(), Box<dyn Error>> {
92 let mut reader = configure_csv_reader(csv_file)?;
94
95 let file_name = csv_file.file_name().unwrap().to_str().unwrap();
96 inject_capec_data(&mut reader, file_name)
97}
98
99fn inject_capec_data(reader: &mut Reader<File>, file: &str) -> Result<(), Box<dyn Error>> {
113 let records = reader.records().collect::<Result<Vec<_>, _>>()?;
114 let mut progress_bar = ProgressText::new(1, records.len(), file.to_string());
115
116 let connection = db_mitre!()?;
117 for record in records {
118 progress_bar.progress();
119
120 let _ = connection.execute("INSERT INTO capec (id, name, abstraction, status, description, alternates, likelihood, severity, attacks, flow, prerequisites, skills, resources, indicators, consequences, mitigations, instances, weaknesses, taxonomy)
121 VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, ?16, ?17, ?18, ?19)", params![record[0].parse::<i64>()?, record[1].to_string(), record[2].to_string(), record[3].to_string(), record[4].to_string(),
122 text_or_null(record[5].to_string()), text_or_null(record[6].to_string()), text_or_null(record[7].to_string()), text_or_null(record[8].to_string()), text_or_null(record[9].to_string()), text_or_null(record[10].to_string()), text_or_null(record[11].to_string()), text_or_null(record[12].to_string()), text_or_null(record[13].to_string()), text_or_null(record[14].to_string()),
123 text_or_null(record[15].to_string()), text_or_null(record[16].to_string()), text_or_null(record[17].to_string()), text_or_null(record[18].to_string())]);
124 }
125
126 println!("\nCAPEC data injected into SQLite database successfully.");
127 Ok(())
128}
129
130pub struct CapecTable;
131
132impl MitreDatabase for CapecTable {
133 fn table_definitions(&self) -> Vec<(&'static str, &'static str)> {
134 vec![(
135 "capec",
136 "CREATE TABLE IF NOT EXISTS capec (
137 id INTEGER PRIMARY KEY,
138 name TEXT NOT NULL,
139 abstraction TEXT,
140 status TEXT,
141 description TEXT,
142 alternates TEXT,
143 likelihood TEXT,
144 severity TEXT,
145 attacks TEXT,
146 flow TEXT,
147 prerequisites TEXT,
148 skills TEXT,
149 resources TEXT,
150 indicators TEXT,
151 consequences TEXT,
152 mitigations TEXT,
153 instances TEXT,
154 weaknesses TEXT,
155 taxonomy TEXT)",
156 )]
157 }
158
159 fn index_definitions(&self) -> Vec<(&'static str, &'static str)> {
160 vec![
161 (
162 "weaknesses_idx",
163 "CREATE INDEX IF NOT EXISTS weaknesses_idx ON capec (weaknesses)",
164 ),
165 ]
166 }
167}
168
169pub fn find_capec_list(cwe_id: u32) -> Result<Vec<Option<CapecRecord>>, Box<dyn Error>> {
187 let binding = db_mitre!()?;
188 let statement = binding.prepare("SELECT id FROM capec WHERE weaknesses LIKE :cwe");
189 let mut binding = statement?;
190 let mut rows = binding.query(&[(":cwe", format!("%::{cwe_id}::%").as_str())])?;
191 let mut result: Vec<Option<CapecRecord>> = vec![];
192 while let Some(row) = rows.next()? {
193 result.push(find_capec_by_id(row.get(0)?));
194 }
195
196 Ok(result)
197}
198
199fn extract_capec_records(rows: &mut Rows) -> Vec<CapecRecord> {
200 let mut records = Vec::new();
201 while let Some(row) = rows.next().unwrap() {
202 let result = find_capec_by_id(row.get(0).unwrap());
203 if let Some(result) = result {
204 records.push(result);
205 }
206 }
207
208 records
209}
210
211pub fn build_tree_schema(capec: &CapecRecord) -> Tree {
231 let mut builder = TreeBuilder::new();
232
233 let cwe_node = builder.node(section!(format!("CAPEC-{}", capec.id), Color::Green).to_string());
234
235 let cwe_ids = parse_id_to_u32(capec.weaknesses.clone());
236 cwe_node.node("CWE");
237 cwe_ids.iter().for_each(|id| {
238 if let Some(cwe) = find_cwe_by_id(*id) {
239 cwe_node.node(
240 format!("CWE-{} - {}", cwe.id, cwe.name)
241 .fg(Color::DarkRed)
242 .to_string(),
243 );
244 eprint!(
245 "{}",
246 format!("Search cves for cwe: CWE-{}\r", cwe.id).fg(Color::DarkYellow)
247 );
248 let query_result =
249 execute_query(build_query(ByCwe, format!("CWE-{}", cwe.id).as_str()));
250
251 let mut cves = cve_from_cwe(&query_result);
252 simplify_cve_list_content(&mut cves);
253
254 if !cves.is_empty() {
255 let cve_node = cwe_node.node("CVE");
256 cves.iter().for_each(|cve| {
257 cve_node.leaf(cve.reference.clone());
258 });
259 cve_node.end();
260 }
261
262 cwe_node.end();
263 }
264 });
265
266 builder.end();
267 println!();
268
269 builder.build()
270}
271
272#[derive(Debug)]
274pub enum CapecDisplayType {
275 All,
277
278 Alternates,
280
281 Attacks,
283
284 Consequences,
286
287 Description,
289
290 Flow,
292
293 Indicators,
295
296 Instances,
298
299 Likelihood,
301
302 Name,
304
305 Mitigations,
307
308 Prerequisites,
310
311 Resources,
313
314 Severity,
316
317 Skills,
319
320 Taxonomy,
322
323 TaxonomyMitreAttackIds,
325
326 Weaknesses,
328}
329
330impl CapecDisplayType {
331 pub fn show(&self, capec_record: &CapecRecord) {
337 match self {
338 Alternates => hierarchical_show(
339 "Alternates",
340 capec_record
341 .alternates
342 .standardize(vec!["TERM", "DESCRIPTION"]),
343 ),
344 Attacks => hierarchical_show(
345 "Attacks",
346 parse_to_id_format(
347 capec_record.attacks.standardize(vec!["NATURE", "CAPEC ID"]),
348 "CAPEC ID:",
349 ),
350 ),
351 Consequences => hierarchical_show("Consequences", capec_record.clone().consequences),
352 Description => hierarchical_show("Description", capec_record.clone().description),
353 Flow => hierarchical_show(
354 "Flow",
355 capec_record.clone().flow.standardize(vec![
356 "STEP",
357 "PHASE",
358 "DESCRIPTION",
359 "TECHNIQUE",
360 ]),
361 ),
362 Indicators => hierarchical_show("Indicators", capec_record.clone().indicators),
363 Instances => hierarchical_show(
364 "Instances",
365 capec_record.instances.transform(vec!["Attack Example"]),
366 ),
367 Likelihood => hierarchical_show("Likelihood", capec_record.clone().likelihood),
368 Name => print!(""),
369 Mitigations => hierarchical_show("Mitigations", capec_record.clone().mitigations),
370 Prerequisites => hierarchical_show("Prerequisites", capec_record.clone().prerequisites),
371 Resources => hierarchical_show("Resources", capec_record.clone().resources),
372 Severity => hierarchical_show("Severity", capec_record.clone().severity),
373 Skills => hierarchical_show(
374 "Skills",
375 capec_record
376 .clone()
377 .skills
378 .standardize(vec!["SKILL", "LEVEL"]),
379 ),
380 Taxonomy => {
381 hierarchical_show(
382 "Classification",
383 capec_record.taxonomy.standardize(vec![
384 "TAXONOMY NAME",
385 "ENTRY ID",
386 "ENTRY NAME",
387 ]),
388 );
389 }
390
391 CapecDisplayType::TaxonomyMitreAttackIds => {
392 println!("{}", section!("Mitre Att&ck", Color::DarkRed));
393 let mitre_attacks = extract_mitre_attack_ids(capec_record.taxonomy.clone());
394 mitre_attacks
395 .iter()
396 .for_each(|id| println!("{:>3} {}", ">", id.fg(Color::DarkRed)));
397 }
398
399 Weaknesses => show_weaknesses("Weaknesses", capec_record.clone().weaknesses),
400 All => {
401 Alternates.show(capec_record);
402 Attacks.show(capec_record);
403 Consequences.show(capec_record);
404 Description.show(capec_record);
405 Flow.show(capec_record);
406 Instances.show(capec_record);
407 Indicators.show(capec_record);
408 Likelihood.show(capec_record);
409 Name.show(capec_record);
410 Mitigations.show(capec_record);
411 Prerequisites.show(capec_record);
412 Resources.show(capec_record);
413 Severity.show(capec_record);
414 Skills.show(capec_record);
415 Taxonomy.show(capec_record);
416 Weaknesses.show(capec_record);
417 }
418 }
419 }
420}
421fn switch_to_display_type(name: &str) -> CapecDisplayType {
422 match name.trim().to_lowercase().as_str() {
423 "all" => All,
424 "alternates" => Alternates,
425 "attacks" => Attacks,
426 "consequences" => Consequences,
427 "description" => Description,
428 "flow" => Flow,
429 "instances" => Instances,
430 "likelihood" => Likelihood,
431 "mitigations" => Mitigations,
432 "name" => Name,
433 "prerequisites" => Prerequisites,
434 "taxonomy" => Taxonomy,
435 "weaknesses" => Weaknesses,
436 _ => All,
437 }
438}
439
440pub fn run_search(args: &[String]) {
441 let extraction = find_domain_and_criteria(args);
442 if let Some(elements) = extraction {
443 let domain = elements.0;
444 let criteria = elements.1;
445
446 let crit_analysis = criteria_analysis(criteria.clone());
447 let (words, operators) = crit_analysis.unwrap();
448 let found = search(
449 prepare_query(
450 format!("SELECT id FROM capec WHERE {domain}"),
451 words,
452 &operators,
453 ),
454 extract_capec_records,
455 );
456 if found.is_empty() {
457 write_error_message(format!("{domain} with {criteria} not found").as_str(), None);
458 }
459
460 found.iter().for_each(|x| {
461 println!("{}", x);
462 switch_to_display_type(domain.as_str()).show(x);
463 });
464 } else {
465 write_error_message_and_exit("Invalid argument: '=' missing. See help.", None);
466 }
467}
468
469#[cfg(test)]
470mod test {
471
472 mod search {
473 use crate::extract_capec_records;
474 use fenir::database::{criteria_analysis, find_domain_and_criteria, prepare_query, search};
475
476 #[test]
477 fn simple_taxonomy_criteria() -> Result<(), &'static str> {
478 let args = &[String::from("taxonomy = Hijack")];
479 let values = find_domain_and_criteria(args);
480 let (domain, argument) = values.unwrap();
481 let (words, operators) = criteria_analysis(argument).unwrap();
482 let result = search(
483 prepare_query(
484 format!("SELECT id FROM capec WHERE {domain}"),
485 words,
486 &operators,
487 ),
488 extract_capec_records,
489 );
490
491 match result.is_empty() {
492 true => Err("taxonomy not found"),
493 false => Ok(()),
494 }
495 }
496
497 #[test]
498 fn taxonomy_criteria_or() -> Result<(), &'static str> {
499 let args = &[String::from("taxonomy = Hijack or impair defense")];
500 let values = find_domain_and_criteria(args);
501 let (domain, argument) = values.unwrap();
502 let (words, operators) = criteria_analysis(argument).unwrap();
503 let result = search(
504 prepare_query(
505 format!("SELECT id FROM capec WHERE {domain}"),
506 words,
507 &operators,
508 ),
509 extract_capec_records,
510 );
511
512 match result.is_empty() {
513 false => Ok(()),
514 true => Err("taxonomy not found"),
515 }
516 }
517
518 #[test]
519 fn taxonomy_criteria_and() -> Result<(), &'static str> {
520 let args = &[String::from("taxonomy = Hijack and Thread")];
521 let values = find_domain_and_criteria(args);
522 let (domain, argument) = values.unwrap();
523 let (words, operators) = criteria_analysis(argument).unwrap();
524 let result = search(
525 prepare_query(
526 format!("SELECT id FROM capec WHERE {domain}"),
527 words,
528 &operators,
529 ),
530 extract_capec_records,
531 );
532
533 match result.is_empty() {
534 true => Err("taxonomy not found"),
535 false => Ok(()),
536 }
537 }
538
539 #[test]
540 fn taxonomy_criteria_or_and_not() -> Result<(), &'static str> {
541 let args = &[String::from(
542 "taxonomy = Hijack or impair defense and not Thread",
543 )];
544 let values = find_domain_and_criteria(args);
545 let (domain, argument) = values.unwrap();
546 let (words, operators) = criteria_analysis(argument).unwrap();
547 let result = search(
548 prepare_query(
549 format!("SELECT id FROM capec WHERE {domain}"),
550 words,
551 &operators,
552 ),
553 extract_capec_records,
554 );
555
556 match result.is_empty() {
557 false => Ok(()),
558 true => Err("taxonomy not found"),
559 }
560 }
561
562 #[test]
563 fn taxonomy_criteria_or_and_not_second() -> Result<(), &'static str> {
564 let args = &[String::from(
565 "taxonomy = Hijack or impair and defense and not Thread",
566 )];
567 let values = find_domain_and_criteria(args);
568 let (domain, argument) = values.unwrap();
569 let (words, operators) = criteria_analysis(argument).unwrap();
570 let result = search(
571 prepare_query(
572 format!("SELECT id FROM capec WHERE {domain}"),
573 words,
574 &operators,
575 ),
576 extract_capec_records,
577 );
578
579 match result.is_empty() {
580 false => Ok(()),
581 true => Err("taxonomy not found"),
582 }
583 }
584
585 #[test]
586 fn taxonomy_not_only_criteria() -> Result<(), &'static str> {
587 let args = &[String::from("taxonomy not exploit")];
588 let values = find_domain_and_criteria(args);
589 let (domain, argument) = values.unwrap();
590 let (words, operators) = criteria_analysis(argument).unwrap();
591 let result = search(
592 prepare_query(
593 format!("SELECT id FROM capec WHERE {domain}"),
594 words,
595 &operators,
596 ),
597 extract_capec_records,
598 );
599
600 match result.is_empty() {
601 false => Ok(()),
602 true => Err("taxonomy not found"),
603 }
604 }
605
606 #[test]
607 fn skills_not_or_not_complex_criteria() -> Result<(), &'static str> {
608 let args = &[String::from("skills not redirection or not attacker")];
609 let values = find_domain_and_criteria(args);
610 let (domain, argument) = values.unwrap();
611 let (words, operators) = criteria_analysis(argument).unwrap();
612 let result = search(
613 prepare_query(
614 format!("SELECT id FROM capec WHERE {domain}"),
615 words,
616 &operators,
617 ),
618 extract_capec_records,
619 );
620
621 match result.is_empty() {
622 true => Err("skills not found"),
623 false => Ok(()),
624 }
625 }
626 }
627
628 mod mitre {
629 use fenir::capec::extract_mitre_attack_ids;
630 use fenir::database::find_capec_by_id;
631
632 #[test]
633 fn find_mitre_attacks_ids() -> Result<(), &'static str> {
634 let capec = find_capec_by_id(13).unwrap();
635 let ids = extract_mitre_attack_ids(capec.taxonomy);
636
637 match ids.is_empty() {
638 false => Ok(()),
639 true => Err("no mitre attached ids"),
640 }
641 }
642
643 #[test]
644 fn find_mitre_attacks_empty() -> Result<(), &'static str> {
645 let ids = extract_mitre_attack_ids("".to_string());
646
647 match ids.is_empty() {
648 true => Ok(()),
649 false => Err("Mitre ids found"),
650 }
651 }
652 }
653}