diff --git a/CHANGELOG-Japanese.md b/CHANGELOG-Japanese.md index e5c9f191d..6451c695d 100644 --- a/CHANGELOG-Japanese.md +++ b/CHANGELOG-Japanese.md @@ -9,6 +9,7 @@ * `i`: (insensitive) 大文字小文字を区別しないマッチングを無効にする。 * `m`: (multi-line) 複数行にまたがってマッチする。`^` /`$` は行頭/行末にマッチする。 * `s`: (single-line) ドット文字 (`.`) は改行文字を含むすべての文字にマッチする。 +- Sigma V2の`|exists`モディファイアに対応した。 (#1400) (@hitenkoku) **改善:** diff --git a/CHANGELOG.md b/CHANGELOG.md index d8b21535f..5f3429f45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ * `i`: (insensitive) disable case-sensitive matching. * `m`: (multi-line) match across multiple lines. `^` /`$` match the start/end of line. * `s`: (single-line) the dot character (`.`) matches all characters, including the newline character. +- Support for the Sigma V2 `|exists` modifier. (#1400) (@hitenkoku) **Enhancements:** diff --git a/src/detections/rule/matchers.rs b/src/detections/rule/matchers.rs index 4d9cd5921..5cb500902 100644 --- a/src/detections/rule/matchers.rs +++ b/src/detections/rule/matchers.rs @@ -578,9 +578,9 @@ impl LeafMatcher for DefaultMatcher { fn is_match(&self, event_value: Option<&String>, recinfo: &EvtxRecordInfo) -> bool { let pipe: &PipeElement = self.pipes.first().unwrap_or(&PipeElement::Wildcard); let match_result = match pipe { - PipeElement::EqualsField(_) | PipeElement::Endswithfield(_) => { - Some(pipe.is_eqfield_match(event_value, recinfo)) - } + PipeElement::Exists(..) + | PipeElement::EqualsField(_) + | PipeElement::Endswithfield(_) => Some(pipe.is_eqfield_match(event_value, recinfo)), PipeElement::Cidr(ip_result) => match ip_result { Ok(matcher_ip) => { let val = String::default(); @@ -686,6 +686,7 @@ enum PipeElement { ReMultiLine, ReSingleLine, Wildcard, + Exists(String, String), EqualsField(String), Endswithfield(String), Base64offset, @@ -702,6 +703,10 @@ impl PipeElement { "endswith" => Option::Some(PipeElement::Endswith), "contains" => Option::Some(PipeElement::Contains), "re" => Option::Some(PipeElement::Re), + "exists" => Option::Some(PipeElement::Exists( + key_list[0].split('|').collect::>()[0].to_string(), + pattern.to_string(), + )), "reignorecase" => Option::Some(PipeElement::ReIgnoreCase), "resingleline" => Option::Some(PipeElement::ReSingleLine), "remultiline" => Option::Some(PipeElement::ReMultiLine), @@ -735,6 +740,9 @@ impl PipeElement { fn is_eqfield_match(&self, event_value: Option<&String>, recinfo: &EvtxRecordInfo) -> bool { match self { + PipeElement::Exists(eq_key, val) => { + val.to_lowercase() == recinfo.get_value(eq_key).is_some().to_string() + } PipeElement::EqualsField(eq_key) => { let eq_value = recinfo.get_value(eq_key); // Evtxのレコードに存在しないeventkeyを指定された場合はfalseにする @@ -3275,6 +3283,31 @@ mod tests { ); } + #[test] + fn test_exists_true() { + let rule_str = r#" + enabled: true + detection: + selection1: + Channel|exists: true + condition: selection1 + "#; + + let record_json_str = r#" + { + "Event": { + "System": { + "EventID": 1, + "Channel": "Microsoft-Windows-Sysmon/Operational" + }, + "EventData": { + "CurrentDirectory": "C:\\Windows\\system32\\" + } + } + }"#; + check_select(rule_str, record_json_str, true); + } + #[test] fn test_re_caseinsensitive_detect() { let rule_str = r#" @@ -3293,6 +3326,31 @@ mod tests { check_select(rule_str, record_json_str, true); } + #[test] + fn test_exists_null_true() { + let rule_str = r#" + enabled: true + detection: + selection1: + Channel|exists: true + condition: selection1 + "#; + + let record_json_str = r#" + { + "Event": { + "System": { + "EventID": 1, + "Channel": "" + }, + "EventData": { + "CurrentDirectory": "C:\\Windows\\system32\\" + } + } + }"#; + check_select(rule_str, record_json_str, true); + } + #[test] fn test_re_multiline_detect() { let rule_str = r#" @@ -3311,6 +3369,31 @@ mod tests { check_select(rule_str, record_json_str, true); } + #[test] + fn test_exists_false() { + let rule_str = r#" + enabled: true + detection: + selection1: + Dummy|exists: false + condition: selection1 + "#; + + let record_json_str = r#" + { + "Event": { + "System": { + "EventID": 1, + "Channel": "" + }, + "EventData": { + "CurrentDirectory": "C:\\Windows\\system32\\" + } + } + }"#; + check_select(rule_str, record_json_str, true); + } + #[test] fn test_re_singleline_detect() { let rule_str = r#"