add equalsfield pipe (#467)

This commit is contained in:
James / hach1yon
2022-03-30 11:49:20 +09:00
committed by GitHub
parent 7861174a93
commit bca578b89e
4 changed files with 253 additions and 74 deletions

View File

@@ -1,5 +1,5 @@
use regex::Regex;
use std::collections::VecDeque;
use std::{cmp::Ordering, collections::VecDeque};
use yaml_rust::Yaml;
use crate::detections::{detection::EvtxRecordInfo, utils};
@@ -192,6 +192,7 @@ pub struct DefaultMatcher {
re: Option<Regex>,
pipes: Vec<PipeElement>,
key_list: Vec<String>,
eqfield_key: Option<String>,
}
impl DefaultMatcher {
@@ -200,9 +201,14 @@ impl DefaultMatcher {
re: Option::None,
pipes: Vec::new(),
key_list: Vec::new(),
eqfield_key: Option::None,
}
}
pub fn get_eqfield_key(&self) -> Option<&String> {
self.eqfield_key.as_ref()
}
/// このmatcherの正規表現とマッチするかどうか判定します。
/// 判定対象の文字列とこのmatcherが保持する正規表現が完全にマッチした場合のTRUEを返します。
/// 例えば、判定対象文字列が"abc"で、正規表現が"ab"の場合、正規表現は判定対象文字列の一部分にしか一致していないので、この関数はfalseを返します。
@@ -265,6 +271,7 @@ impl LeafMatcher for DefaultMatcher {
"endswith" => Option::Some(PipeElement::Endswith),
"contains" => Option::Some(PipeElement::Contains),
"re" => Option::Some(PipeElement::Re),
"equalsfield" => Option::Some(PipeElement::EqualsField),
_ => Option::None,
};
if pipe_element.is_none() {
@@ -285,34 +292,54 @@ impl LeafMatcher for DefaultMatcher {
);
return Result::Err(vec![errmsg]);
}
let is_re = &self
let is_eqfield = self
.pipes
.iter()
.any(|pipe_element| matches!(pipe_element, PipeElement::Re));
// 正規表現ではない場合、ワイルドカードであることを表す。
// ワイルドカードは正規表現でマッチングするので、ワイルドカードを正規表現に変換するPipeを内部的に追加することにする。
if !is_re {
self.pipes.push(PipeElement::Wildcard);
}
.any(|pipe_element| matches!(pipe_element, PipeElement::EqualsField));
if is_eqfield {
// PipeElement::EqualsFieldは特別
self.eqfield_key = Option::Some(pattern);
} else {
// 正規表現ではない場合、ワイルドカードであることを表す。
// ワイルドカードは正規表現でマッチングするので、ワイルドカードを正規表現に変換するPipeを内部的に追加することにする。
let is_re = self
.pipes
.iter()
.any(|pipe_element| matches!(pipe_element, PipeElement::Re));
if !is_re {
self.pipes.push(PipeElement::Wildcard);
}
// パターンをPipeで処理する。
let pattern = DefaultMatcher::from_pattern_to_regex_str(pattern, &self.pipes);
// Pipeで処理されたパターンを正規表現に変換
let re_result = Regex::new(&pattern);
if re_result.is_err() {
let errmsg = format!(
"Cannot parse regex. [regex:{}, key:{}]",
pattern,
utils::concat_selection_key(key_list)
);
return Result::Err(vec![errmsg]);
let pattern = DefaultMatcher::from_pattern_to_regex_str(pattern, &self.pipes);
// Pipeで処理されたパターンを正規表現に変換
let re_result = Regex::new(&pattern);
if re_result.is_err() {
let errmsg = format!(
"Cannot parse regex. [regex:{}, key:{}]",
pattern,
utils::concat_selection_key(key_list)
);
return Result::Err(vec![errmsg]);
}
self.re = re_result.ok();
}
self.re = re_result.ok();
Result::Ok(())
}
fn is_match(&self, event_value: Option<&String>, _recinfo: &EvtxRecordInfo) -> bool {
fn is_match(&self, event_value: Option<&String>, recinfo: &EvtxRecordInfo) -> bool {
// PipeElement::EqualsFieldが設定されていた場合
if let Some(eqfield_key) = &self.eqfield_key {
let another_value = recinfo.get_value(eqfield_key);
// Evtxのレコードに存在しないeventkeyを指定された場合はfalseにする
if event_value.is_none() || another_value.is_none() {
return false;
}
return another_value.unwrap().cmp(event_value.unwrap()) == Ordering::Equal;
}
// yamlにnullが設定されていた場合
// keylistが空(==JSONのgrep検索)の場合、無視する。
if self.key_list.is_empty() && self.re.is_none() {
@@ -341,6 +368,7 @@ enum PipeElement {
Contains,
Re,
Wildcard,
EqualsField,
}
impl PipeElement {
@@ -377,10 +405,9 @@ impl PipeElement {
PipeElement::Endswith => fn_add_asterisk_begin(pattern),
// containsの場合はpatternの前後にwildcardを足すことで対応する
PipeElement::Contains => fn_add_asterisk_end(fn_add_asterisk_begin(pattern)),
// 正規表現の場合は特に処理する必要無い
PipeElement::Re => pattern,
// WildCardは正規表現に変換する。
PipeElement::Wildcard => PipeElement::pipe_pattern_wildcard(pattern),
_ => pattern,
}
}
@@ -1707,4 +1734,128 @@ mod tests {
}
}
}
#[test]
fn test_eq_field() {
// equalsfieldsで正しく検知できることを確認
let rule_str = r#"
detection:
selection:
Channel|equalsfield: Computer
details: 'command=%CommandLine%'
"#;
let record_json_str = r#"
{
"Event": {"System": {"EventID": 4103, "Channel": "Security", "Computer": "Security" }},
"Event_attributes": {"xmlns": "http://schemas.microsoft.com/win/2004/08/events/event"}
}"#;
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
let keys = detections::rule::get_detection_keys(&rule_node);
let recinfo = utils::create_rec_info(record, "testpath".to_owned(), &keys);
assert!(rule_node.select(&recinfo));
}
Err(_) => {
panic!("Failed to parse json record.");
}
}
}
#[test]
fn test_eq_field_notdetect() {
// equalsfieldsの検知できないパターン
// equalsfieldsで正しく検知できることを確認
let rule_str = r#"
detection:
selection:
Channel|equalsfield: Computer
details: 'command=%CommandLine%'
"#;
let record_json_str = r#"
{
"Event": {"System": {"EventID": 4103, "Channel": "Security", "Computer": "Powershell" }},
"Event_attributes": {"xmlns": "http://schemas.microsoft.com/win/2004/08/events/event"}
}"#;
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
let keys = detections::rule::get_detection_keys(&rule_node);
let recinfo = utils::create_rec_info(record, "testpath".to_owned(), &keys);
assert!(!rule_node.select(&recinfo));
}
Err(_) => {
panic!("Failed to parse json record.");
}
}
}
#[test]
fn test_eq_field_emptyfield() {
// 存在しないフィールドを指定した場合は検知しない
let rule_str = r#"
detection:
selection:
Channel|equalsfield: NoField
details: 'command=%CommandLine%'
"#;
let record_json_str = r#"
{
"Event": {"System": {"EventID": 4103, "Channel": "Security", "Computer": "Securiti" }},
"Event_attributes": {"xmlns": "http://schemas.microsoft.com/win/2004/08/events/event"}
}"#;
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
let keys = detections::rule::get_detection_keys(&rule_node);
let recinfo = utils::create_rec_info(record, "testpath".to_owned(), &keys);
assert!(!rule_node.select(&recinfo));
}
Err(_) => {
panic!("Failed to parse json record.");
}
}
let rule_str = r#"
detection:
selection:
NoField|equalsfield: Channel
details: 'command=%CommandLine%'
"#;
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
let keys = detections::rule::get_detection_keys(&rule_node);
let recinfo = utils::create_rec_info(record, "testpath".to_owned(), &keys);
assert!(!rule_node.select(&recinfo));
}
Err(_) => {
panic!("Failed to parse json record.");
}
}
let rule_str = r#"
detection:
selection:
NoField|equalsfield: NoField1
details: 'command=%CommandLine%'
"#;
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
let keys = detections::rule::get_detection_keys(&rule_node);
let recinfo = utils::create_rec_info(record, "testpath".to_owned(), &keys);
assert!(!rule_node.select(&recinfo));
}
Err(_) => {
panic!("Failed to parse json record.");
}
}
}
}

