swagger/scan/active/
mod.rs

1use super::*;
2use strum::IntoEnumIterator;
3
4mod additional_checks;
5mod flow;
6mod http_client;
7mod logs;
8mod response_checks;
9mod utils;
10
11pub use http_client::Authorization;
12use http_client::*;
13pub use logs::*;
14use serde_json::json;
15use std::{collections::HashSet, iter};
16
17type CheckRetVal = (Vec<(ResponseData, AttackResponse)>, AttackLog);
18
19#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
20pub enum ActiveScanType {
21    Full,
22    Partial(Vec<ActiveChecks>),
23    NonInvasive,
24    OnlyTests,
25}
26
27type PayloadMap = HashMap<Vec<String>, Schema>;
28
29#[derive(Debug, Clone, Serialize, Default, PartialEq)]
30pub struct Payload {
31    pub payload: Value,
32    pub map: PayloadMap,
33}
34#[derive(Debug, Clone, Serialize, Default, PartialEq)]
35pub struct Path {
36    pub path_item: PathItem,
37    pub path: String,
38}
39
40#[derive(Debug, Clone, Serialize, Default, PartialEq)]
41struct OASMap {
42    pub path: Path,
43    pub payload: Payload,
44}
45
46#[derive(Default, Debug)]
47pub struct ResponseData {
48    location: String,
49    alert_text: String,
50    serverity: Level,
51}
52
53#[derive(Debug, Clone, Serialize, Default, PartialEq)]
54pub struct ActiveScan<T>
55where
56    T: Serialize,
57{
58    oas: T,
59    oas_value: Value,
60    verbosity: u8,
61    pub checks: Vec<ActiveChecks>,
62    payloads: Vec<OASMap>,
63    logs: AttackLog,
64}
65
66impl<T: OAS + Serialize + for<'de> Deserialize<'de>> ActiveScan<T> {
67    pub fn new(oas_value: Value) -> Result<Self, &'static str> {
68        let oas = match serde_json::from_value::<T>(oas_value.clone()) {
69            Ok(oas) => oas,
70            Err(e) => {
71                println!("{:?}", e);
72                return Err("Failed at deserializing swagger value to a swagger struct, please check the swagger definition");
73            }
74        };
75        let payloads = Self::payloads_generator(&oas, &oas_value);
76        Ok(ActiveScan {
77            oas,
78            oas_value,
79            checks: vec![],
80            verbosity: 0,
81            payloads,
82            logs: AttackLog::default(),
83        })
84    }
85    pub async fn run(&mut self, tp: ActiveScanType, auth: &Authorization) {
86        match tp {
87            ActiveScanType::Full => {
88                for check in ActiveChecks::iter() {
89                    self.checks.push(self.run_check(check, auth).await);
90                }
91            }
92            ActiveScanType::NonInvasive => {
93                for check in ActiveChecks::iter() {
94                    self.checks.push(self.run_check(check, auth).await);
95                }
96            }
97            ActiveScanType::OnlyTests => {
98                for check in ActiveChecks::iter() {
99                    self.checks.push(self.run_check(check, auth).await);
100                }
101            }
102            ActiveScanType::Partial(checks) => {
103                for check in checks {
104                    self.checks.push(self.run_check(check, auth).await);
105                }
106            }
107        };
108    }
109    pub fn print(&self, verbosity: u8) {
110        match verbosity {
111            //TODO support verbosity
112            0 => {
113                print_active_alerts_verbose(self.checks.clone());
114            }
115            1 => {
116                print_active_alerts(self.checks.clone());
117            }
118            _ => (),
119        }
120    }
121    pub fn print_to_file_string(&self) -> String {
122        String::new()
123    }
124
125    fn payloads_generator(oas: &T, oas_value: &Value) -> Vec<OASMap> {
126        let mut payloads = vec![];
127        for (path, path_item) in oas.get_paths() {
128            payloads.push(OASMap {
129                path: (Path {
130                    path_item: path_item.clone(),
131                    path,
132                }),
133                payload: Self::build_payload(oas_value, &path_item),
134            });
135        }
136        payloads
137    }
138
139    pub fn build_payload(oas_value: &Value, path_item: &PathItem) -> Payload {
140        let mut payload = json!({});
141        let mut map: PayloadMap = HashMap::new();
142        for (_, op) in path_item.get_ops() {
143            if let Some(req) = &op.request_body {
144                for (_, med_t) in req.inner(oas_value).content {
145                    let mut path = Vec::<String>::new();
146                    if let Some(s_ref) = &med_t.schema {
147                        let mut visited_schemes = HashSet::new();
148                        path.push(Self::get_name_s_ref(s_ref, oas_value, &None));
149                        payload = Self::unwind_schema(
150                            oas_value,
151                            s_ref,
152                            &mut map,
153                            &mut path,
154                            &mut visited_schemes,
155                        );
156                    }
157                }
158            }
159        }
160        Payload { payload, map }
161    }
162
163    pub fn unwind_schema(
164        oas_value: &Value,
165        reference: &SchemaRef,
166        map: &mut HashMap<Vec<String>, Schema>,
167        path: &mut Vec<String>,
168        visited_schemes: &mut HashSet<String>,
169    ) -> Value {
170        let mut payload = json!({});
171        let reference = reference.inner(oas_value);
172        if let Some(example) = reference.example {
173            payload = example;
174        } else if let Some(prop_map) = reference.properties {
175            for (name, schema) in prop_map {
176                path.push(name.clone());
177                payload[&name] = match schema {
178                    SchemaRef::Ref(ref r) => {
179                        if visited_schemes.contains(&r.param_ref) {
180                            panic!("Circular reference detected");
181                        }
182                        visited_schemes.insert(r.param_ref.clone());
183                        let ret =
184                            Self::unwind_schema(oas_value, &schema, map, path, visited_schemes);
185                        visited_schemes.remove(&r.param_ref);
186                        ret
187                    }
188                    SchemaRef::Schema(schema) => {
189                        map.insert(path.clone(), *schema.clone());
190                        path.pop();
191                        if let Some(example) = schema.example {
192                            example
193                        } else {
194                            Self::gen_default_value(schema)
195                        }
196                    }
197                };
198            }
199        } else if let Some(item_ref) = reference.items {
200            // dup code from properties, probably could be improved
201            payload = json!([match *item_ref {
202                SchemaRef::Ref(ref r) => {
203                    if visited_schemes.contains(&r.param_ref) {
204                        panic!("Circular reference detected");
205                    }
206                    visited_schemes.insert(r.param_ref.clone());
207                    let ret = Self::unwind_schema(oas_value, &item_ref, map, path, visited_schemes);
208                    visited_schemes.remove(&r.param_ref);
209                    ret
210                }
211                SchemaRef::Schema(schema) => {
212                    map.insert(path.clone(), *schema.clone());
213                    path.pop();
214                    if let Some(example) = schema.example {
215                        example
216                    } else {
217                        Self::gen_default_value(schema)
218                    }
219                }
220            }]);
221        }
222        payload
223    }
224
225    pub fn gen_default_value(schema: Box<Schema>) -> Value {
226        let ret: Value = if let Some(data_type) = schema.schema_type {
227            match data_type.as_str() {
228                "string" => {
229                    if let Some(num) = schema.min_length {
230                        json!(iter::repeat(['B', 'L', 'S', 'T'])
231                            .flatten()
232                            .take(num.try_into().unwrap())
233                            .collect::<String>())
234                    } else {
235                        json!("BLST")
236                    }
237                }
238                "integer" => {
239                    if let Some(num) = schema.minimum {
240                        json!(num)
241                    } else {
242                        json!(5usize)
243                    }
244                }
245                "boolean" => {
246                    json!(false)
247                }
248                _ => {
249                    json!({})
250                }
251            }
252        } else {
253            json!({})
254        };
255        ret
256    }
257
258    pub fn get_name_s_ref(s_ref: &SchemaRef, value: &Value, name: &Option<String>) -> String {
259        let schema = s_ref.inner(value);
260        if let Some(ref t) = schema.title {
261            t.to_string()
262        } else if let SchemaRef::Ref(r) = s_ref {
263            r.param_ref.split('/').last().unwrap().to_string()
264        } else if let Some(n) = name {
265            n.to_string()
266        } else {
267            String::new()
268        }
269    }
270}
271
272impl ActiveChecks {
273    pub fn parse_check_list(list: Vec<String>, exclude: bool) -> Vec<ActiveChecks> {
274        let mut checks = Vec::new();
275        for check in list.iter() {
276            let check = Self::from_string(check);
277            if let Some(c) = check {
278                checks.push(c);
279            }
280        }
281        if exclude {
282            let mut ex_checks: Vec<_> = Self::iter().collect();
283            ex_checks.retain(|x| !checks.contains(x));
284            return ex_checks;
285        }
286        checks
287    }
288}