mod common;
use common::{engine_from, matches, titles_for, try_compile};
use serde_json::json;
#[test]
fn cidr_rejects_contains() {
let err = try_compile(
r#"
title: Cidr Contains
logsource: { category: test }
detection:
selection:
Address|cidr|contains: "192.168.0.0/16"
condition: selection
"#,
);
assert!(err.is_err(), "cidr+contains should fail: {err:?}");
}
#[test]
fn re_rejects_contains() {
let err = try_compile(
r#"
title: Re Contains
logsource: { category: test }
detection:
selection:
CommandLine|re|contains: ".*whoami.*"
condition: selection
"#,
);
assert!(err.is_err(), "re+contains should fail: {err:?}");
}
#[test]
fn numeric_gt_rejects_contains() {
let err = try_compile(
r#"
title: Gt Contains
logsource: { category: test }
detection:
selection:
Port|gt|contains: "80"
condition: selection
"#,
);
assert!(err.is_err(), "gt+contains should fail: {err:?}");
}
#[test]
fn base64_rejects_base64offset() {
let err = try_compile(
r#"
title: Base64 Both
logsource: { category: test }
detection:
selection:
Data|base64|base64offset: "test"
condition: selection
"#,
);
assert!(err.is_err(), "base64+base64offset should fail: {err:?}");
}
#[test]
fn wide_rejects_utf16() {
let err = try_compile(
r#"
title: Wide Utf16
logsource: { category: test }
detection:
selection:
CommandLine|wide|utf16: 'evil'
condition: selection
"#,
);
assert!(err.is_err(), "wide+utf16 should fail: {err:?}");
}
#[test]
fn multiline_without_re_rejected() {
let err = try_compile(
r#"
title: Multiline No Re
logsource: { category: test }
detection:
selection:
Image|multiline: 'test'
condition: selection
"#,
);
assert!(err.is_err(), "multiline without re should fail: {err:?}");
}
#[test]
fn windash_rejects_gt() {
let err = try_compile(
r#"
title: Windash Gt
logsource: { category: test }
detection:
selection:
Port|windash|gt: 80
condition: selection
"#,
);
assert!(err.is_err(), "windash+gt should fail: {err:?}");
}
#[test]
fn all_on_single_value_rejected() {
let err = try_compile(
r#"
title: All Single
logsource: { category: test }
detection:
selection:
Image|all: 'notepad.exe'
condition: selection
"#,
);
assert!(err.is_err(), "|all on a single value should fail: {err:?}");
}
#[test]
fn all_with_multiple_values_requires_every_value() {
let engine = engine_from(
r#"
title: All Multi
logsource: { category: test }
detection:
selection:
CommandLine|contains|all:
- 'powershell'
- '-enc'
- 'http'
condition: selection
"#,
);
assert!(matches(
&engine,
&json!({"CommandLine": "powershell.exe -enc http://evil.com/x"})
));
assert!(!matches(
&engine,
&json!({"CommandLine": "powershell.exe -enc dummy"})
));
}
#[test]
fn wide_base64_chain_matches_encoded_payload() {
let engine = engine_from(
r#"
title: Wide Base64
logsource: { category: test }
detection:
selection:
Payload|wide|base64: 'Test'
condition: selection
"#,
);
assert!(matches(&engine, &json!({"Payload": "VABlAHMAdAA="})));
assert!(!matches(&engine, &json!({"Payload": "VGVzdA=="})));
}
#[test]
fn base64offset_matches_plain_base64_contains() {
let engine = engine_from(
r#"
title: Base64Offset
logsource: { category: test }
detection:
selection:
Data|base64offset: 'Test'
condition: selection
"#,
);
assert!(matches(&engine, &json!({"Data": "prefix VGVzdA== suffix"})));
assert!(!matches(&engine, &json!({"Data": "nope"})));
}
#[test]
fn windash_matches_slash_variant() {
let engine = engine_from(
r#"
title: Windash
logsource: { category: test }
detection:
selection:
CommandLine|windash|contains: '-Force'
condition: selection
"#,
);
assert!(matches(
&engine,
&json!({"CommandLine": "powershell /Force"})
));
assert!(matches(
&engine,
&json!({"CommandLine": "powershell -Force"})
));
assert!(!matches(
&engine,
&json!({"CommandLine": "powershell -Help"})
));
}
#[test]
fn cased_is_case_sensitive() {
let engine = engine_from(
r#"
title: Cased
logsource: { category: test }
detection:
selection:
CommandLine|cased: 'PowerShell'
condition: selection
"#,
);
assert!(matches(&engine, &json!({"CommandLine": "PowerShell"})));
assert!(!matches(&engine, &json!({"CommandLine": "powershell"})));
}
#[test]
fn startswith_and_endswith() {
let engine = engine_from(
r#"
title: Affixes
logsource: { category: test }
detection:
selection:
Image|startswith: 'C:\\Windows'
Image|endswith: 'cmd.exe'
condition: selection
"#,
);
assert!(matches(
&engine,
&json!({"Image": "C:\\Windows\\System32\\cmd.exe"})
));
assert!(!matches(
&engine,
&json!({"Image": "C:\\Windows\\System32\\powershell.exe"})
));
}
#[test]
fn exists_true_and_false() {
let engine = engine_from(
r#"
title: Exists True
logsource: { category: test }
detection:
selection:
Image|exists: true
condition: selection
"#,
);
assert!(matches(&engine, &json!({"Image": "foo.exe"})));
assert!(!matches(&engine, &json!({"CommandLine": "foo"})));
let engine = engine_from(
r#"
title: Exists False
logsource: { category: test }
detection:
selection:
Image|exists: false
condition: selection
"#,
);
assert!(!matches(&engine, &json!({"Image": "foo.exe"})));
assert_eq!(
titles_for(&engine, &json!({"CommandLine": "foo"})),
vec!["Exists False".to_string()]
);
}
#[test]
fn numeric_comparisons() {
let engine = engine_from(
r#"
title: Numeric Gt
logsource: { category: test }
detection:
selection:
Port|gt: 80
condition: selection
"#,
);
assert!(matches(&engine, &json!({"Port": 443})));
assert!(!matches(&engine, &json!({"Port": 80})));
let engine = engine_from(
r#"
title: Numeric Eq
logsource: { category: test }
detection:
selection:
Port: 80
condition: selection
"#,
);
assert!(matches(&engine, &json!({"Port": 80})));
assert!(!matches(&engine, &json!({"Port": 443})));
}
#[test]
fn fieldref_compiles_and_matches() {
let engine = engine_from(
r#"
title: FieldRef
logsource: { category: test }
detection:
selection:
TargetImage|fieldref: 'SourceImage'
condition: selection
"#,
);
assert!(matches(
&engine,
&json!({"TargetImage": "a.exe", "SourceImage": "a.exe"})
));
assert!(!matches(
&engine,
&json!({"TargetImage": "a.exe", "SourceImage": "b.exe"})
));
}
#[test]
fn cidr_matches_network() {
let engine = engine_from(
r#"
title: Cidr
logsource: { category: test }
detection:
selection:
DestinationIp|cidr: '192.168.0.0/16'
condition: selection
"#,
);
assert!(matches(&engine, &json!({"DestinationIp": "192.168.1.10"})));
assert!(!matches(&engine, &json!({"DestinationIp": "10.0.0.1"})));
}