View File

@@ -106,19 +106,21 @@ pub fn get_detection_keys(node: &RuleNode) -> Vec<String> {
for key in detection.name_to_selection.keys() {
let selection = &detection.name_to_selection[key];
let desc = selection.get_descendants();
let keys = desc.iter().filter_map(|node| {
desc.iter().for_each(|node| {
if !node.is::<LeafSelectionNode>() {
return Option::None;
return;
}
let node = node.downcast_ref::<LeafSelectionNode>().unwrap();
let key = node.get_key();
if key.is_empty() {
return Option::None;
}
Option::Some(key.to_string())
let keys = node.get_keys();
let keys = keys.iter().filter_map(|key| {
if key.is_empty() {
return None;
}
Some(key.to_string())
});
ret.extend(keys);
});
ret.extend(keys);
}
ret

View File

@@ -3,7 +3,7 @@ use downcast_rs::Downcast;
use std::{sync::Arc, vec};
use yaml_rust::Yaml;
use super::matchers;
use super::matchers::{self, DefaultMatcher};
// Ruleファイルの detection- selection配下のードはこのtraitを実装する。
pub trait SelectionNode: Downcast {
@@ -250,6 +250,24 @@ impl LeafSelectionNode {
&self.key
}
pub fn get_keys(&self) -> Vec<&String> {
let mut keys = vec![];
if !self.key.is_empty() {
keys.push(&self.key);
}
if let Some(matcher) = &self.matcher {
let matcher = matcher.downcast_ref::<DefaultMatcher>();
if let Some(matcher) = matcher {
if let Some(eq_key) = matcher.get_eqfield_key() {
keys.push(eq_key);
}
}
}
keys
}
fn _create_key(&self) -> String {
if self.key_list.is_empty() {
return String::default();