Skip to content

Commit

Permalink
Merge pull request #890 from Yamato-Security/imporve-speed-by-using-s…
Browse files Browse the repository at this point in the history
…tarts-ends-with

Imporving speed by changing `wildcard` search process from `regular expression` match to `starts_with/ends_with` match
  • Loading branch information
YamatoSecurity committed Jan 23, 2023
2 parents 67244df + 1e30acf commit 8826515
Show file tree
Hide file tree
Showing 3 changed files with 239 additions and 5 deletions.
1 change: 1 addition & 0 deletions CHANGELOG-Japanese.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
- イベントIDの完全比較を行うことで、約3%の速度向上とメモリ使用量の削減を実現した。 (#882) (@fukusuket)
- 正規表現使用前のフィルタリングにより、約14%の速度向上とメモリ使用量の削減を実現した。 (#883) (@fukusuket)
- 正規表現ではなく大文字小文字を区別しない比較により、約8%の速度向上とメモリ使用量の削減を実現した。 (#884) (@fukusuket)
- ワイルドカード表現における正規表現の使用量を削減することで、約5%の速度向上とメモリ使用量の削減を実現した。 (#890) (@fukusuket)

**バグ修正:**

Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
- Approximately 3% speed increase and memory usage reduction by performing exact string matching on Event IDs. (#882) (@fukusuket)
- Approximately 14% speed increase and memory usage reduction by filtering before regex usage. (#883) (@fukusuket)
- Approximately 8% speed increase and memory usage reduction by case-insensitive comparisons instead of regex usage. (#884) (@fukusuket)
- Approximately 5% speed increase and memory usage reduction by reducing regex usage in wildcard expressions. (#890) (@fukusuket)

**Bug Fixes:**

Expand Down
242 changes: 237 additions & 5 deletions src/detections/rule/matchers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,8 @@ impl LeafMatcher for AllowlistFileMatcher {
pub struct DefaultMatcher {
re: Option<Regex>,
match_str: Option<String>,
starts_with: Option<String>,
ends_with: Option<String>,
pipes: Vec<PipeElement>,
key_list: Nested<String>,
}
Expand All @@ -194,6 +196,8 @@ impl DefaultMatcher {
DefaultMatcher {
re: Option::None,
match_str: Option::None,
starts_with: Option::None,
ends_with: Option::None,
pipes: Vec::new(),
key_list: Nested::<String>::new(),
}
Expand Down Expand Up @@ -278,10 +282,25 @@ impl LeafMatcher for DefaultMatcher {
}
let n = self.pipes.len();
if n == 0 {
// 正規表現マッチは遅いため、できるだけstd::stringのlen/stars_with/ends_with/containsでマッチ判定させるための処理
self.match_str = match select_value.as_str() {
None => None, // strに変換できない場合は、文字列長マッチによる比較はしない
Some(s) if s.contains('*') | s.contains('?') => None, //ワイルドカードを含む場合は、文字列長マッチによる比較はしない
Some(s) => Some(s.to_string()),
None => None,
Some(s) if s.contains('?') => None, // ?を含む場合は、正規表現マッチのみ
Some(s) if s.chars().filter(|c| *c == '*').count() == 1 && s.is_ascii() => {
// ワイルドカード1文字だけ含む場合は、is_match()で、starts_with/ends_with相当のマッチに変換
if s.starts_with('*') {
if let Some(x) = s.strip_prefix('*') {
self.ends_with = Some(x.replace(r"\\", r"\"));
}
} else if s.ends_with('*') && !s.ends_with(r"\\\*") {
if let Some(x) = s.strip_suffix('*') {
self.starts_with = Some(x.replace(r"\\", r"\"));
}
}
None
}
Some(s) if s.contains('*') => None, //先頭・末尾のワイルドカード以外は、正規表現マッチのみ
Some(s) => Some(s.replace(r"\\", r"\")), //ワイルドカードを含まない場合は、is_match()で、文字列長マッチに変換
};
} else if n >= 2 {
// 現状では複数のパイプは対応していない
Expand Down Expand Up @@ -359,13 +378,36 @@ impl LeafMatcher for DefaultMatcher {
} else {
// 通常の検索はこっち
if let Some(match_str) = &self.match_str {
// パイプやワイルドカードを持つ場合は、この分岐には入らず、以降の正規表現マッチのみ
// 正規表現マッチは重いので、文字列の長さが一致するかをまずチェックする
//ワイルドカードを含まない場合はこの分岐。文字数を比較
if match_str.len() == event_value_str.len() {
return match_str.eq_ignore_ascii_case(event_value_str);
}
return false;
}
if let Some(match_str) = &self.starts_with {
// ワイルドカードを末尾に1つだけ含む場合はこの分岐。starts_with相当のマッチで比較
let len = match_str.len();
if len > event_value_str.len() {
return false;
}
if event_value_str.is_ascii() {
// マルチバイト文字を含む場合は、index out of boundsになるため、asciiのみ
return match_str.eq_ignore_ascii_case(&event_value_str[0..len]);
}
}
if let Some(match_str) = &self.ends_with {
// ワイルドカードを先頭に1つだけ含む場合はこの分岐。ends_with相当のマッチで比較
let len1 = match_str.len();
let len2 = event_value_str.len();
if len1 > len2 {
return false;
}
if event_value_str.is_ascii() {
// マルチバイト文字を含む場合は、index out of boundsになるため、asciiのみ
return match_str.eq_ignore_ascii_case(&event_value_str[len2 - len1..]);
}
}
// 文字数/starts_with/ends_with検索に変換できなかった場合は、正規表現マッチで比較
self.is_regex_fullmatch(event_value_str)
}
}
Expand Down Expand Up @@ -1895,4 +1937,194 @@ mod tests {

check_select(rule_str, record_json_str, false);
}

#[test]
fn test_wildcard_converted_starts_with() {
// ワイルドカード1文字を末尾に含む場合、stars_with相当のマッチ
let rule_str = r#"
enabled: true
detection:
selection:
Computer: A-*
details: 'command=%CommandLine%'
"#;

let record_json_str = r#"{
"Event": {"System": {"EventID": 4103, "Channel": "Security", "Computer": "A-HOST"}},
"Event_attributes": {"xmlns": "http://schemas.microsoft.com/win/2004/08/events/event"}
}"#;

check_select(rule_str, record_json_str, true);
}

#[test]
fn test_wildcard_converted_starts_with_notdetect() {
// ワイルドカード1文字を末尾に含む場合、stars_with相当のマッチ
let rule_str = r#"
enabled: true
detection:
selection:
Computer: AA-*
details: 'command=%CommandLine%'
"#;

let record_json_str = r#"{
"Event": {"System": {"EventID": 4103, "Channel": "Security", "Computer": "A-HOST"}},
"Event_attributes": {"xmlns": "http://schemas.microsoft.com/win/2004/08/events/event"}
}"#;

check_select(rule_str, record_json_str, false);
}

#[test]
fn test_wildcard_converted_starts_with_exact_val() {
// ワイルドカード1文字を末尾に含みかつ、*を除く比較対象文字がちょうど一致する場合
let rule_str = r#"
enabled: true
detection:
selection:
Computer: A-HOST*
details: 'command=%CommandLine%'
"#;

let record_json_str = r#"{
"Event": {"System": {"EventID": 4103, "Channel": "Security", "Computer": "A-HOST"}},
"Event_attributes": {"xmlns": "http://schemas.microsoft.com/win/2004/08/events/event"}
}"#;

check_select(rule_str, record_json_str, true);
}

#[test]
fn test_wildcard_converted_starts_with_shorter_val_notdetect() {
// ワイルドカード1文字を末尾に含みかつ、比較対象文字のほうが文字数が少ない場合はマッチしない
let rule_str = r#"
enabled: true
detection:
selection:
Computer: A-HOST-*
details: 'command=%CommandLine%'
"#;

let record_json_str = r#"{
"Event": {"System": {"EventID": 4103, "Channel": "Security", "Computer": "A-HOST"}},
"Event_attributes": {"xmlns": "http://schemas.microsoft.com/win/2004/08/events/event"}
}"#;

check_select(rule_str, record_json_str, false);
}

#[test]
fn test_wildcard_converted_starts_with_multibytes() {
//ワイルドカードを含むかつascii以外のパターンは正規表現マッチ
let rule_str = r#"
enabled: true
detection:
selection:
Computer: 社員端末*
details: 'command=%CommandLine%'
"#;

let record_json_str = r#"{
"Event": {"System": {"EventID": 4103, "Channel": "Security", "Computer": "社員端末A"}},
"Event_attributes": {"xmlns": "http://schemas.microsoft.com/win/2004/08/events/event"}
}"#;

check_select(rule_str, record_json_str, true);
}

#[test]
fn test_wildcard_converted_ends_with() {
// ワイルドカード1文字を先頭に含む場合、ends_with相当のマッチ
let rule_str = r#"
enabled: true
detection:
selection:
Computer: '*-HOST'
details: 'command=%CommandLine%'
"#;

let record_json_str = r#"{
"Event": {"System": {"EventID": 4103, "Channel": "Security", "Computer": "A-HOST"}},
"Event_attributes": {"xmlns": "http://schemas.microsoft.com/win/2004/08/events/event"}
}"#;

check_select(rule_str, record_json_str, true);
}

#[test]
fn test_wildcard_converted_ends_with_starts_with_exact_val() {
// ワイルドカード1文字を先頭に含みかつ、*を除く比較対象文字がちょうど一致する場合
let rule_str = r#"
enabled: true
detection:
selection:
Computer: '*A-HOST'
details: 'command=%CommandLine%'
"#;

let record_json_str = r#"{
"Event": {"System": {"EventID": 4103, "Channel": "Security", "Computer": "A-HOST"}},
"Event_attributes": {"xmlns": "http://schemas.microsoft.com/win/2004/08/events/event"}
}"#;

check_select(rule_str, record_json_str, true);
}

#[test]
fn test_wildcard_converted_ends_with_shorter_val_notdetect() {
// ワイルドカード1文字を先頭に含みかつ、比較対象文字のほうが文字数が少ない場合はマッチしない
let rule_str = r#"
enabled: true
detection:
selection:
Computer: '*-HOSTA'
details: 'command=%CommandLine%'
"#;

let record_json_str = r#"{
"Event": {"System": {"EventID": 4103, "Channel": "Security", "Computer": "A-HOST"}},
"Event_attributes": {"xmlns": "http://schemas.microsoft.com/win/2004/08/events/event"}
}"#;

check_select(rule_str, record_json_str, false);
}

#[test]
fn test_only_wildcard() {
// ワイルドカードだけの場合、ends_with相当のマッチ
let rule_str = r#"
enabled: true
detection:
selection:
Computer: '*'
details: 'command=%CommandLine%'
"#;

let record_json_str = r#"{
"Event": {"System": {"EventID": 4103, "Channel": "Security", "Computer": "A-HOST"}},
"Event_attributes": {"xmlns": "http://schemas.microsoft.com/win/2004/08/events/event"}
}"#;

check_select(rule_str, record_json_str, true);
}

#[test]
fn test_two_wildcards() {
// ワイルドカード2文字以上を含む場合、正規表現マッチ
let rule_str = r#"
enabled: true
detection:
selection:
Computer: '*-HOST-*'
details: 'command=%CommandLine%'
"#;

let record_json_str = r#"{
"Event": {"System": {"EventID": 4103, "Channel": "Security", "Computer": "A-HOST-1"}},
"Event_attributes": {"xmlns": "http://schemas.microsoft.com/win/2004/08/events/event"}
}"#;

check_select(rule_str, record_json_str, true);
}
}

0 comments on commit 8826515

Please sign in to comment.