1use crate::CweDisplayType::{
2 Attacks, Consequences, Description, Detections, Extended, Mitigations, Name,
3};
4use csv::Reader;
5use fenir::capec::show_attacks;
6use fenir::cve::cve_from_cwe;
7use fenir::cwe::CweRecord;
8use fenir::database::{
9 criteria_analysis, db_mitre_path, execute_query, find_capec_by_id,
10 find_cwe_by_id, find_domain_and_criteria, parse_id_to_u32, prepare_query, search,
11 text_or_null, MitreDatabase,
12};
13
14use fenir::facilities::{configure_csv_reader, hierarchical_show, ProgressText, Uppercase};
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::{db_mitre, header, section, section_level};
21use rusqlite::{params, Connection, Rows};
22use std::error::Error;
23use std::fs::File;
24use std::path::Path;
25use termint::{
26 enums::{Color, Modifier},
27 widgets::ToSpan,
28};
29use treelog::{builder::TreeBuilder, Tree};
30
31#[derive(Debug)]
33pub enum CweDisplayType {
34 All,
36 Attacks,
38 Consequences,
40 Description,
42 Detections,
44 Extended,
46 Mitigations,
48 Name,
50}
51
52impl CweDisplayType {
53 pub fn show(&self, cwe_record: &CweRecord) {
54 Self::show_record(self, cwe_record);
55 }
56
57 fn show_record(cwe_display_type: &CweDisplayType, cwe_record: &CweRecord) {
58 match cwe_display_type {
59 Description => println!(
60 "{} {}",
61 format!("{:>5} Description:", "-").fg(Color::DarkBlue),
62 cwe_record.description
63 ),
64
65 Extended => {
66 if !cwe_record.extended.is_empty() {
67 println!(
68 "{} {}",
69 format!("{:>5} Extended:", "-").fg(Color::DarkBlue),
70 cwe_record.extended
71 )
72 }
73 }
74
75 Attacks => {
76 println!();
77 section_level!(2, "Attacks");
78 show_attacks(cwe_record.attacks.clone());
79 }
80
81 Consequences => {
82 if !cwe_record.consequences.is_empty() {
83 hierarchical_show("Consequences", cwe_record.clone().consequences)
84 }
85 }
86
87 Detections => {
88 if !cwe_record.detections.is_empty() {
89 hierarchical_show("Detections", cwe_record.clone().detections)
90 }
91 }
92
93 Mitigations => {
94 if !cwe_record.mitigations.is_empty() {
95 hierarchical_show("Mitigations", cwe_record.clone().mitigations)
96 }
97 }
98
99 Name => print!(""),
100
101 CweDisplayType::All => {
102 Self::show_record(&Description, cwe_record);
103 Self::show_record(&Extended, cwe_record);
104 Self::show_record(&Consequences, cwe_record);
105 Self::show_record(&Detections, cwe_record);
106 Self::show_record(&Mitigations, cwe_record);
107 Self::show_record(&Attacks, cwe_record);
108 }
109 }
110 }
111}
112
113pub fn inject_csv_into_cwe_table(cwe_csv_path: &Path) -> rusqlite::Result<(), Box<dyn Error>> {
140 let mut reader = configure_csv_reader(cwe_csv_path)?;
142
143 let file_name = cwe_csv_path.file_name().unwrap().to_str().unwrap();
144 inject_cwe_data(&mut reader, file_name)
145}
146
147fn inject_cwe_data(reader: &mut Reader<File>, file: &str) -> rusqlite::Result<(), Box<dyn Error>> {
178 let records = reader.records().collect::<rusqlite::Result<Vec<_>, _>>()?;
179 let mut progress_bar = ProgressText::new(1, records.len(), file.to_string());
180
181 let connection = db_mitre!()?;
182 for record in records {
183 progress_bar.progress();
184
185 let _ = connection.execute(
186 "INSERT INTO cwe (id, name, description, extended, consequences, detections, mitigations, attacks) VALUES (?1, ?2, ?3, ?4, ?5,?6, ?7, ?8)",
187 params![record[0].parse::<i64>()?, record[1].to_string(), record[4].to_string(), text_or_null(record[5].to_string()), record[14].to_string(),
188 text_or_null(record[15].to_string()), text_or_null(record[16].to_string()), text_or_null(record[21].to_string()),],
189 );
190 }
191
192 println!("\nCWE data injected into SQLite database successfully.");
193 Ok(())
194}
195
196pub struct CweTable;
197
198impl MitreDatabase for CweTable {
199 fn table_definitions(&self) -> Vec<(&'static str, &'static str)> {
200 vec![(
201 "cwe",
202 "CREATE TABLE IF NOT EXISTS cwe (
203 id INTEGER PRIMARY KEY,
204 name TEXT NOT NULL,
205 description TEXT NOT NULL,
206 extended TEXT,
207 consequences TEXT,
208 detections TEXT,
209 mitigations TEXT,
210 attacks TEXT)",
211 )]
212 }
213
214 fn index_definitions(&self) -> Vec<(&'static str, &'static str)> {
215 vec![]
216 }
217}
218
219pub fn build_tree_schema(cwe: &CweRecord) -> Tree {
239 let mut builder = TreeBuilder::new();
240 let cwe_str = format!("CWE-{}", cwe.id);
241 builder.node(header!(cwe_str).to_string());
242
243
244 builder.node("CAPEC".fg(Color::DarkRed).to_string());
245 let capec_ids = parse_id_to_u32(cwe.attacks.clone());
246 for capec_id in capec_ids {
247 if let Some(capec) = find_capec_by_id(capec_id) {
248 builder.leaf(
249 format!("CAPEC-{} - {}", capec_id, capec.name)
250 .fg(Color::DarkRed)
251 .to_string(),
252 );
253 }
254 }
255
256 builder.end();
257
258
259 builder.node("CVE");
260 let cve_results = execute_query(build_query(ByCwe, cwe_str.as_str()));
261 let mut cves = cve_from_cwe(&cve_results);
262 simplify_cve_list_content(&mut cves);
263 if cves.is_empty() {
264 builder.leaf("None");
265 } else {
266 cves.iter().for_each(|x| {
267 builder.leaf(x.reference.clone());
268 });
269 }
270 builder.end();
271
272 builder.build()
273}
274
275pub fn run_search(args: &[String]) {
276 let extraction = find_domain_and_criteria(args);
277 if let Some(elements) = extraction {
278 let domain = elements.0;
279 let criteria = elements.1;
280
281 let crit_analysis = criteria_analysis(criteria.clone());
282 let (words, operators) = crit_analysis.unwrap();
283 let found = search(
284 prepare_query(
285 format!("SELECT id FROM cwe WHERE {domain}"),
286 words,
287 &operators,
288 ),
289 extract_cwe_records,
290 );
291 if found.is_empty() {
292 write_error_message(
293 format!("Domain '{domain}' with criteria '{criteria}' not found").as_str(),
294 None,
295 );
296 }
297
298 found.iter().for_each(|x| {
299 println!("{}", x);
300 CweDisplayType::show_record(&switch_to_display_type(domain.as_str()), x)
301 });
302 } else {
303 write_error_message_and_exit("Invalid argument: '=' missing. See help.", None);
304 }
305}
306
307fn switch_to_display_type(name: &str) -> CweDisplayType {
308 match name.trim().to_lowercase().as_str() {
309 "consequences" => Consequences,
310 "detections" => Detections,
311 "description" => Description,
312 "extended" => Extended,
313 "mitigations" => Mitigations,
314 "name" => Name,
315 _ => CweDisplayType::All,
316 }
317}
318
319fn extract_cwe_records(rows: &mut Rows) -> Vec<CweRecord> {
320 let mut records = Vec::new();
321 while let Some(row) = rows.next().unwrap() {
322 let result = find_cwe_by_id(row.get(0).unwrap());
323 if let Some(result) = result {
324 records.push(result);
325 }
326 }
327
328 records
329}
330
331#[cfg(test)]
332mod tests {
333 use crate::{find_cwe_by_id, inject_csv_into_cwe_table};
334 use fenir::cwe::CweRecord;
335 use fenir::database::{refresh_mitre_database, MitreDefinition, MITRE_DB_NAME};
336 use fenir::os::{create_tyr_home, find_tyr_home_path};
337
338 fn setup() {
339 let tyr_home = find_tyr_home_path();
340 if !tyr_home.exists() {
341 create_tyr_home(&tyr_home);
342
343 refresh_mitre_database(CweRecord::define(), inject_csv_into_cwe_table);
344 }
345 }
346
347 #[test]
348 fn test_cwe_invalid_none() {
349 assert_eq!(None, find_cwe_by_id(10000000));
350 }
351
352 #[test]
353 fn test_cwe_valid_parameter_not_none() {
354 setup();
355
356 assert_ne!(None, find_cwe_by_id(89));
357 }
358
359 #[test]
360 fn test_cwe_update_csv_reference() {
361 setup();
362
363 let tyr_home = find_tyr_home_path();
364
365 assert!(tyr_home.join(MITRE_DB_NAME).exists());
366 }
367
368 #[test]
369 fn test_cwe_name_valid() {
370 let cwe = find_cwe_by_id(112);
371 assert_eq!("Missing XML Validation", cwe.unwrap().name);
372 }
373
374 #[test]
375 fn test_cwe_description_valid() {
376 let cwe = find_cwe_by_id(90);
377
378 assert_eq!(
379 "The product constructs all or part of an LDAP query using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended LDAP query when it is sent to a downstream component.",
380 cwe.unwrap().description
381 );
382 }
383
384 #[test]
385 fn test_cwe_extended_valid() {
386 let cwe = find_cwe_by_id(91);
387
388 assert_eq!(
389 "Within XML, special elements could include reserved words or characters such as <, >, , and &, which could then be used to add new data or modify XML syntax.",
390 cwe.unwrap().extended
391 );
392 }
393
394 #[test]
395 fn test_cwe_id_valid() {
396 let cwe = find_cwe_by_id(91);
397
398 assert_eq!(91, cwe.unwrap().id);
399 }
400
401 #[test]
402 fn test_cwe_consequences_valid() {
403 let cwe = find_cwe_by_id(91);
404
405 assert_eq!(
406 "::SCOPE:Confidentiality:SCOPE:Integrity:SCOPE:Availability:IMPACT:Execute Unauthorized Code or Commands:IMPACT:Read Application Data:IMPACT:Modify Application Data::",
407 cwe.unwrap().consequences
408 );
409 }
410
411 #[test]
412 fn test_cwe_detections_valid() {
413 let cwe = find_cwe_by_id(91);
414
415 assert_eq!(
416 "::METHOD:Automated Static Analysis:DESCRIPTION:Automated static analysis, commonly referred to as Static Application Security Testing (SAST), can find some instances of this weakness by analyzing source code (or binary/compiled code) without having to execute it. Typically, this is done by building a model of data flow and control flow, then searching for potentially-vulnerable patterns that connect sources (origins of input) with sinks (destinations where the data interacts with external components, a lower layer such as the OS, etc.):EFFECTIVENESS:High::",
417 cwe.unwrap().detections
418 );
419 }
420
421 #[test]
422 fn test_cwe_mitigations_valid() {
423 let cwe = find_cwe_by_id(91);
424
425 assert_eq!(
426 "::PHASE:Implementation:STRATEGY:Input Validation:DESCRIPTION:Assume all input is malicious. Use an accept known good input validation strategy, i.e., use a list of acceptable inputs that strictly conform to specifications. Reject any input that does not strictly conform to specifications, or transform it into something that does. When performing input validation, consider all potentially relevant properties, including length, type of input, the full range of acceptable values, missing or extra inputs, syntax, consistency across related fields, and conformance to business rules. As an example of business rule logic, boat may be syntactically valid because it only contains alphanumeric characters, but it is not valid if the input is only expected to contain colors such as red or blue. Do not rely exclusively on looking for malicious or malformed inputs. This is likely to miss at least one undesirable input, especially if the code's environment changes. This can give attackers enough room to bypass the intended validation. However, denylists can be useful for detecting potential attacks or determining which inputs are so malformed that they should be rejected outright.::",
427 cwe.unwrap().mitigations
428 );
429 }
430}