cherrybomb_engine/
lib.rs

1pub mod config;
2mod info;
3mod scan;
4use crate::config::Verbosity;
5use crate::info::eps::EpTable;
6use crate::info::params::ParamTable;
7use crate::scan::active::active_scanner;
8use crate::scan::active::http_client::auth::Authorization;
9use cherrybomb_oas::legacy::legacy_oas::*;
10use config::Config;
11use scan::checks::{ActiveChecks, PassiveChecks};
12use scan::passive::passive_scanner;
13use scan::*;
14use serde_json::{json, Value};
15use std::collections::{HashMap, HashSet};
16use std::vec;
17use strum::IntoEnumIterator;
18use serde_yaml;
19use anyhow::anyhow;
20
21fn verbose_print(config: &Config, required: Option<Verbosity>, message: &str) {
22    let required = required.unwrap_or(Verbosity::Normal);
23    if config.verbosity >= required {
24        println!("{message}");
25    }
26}
27
28pub async fn run(config: &mut Config) -> anyhow::Result<Value> {
29    verbose_print(config, None, "Starting Cherrybomb...");
30    verbose_print(config, None, "Opening OAS file...");
31
32    let (oas,oas_json) = if let Some(ext)= config.file.extension(){
33        verbose_print(config, None, "Reading OAS file...");
34        let oas_file = match std::fs::read_to_string(&config.file) {
35            Ok(file) => file,
36            Err(e) => return Err(anyhow::Error::msg(format!("Error opening OAS file: {}", e))),
37        };
38        let oas_json: Value = match ext.to_str() {
39            Some("json") => {
40                verbose_print(config, None, "Parsing OAS file...");
41                match serde_json::from_str(&oas_file) {
42                    Ok(json) => json,
43                    Err(e) => return Err(anyhow::Error::msg(format!("Error parsing OAS file: {}", e))),
44                }
45            }
46            Some("yaml") | Some("yml") => {
47                verbose_print(config, None, "Parsing OAS file...");
48                match serde_yaml::from_str(&oas_file) {
49                    Ok(yaml) => yaml,
50                    Err(e) => return Err(anyhow::Error::msg(format!("Error parsing OAS file: {}", e))),
51                }
52            }
53            _ => return Err(anyhow::Error::msg("Unsupported config file extension")),
54        };
55        let oas: OAS3_1 = match serde_json::from_value(oas_json.clone().into()) {
56            Ok(oas) => oas,
57            Err(e) => return Err(anyhow::Error::msg(format!("Error creating OAS struct: {}", e))),
58        };
59        (oas,oas_json)
60    }else {
61        return Err(anyhow!("Misconfigured file extention"));
62    };
63    match config.profile {
64        config::Profile::Info => run_profile_info(&config, &oas, &oas_json),
65        config::Profile::Normal => run_normal_profile(config, &oas, &oas_json).await,
66        config::Profile::Active => {
67            if !&config.passive_include.is_empty() {
68                config.passive_checks = config.passive_include.clone(); //passive include into passive checks
69                run_normal_profile(config, &oas, &oas_json).await
70            } else {
71                run_active_profile(config, &oas, &oas_json).await
72            }
73        }
74
75        config::Profile::Passive => {
76            if !&config.active_include.is_empty() {
77                config.active_checks = config.active_include.clone();
78                run_normal_profile(config, &oas, &oas_json).await
79            } else {
80                run_passive_profile(config, &oas, &oas_json)
81            }
82        }
83        config::Profile::Full => run_full_profile(config, &oas, &oas_json).await,
84        config::Profile::OWASP => todo!("not implemented yet!"),
85    }
86}
87
88fn run_profile_info(config: &Config, oas: &OAS3_1, oas_json: &Value) -> anyhow::Result<Value> {
89    // Creating parameter list
90    verbose_print(config, None, "Creating param list...");
91    let param_scan = ParamTable::new::<OAS3_1>(oas_json);
92    let param_result: HashMap<&str, Value> = param_scan
93        .params
94        .iter()
95        .map(|param| (param.name.as_str(), json!(param)))
96        .collect();
97
98    //Creating endpoint
99    verbose_print(config, None, "Create endpoint list");
100    let ep_table = EpTable::new::<OAS3_1>(oas_json);
101    let endpoint_result: HashMap<&str, Value> = ep_table
102        .eps
103        .iter()
104        .map(|param| (param.path.as_str(), json!(param)))
105        .collect();
106
107    verbose_print(config, None, "Creating report...");
108    let report = json!({
109
110        "params": param_result,
111        "endpoints": endpoint_result,
112    });
113    Ok(report)
114}
115
116async fn run_active_profile(
117    config: &mut Config,
118    oas: &OAS3_1,
119    oas_json: &Value,
120) -> anyhow::Result<Value> {
121    // Creating active scan struct
122    verbose_print(
123        config,
124        Some(Verbosity::Debug),
125        "Creating active scan struct...",
126    );
127    let mut active_scan = match active_scanner::ActiveScan::new(oas.clone(), oas_json.clone()) {
128        Ok(scan) => scan,
129        Err(e) => {
130            return Err(anyhow::anyhow!("Error creating active scan struct: {}", e));
131        }
132    };
133
134    // Running active scan
135    verbose_print(config, None, "Running active scan...");
136    let temp_auth = config.get_auth();
137    let active_result: HashMap<&str, Vec<Alert>> = match config.active_checks.is_empty() {
138        true => {
139            // if empty, active profile and exclude_active checks is set
140            //create a vec of active scan checks
141
142            let all_active_checks = ActiveChecks::iter().map(|x| x.name().to_string()).collect();
143            config.update_checks_active(all_active_checks);
144
145            //create a vec of active scan checks
146            let active_checks = ActiveChecks::create_checks(&config.active_checks);
147
148            active_scan
149                .run(
150                    active_scanner::ActiveScanType::Partial(active_checks),
151                    &temp_auth,
152                )
153                .await;
154            active_scan
155                .checks
156                .iter()
157                .map(|check| (check.name(), check.inner()))
158                .collect()
159        }
160        false => {
161            // if the active_checks not empty, active include_checks and passive profile is set
162
163            // let active_checks_to_run = ActiveChecks::iter()
164            //     .filter(|check| config.active_checks.contains(&check.name().to_string()))
165            //     .collect();
166            let active_checks_to_run = ActiveChecks::create_checks(&config.active_checks.clone()); //create active check vec
167            active_scan
168                .run(
169                    active_scanner::ActiveScanType::Partial(active_checks_to_run),
170                    &temp_auth,
171                )
172                .await;
173            active_scan
174                .checks
175                .iter()
176                .map(|check| (check.name(), check.inner()))
177                .collect()
178        }
179    };
180
181    Ok(json!({ "active": active_result }))
182}
183
184fn run_passive_profile(
185    config: &mut Config,
186    oas: &OAS3_1,
187    oas_json: &Value,
188) -> anyhow::Result<Value> {
189    verbose_print(
190        config,
191        Some(Verbosity::Debug),
192        "Creating passive scan struct...",
193    );
194    // Creating passive scan struct
195    let mut passive_scan = passive_scanner::PassiveSwaggerScan {
196        swagger: oas.clone(),
197        swagger_value: oas_json.clone(),
198        passive_checks: vec![],
199        verbosity: 0,
200    };
201
202    // Running passive scan
203    verbose_print(config, None, "Running passive scan...");
204
205    let passive_result: HashMap<&str, Vec<Alert>> = match config.passive_checks.is_empty() {
206        true => {
207            // if passive_checks empty, passive profile and exclude passive checks is set
208            let all_passive_checks = PassiveChecks::iter()
209                .map(|x| x.name().to_string())
210                .collect(); //collect all passive checks
211            config.update_checks_passive(all_passive_checks);
212
213            //create vector of passive checks to run
214
215            // let passive_checks_to_run = PassiveChecks::iter()
216            //     .filter(|check| config.active_checks.contains(&check.name().to_string()))
217            //     .collect();
218
219            let passive_checks_to_run = PassiveChecks::create_checks(&config.passive_checks);
220            passive_scan.run(passive_scanner::PassiveScanType::Partial(
221                passive_checks_to_run,
222            ));
223            passive_scan
224                .passive_checks
225                .iter()
226                .map(|check| (check.name(), check.inner()))
227                .collect()
228        }
229        false => {
230            // if the passive_checks not empty, so passive include_checks and active profile is set
231
232            // let passive_checks_to_run = PassiveChecks::iter()
233            //     .filter(|check| config.passive_checks.contains(&check.name().to_string()))
234            //     .collect(); //create vec of passive checks
235
236            let passive_checks_to_run = PassiveChecks::create_checks(&config.passive_checks);
237            passive_scan.run(passive_scanner::PassiveScanType::Partial(
238                passive_checks_to_run,
239            ));
240            passive_scan
241                .passive_checks
242                .iter()
243                .map(|check| (check.name(), check.inner()))
244                .collect()
245        }
246    };
247    Ok(json!({"passive": passive_result}))
248}
249
250async fn run_normal_profile(
251    config: &mut Config,
252    oas: &OAS3_1,
253    oas_json: &Value,
254) -> anyhow::Result<Value> {
255    let mut report = json!({});
256    let mut results = HashMap::from([
257        ("passive", run_passive_profile(config, oas, oas_json)),
258        ("active", run_active_profile(config, oas, oas_json).await),
259    ]);
260    for (key, value) in results.iter_mut() {
261        match value {
262            Ok(result) => {
263                if let Some(val) = result.get(key) {
264                    report[key] = val.clone();
265                }
266            }
267            Err(e) => {
268                verbose_print(
269                    config,
270                    None,
271                    &format!("WARNING: Error running {key} scan: {e}"),
272                );
273            }
274        }
275    }
276    Ok(report)
277}
278
279async fn run_full_profile(
280    config: &mut Config,
281    oas: &OAS3_1,
282    oas_json: &Value,
283) -> anyhow::Result<Value> {
284    let mut report = json!({});
285    let mut results = HashMap::from([
286        ("active", run_active_profile(config, oas, oas_json).await),
287        ("passive", run_passive_profile(config, oas, oas_json)),
288        ("params", run_profile_info(config, oas, oas_json)),
289        ("endpoints", run_profile_info(config, oas, oas_json)),
290    ]);
291    for (key, value) in results.iter_mut() {
292        match value {
293            Ok(result) => {
294                if let Some(val) = result.get(key) {
295                    report[key] = val.clone();
296                }
297            }
298            Err(e) => {
299                verbose_print(
300                    config,
301                    None,
302                    &format!("WARNING: Error running {} scan: {}", key, e),
303                );
304            }
305        }
306    }
307    Ok(report)
308}