const SQL_ERROR_SIGNATURES: &[&str] = &[
"sql syntax",
"syntax error at or near",
"unclosed quotation mark",
"quoted string not properly terminated",
"you have an error in your sql syntax",
"sqlite3.operationalerror",
"psql:",
"pg::",
"ora-00933",
"odbc sql",
"mysql_fetch",
"warning: mysql",
];
pub fn response_indicates_sqli(body: &str) -> bool {
let lower = body.to_ascii_lowercase();
SQL_ERROR_SIGNATURES.iter().any(|sig| lower.contains(sig))
}
const INPUT_SOURCES: &[&str] = &[
"input",
"req.",
"request.",
"params",
"query_params",
"body",
"argv",
"user_input",
"form",
".get(",
".post(",
"getparameter",
"read_line",
"stdin",
"$_get",
"$_post",
"$_request",
];
const SANITIZERS: &[&str] = &[
"sanitize",
"escape",
"prepared",
"prepare(",
"parameterized",
"bind_param",
"bindparam",
"placeholder",
"?",
"$1",
"%s",
":param",
];
pub fn line_reads_input(line: &str) -> bool {
let l = line.to_ascii_lowercase();
INPUT_SOURCES.iter().any(|s| l.contains(s))
}
const DYNAMIC_STRING_SIGNALS: &[&str] = &[
"\" +", "+ \"", "' +", "+ '", "f\"", "f'", ".format(", "${", "format!", "%(", ];
pub fn line_builds_dynamic_string(line: &str) -> bool {
let l = line.to_ascii_lowercase();
DYNAMIC_STRING_SIGNALS.iter().any(|s| l.contains(s))
}
pub fn line_reaches_sink(line: &str) -> bool {
line_reads_input(line) || line_builds_dynamic_string(line)
}
pub fn line_is_sanitized(line: &str) -> bool {
let l = line.to_ascii_lowercase();
SANITIZERS.iter().any(|s| l.contains(s))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn detects_sql_errors() {
assert!(response_indicates_sqli(
"You have an error in your SQL syntax near ..."
));
assert!(!response_indicates_sqli("login failed"));
}
#[test]
fn input_and_sanitizer_detection() {
assert!(line_reads_input(
"let q = format!(\"SELECT * WHERE u={}\", req.user_input);"
));
assert!(line_is_sanitized(
"stmt = conn.prepare(\"SELECT * WHERE u=?\");"
));
assert!(!line_is_sanitized("let q = \"SELECT 1\";"));
}
}