bext-waf 0.2.0

Web Application Firewall for bext — rate limiting, IP filtering, GeoIP, rule engine
Documentation
//! Server-Side Includes (SSI) injection detection.
//!
//! Detects `<!--#exec cmd="..."-->` and related SSI directives that can
//! execute commands when processed by Apache mod_include or nginx ssi module.

use std::sync::OnceLock;

use regex::RegexSet;

static SSI_PATTERNS: OnceLock<RegexSet> = OnceLock::new();

static SSI_DESCRIPTIONS: &[&str] = &[
    "SSI exec directive (<!--#exec)",
    "SSI include directive (<!--#include)",
    "SSI config/echo/set directive",
];

fn patterns() -> &'static RegexSet {
    SSI_PATTERNS.get_or_init(|| {
        RegexSet::new([
            // 0: <!--#exec cmd="..." --> or <!--#exec cgi="..." -->
            r"(?i)<!--\s*#\s*exec\b",
            // 1: <!--#include virtual="..." --> or <!--#include file="..." -->
            r"(?i)<!--\s*#\s*include\b",
            // 2: <!--#config/echo/set/... -->
            r"(?i)<!--\s*#\s*(config|echo|set|if|elif|else|endif|fsize|flastmod)\b",
        ])
        .expect("SSI regex patterns must compile")
    })
}

/// Check an input string for SSI injection patterns.
pub fn check_ssi(input: &str) -> Option<String> {
    let set = patterns();
    let matches: Vec<_> = set.matches(input).into_iter().collect();
    if matches.is_empty() {
        None
    } else {
        let idx = matches[0];
        Some(
            SSI_DESCRIPTIONS
                .get(idx)
                .unwrap_or(&"SSI injection")
                .to_string(),
        )
    }
}

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

    #[test]
    fn detects_exec() {
        assert!(check_ssi(r#"<!--#exec cmd="id" -->"#).is_some());
        assert!(check_ssi(r#"<!--#exec cgi="/cgi-bin/hack.cgi" -->"#).is_some());
    }

    #[test]
    fn detects_include() {
        assert!(check_ssi(r#"<!--#include virtual="/etc/passwd" -->"#).is_some());
    }

    #[test]
    fn detects_config() {
        assert!(check_ssi(r#"<!--#config timefmt="%Y" -->"#).is_some());
        assert!(check_ssi(r#"<!--#echo var="DOCUMENT_ROOT" -->"#).is_some());
    }

    #[test]
    fn allows_normal_html_comments() {
        assert!(check_ssi("<!-- This is a comment -->").is_none());
        assert!(check_ssi("<!-- TODO: fix this -->").is_none());
    }

    #[test]
    fn allows_normal_text() {
        assert!(check_ssi("Hello world").is_none());
    }
}