use crate::solve::{Solution, preimage_for};
use crate::transduce::{Pipeline, Stage};
use wafrift_grammar::grammar::equiv::{DeliveryShape, Dialect, EquivPayload};
struct NamedSink {
tag: &'static str,
pipeline: Pipeline,
}
fn default_sinks() -> Vec<NamedSink> {
vec![
NamedSink {
tag: "norm_mismatch_double_url",
pipeline: Pipeline(vec![Stage::DoubleUrlDecode]),
},
NamedSink {
tag: "norm_mismatch_json_unescape",
pipeline: Pipeline(vec![Stage::JsonUnescape]),
},
NamedSink {
tag: "norm_mismatch_html_entity",
pipeline: Pipeline(vec![Stage::HtmlEntityDecode]),
},
NamedSink {
tag: "norm_mismatch_nfkc",
pipeline: Pipeline(vec![Stage::NfkcNormalize]),
},
NamedSink {
tag: "norm_mismatch_bestfit",
pipeline: Pipeline(vec![Stage::BestFitDownconvert]),
},
]
}
#[must_use]
pub fn norm_mismatch_members(attack: &str, param: &str) -> Vec<EquivPayload> {
let mut out = Vec::new();
let mut seen_payloads = std::collections::HashSet::new();
for ns in default_sinks() {
let pre = preimage_for(attack.as_bytes(), &ns.pipeline, false);
if pre == attack.as_bytes() {
continue;
}
let payload = String::from_utf8_lossy(&pre).into_owned();
if !seen_payloads.insert(payload.clone()) {
continue;
}
out.push(EquivPayload {
payload,
delivery: DeliveryShape::Query {
param: param.to_string(),
},
dialect: Dialect::Generic,
rules: vec![ns.tag],
});
}
out
}
#[must_use]
pub fn solution_member(sol: &Solution, param: &str) -> Option<EquivPayload> {
let payload = match std::str::from_utf8(&sol.input) {
Ok(s) => s.to_string(),
Err(_) => {
tracing::warn!(
bytes = sol.input.len(),
"solver returned non-UTF-8 bypass candidate; \
dropping (lossy conversion would mangle the bytes \
the oracle compared against)"
);
return None;
}
};
Some(EquivPayload {
payload,
delivery: DeliveryShape::Query {
param: param.to_string(),
},
dialect: Dialect::Generic,
rules: vec!["solver_bypass"],
})
}
#[must_use]
pub fn sink_for_tag(tag: &str) -> Option<Pipeline> {
default_sinks()
.into_iter()
.find(|ns| ns.tag == tag)
.map(|ns| ns.pipeline)
}