Skip to main content

get_capec/
lib.rs

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
64/// Injects data from a CSV file into the CAPEC table in the specified SQLite database.
65///
66/// This function performs the following steps:
67/// 1. Opens a connection to the specified SQLite database.
68/// 2. Initializes the CAPEC table in the database by calling `initialize_capec_table`.
69/// 3. Configures and opens the CSV file for reading using `configure_csv_reader`.
70/// 4. Reads the data from the CSV file and inserts it into the CAPEC table by calling `inject_capec_data`.
71///
72/// # Arguments
73///
74/// * `csv_file` - A string slice that holds the file path of the CSV file containing the CAPEC data.
75///
76/// # Returns
77///
78/// * `Result<(), Box<dyn Error>>` - Returns `Ok(())` if the operation is successful, or an error wrapped in a `Box` if failed.
79///
80/// # Errors
81///
82/// * This function returns an error if connecting to the SQLite database fails.
83/// * It returns an error if reading from the CSV file fails.
84/// * It can also return an error if inserting data into the SQLite database fails.
85///
86/// # Example
87///
88///
89/// `inject_csv_into_capec_table(Path::new("path/to/capec.csv")).unwrap();`
90///
91pub fn inject_csv_into_capec_table(csv_file: &Path) -> Result<(), Box<dyn Error>> {
92    // Open the CSV file and read its content
93    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
99/// Injects CAPEC data from a CSV file into an SQLite database.
100///
101/// # Arguments
102///
103/// * `reader` - A mutable reference to a CSV reader that provides access to CSV records.
104///
105/// # Returns
106///
107/// This function returns a `Result` with an empty tuple on success, or a boxed `dyn Error` trait object on failure.
108///
109/// # Errors
110///
111/// Returns an error if there is any issue with reading the CSV records or executing the SQL insert commands.
112fn 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
169/// Find the list of Capec from the CWE table.
170///
171/// # Argument
172///
173/// * `cwe_id` - CWE's id for which we want the list of corresponding CAPEC records
174///
175/// # Return
176///
177/// The list of associated Capec records.
178///
179/// # Example
180///
181/// ```rust
182/// use get_capec::find_capec_list;
183///
184/// let _ = find_capec_list(1);
185/// ```
186pub 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
211/// Create the tree of the capec schema. This schema included the list of associated CWE and CVE for a capec record
212///
213/// # Argument
214/// * `capec` - Capec record for which the schema must be built
215///
216/// # Return
217/// The tree schema.
218///
219/// # Example
220///
221/// ```rust
222/// use fenir::database::find_capec_by_id;
223/// use get_capec::build_tree_schema;
224///
225/// let capec = find_capec_by_id(1).unwrap();
226/// let schema = build_tree_schema(&capec);
227///
228/// println!("{}", schema.render_to_string())
229/// ```
230pub 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/// Enum representing different types of CAPEC information to display
273#[derive(Debug)]
274pub enum CapecDisplayType {
275    /// Display all information
276    All,
277
278    /// Display alternates
279    Alternates,
280
281    /// Display attacks
282    Attacks,
283
284    /// Display Consequences
285    Consequences,
286
287    /// Display description
288    Description,
289
290    /// Display flow
291    Flow,
292
293    /// Display indicators
294    Indicators,
295
296    /// Display instances
297    Instances,
298
299    /// Display likelihood
300    Likelihood,
301
302    /// Display name
303    Name,
304
305    /// Display mitigations
306    Mitigations,
307
308    /// Display prerequisites
309    Prerequisites,
310
311    /// Display resources
312    Resources,
313
314    /// Display severity
315    Severity,
316
317    /// Display skills
318    Skills,
319
320    /// Display taxonomy
321    Taxonomy,
322
323    /// Show only MITRE ATTACK ids
324    TaxonomyMitreAttackIds,
325
326    /// Display weaknesses
327    Weaknesses,
328}
329
330impl CapecDisplayType {
331    /// Shows capec records elements according to the enum type that's specifying what should be displayed.
332    ///
333    /// # Arguments
334    ///
335    /// * `capec_record` - The cwe records for displaying    
336    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}