use super::ProbeContext;
use std::collections::HashMap;
pub fn extract_cmdi_features(ctx: &ProbeContext, features: &mut HashMap<String, f64>) {
let body = &ctx.response.body;
let body_lower = body.to_lowercase();
let os_patterns = [
"uid=", "root:x:", "www-data", "total ", "drwx", "Directory of", "Volume Serial", "Windows IP Config", "inet ", ];
for pattern in &os_patterns {
if body.contains(pattern) && !ctx.baseline.body.contains(pattern) {
features.insert("cmdi:os_output_detected".into(), 0.95);
break;
}
}
if body.contains("root:x:0:0") || body.contains("root:*:0:0") {
features.insert("cmdi:etc_passwd_leaked".into(), 1.0);
}
let cmd_errors = [
"command not found",
"not recognized as an internal",
"sh: ",
"bash: ",
"cmd.exe",
"Permission denied",
"No such file or directory",
];
for err in &cmd_errors {
if body_lower.contains(&err.to_lowercase()) && !ctx.baseline.body.to_lowercase().contains(&err.to_lowercase()) {
features.insert("cmdi:command_error_message".into(), 0.85);
break;
}
}
if let Some(injected_delay) = ctx.injected_delay {
let actual_delay_s = (ctx.response.response_time_ms as f64) / 1000.0;
let baseline_s = (ctx.baseline.response_time_ms as f64) / 1000.0;
let extra_delay = actual_delay_s - baseline_s;
if extra_delay > (injected_delay * 0.7) {
features.insert(
"cmdi:time_delay_detected".into(),
(extra_delay / injected_delay).min(1.0),
);
}
}
if ctx.response.status == 403
|| body_lower.contains("blocked")
|| body_lower.contains("waf")
|| body_lower.contains("firewall")
{
features.insert("cmdi:waf_blocked".into(), 1.0);
}
}
#[cfg(test)]
mod tests {
use super::super::tests::*;
use super::*;
#[test]
fn test_etc_passwd_leak() {
let response = make_response("root:x:0:0:root:/root:/bin/bash\ndaemon:x:1:1:", 200);
let ctx = make_ctx("cmdi", ";cat /etc/passwd", response);
let mut features = HashMap::new();
extract_cmdi_features(&ctx, &mut features);
assert!(features.contains_key("cmdi:etc_passwd_leaked"));
assert!(features.contains_key("cmdi:os_output_detected"));
}
#[test]
fn test_command_error() {
let response = make_response("sh: 1: test_cmd: not found", 200);
let ctx = make_ctx("cmdi", ";test_cmd", response);
let mut features = HashMap::new();
extract_cmdi_features(&ctx, &mut features);
assert!(features.contains_key("cmdi:command_error_message"));
}
#[test]
fn test_cmdi_time_based() {
let mut response = make_response("OK", 200);
response.response_time_ms = 5100;
let mut ctx = make_ctx("cmdi", ";sleep 5", response);
ctx.injected_delay = Some(5.0);
ctx.baseline.response_time_ms = 100;
let mut features = HashMap::new();
extract_cmdi_features(&ctx, &mut features);
assert!(features.contains_key("cmdi:time_delay_detected"));
}
}