diff --git a/src/args.rs b/src/args.rs index 63209af..83be2ea 100644 --- a/src/args.rs +++ b/src/args.rs @@ -99,7 +99,6 @@ pub fn get_config() -> (Config, usize) { .arg( Arg::with_name("headers") .short("H") - .long("header") .help("Example: -H 'one:one' 'two:two'") .takes_value(true) .min_values(1) @@ -109,6 +108,12 @@ pub fn get_config() -> (Config, usize) { .long("as-body") .help("Send parameters via body.\nBuilt in body types that can be detected automatically: json, urlencode") ) + .arg( + Arg::with_name("headers-discovery") + .long("headers") + .help("Switch to header discovery mode") + .conflicts_with("as-body") + ) .arg( Arg::with_name("force") .long("force") @@ -416,10 +421,10 @@ pub fn get_config() -> (Config, usize) { .unwrap_or("https://something.something") .to_string(); - if !args.is_present("as-body") && url.contains('?') && url.contains('=') && !url.contains("%s") { + if !args.is_present("as-body") && !args.is_present("headers-discovery") && url.contains('?') && url.contains('=') && !url.contains("%s") { url.push_str("&%s"); path.push_str("&%s"); - } else if !args.is_present("as-body") && !url.contains("%s") { + } else if !args.is_present("as-body") && !args.is_present("headers-discovery") && !url.contains("%s") { url.push_str("?%s"); path.push_str("?%s"); } @@ -440,6 +445,8 @@ pub fn get_config() -> (Config, usize) { if parameter_template.is_empty() { if body_type.contains("json") && args.is_present("as-body") { parameter_template = "\"%k\":\"%v\", "; + } else if args.is_present("headers-discovery") { + parameter_template = "%k=%v; "; } else { parameter_template = "%k=%v&"; } @@ -510,6 +517,7 @@ pub fn get_config() -> (Config, usize) { save_responses: args.value_of("save-responses").unwrap_or("").to_string(), output_format: args.value_of("output-format").unwrap_or("").to_string(), as_body: args.is_present("as-body"), + headers_discovery: args.is_present("headers-discovery"), force: args.is_present("force"), disable_response_correction: args.is_present("disable-response-correction"), disable_custom_parameters: args.is_present("disable-custom-parameters"), diff --git a/src/requests.rs b/src/requests.rs index ef856b1..a24c509 100644 --- a/src/requests.rs +++ b/src/requests.rs @@ -1,6 +1,6 @@ use crate::{ structs::{Config, ResponseData, Stable}, - utils::{compare, beautify_html, beautify_json, make_body, make_query, make_hashmap, random_line}, + utils::{compare, beautify_html, beautify_json, make_body, make_query, make_header_value, make_hashmap, random_line}, }; use colored::*; use reqwest::Client; @@ -105,19 +105,24 @@ pub async fn random_request( } fn create_request( - url: &str, - body: String, config: &Config, + query: String, client: &Client ) -> reqwest::RequestBuilder { + let url: String = if config.url.contains("%s") { + config.url.replace("%s", &query) + } else { + config.url.clone() + }; + let mut client = if config.as_body { match config.method.as_str() { - "GET" => client.get(url).body(body), - "POST" => client.post(url).body(body), - "PUT" => client.put(url).body(body), - "PATCH" => client.patch(url).body(body), - "DELETE" => client.delete(url).body(body), - "HEAD" => client.head(url).body(body), + "GET" => client.get(url).body(query.clone()), + "POST" => client.post(url).body(query.clone()), + "PUT" => client.put(url).body(query.clone()), + "PATCH" => client.patch(url).body(query.clone()), + "DELETE" => client.delete(url).body(query.clone()), + "HEAD" => client.head(url).body(query.clone()), _ => { writeln!(io::stderr(), "Method is not supported").ok(); std::process::exit(1); @@ -151,7 +156,11 @@ fn create_request( }; for (key, value) in config.headers.iter() { - client = client.header(key, value.replace("{{random}}", &random_line(config.value_size))); + if value.contains("%s") && config.headers_discovery { + client = client.header(key, value.replace("%s", &query).replace("{{random}}", &random_line(config.value_size))); + } else { + client = client.header(key, value.replace("{{random}}", &random_line(config.value_size))); + }; } client @@ -163,50 +172,51 @@ pub async fn request( initial_query: &HashMap, reflections: usize, ) -> ResponseData { - let mut query: HashMap = HashMap::with_capacity(initial_query.len()); + let mut hashmap_query: HashMap = HashMap::with_capacity(initial_query.len()); for (k, v) in initial_query.iter() { - query.insert(k.to_string(), v.replace("%random%_", "")); + hashmap_query.insert(k.to_string(), v.replace("%random%_", "")); } - let body: String = if config.as_body && !query.is_empty() { - make_body(&config, &query) + let query: String = if !hashmap_query.is_empty() { + if config.as_body { + make_body(&config, &hashmap_query) + } else if config.headers_discovery { + make_header_value(&config, &hashmap_query) + } else { + make_query(&config, &hashmap_query) + } } else { String::new() }; std::thread::sleep(config.delay); - let url: String = if config.url.contains("%s") { - config.url.replace("%s", &make_query(&query, config)) - } else { - config.url.clone() - }; - - let url: &str = &url; + let url: &str = &config.url; - let res = match create_request(url, body.clone(), config, client).send().await { + let res = match create_request(config, query, client).send().await { Ok(val) => val, Err(_) => { //Try to make a random request instead - let mut random_query: HashMap = HashMap::with_capacity(query.len()); + let mut random_query: HashMap = HashMap::with_capacity(hashmap_query.len()); for (k, v) in make_hashmap( - &(0..query.len()).map(|_| random_line(config.value_size)).collect::>(), + &(0..hashmap_query.len()).map(|_| random_line(config.value_size)).collect::>(), config.value_size, ) { random_query.insert(k.to_string(), v.replace("%random%_", "")); } - let body: String = if config.as_body && !query.is_empty() { - make_body(&config, &random_query) + let random_query: String = if !random_query.is_empty() { + if config.as_body { + make_body(&config, &random_query) + } else if config.headers_discovery { + make_header_value(&config, &random_query) + } else { + make_query(&config, &random_query) + } } else { String::new() }; - let url: String = if config.url.contains("%s") { - config.url.replace("%s", &make_query(&random_query, config)) - } else { - config.url.clone() - }; - match create_request(&url, body.clone(), config, client).send().await { + match create_request(config, random_query.clone(), client).send().await { Ok(_) => return ResponseData { text: String::new(), code: 0, @@ -223,7 +233,7 @@ pub async fn request( }; writeln!(io::stderr(), "[~] error at the {} observed. Wait 50 sec and repeat.", config.url).ok(); std::thread::sleep(Duration::from_secs(50)); - match create_request(&url, body.clone(), config, client).send().await { + match create_request(config, random_query, client).send().await { Ok(_) => return ResponseData { text: String::new(), code: 0, diff --git a/src/structs.rs b/src/structs.rs index 269bf1f..c4c1486 100644 --- a/src/structs.rs +++ b/src/structs.rs @@ -54,10 +54,10 @@ pub struct Config { pub encode: bool, pub test: bool, pub as_body: bool, + pub headers_discovery: bool, pub verbose: u8, pub is_json: bool, pub disable_cachebuster: bool, - //pub verify: bool, pub delay: Duration, pub value_size: usize, pub learn_requests_count: usize, diff --git a/src/utils.rs b/src/utils.rs index 5dc77c3..cdcb10b 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -109,35 +109,29 @@ pub fn heuristic(body: &str) -> Vec { } pub fn generate_request(config: &Config, initial_query: &HashMap) -> String { - let mut query: HashMap = HashMap::with_capacity(initial_query.len()); + let mut hashmap_query: HashMap = HashMap::with_capacity(initial_query.len()); for (k, v) in initial_query.iter() { - query.insert(k.to_string(), v.replace("%random%_", "")); + hashmap_query.insert(k.to_string(), v.replace("%random%_", "")); } + let query: String = if !hashmap_query.is_empty() { + if config.as_body { + make_body(&config, &hashmap_query) + } else if config.headers_discovery { + make_header_value(&config, &hashmap_query) + } else { + make_query(&config, &hashmap_query) + } + } else { + String::new() + }; + let mut req: String = String::with_capacity(1024); req.push_str(&config.url); req.push('\n'); req.push_str(&config.method); req.push(' '); - - if !config.as_body { - let mut query_string = String::new(); - for (k, v) in query.iter() { - query_string.push_str(k); - query_string.push('='); - query_string.push_str(v); - query_string.push('&'); - } - query_string.pop(); //remove the last & - - query_string = if config.encode { - utf8_percent_encode(&query_string, &FRAGMENT).to_string() - } else { - query_string - }; - - req.push_str(&config.path.replace("%s", &query_string)); - } + req.push_str(&config.path.replace("%s", &query)); if config.http2 { req.push_str(" HTTP/2\n"); @@ -153,19 +147,17 @@ pub fn generate_request(config: &Config, initial_query: &HashMap for (key, value) in config.headers.iter() { req.push_str(key); req.push_str(": "); - req.push_str(&value.replace("{{random}}", &random_line(config.value_size))); + if value.contains("%s") && config.headers_discovery { + req.push_str(&value.replace("%s", &query).replace("{{random}}", &random_line(config.value_size))); + } else { + req.push_str(&value.replace("{{random}}", &random_line(config.value_size))); + } req.push('\n'); } - let body: String = if config.as_body && !query.is_empty() { - make_body(&config, &query) - } else { - config.body.to_owned() - }; - - if !body.is_empty() { + if config.as_body && !query.is_empty() { req.push('\n'); - req.push_str(&body); + req.push_str(&query); req.push('\n'); } @@ -215,6 +207,10 @@ pub fn adjust_body(body: &str, t: &str) -> String { } } +pub fn make_header_value(config: &Config, query: &HashMap) -> String { + make_query(config, query) +} + pub fn make_body(config: &Config, query: &HashMap) -> String { let mut body: String = String::new(); @@ -238,8 +234,8 @@ pub fn make_body(config: &Config, query: &HashMap) -> String { body } -pub fn make_query(params: &HashMap, config: &Config) -> String { - let mut query: String = String::from(""); +pub fn make_query(config: &Config, params: &HashMap) -> String { + let mut query: String = String::new(); for (k, v) in params { query = query + &config.parameter_template.replace("%k", k).replace("%v", v); @@ -275,7 +271,7 @@ pub fn make_hashmap( hashmap } -pub fn parse_request(config: Config, proto: &str, request: &str, custom_value_template: bool) -> Option { +pub fn parse_request(config: Config, proto: &str, request: &str, custom_parameter_template: bool) -> Option { let mut lines = request.lines(); let mut host = String::new(); let mut content_type = String::new(); @@ -286,10 +282,14 @@ pub fn parse_request(config: Config, proto: &str, request: &str, custom_value_te let http2: bool = firstline.next()?.to_string().contains("HTTP/2"); - let mut parameter_template = if !custom_value_template { - match config.body_type == "json" { - true => String::from("\"%k\":\"%v\", "), - false => String::from("%k=%v&") + let mut parameter_template = if !custom_parameter_template { + if config.headers_discovery { + String::from("%k=%v; ") + } else { + match config.body_type == "json" { + true => String::from("\"%k\":\"%v\", "), + false => String::from("%k=%v&") + } } } else { config.parameter_template.clone() @@ -332,7 +332,7 @@ pub fn parse_request(config: Config, proto: &str, request: &str, custom_value_te } //check whether the body type can be json - let body_type = if config.body_type.contains('-') && config.as_body && !custom_value_template + let body_type = if config.body_type.contains('-') && config.as_body && !custom_parameter_template && ( content_type.contains("json") || (!body.is_empty() && body.starts_with('{') ) ) {