Skip to content

Commit

Permalink
fix: refactored http placeholders parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
evilsocket committed Jan 16, 2024
1 parent b45101b commit 0978500
Show file tree
Hide file tree
Showing 3 changed files with 147 additions and 72 deletions.
176 changes: 118 additions & 58 deletions src/plugins/http/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ mod csrf;
mod ntlm;
pub(crate) mod options;
mod payload;
mod placeholders;
mod ua;

#[ctor]
Expand Down Expand Up @@ -123,12 +124,7 @@ impl HTTP {
"".to_owned()
};

let path = target_url
.path()
.replace("%7BUSERNAME%7D", "{USERNAME}")
.replace("%7BPASSWORD%7D", "{PASSWORD}")
.replace("%7BPAYLOAD%7D", "{PAYLOAD}"); // undo query encoding of interpolation params

let path = placeholders::interpolate(target_url.path(), creds);
let query = if let Some(query) = target_url.query() {
format!("?{}", query)
} else {
Expand All @@ -147,9 +143,7 @@ impl HTTP {
target_url.to_string()
};

Ok(target_url
.replace("{USERNAME}", &creds.username)
.replace("{PASSWORD}", &creds.password))
Ok(placeholders::interpolate(&target_url, creds))
}

fn setup_request_body(
Expand Down Expand Up @@ -374,7 +368,7 @@ impl HTTP {
let headers = self.setup_headers();
let url = if target.contains("{PAYLOAD}") {
// by interpolation
target.replace("{PAYLOAD}", &creds.username)
placeholders::interpolate(&target, creds)
} else {
// by appending
format!(
Expand Down Expand Up @@ -610,56 +604,122 @@ impl Plugin for HTTP {
mod tests {
use reqwest::header::{HeaderValue, CONTENT_TYPE};

use crate::{options::Options, plugins::Plugin};
use crate::{creds::Credentials, options::Options, plugins::Plugin};

use super::{Strategy, HTTP};
/*
#[test]
fn test_get_target_url_adds_default_schema_and_path() {
let http = HTTP::new(Strategy::Request);
assert_eq!(
"http://localhost:3000/",
http.get_target_url("localhost:3000").unwrap()
);
}
#[test]
fn test_get_target_url_adds_default_schema() {
let http = HTTP::new(Strategy::Request);
assert_eq!(
"http://localhost:3000/somepath",
http.get_target_url("localhost:3000/somepath").unwrap()
);
}
#[test]
fn test_get_target_url_adds_default_path() {
let http = HTTP::new(Strategy::Request);
assert_eq!(
"https://localhost:3000/",
http.get_target_url("https://localhost:3000").unwrap()
);
}
#[test]
fn test_get_target_url_preserves_query() {
let http = HTTP::new(Strategy::Request);
assert_eq!(
"http://localhost:3000/?foo=bar",
http.get_target_url("localhost:3000/?foo=bar").unwrap()
);
}
#[test]
fn test_get_target_url_preserves_query_with_placeholder() {
let http = HTTP::new(Strategy::Request);
assert_eq!(
"http://localhost:3000/?username={USERNAME}",
http.get_target_url("localhost:3000/?username={USERNAME}")
.unwrap()
);
}
*/

#[test]
fn test_get_target_url_adds_default_schema_and_path() {
let creds = Credentials {
target: "localhost:3000".to_owned(),
username: String::new(),
password: String::new(),
};
let http = HTTP::new(Strategy::Request);
assert_eq!(
"http://localhost:3000/",
http.get_target_url(&creds).unwrap()
);
}

#[test]
fn test_get_target_url_adds_default_schema() {
let creds = Credentials {
target: "localhost:3000/somepath".to_owned(),
username: String::new(),
password: String::new(),
};
let http = HTTP::new(Strategy::Request);
assert_eq!(
"http://localhost:3000/somepath",
http.get_target_url(&creds).unwrap()
);
}

#[test]
fn test_get_target_url_adds_default_path() {
let creds = Credentials {
target: "https://localhost:3000".to_owned(),
username: String::new(),
password: String::new(),
};
let http = HTTP::new(Strategy::Request);
assert_eq!(
"https://localhost:3000/",
http.get_target_url(&creds).unwrap()
);
}

#[test]
fn test_get_target_url_preserves_query() {
let creds = Credentials {
target: "localhost:3000/?foo=bar".to_owned(),
username: String::new(),
password: String::new(),
};
let http = HTTP::new(Strategy::Request);
assert_eq!(
"http://localhost:3000/?foo=bar",
http.get_target_url(&creds).unwrap()
);
}

#[test]
fn test_get_target_url_interpolates_query_with_username_placeholder() {
let creds = Credentials {
target: "localhost:3000/?username={USERNAME}".to_owned(),
username: "bob".to_owned(),
password: String::new(),
};
let http = HTTP::new(Strategy::Request);
assert_eq!(
"http://localhost:3000/?username=bob",
http.get_target_url(&creds).unwrap()
);
}

#[test]
fn test_get_target_url_interpolates_query_with_password_placeholder() {
let creds = Credentials {
target: "localhost:3000/?p={PASSWORD}".to_owned(),
username: String::new(),
password: "f00b4r".to_owned(),
};
let http = HTTP::new(Strategy::Request);
assert_eq!(
"http://localhost:3000/?p=f00b4r",
http.get_target_url(&creds).unwrap()
);
}

#[test]
fn test_get_target_url_interpolates_query_with_payload_placeholder() {
let creds = Credentials {
target: "localhost:3000/?p={PAYLOAD}".to_owned(),
username: "something".to_owned(),
password: String::new(),
};
let http = HTTP::new(Strategy::Request);
assert_eq!(
"http://localhost:3000/?p=something",
http.get_target_url(&creds).unwrap()
);
}

#[test]
fn test_get_target_url_interpolates_query_urlencoded() {
let creds = Credentials {
target: "localhost:3000/?p=%7BPAYLOAD%7D".to_owned(),
username: "something".to_owned(),
password: String::new(),
};
let http = HTTP::new(Strategy::Request);
assert_eq!(
"http://localhost:3000/?p=something",
http.get_target_url(&creds).unwrap()
);
}

#[test]
fn test_plugin_setup_fails_if_no_payload_provided_for_post() {
let mut http = HTTP::new(Strategy::Request);
Expand Down
18 changes: 4 additions & 14 deletions src/plugins/http/payload.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use crate::Credentials;

const USERNAME_PLACEHOLDER: &str = "{USERNAME}";
const PASSWORD_PLACEHOLDER: &str = "{PASSWORD}";
use super::placeholders;

pub(crate) fn parse_fields(
payload: Option<&String>,
Expand All @@ -13,11 +12,8 @@ pub(crate) fn parse_fields(
for keyval in raw.split('&') {
let parts: Vec<&str> = keyval.splitn(2, '=').collect();
let key = parts[0].to_owned();
let value = match parts[1] {
USERNAME_PLACEHOLDER => creds.username.to_owned(),
PASSWORD_PLACEHOLDER => creds.password.to_owned(),
_ => parts[1].to_owned(),
};
let value = placeholders::interpolate(parts[1], creds);

parsed.push((key, value));
}

Expand All @@ -27,11 +23,5 @@ pub(crate) fn parse_fields(
}

pub(crate) fn parse_body(payload: Option<&String>, creds: &Credentials) -> Option<String> {
if let Some(raw) = payload {
return Some(
raw.replace(USERNAME_PLACEHOLDER, &creds.username)
.replace(PASSWORD_PLACEHOLDER, &creds.password),
);
}
None
payload.map(|raw| placeholders::interpolate(raw, creds))
}
25 changes: 25 additions & 0 deletions src/plugins/http/placeholders.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
use crate::creds::Credentials;

const USERNAME: &str = "{USERNAME}";
const PASSWORD: &str = "{PASSWORD}";
const PAYLOAD: &str = "{PAYLOAD}";

pub(crate) fn interpolate(data: &str, creds: &Credentials) -> String {
let mut parsed = data.to_owned();

// undo query encoding of interpolation params
for placeholder in vec![USERNAME, PASSWORD, PAYLOAD] {
let encoded_lwr = placeholder.replace("{", "%7b").replace("}", "%7d");
let encoded_upr = placeholder.replace("{", "%7B").replace("}", "%7D");

parsed = parsed
.replace(&encoded_lwr, placeholder)
.replace(&encoded_upr, placeholder);
}

// interpolate placeholders
parsed
.replace(USERNAME, &creds.username)
.replace(PAYLOAD, &creds.username)
.replace(PASSWORD, &creds.password)
}

0 comments on commit 0978500

Please sign in to comment.