swagger/scan/active/
additional_checks.rs

1use super::utils::create_payload_for_get;
2use super::*;
3// use colored::*;
4use serde_json::json;
5
6pub fn change_payload(orig: &Value, path: &[String], new_val: Value) -> Value {
7    let mut change = &mut json!(null);
8    let mut ret = orig.clone();
9    for path_part in path.iter() {
10        change = &mut ret[path_part];
11    }
12    *change = new_val;
13    ret.clone()
14}
15
16
17impl<T: OAS + Serialize> ActiveScan<T> {
18    pub async fn check_min_max(&self, auth: &Authorization) -> CheckRetVal {
19        let mut ret_val = CheckRetVal::default();
20        for oas_map in self.payloads.iter() {
21            for (json_path, schema) in &oas_map.payload.map {
22                let test_vals = Vec::from([
23                    schema.minimum.map(|min| ("minimum", min - 1.0)),
24                    schema.maximum.map(|max| ("maximum", max + 1.0)),
25                ]);
26                for val in test_vals.into_iter().flatten() {
27                    for (m, op) in oas_map
28                        .path
29                        .path_item
30                        .get_ops()
31                        .iter()
32                        .filter(|(m, _)| m == &Method::POST)
33                    {
34                        let vec_param =
35                            create_payload_for_get(&self.oas_value, op, Some("".to_string()));
36                        let req = AttackRequest::builder()
37                            .servers(self.oas.servers(), true)
38                            .path(&oas_map.path.path)
39                            .method(*m)
40                            .headers(vec![])
41                            .parameters(vec_param.clone())
42                            .auth(auth.clone())
43                            .payload(
44                                &change_payload(&oas_map.payload.payload, json_path, json!(val.1))
45                                    .to_string(),
46                            )
47                            .build();
48                        let response_vector = req.send_request_all_servers(self.verbosity > 0).await;
49                        for response in response_vector {
50                            ret_val.1.push(&req, &response, "Testing  /max values".to_string());
51                            ret_val.0.push((
52                                ResponseData {
53                                    location: oas_map.path.path.clone(),
54                                    alert_text: format!(
55                                        "The {} for {:?} is not enforced by the server",
56                                        val.0, json_path
57                                    ),
58                                    serverity: Level::Low,
59                                },
60                                response,
61                            ));
62                        }
63                    }
64                }
65            }
66        }
67        ret_val
68    }
69
70    pub async fn check_open_redirect(&self, auth: &Authorization) -> CheckRetVal {
71        let mut ret_val = CheckRetVal::default();
72        for (path, item) in &self.oas.get_paths() {
73            for (m, op) in item.get_ops().iter().filter(|(m, _)| m == &Method::GET) {
74                let vec_param = create_payload_for_get(
75                    &self.oas_value,
76                    op,
77                    Some("http://www.google.com".to_string()),
78                );
79                for param_item in &vec_param {
80                    // dbg!(&param_item);
81                    if param_item.dm == QuePay::Query
82                        && LIST_PARAM.contains(&param_item.name.as_str())
83                    {
84                        let param_to_redirect = param_item.name.to_owned();
85                        let req = AttackRequest::builder()
86                            .servers(self.oas.servers(), true)
87                            .path(path)
88                            .parameters(vec_param.clone())
89                            .auth(auth.clone())
90                            .method(*m)
91                            .headers(vec![])
92                            .auth(auth.clone())
93                            .build();
94                        let response_vector = req.send_request_all_servers(self.verbosity > 0).await;
95                        for response in response_vector {
96                            ret_val.1.push(&req, &response, "Testing  /max values".to_string());
97                            ret_val.0.push((
98                                ResponseData {
99                                    location: path.clone(),
100                                    alert_text: format!(
101                                        "The parameter {} seems to be vulnerable to open-redirect, location: {}  "
102                                        , param_to_redirect, path),
103                                    serverity: Level::Medium,
104                                },
105                                response,
106                            ));
107                        }
108                        break; // TODO what is this?
109                    }
110                }
111            }
112        }
113        ret_val
114    }
115
116    pub async fn check_string_length_max(&self, auth: &Authorization) -> CheckRetVal {
117        let mut ret_val = CheckRetVal::default();
118        for oas_map in self.payloads.iter() {
119            for (json_path, schema) in &oas_map.payload.map {
120                if let Some(max_len) = schema.max_length {
121                    let new_string = iter::repeat(['B', 'L', 'S', 'T'])
122                        .flatten()
123                        .take(max_len.try_into().unwrap())
124                        .collect::<String>();
125                    for (m, op) in oas_map
126                        .path
127                        .path_item
128                        .get_ops()
129                        .iter()
130                        .filter(|(m, _)| m == &Method::POST)
131                    {
132                        let vec_param =
133                            create_payload_for_get(&self.oas_value, op, Some("".to_string()));
134
135                        let url = self.oas.servers();
136
137                        let req = AttackRequest::builder()
138                            .servers(url, true)
139                            .path(&oas_map.path.path)
140                            .method(*m)
141                            .headers(vec![])
142                            .parameters(vec_param.clone())
143                            .auth(auth.clone())
144                            .headers(Vec::from([MHeader {
145                                name: "Content-Type".to_string(),
146                                value: "application/json".to_string(),
147                            }]))
148                            .payload(
149                                &change_payload(
150                                    &oas_map.payload.payload,
151                                    json_path,
152                                    json!(new_string),
153                                )
154                                    .to_string(),
155                            )
156                            .build();
157                        let response_vector = req.send_request_all_servers(self.verbosity > 0).await;
158                        for response in response_vector {
159                            ret_val.1.push(&req, &response, "Testing  /max values".to_string());
160                            ret_val.0.push((
161                                ResponseData {
162                                    location: oas_map.path.path.clone(),
163                                    alert_text: format!(
164                                        "The {} length limit for {:?} is not enforced by the server",
165                                        max_len,
166                                        json_path
167                                    ),
168                                    serverity: Level::Low,
169                                },
170                                response,
171                            ));
172                        }
173                    }
174                }
175            }
176        }
177        ret_val
178    }
179
180    pub async fn check_parameter_pollution(
181        &self,
182        auth: &Authorization,
183    ) -> (CheckRetVal, Vec<String>) {
184        let mut ret_val = CheckRetVal::default();
185        let vec_polluted = vec!["blstparamtopollute".to_string()];
186        //   let base_url = server.unwrap().get(0).unwrap().clone();
187        for (path, item) in &self.oas.get_paths() {
188            for (m, op) in item.get_ops() {
189                let _text = path.to_string();
190                if m == Method::GET {
191                    let mut vec_param = create_payload_for_get(&self.oas_value, op, None);
192                    let indices = vec_param
193                        .iter()
194                        .enumerate()
195                        .filter(|(_, x)| x.dm == QuePay::Query)
196                        .map(|(index, _)| index)
197                        .collect::<Vec<_>>();
198                    for i in indices {
199                        let param_query_pollute = vec_param.get(i).unwrap().clone();
200                        vec_param.push(param_query_pollute);
201                        let req = AttackRequest::builder()
202                            .servers(self.oas.servers(), true)
203                            .path(path)
204                            .auth(auth.clone())
205                            .parameters(vec_param.clone())
206                            .method(m)
207                            .headers(vec![])
208                            .auth(auth.clone())
209                            .build();
210                        let response_vector = req.send_request_all_servers(self.verbosity > 0).await;
211                        for response in response_vector {
212                            ret_val.1.push(&req, &response, "Testing get parameter pollution ".to_string());
213                            ret_val.0.push((
214                                ResponseData {
215                                    location: path.clone(),
216                                    alert_text: format!(
217                                        "The {} parameter in the {} endpoint seems to be vulnerable to parameter pollution"
218                                        , vec_param.last().unwrap().name, path),
219                                    serverity: Level::Medium,
220                                },
221                                response,
222                            ));
223                        }
224                        vec_param.remove(vec_param.len() - 1);
225                    }
226                }
227            }
228        }
229        (ret_val, vec_polluted)
230    }
231
232
233    pub async fn check_ssl(&self, auth: &Authorization) -> CheckRetVal {
234        let mut ret_val = CheckRetVal::default();
235        let req = AttackRequest::builder()
236            .servers(self.oas.servers(), false)
237            .path("")
238            .auth(auth.clone())
239            .parameters(vec![])
240            .method(Method::GET)
241            .headers(vec![])
242            .auth(auth.clone())
243            .build();
244        let response_vector = req.send_request_all_servers(self.verbosity > 0).await;
245        for (response,server) in response_vector.iter().zip(req.servers.iter()) {
246            ret_val.1.push(&req, response, "Testing SSL".to_string());
247            ret_val.0.push((
248                ResponseData {
249                    location: server.base_url.clone(),
250                    alert_text: format!(
251                        "The server does not seem to be using SSL, status code: {}",
252                        response.status
253                    ),
254                    serverity: Level::Low,
255                },
256                response.clone(),
257            ));
258        }
259        ret_val
260    }
261
262    pub async fn check_authentication(&self, _auth: &Authorization) -> CheckRetVal {
263        let mut ret_val = CheckRetVal::default();
264        for oas_map in self.payloads.iter() {
265            for _schema in oas_map.payload.map.values() {
266                for (m, op) in oas_map.path.path_item.get_ops().iter() {
267                    let vec_param =
268                        create_payload_for_get(&self.oas_value, op, Some("".to_string()));
269                    if let Some(_value) = &op.security {
270                        let req: AttackRequest =
271                            if m == &Method::POST {
272                                AttackRequest::builder() //TODO THIS IF STATEMENT CAN BE MOVED INTO THE BUILDER
273                                    .servers(self.oas.servers(), true)
274                                    .path(&oas_map.path.path)
275                                    .method(*m)
276                                    .headers(vec![])
277                                    .parameters(vec_param.clone())
278                                    //.auth(auth.clone())
279                                    .payload(&oas_map.payload.payload.to_string())
280                                    .build()
281                            } else {
282                                AttackRequest::builder()
283                                    .servers(self.oas.servers(), true)
284                                    .path(&oas_map.path.path)
285                                    .method(*m)
286                                    .headers(vec![])
287                                    .parameters(vec_param.clone())
288                                    .build()
289                            };
290                        let response_vector = req.send_request_all_servers(self.verbosity > 0).await;
291                        for response in response_vector {
292                            ret_val.1.push(&req, &response, "Testing authentication".to_string());
293                            ret_val.0.push((
294                                ResponseData {
295                                    location: oas_map.path.path.clone(),
296                                    alert_text: format!(
297                                        "The {} endpoint does not seem to require authentication",
298                                        oas_map.path.path
299                                    ),
300                                   serverity: Level::High,
301                                },
302                                response,
303                            ));
304                        }
305                    }
306                }
307            }
308        }
309        ret_val
310    }
311
312    pub async fn check_method_permissions_active(&self, auth: &Authorization) -> CheckRetVal {
313        let mut ret_val = CheckRetVal::default();
314        for (path, item) in &self.oas.get_paths() {
315            let current_method_set = item
316                .get_ops()
317                .iter()
318                .map(|(m, _)| m)
319                .cloned()
320                .collect::<HashSet<_>>();
321
322            let vec_param =
323                create_payload_for_get(&self.oas_value, item.get_ops()[0].1, Some("".to_string()));
324
325            let all_method_set = HashSet::from(LIST_METHOD);
326            for method in all_method_set.difference(&current_method_set).cloned() {
327                let req = AttackRequest::builder()
328                    .servers(self.oas.servers(), true)
329                    .path(path)
330                    .parameters(vec_param.clone())
331                    .auth(auth.clone())
332                    .method(method)
333                    .headers(vec![])
334                    .build();
335                let response_vector = req.send_request_all_servers(self.verbosity > 0).await;
336                for response in response_vector {
337                    ret_val.1.push(&req, &response, "Testing method permissions".to_string());
338                    ret_val.0.push((
339                        ResponseData {
340                            location: path.clone(),
341                            alert_text: format!(
342                                "The {} endpoint accepts {:?} although its not documented to",
343                                path, method
344                            ),
345                            serverity: Level::High,
346                        },
347                        response,
348                    ));
349                }
350            }
351        }
352        ret_val
353    }
354}
355
356const LIST_METHOD: [Method; 3] = [Method::GET, Method::POST, Method::PUT];
357
358const LIST_PARAM: [&str; 85] = [
359    "photoUrls",
360    "page",
361    "url",
362    "ret",
363    "r2",
364    "img",
365    "u",
366    "return",
367    "r",
368    "URL",
369    "next",
370    "redirect",
371    "redirectBack",
372    "AuthState",
373    "referer",
374    "redir",
375    "l",
376    "aspxerrorpath",
377    "image_path",
378    "ActionCodeURL",
379    "return_url",
380    "link",
381    "q",
382    "location",
383    "ReturnUrl",
384    "uri",
385    "referrer",
386    "returnUrl",
387    "forward",
388    "file",
389    "rb",
390    "end_display",
391    "urlact",
392    "from",
393    "goto",
394    "path",
395    "redirect_url",
396    "old",
397    "pathlocation",
398    "successTarget",
399    "returnURL",
400    "urlsito",
401    "newurl",
402    "Url",
403    "back",
404    "retour",
405    "odkazujuca_linka",
406    "r_link",
407    "cur_url",
408    "H_name",
409    "ref",
410    "topic",
411    "resource",
412    "returnTo",
413    "home",
414    "node",
415    "sUrl",
416    "href",
417    "linkurl",
418    "returnto",
419    "redirecturl",
420    "SL",
421    "st",
422    "errorUrl",
423    "media",
424    "destination",
425    "targeturl",
426    "return_to",
427    "cancel_url",
428    "doc",
429    "GO",
430    "ReturnTo",
431    "anything",
432    "FileName",
433    "logoutRedirectURL",
434    "list",
435    "startUrl",
436    "service",
437    "redirect_to",
438    "end_url",
439    "_next",
440    "noSuchEntryRedirect",
441    "context",
442    "returnurl",
443    "ref_url",
444];