Feature/sigmarule wildcard regex caseinsensitive#119 (#123)
* under constructing * underconstructing * fix rule file for SIGMA rule. * wildcard case insensetive. * refactor * Update src/detections/rule.rs add test triple backshash Co-authored-by: itiB <is0312vx@ed.ritsumei.ac.jp> * remove unnecessary if statement Co-authored-by: itiB <is0312vx@ed.ritsumei.ac.jp>
This commit is contained in:
@@ -9,7 +9,7 @@ detection:
|
|||||||
Channel: Microsoft-Windows-PowerShell/Operational
|
Channel: Microsoft-Windows-PowerShell/Operational
|
||||||
EventID: 4104
|
EventID: 4104
|
||||||
Path: null
|
Path: null
|
||||||
ScriptBlockText: '.+'
|
ScriptBlockText|re: '.+'
|
||||||
# condition: selection
|
# condition: selection
|
||||||
falsepositives:
|
falsepositives:
|
||||||
- unknown
|
- unknown
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ detection:
|
|||||||
selection:
|
selection:
|
||||||
Channel: Security
|
Channel: Security
|
||||||
EventID: 4674
|
EventID: 4674
|
||||||
ProcessName: '(?i)C:\WINDOWS\SYSTEM32\SERVICE.EXE' # (?i) means case insesitive for Rust Regex
|
ProcessName|re: '(?i)C:\WINDOWS\SYSTEM32\SERVICE.EXE' # (?i) means case insesitive for Rust Regex
|
||||||
AccessMask: '%%1539'
|
AccessMask: '%%1539'
|
||||||
# condition: selection
|
# condition: selection
|
||||||
falsepositives:
|
falsepositives:
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ detection:
|
|||||||
selection:
|
selection:
|
||||||
Channel: Security
|
Channel: Security
|
||||||
EventID: 4688
|
EventID: 4688
|
||||||
CommandLine: '.+'
|
CommandLine|re: '.+'
|
||||||
# condition: selection
|
# condition: selection
|
||||||
falsepositives:
|
falsepositives:
|
||||||
- unknown
|
- unknown
|
||||||
|
|||||||
@@ -8,8 +8,7 @@ detection:
|
|||||||
selection:
|
selection:
|
||||||
Channel: Security
|
Channel: Security
|
||||||
EventID: 4672
|
EventID: 4672
|
||||||
PrivilegeList:
|
PrivilegeList|contains: SeDebugPrivilege
|
||||||
contain: SeDebugPrivilege
|
|
||||||
# condition: selection
|
# condition: selection
|
||||||
falsepositives:
|
falsepositives:
|
||||||
- unknown
|
- unknown
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ detection:
|
|||||||
selection:
|
selection:
|
||||||
Channel: Sysmon
|
Channel: Sysmon
|
||||||
EventID: 1
|
EventID: 1
|
||||||
CommandLine: '.+'
|
CommandLine|re: '.+'
|
||||||
# condition: selection
|
# condition: selection
|
||||||
falsepositives:
|
falsepositives:
|
||||||
- unknown
|
- unknown
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ detection:
|
|||||||
selection:
|
selection:
|
||||||
Channel: Windows PowerShell
|
Channel: Windows PowerShell
|
||||||
EventID: 400
|
EventID: 400
|
||||||
EventData: '[\s\S]*EngineVersion=2.0[\s\S]*'
|
EventData|re: '[\s\S]*EngineVersion=2\.0[\s\S]*'
|
||||||
falsepositives:
|
falsepositives:
|
||||||
- unknown
|
- unknown
|
||||||
level: medium
|
level: medium
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
extern crate regex;
|
extern crate regex;
|
||||||
|
|
||||||
use mopa::mopafy;
|
use mopa::mopafy;
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
use std::{collections::HashMap, sync::Arc, vec};
|
use std::{collections::HashMap, sync::Arc, vec};
|
||||||
|
|
||||||
@@ -600,7 +601,6 @@ impl AggegationConditionCompiler {
|
|||||||
.replacen("|", "", 1);
|
.replacen("|", "", 1);
|
||||||
|
|
||||||
let tokens = self.tokenize(aggregation_str)?;
|
let tokens = self.tokenize(aggregation_str)?;
|
||||||
|
|
||||||
return self.parse(tokens);
|
return self.parse(tokens);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1247,7 +1247,9 @@ impl LeafSelectionNode {
|
|||||||
return String::default();
|
return String::default();
|
||||||
}
|
}
|
||||||
|
|
||||||
return self.key_list[0].to_string();
|
let topkey = self.key_list[0].to_string();
|
||||||
|
let values: Vec<&str> = topkey.split("|").collect();
|
||||||
|
return values[0].to_string();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// JSON形式のEventJSONから値を取得する関数 aliasも考慮されている。
|
/// JSON形式のEventJSONから値を取得する関数 aliasも考慮されている。
|
||||||
@@ -1263,10 +1265,10 @@ impl LeafSelectionNode {
|
|||||||
/// 上から順番に調べて、一番始めに一致したMatcherが適用される
|
/// 上から順番に調べて、一番始めに一致したMatcherが適用される
|
||||||
fn get_matchers(&self) -> Vec<Box<dyn LeafMatcher>> {
|
fn get_matchers(&self) -> Vec<Box<dyn LeafMatcher>> {
|
||||||
return vec![
|
return vec![
|
||||||
Box::new(RegexMatcher::new()),
|
|
||||||
Box::new(MinlengthMatcher::new()),
|
Box::new(MinlengthMatcher::new()),
|
||||||
Box::new(RegexesFileMatcher::new()),
|
Box::new(RegexesFileMatcher::new()),
|
||||||
Box::new(WhitelistFileMatcher::new()),
|
Box::new(WhitelistFileMatcher::new()),
|
||||||
|
Box::new(DefaultMatcher::new()),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1297,7 +1299,7 @@ impl SelectionNode for LeafSelectionNode {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
if self.key_list.len() > 0 && self.key_list[0].to_string() == "EventData" {
|
if self.get_key() == "EventData" {
|
||||||
let values = utils::get_event_value(&"Event.EventData.Data".to_string(), event_record);
|
let values = utils::get_event_value(&"Event.EventData.Data".to_string(), event_record);
|
||||||
if values.is_none() {
|
if values.is_none() {
|
||||||
return self.matcher.as_ref().unwrap().is_match(Option::None);
|
return self.matcher.as_ref().unwrap().is_match(Option::None);
|
||||||
@@ -1305,7 +1307,6 @@ impl SelectionNode for LeafSelectionNode {
|
|||||||
|
|
||||||
// 配列じゃなくて、文字列や数値等の場合は普通通りに比較する。
|
// 配列じゃなくて、文字列や数値等の場合は普通通りに比較する。
|
||||||
let eventdata_data = values.unwrap();
|
let eventdata_data = values.unwrap();
|
||||||
|
|
||||||
if eventdata_data.is_boolean() || eventdata_data.is_i64() || eventdata_data.is_string()
|
if eventdata_data.is_boolean() || eventdata_data.is_i64() || eventdata_data.is_string()
|
||||||
{
|
{
|
||||||
return self
|
return self
|
||||||
@@ -1337,35 +1338,11 @@ impl SelectionNode for LeafSelectionNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn init(&mut self) -> Result<(), Vec<String>> {
|
fn init(&mut self) -> Result<(), Vec<String>> {
|
||||||
let mut fixed_key_list = Vec::new(); // |xx を排除したkey_listを作成する
|
let match_key_list = self.key_list.clone();
|
||||||
for key in &self.key_list {
|
|
||||||
if key.contains('|') {
|
|
||||||
let v: Vec<&str> = key.split('|').collect();
|
|
||||||
self.matcher = match v[1] {
|
|
||||||
"startswith" => Some(Box::new(StartsWithMatcher::new())),
|
|
||||||
"endswith" => Some(Box::new(EndsWithMatcher::new())),
|
|
||||||
"contains" => Some(Box::new(ContainsMatcher::new())),
|
|
||||||
_ => {
|
|
||||||
return Result::Err(vec![format!(
|
|
||||||
"Found unknown key option. option: {}",
|
|
||||||
v[1]
|
|
||||||
)])
|
|
||||||
}
|
|
||||||
};
|
|
||||||
fixed_key_list.push(v[0].to_string());
|
|
||||||
} else {
|
|
||||||
fixed_key_list.push(key.to_string());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.key_list = fixed_key_list;
|
|
||||||
let mut match_key_list = self.key_list.clone();
|
|
||||||
match_key_list.remove(0);
|
|
||||||
if self.matcher.is_none() {
|
|
||||||
let matchers = self.get_matchers();
|
let matchers = self.get_matchers();
|
||||||
self.matcher = matchers
|
self.matcher = matchers
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.find(|matcher| matcher.is_target_key(&match_key_list));
|
.find(|matcher| matcher.is_target_key(&match_key_list));
|
||||||
}
|
|
||||||
|
|
||||||
// 一致するmatcherが見つからないエラー
|
// 一致するmatcherが見つからないエラー
|
||||||
if self.matcher.is_none() {
|
if self.matcher.is_none() {
|
||||||
@@ -1404,105 +1381,20 @@ impl SelectionNode for LeafSelectionNode {
|
|||||||
// 新規にLeafMatcherを実装するクラスを作成した場合、
|
// 新規にLeafMatcherを実装するクラスを作成した場合、
|
||||||
// LeafSelectionNodeのget_matchersクラスの戻り値の配列に新規作成したクラスのインスタンスを追加する。
|
// LeafSelectionNodeのget_matchersクラスの戻り値の配列に新規作成したクラスのインスタンスを追加する。
|
||||||
trait LeafMatcher: mopa::Any {
|
trait LeafMatcher: mopa::Any {
|
||||||
|
/// 指定されたkey_listにマッチするLeafMatcherであるかどうか判定する。
|
||||||
fn is_target_key(&self, key_list: &Vec<String>) -> bool;
|
fn is_target_key(&self, key_list: &Vec<String>) -> bool;
|
||||||
|
|
||||||
|
/// 引数に指定されたJSON形式のデータがマッチするかどうか判定する。
|
||||||
|
/// main.rsでWindows Event LogをJSON形式に変換していて、そのJSON形式のWindowsのイベントログデータがここには来る
|
||||||
|
/// 例えば正規表現でマッチするロジックなら、ここに正規表現でマッチさせる処理を書く。
|
||||||
fn is_match(&self, event_value: Option<&Value>) -> bool;
|
fn is_match(&self, event_value: Option<&Value>) -> bool;
|
||||||
|
|
||||||
|
/// 初期化ロジックをここに記載します。
|
||||||
|
/// ルールファイルの書き方が間違っている等の原因により、正しくルールファイルからパースできない場合、戻り値のResult型でエラーを返してください。
|
||||||
fn init(&mut self, key_list: &Vec<String>, select_value: &Yaml) -> Result<(), Vec<String>>;
|
fn init(&mut self, key_list: &Vec<String>, select_value: &Yaml) -> Result<(), Vec<String>>;
|
||||||
}
|
}
|
||||||
mopafy!(LeafMatcher);
|
mopafy!(LeafMatcher);
|
||||||
|
|
||||||
/// 正規表現で比較するロジックを表すクラス
|
|
||||||
struct RegexMatcher {
|
|
||||||
re: Option<Regex>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RegexMatcher {
|
|
||||||
fn new() -> RegexMatcher {
|
|
||||||
return RegexMatcher {
|
|
||||||
re: Option::None, // empty
|
|
||||||
};
|
|
||||||
}
|
|
||||||
fn is_regex_fullmatch(&self, re: &Regex, value: String) -> bool {
|
|
||||||
return re.find_iter(&value).any(|match_obj| {
|
|
||||||
return match_obj.as_str().to_string() == value;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LeafMatcher for RegexMatcher {
|
|
||||||
fn is_target_key(&self, key_list: &Vec<String>) -> bool {
|
|
||||||
if key_list.is_empty() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if key_list.len() == 1 {
|
|
||||||
return key_list.get(0).unwrap_or(&"".to_string()) == &"regex".to_string();
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn init(&mut self, key_list: &Vec<String>, select_value: &Yaml) -> Result<(), Vec<String>> {
|
|
||||||
if select_value.is_null() {
|
|
||||||
self.re = Option::None;
|
|
||||||
return Result::Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
// stringで比較する。
|
|
||||||
let yaml_value = match select_value {
|
|
||||||
Yaml::Boolean(b) => Option::Some(b.to_string()),
|
|
||||||
Yaml::Integer(i) => Option::Some(i.to_string()),
|
|
||||||
Yaml::Real(r) => Option::Some(r.to_string()),
|
|
||||||
Yaml::String(s) => Option::Some(s.to_owned()),
|
|
||||||
_ => Option::None,
|
|
||||||
};
|
|
||||||
// ここには来ないはず
|
|
||||||
if yaml_value.is_none() {
|
|
||||||
let errmsg = format!(
|
|
||||||
"unknown error occured. [key:{}]",
|
|
||||||
concat_selection_key(key_list)
|
|
||||||
);
|
|
||||||
return Result::Err(vec![errmsg]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 指定された正規表現が間違っていて、パースに失敗した場合
|
|
||||||
let yaml_str = yaml_value.unwrap();
|
|
||||||
let re_result = Regex::new(&yaml_str);
|
|
||||||
if re_result.is_err() {
|
|
||||||
let errmsg = format!(
|
|
||||||
"cannot parse regex. [regex:{}, key:{}]",
|
|
||||||
yaml_str,
|
|
||||||
concat_selection_key(key_list)
|
|
||||||
);
|
|
||||||
return Result::Err(vec![errmsg]);
|
|
||||||
}
|
|
||||||
self.re = re_result.ok();
|
|
||||||
|
|
||||||
return Result::Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_match(&self, event_value: Option<&Value>) -> bool {
|
|
||||||
// unwrap_orの引数に""ではなく" "を指定しているのは、
|
|
||||||
// event_valueが文字列じゃない場合にis_event_value_nullの値がfalseになるように、len() == 0とならない値を指定している。
|
|
||||||
let is_event_value_null = event_value.is_none()
|
|
||||||
|| event_value.unwrap().is_null()
|
|
||||||
|| event_value.unwrap().as_str().unwrap_or(" ").len() == 0;
|
|
||||||
|
|
||||||
// yamlにnullが設定されていた場合
|
|
||||||
if self.re.is_none() {
|
|
||||||
return is_event_value_null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return match event_value.unwrap_or(&Value::Null) {
|
|
||||||
Value::Bool(b) => self.is_regex_fullmatch(self.re.as_ref().unwrap(), b.to_string()),
|
|
||||||
Value::String(s) => self.is_regex_fullmatch(self.re.as_ref().unwrap(), s.to_owned()),
|
|
||||||
Value::Number(n) => self.is_regex_fullmatch(self.re.as_ref().unwrap(), n.to_string()),
|
|
||||||
_ => false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 指定された文字数以上であることをチェックするクラス。
|
/// 指定された文字数以上であることをチェックするクラス。
|
||||||
struct MinlengthMatcher {
|
struct MinlengthMatcher {
|
||||||
min_len: i64,
|
min_len: i64,
|
||||||
@@ -1516,11 +1408,11 @@ impl MinlengthMatcher {
|
|||||||
|
|
||||||
impl LeafMatcher for MinlengthMatcher {
|
impl LeafMatcher for MinlengthMatcher {
|
||||||
fn is_target_key(&self, key_list: &Vec<String>) -> bool {
|
fn is_target_key(&self, key_list: &Vec<String>) -> bool {
|
||||||
if key_list.len() != 1 {
|
if key_list.len() != 2 {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return key_list.get(0).unwrap() == "min_length";
|
return key_list.get(1).unwrap() == "min_length";
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init(&mut self, key_list: &Vec<String>, select_value: &Yaml) -> Result<(), Vec<String>> {
|
fn init(&mut self, key_list: &Vec<String>, select_value: &Yaml) -> Result<(), Vec<String>> {
|
||||||
@@ -1562,11 +1454,11 @@ impl RegexesFileMatcher {
|
|||||||
|
|
||||||
impl LeafMatcher for RegexesFileMatcher {
|
impl LeafMatcher for RegexesFileMatcher {
|
||||||
fn is_target_key(&self, key_list: &Vec<String>) -> bool {
|
fn is_target_key(&self, key_list: &Vec<String>) -> bool {
|
||||||
if key_list.len() != 1 {
|
if key_list.len() != 2 {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return key_list.get(0).unwrap() == "regexes";
|
return key_list.get(1).unwrap() == "regexes";
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init(&mut self, key_list: &Vec<String>, select_value: &Yaml) -> Result<(), Vec<String>> {
|
fn init(&mut self, key_list: &Vec<String>, select_value: &Yaml) -> Result<(), Vec<String>> {
|
||||||
@@ -1598,6 +1490,7 @@ impl LeafMatcher for RegexesFileMatcher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn is_match(&self, event_value: Option<&Value>) -> bool {
|
fn is_match(&self, event_value: Option<&Value>) -> bool {
|
||||||
|
//TODO Wildcardの場合、CaseInsensitiveなので、ToLowerする。
|
||||||
return match event_value.unwrap_or(&Value::Null) {
|
return match event_value.unwrap_or(&Value::Null) {
|
||||||
Value::String(s) => !utils::check_regex(s, 0, &self.regexes_csv_content).is_empty(),
|
Value::String(s) => !utils::check_regex(s, 0, &self.regexes_csv_content).is_empty(),
|
||||||
Value::Number(n) => {
|
Value::Number(n) => {
|
||||||
@@ -1624,11 +1517,11 @@ impl WhitelistFileMatcher {
|
|||||||
|
|
||||||
impl LeafMatcher for WhitelistFileMatcher {
|
impl LeafMatcher for WhitelistFileMatcher {
|
||||||
fn is_target_key(&self, key_list: &Vec<String>) -> bool {
|
fn is_target_key(&self, key_list: &Vec<String>) -> bool {
|
||||||
if key_list.len() != 1 {
|
if key_list.len() != 2 {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return key_list.get(0).unwrap() == "whitelist";
|
return key_list.get(1).unwrap() == "whitelist";
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init(&mut self, key_list: &Vec<String>, select_value: &Yaml) -> Result<(), Vec<String>> {
|
fn init(&mut self, key_list: &Vec<String>, select_value: &Yaml) -> Result<(), Vec<String>> {
|
||||||
@@ -1671,23 +1564,48 @@ impl LeafMatcher for WhitelistFileMatcher {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 指定された文字列で始まるか調べるクラス
|
/// デフォルトのマッチクラス
|
||||||
struct StartsWithMatcher {
|
/// ワイルドカードの処理やパイプ
|
||||||
start_text: String,
|
struct DefaultMatcher {
|
||||||
|
re: Option<Regex>,
|
||||||
|
pipes: Vec<PipeElement>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StartsWithMatcher {
|
impl DefaultMatcher {
|
||||||
fn new() -> StartsWithMatcher {
|
fn new() -> DefaultMatcher {
|
||||||
return StartsWithMatcher {
|
return DefaultMatcher {
|
||||||
start_text: String::from(""),
|
re: Option::None,
|
||||||
|
pipes: Vec::new(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// このmatcherの正規表現とマッチするかどうか判定します。
|
||||||
|
/// 判定対象の文字列とこのmatcherが保持する正規表現が完全にマッチした場合のTRUEを返します。
|
||||||
|
/// 例えば、判定対象文字列が"abc"で、正規表現が"ab"の場合、正規表現は判定対象文字列の一部分にしか一致していないので、この関数はfalseを返します。
|
||||||
|
fn is_regex_fullmatch(&self, value: String) -> bool {
|
||||||
|
return self
|
||||||
|
.re
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.find_iter(&value)
|
||||||
|
.any(|match_obj| {
|
||||||
|
return match_obj.as_str().to_string() == value;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LeafMatcher for StartsWithMatcher {
|
/// YEAのルールファイルのフィールド名とそれに続いて指定されるパイプを、正規表現形式の文字列に変換します。
|
||||||
fn is_target_key(&self, _: &Vec<String>) -> bool {
|
/// ワイルドカードの文字列を正規表現にする処理もこのメソッドに実装されています。patternにワイルドカードの文字列を指定して、pipesにPipeElement::Wildcardを指定すればOK!!
|
||||||
// ContextInfo|startswith のような場合にLeafをStartsWithMatcherにする。
|
fn from_pattern_to_regex_str(pattern: String, pipes: &Vec<PipeElement>) -> String {
|
||||||
return false;
|
// パターンをPipeで処理する。
|
||||||
|
return pipes.iter().fold(pattern, |acc, pipe| {
|
||||||
|
return pipe.pipe_pattern(acc);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LeafMatcher for DefaultMatcher {
|
||||||
|
fn is_target_key(&self, key_list: &Vec<String>) -> bool {
|
||||||
|
return key_list.len() == 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init(&mut self, key_list: &Vec<String>, select_value: &Yaml) -> Result<(), Vec<String>> {
|
fn init(&mut self, key_list: &Vec<String>, select_value: &Yaml) -> Result<(), Vec<String>> {
|
||||||
@@ -1695,7 +1613,7 @@ impl LeafMatcher for StartsWithMatcher {
|
|||||||
return Result::Ok(());
|
return Result::Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
// stringに変換
|
// patternをパースする
|
||||||
let yaml_value = match select_value {
|
let yaml_value = match select_value {
|
||||||
Yaml::Boolean(b) => Option::Some(b.to_string()),
|
Yaml::Boolean(b) => Option::Some(b.to_string()),
|
||||||
Yaml::Integer(i) => Option::Some(i.to_string()),
|
Yaml::Integer(i) => Option::Some(i.to_string()),
|
||||||
@@ -1710,135 +1628,247 @@ impl LeafMatcher for StartsWithMatcher {
|
|||||||
);
|
);
|
||||||
return Result::Err(vec![errmsg]);
|
return Result::Err(vec![errmsg]);
|
||||||
}
|
}
|
||||||
|
let pattern = yaml_value.unwrap();
|
||||||
|
|
||||||
self.start_text = yaml_value.unwrap();
|
// Pipeが指定されていればパースする
|
||||||
return Result::Ok(());
|
let mut keys: VecDeque<&str> = key_list.get(0).unwrap().split("|").collect(); // key_listが空はあり得ない
|
||||||
}
|
keys.pop_front();
|
||||||
|
while !keys.is_empty() {
|
||||||
fn is_match(&self, event_value: Option<&Value>) -> bool {
|
let key = keys.pop_front().unwrap();
|
||||||
// 調査する文字列がself.start_textで始まるならtrueを返す
|
let pipe_element = match key {
|
||||||
return match event_value.unwrap_or(&Value::Null) {
|
"startswith" => Option::Some(PipeElement::Startswith),
|
||||||
Value::String(s) => s.starts_with(&self.start_text),
|
"endswith" => Option::Some(PipeElement::Endswith),
|
||||||
Value::Number(n) => n.to_string().starts_with(&self.start_text),
|
"contains" => Option::Some(PipeElement::Contains),
|
||||||
_ => false,
|
"re" => Option::Some(PipeElement::Re),
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 指定された文字列で終わるか調べるクラス
|
|
||||||
struct EndsWithMatcher {
|
|
||||||
end_text: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl EndsWithMatcher {
|
|
||||||
fn new() -> EndsWithMatcher {
|
|
||||||
return EndsWithMatcher {
|
|
||||||
end_text: String::from(""),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LeafMatcher for EndsWithMatcher {
|
|
||||||
fn is_target_key(&self, _: &Vec<String>) -> bool {
|
|
||||||
// ContextInfo|endswith のような場合にLeafをEndsWithMatcherにする。
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn init(&mut self, key_list: &Vec<String>, select_value: &Yaml) -> Result<(), Vec<String>> {
|
|
||||||
if select_value.is_null() {
|
|
||||||
return Result::Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
// stringに変換
|
|
||||||
let yaml_value = match select_value {
|
|
||||||
Yaml::Boolean(b) => Option::Some(b.to_string()),
|
|
||||||
Yaml::Integer(i) => Option::Some(i.to_string()),
|
|
||||||
Yaml::Real(r) => Option::Some(r.to_string()),
|
|
||||||
Yaml::String(s) => Option::Some(s.to_owned()),
|
|
||||||
_ => Option::None,
|
_ => Option::None,
|
||||||
};
|
};
|
||||||
if yaml_value.is_none() {
|
if pipe_element.is_none() {
|
||||||
let errmsg = format!(
|
let errmsg = format!(
|
||||||
"unknown error occured. [key:{}]",
|
"unknown pipe element was specified. key:{}",
|
||||||
concat_selection_key(key_list)
|
concat_selection_key(key_list)
|
||||||
);
|
);
|
||||||
return Result::Err(vec![errmsg]);
|
return Result::Err(vec![errmsg]);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.end_text = yaml_value.unwrap();
|
self.pipes.push(pipe_element.unwrap());
|
||||||
return Result::Ok(());
|
|
||||||
}
|
}
|
||||||
|
if self.pipes.len() >= 2 {
|
||||||
fn is_match(&self, event_value: Option<&Value>) -> bool {
|
// 現状では複数のパイプは対応していない
|
||||||
// 調査する文字列がself.end_textで終わるならtrueを返す
|
|
||||||
return match event_value.unwrap_or(&Value::Null) {
|
|
||||||
Value::String(s) => s.ends_with(&self.end_text),
|
|
||||||
Value::Number(n) => n.to_string().ends_with(&self.end_text),
|
|
||||||
_ => false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 指定された文字列が含まれるか調べるクラス
|
|
||||||
struct ContainsMatcher {
|
|
||||||
pattern: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ContainsMatcher {
|
|
||||||
fn new() -> ContainsMatcher {
|
|
||||||
return ContainsMatcher {
|
|
||||||
pattern: String::from(""),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LeafMatcher for ContainsMatcher {
|
|
||||||
fn is_target_key(&self, _: &Vec<String>) -> bool {
|
|
||||||
// ContextInfo|contains のような場合にLeafをContainsMatcherにする。
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn init(&mut self, key_list: &Vec<String>, select_value: &Yaml) -> Result<(), Vec<String>> {
|
|
||||||
if select_value.is_null() {
|
|
||||||
return Result::Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
// stringに変換
|
|
||||||
let yaml_value = match select_value {
|
|
||||||
Yaml::Boolean(b) => Option::Some(b.to_string()),
|
|
||||||
Yaml::Integer(i) => Option::Some(i.to_string()),
|
|
||||||
Yaml::Real(r) => Option::Some(r.to_string()),
|
|
||||||
Yaml::String(s) => Option::Some(s.to_owned()),
|
|
||||||
_ => Option::None,
|
|
||||||
};
|
|
||||||
if yaml_value.is_none() {
|
|
||||||
let errmsg = format!(
|
let errmsg = format!(
|
||||||
"unknown error occured. [key:{}]",
|
"multiple pipe element can't be used. key:{}",
|
||||||
concat_selection_key(key_list)
|
concat_selection_key(key_list)
|
||||||
);
|
);
|
||||||
return Result::Err(vec![errmsg]);
|
return Result::Err(vec![errmsg]);
|
||||||
}
|
}
|
||||||
|
let is_re = &self.pipes.iter().any(|pipe_element| {
|
||||||
|
return match pipe_element {
|
||||||
|
PipeElement::Re => true,
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
// 正規表現ではない場合、ワイルドカードであることを表す。
|
||||||
|
// ワイルドカードは正規表現でマッチングするので、ワイルドカードを正規表現に変換するPipeを内部的に追加することにする。
|
||||||
|
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,
|
||||||
|
concat_selection_key(key_list)
|
||||||
|
);
|
||||||
|
return Result::Err(vec![errmsg]);
|
||||||
|
}
|
||||||
|
self.re = re_result.ok();
|
||||||
|
|
||||||
self.pattern = yaml_value.unwrap();
|
|
||||||
return Result::Ok(());
|
return Result::Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_match(&self, event_value: Option<&Value>) -> bool {
|
fn is_match(&self, event_value: Option<&Value>) -> bool {
|
||||||
// 調査する文字列にself.patternが含まれるならtrueを返す
|
// unwrap_orの引数に""ではなく" "を指定しているのは、
|
||||||
return match event_value.unwrap_or(&Value::Null) {
|
// event_valueが文字列じゃない場合にis_event_value_nullの値がfalseになるように、len() == 0とならない値を指定している。
|
||||||
Value::String(s) => s.contains(&self.pattern),
|
let is_event_value_null = event_value.is_none()
|
||||||
Value::Number(n) => n.to_string().contains(&self.pattern),
|
|| event_value.unwrap().is_null()
|
||||||
_ => false,
|
|| event_value.unwrap().as_str().unwrap_or(" ").len() == 0;
|
||||||
|
|
||||||
|
// yamlにnullが設定されていた場合
|
||||||
|
if self.re.is_none() {
|
||||||
|
return is_event_value_null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSON形式のEventLogデータをstringに変換
|
||||||
|
let event_value_str = match event_value.unwrap_or(&Value::Null) {
|
||||||
|
Value::Bool(b) => Option::Some(b.to_string()),
|
||||||
|
Value::String(s) => Option::Some(s.to_owned()),
|
||||||
|
Value::Number(n) => Option::Some(n.to_string()),
|
||||||
|
_ => Option::None,
|
||||||
};
|
};
|
||||||
|
if event_value_str.is_none() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 変換したデータに対してパイプ処理を実行する。
|
||||||
|
let event_value_str = (&self.pipes)
|
||||||
|
.iter()
|
||||||
|
.fold(event_value_str.unwrap(), |acc, pipe| {
|
||||||
|
return pipe.pipe_eventlog_data(acc);
|
||||||
|
});
|
||||||
|
|
||||||
|
return self.is_regex_fullmatch(event_value_str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// パイプ(|)で指定される要素を表すクラス。
|
||||||
|
enum PipeElement {
|
||||||
|
Startswith,
|
||||||
|
Endswith,
|
||||||
|
Contains,
|
||||||
|
Re,
|
||||||
|
Wildcard,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PipeElement {
|
||||||
|
/// WindowsEventLogのJSONデータに対してパイプ処理します。
|
||||||
|
fn pipe_eventlog_data(&self, pattern: String) -> String {
|
||||||
|
return match self {
|
||||||
|
// wildcardはcase sensetiveなので、全て小文字にして比較する。
|
||||||
|
PipeElement::Wildcard => pattern.to_lowercase(),
|
||||||
|
_ => pattern,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// patternをパイプ処理します
|
||||||
|
fn pipe_pattern(&self, pattern: String) -> String {
|
||||||
|
// enumでポリモーフィズムを実装すると、一つのメソッドに全部の型の実装をする感じになる。Java使い的にはキモイ感じがする。
|
||||||
|
let fn_add_asterisk_end = |patt: String| {
|
||||||
|
if patt.ends_with("//*") {
|
||||||
|
return patt;
|
||||||
|
} else if patt.ends_with("/*") {
|
||||||
|
return patt + "*";
|
||||||
|
} else if patt.ends_with("*") {
|
||||||
|
return patt;
|
||||||
|
} else {
|
||||||
|
return patt + "*";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let fn_add_asterisk_begin = |patt: String| {
|
||||||
|
if patt.starts_with("//*") {
|
||||||
|
return patt;
|
||||||
|
} else if patt.starts_with("/*") {
|
||||||
|
return "*".to_string() + &patt;
|
||||||
|
} else if patt.starts_with("*") {
|
||||||
|
return patt;
|
||||||
|
} else {
|
||||||
|
return "*".to_string() + &patt;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let val: String = match self {
|
||||||
|
// startswithの場合はpatternの最後にwildcardを足すことで対応する
|
||||||
|
PipeElement::Startswith => fn_add_asterisk_end(pattern),
|
||||||
|
// endswithの場合はpatternの最初にwildcardを足すことで対応する
|
||||||
|
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),
|
||||||
|
};
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// PipeElement::Wildcardのパイプ処理です。
|
||||||
|
/// pipe_pattern()に含めて良い処理ですが、複雑な処理になってしまったので別関数にしました。
|
||||||
|
fn pipe_pattern_wildcard(pattern: String) -> String {
|
||||||
|
// wildcardはcase sensetiveなので、全て小文字にして比較する。
|
||||||
|
let pattern = pattern.to_lowercase();
|
||||||
|
let wildcards = vec!["*".to_string(), "?".to_string()];
|
||||||
|
|
||||||
|
// patternをwildcardでsplitした結果をpattern_splitsに入れる
|
||||||
|
// 以下のアルゴリズムの場合、pattern_splitsの偶数indexの要素はwildcardじゃない文字列となり、奇数indexの要素はwildcardが入る。
|
||||||
|
let mut idx = 0;
|
||||||
|
let mut pattern_splits = vec![];
|
||||||
|
let mut cur_str = String::default();
|
||||||
|
while idx < pattern.len() {
|
||||||
|
let prev_idx = idx;
|
||||||
|
for wildcard in &wildcards {
|
||||||
|
let cur_pattern: String = pattern.chars().skip(idx).collect::<String>();
|
||||||
|
if cur_pattern.starts_with(&format!(r"\\{}", wildcard)) {
|
||||||
|
// wildcardの前にエスケープ文字が2つある場合
|
||||||
|
cur_str = format!("{}{}", cur_str, r"\");
|
||||||
|
pattern_splits.push(cur_str);
|
||||||
|
pattern_splits.push(wildcard.to_string());
|
||||||
|
|
||||||
|
cur_str = String::default();
|
||||||
|
idx += 3;
|
||||||
|
break;
|
||||||
|
} else if cur_pattern.starts_with(&format!(r"\{}", wildcard)) {
|
||||||
|
// wildcardの前にエスケープ文字が1つある場合
|
||||||
|
cur_str = format!("{}{}", cur_str, wildcard);
|
||||||
|
idx += 2;
|
||||||
|
break;
|
||||||
|
} else if cur_pattern.starts_with(wildcard) {
|
||||||
|
// wildcardの場合
|
||||||
|
pattern_splits.push(cur_str);
|
||||||
|
pattern_splits.push(wildcard.to_string());
|
||||||
|
|
||||||
|
cur_str = String::default();
|
||||||
|
idx += 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 上記のFor文でHitした場合はcontinue
|
||||||
|
if prev_idx != idx {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
cur_str = format!(
|
||||||
|
"{}{}",
|
||||||
|
cur_str,
|
||||||
|
pattern.chars().skip(idx).take(1).collect::<String>()
|
||||||
|
);
|
||||||
|
idx += 1;
|
||||||
|
}
|
||||||
|
// 最後の文字がwildcardじゃない場合は、cur_strに文字が入っているので、それをpattern_splitsに入れておく
|
||||||
|
if !cur_str.is_empty() {
|
||||||
|
pattern_splits.push(cur_str);
|
||||||
|
}
|
||||||
|
|
||||||
|
// SIGMAルールのwildcard表記から正規表現の表記に変換します。
|
||||||
|
return pattern_splits.iter().enumerate().fold(
|
||||||
|
String::default(),
|
||||||
|
|acc: String, (idx, pattern)| {
|
||||||
|
let regex_value = if idx % 2 == 0 {
|
||||||
|
// wildcardじゃない場合はescapeした文字列を返す
|
||||||
|
regex::escape(pattern)
|
||||||
|
} else {
|
||||||
|
// wildcardの場合、"*"は".*"という正規表現に変換し、"?"は"."に変換する。
|
||||||
|
let wildcard_regex_value = if pattern.to_string() == "*" {
|
||||||
|
".*"
|
||||||
|
} else {
|
||||||
|
"."
|
||||||
|
};
|
||||||
|
wildcard_regex_value.to_string()
|
||||||
|
};
|
||||||
|
|
||||||
|
return format!("{}{}", acc, regex_value);
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::detections::rule::{
|
use crate::detections::rule::{
|
||||||
create_rule, AggregationConditionToken, AndSelectionNode, LeafSelectionNode,
|
create_rule, AggregationConditionToken, AndSelectionNode, DefaultMatcher,
|
||||||
MinlengthMatcher, OrSelectionNode, RegexMatcher, RegexesFileMatcher, SelectionNode,
|
LeafSelectionNode, MinlengthMatcher, OrSelectionNode, PipeElement, RegexesFileMatcher,
|
||||||
WhitelistFileMatcher,
|
SelectionNode, WhitelistFileMatcher,
|
||||||
};
|
};
|
||||||
use yaml_rust::YamlLoader;
|
use yaml_rust::YamlLoader;
|
||||||
|
|
||||||
@@ -1909,14 +1939,14 @@ mod tests {
|
|||||||
let matcher = &child_node.matcher;
|
let matcher = &child_node.matcher;
|
||||||
assert_eq!(matcher.is_some(), true);
|
assert_eq!(matcher.is_some(), true);
|
||||||
let matcher = child_node.matcher.as_ref().unwrap();
|
let matcher = child_node.matcher.as_ref().unwrap();
|
||||||
assert_eq!(matcher.is::<RegexMatcher>(), true);
|
assert_eq!(matcher.is::<DefaultMatcher>(), true);
|
||||||
let matcher = matcher.downcast_ref::<RegexMatcher>().unwrap();
|
let matcher = matcher.downcast_ref::<DefaultMatcher>().unwrap();
|
||||||
|
|
||||||
assert_eq!(matcher.re.is_some(), true);
|
assert_eq!(matcher.re.is_some(), true);
|
||||||
let re = matcher.re.as_ref();
|
let re = matcher.re.as_ref();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
re.unwrap().as_str(),
|
re.unwrap().as_str(),
|
||||||
"Microsoft-Windows-PowerShell/Operational"
|
r"Microsoft\-Windows\-PowerShell/Operational".to_lowercase()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1933,8 +1963,8 @@ mod tests {
|
|||||||
let matcher = &child_node.matcher;
|
let matcher = &child_node.matcher;
|
||||||
assert_eq!(matcher.is_some(), true);
|
assert_eq!(matcher.is_some(), true);
|
||||||
let matcher = child_node.matcher.as_ref().unwrap();
|
let matcher = child_node.matcher.as_ref().unwrap();
|
||||||
assert_eq!(matcher.is::<RegexMatcher>(), true);
|
assert_eq!(matcher.is::<DefaultMatcher>(), true);
|
||||||
let matcher = matcher.downcast_ref::<RegexMatcher>().unwrap();
|
let matcher = matcher.downcast_ref::<DefaultMatcher>().unwrap();
|
||||||
|
|
||||||
assert_eq!(matcher.re.is_some(), true);
|
assert_eq!(matcher.re.is_some(), true);
|
||||||
let re = matcher.re.as_ref();
|
let re = matcher.re.as_ref();
|
||||||
@@ -1959,11 +1989,11 @@ mod tests {
|
|||||||
let hostapp_en_matcher = &hostapp_en_node.matcher;
|
let hostapp_en_matcher = &hostapp_en_node.matcher;
|
||||||
assert_eq!(hostapp_en_matcher.is_some(), true);
|
assert_eq!(hostapp_en_matcher.is_some(), true);
|
||||||
let hostapp_en_matcher = hostapp_en_matcher.as_ref().unwrap();
|
let hostapp_en_matcher = hostapp_en_matcher.as_ref().unwrap();
|
||||||
assert_eq!(hostapp_en_matcher.is::<RegexMatcher>(), true);
|
assert_eq!(hostapp_en_matcher.is::<DefaultMatcher>(), true);
|
||||||
let hostapp_en_matcher = hostapp_en_matcher.downcast_ref::<RegexMatcher>().unwrap();
|
let hostapp_en_matcher = hostapp_en_matcher.downcast_ref::<DefaultMatcher>().unwrap();
|
||||||
assert_eq!(hostapp_en_matcher.re.is_some(), true);
|
assert_eq!(hostapp_en_matcher.re.is_some(), true);
|
||||||
let re = hostapp_en_matcher.re.as_ref();
|
let re = hostapp_en_matcher.re.as_ref();
|
||||||
assert_eq!(re.unwrap().as_str(), "Host Application");
|
assert_eq!(re.unwrap().as_str(), "Host Application".to_lowercase());
|
||||||
|
|
||||||
// LeafSelectionNodeである、ホスト アプリケーションノードが正しいことを確認
|
// LeafSelectionNodeである、ホスト アプリケーションノードが正しいことを確認
|
||||||
let hostapp_jp_node = ancestors[1].as_ref() as &dyn SelectionNode;
|
let hostapp_jp_node = ancestors[1].as_ref() as &dyn SelectionNode;
|
||||||
@@ -1973,8 +2003,8 @@ mod tests {
|
|||||||
let hostapp_jp_matcher = &hostapp_jp_node.matcher;
|
let hostapp_jp_matcher = &hostapp_jp_node.matcher;
|
||||||
assert_eq!(hostapp_jp_matcher.is_some(), true);
|
assert_eq!(hostapp_jp_matcher.is_some(), true);
|
||||||
let hostapp_jp_matcher = hostapp_jp_matcher.as_ref().unwrap();
|
let hostapp_jp_matcher = hostapp_jp_matcher.as_ref().unwrap();
|
||||||
assert_eq!(hostapp_jp_matcher.is::<RegexMatcher>(), true);
|
assert_eq!(hostapp_jp_matcher.is::<DefaultMatcher>(), true);
|
||||||
let hostapp_jp_matcher = hostapp_jp_matcher.downcast_ref::<RegexMatcher>().unwrap();
|
let hostapp_jp_matcher = hostapp_jp_matcher.downcast_ref::<DefaultMatcher>().unwrap();
|
||||||
assert_eq!(hostapp_jp_matcher.re.is_some(), true);
|
assert_eq!(hostapp_jp_matcher.re.is_some(), true);
|
||||||
let re = hostapp_jp_matcher.re.as_ref();
|
let re = hostapp_jp_matcher.re.as_ref();
|
||||||
assert_eq!(re.unwrap().as_str(), "ホスト アプリケーション");
|
assert_eq!(re.unwrap().as_str(), "ホスト アプリケーション");
|
||||||
@@ -2072,21 +2102,6 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// #[test]
|
|
||||||
// fn test_get_event_ids() {
|
|
||||||
// let rule_str = r#"
|
|
||||||
// enabled: true
|
|
||||||
// detection:
|
|
||||||
// selection:
|
|
||||||
// EventID: 1234
|
|
||||||
// output: 'command=%CommandLine%'
|
|
||||||
// "#;
|
|
||||||
// let rule_node = parse_rule_from_str(rule_str);
|
|
||||||
// let event_ids = rule_node.get_event_ids();
|
|
||||||
// assert_eq!(event_ids.len(), 1);
|
|
||||||
// assert_eq!(event_ids[0], 1234);
|
|
||||||
// }
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_notdetect_regex_eventid() {
|
fn test_notdetect_regex_eventid() {
|
||||||
// 完全一致なので、前方一致で検知しないことを確認
|
// 完全一致なので、前方一致で検知しないことを確認
|
||||||
@@ -2229,6 +2244,63 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_detect_wildcard_multibyte() {
|
||||||
|
// multi byteの確認
|
||||||
|
let rule_str = r#"
|
||||||
|
enabled: true
|
||||||
|
detection:
|
||||||
|
selection:
|
||||||
|
Channel: ホストアプリケーション
|
||||||
|
output: 'command=%CommandLine%'
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let record_json_str = r#"
|
||||||
|
{
|
||||||
|
"Event": {"System": {"EventID": 4103, "Channel": "ホストアプリケーション"}},
|
||||||
|
"Event_attributes": {"xmlns": "http://schemas.microsoft.com/win/2004/08/events/event"}
|
||||||
|
}"#;
|
||||||
|
|
||||||
|
let rule_node = parse_rule_from_str(rule_str);
|
||||||
|
match serde_json::from_str(record_json_str) {
|
||||||
|
Ok(record) => {
|
||||||
|
assert_eq!(rule_node.select(&record), true);
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
assert!(false, "failed to parse json record.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_detect_wildcard_multibyte_notdetect() {
|
||||||
|
// multi byteの確認
|
||||||
|
let rule_str = r#"
|
||||||
|
enabled: true
|
||||||
|
detection:
|
||||||
|
selection:
|
||||||
|
Channel: ホスとアプリケーション
|
||||||
|
output: 'command=%CommandLine%'
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let record_json_str = r#"
|
||||||
|
{
|
||||||
|
"Event": {"System": {"EventID": 4103, "Channel": "ホストアプリケーション"}},
|
||||||
|
"Event_attributes": {"xmlns": "http://schemas.microsoft.com/win/2004/08/events/event"}
|
||||||
|
}"#;
|
||||||
|
|
||||||
|
let rule_node = parse_rule_from_str(rule_str);
|
||||||
|
match serde_json::from_str(record_json_str) {
|
||||||
|
Ok(record) => {
|
||||||
|
assert_eq!(rule_node.select(&record), false);
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
assert!(false, "failed to parse json record.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_detect_regex_str() {
|
fn test_detect_regex_str() {
|
||||||
// 文字列っぽいデータでも完全一致することを確認
|
// 文字列っぽいデータでも完全一致することを確認
|
||||||
@@ -2520,8 +2592,8 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_notdetect_casesensetive() {
|
fn test_wildcard_case_insensitive() {
|
||||||
// OR条件が正しく検知できることを確認
|
// wildcardは大文字小文字関係なくマッチする。
|
||||||
let rule_str = r#"
|
let rule_str = r#"
|
||||||
enabled: true
|
enabled: true
|
||||||
detection:
|
detection:
|
||||||
@@ -2539,7 +2611,7 @@ mod tests {
|
|||||||
let rule_node = parse_rule_from_str(rule_str);
|
let rule_node = parse_rule_from_str(rule_str);
|
||||||
match serde_json::from_str(record_json_str) {
|
match serde_json::from_str(record_json_str) {
|
||||||
Ok(record) => {
|
Ok(record) => {
|
||||||
assert_eq!(rule_node.select(&record), false);
|
assert_eq!(rule_node.select(&record), true);
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
assert!(false, "failed to parse json record.");
|
assert!(false, "failed to parse json record.");
|
||||||
@@ -2642,7 +2714,6 @@ mod tests {
|
|||||||
detection:
|
detection:
|
||||||
selection:
|
selection:
|
||||||
Channel:
|
Channel:
|
||||||
regex: Security10
|
|
||||||
min_length: 10
|
min_length: 10
|
||||||
output: 'command=%CommandLine%'
|
output: 'command=%CommandLine%'
|
||||||
"#;
|
"#;
|
||||||
@@ -2672,7 +2743,6 @@ mod tests {
|
|||||||
detection:
|
detection:
|
||||||
selection:
|
selection:
|
||||||
Channel:
|
Channel:
|
||||||
regex: Security10
|
|
||||||
min_length: 11
|
min_length: 11
|
||||||
output: 'command=%CommandLine%'
|
output: 'command=%CommandLine%'
|
||||||
"#;
|
"#;
|
||||||
@@ -2701,7 +2771,7 @@ mod tests {
|
|||||||
enabled: true
|
enabled: true
|
||||||
detection:
|
detection:
|
||||||
selection:
|
selection:
|
||||||
Channel: ^Program$
|
Channel|re: ^Program$
|
||||||
output: 'command=%CommandLine%'
|
output: 'command=%CommandLine%'
|
||||||
"#;
|
"#;
|
||||||
|
|
||||||
@@ -3114,7 +3184,7 @@ mod tests {
|
|||||||
detection:
|
detection:
|
||||||
selection:
|
selection:
|
||||||
EventID: 403
|
EventID: 403
|
||||||
EventData: '[\s\S]*EngineVersion=2.0[\s\S]*'
|
EventData|re: '[\s\S]*EngineVersion=2\.0[\s\S]*'
|
||||||
output: 'command=%CommandLine%'
|
output: 'command=%CommandLine%'
|
||||||
"#;
|
"#;
|
||||||
|
|
||||||
@@ -3538,7 +3608,10 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
rule_node.init(),
|
rule_node.init(),
|
||||||
Err(vec!["Found unknown key option. option: failed".to_string()])
|
Err(vec![
|
||||||
|
"unknown pipe element was specified. key:detection -> selection -> Channel|failed"
|
||||||
|
.to_string()
|
||||||
|
])
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4613,6 +4686,64 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_pipe_pattern_wildcard_asterisk() {
|
||||||
|
let value = PipeElement::pipe_pattern_wildcard(r"*ho*ge*".to_string());
|
||||||
|
assert_eq!(".*ho.*ge.*", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_pipe_pattern_wildcard_asterisk2() {
|
||||||
|
let value = PipeElement::pipe_pattern_wildcard(r"\*ho\*\*ge\*".to_string());
|
||||||
|
// wildcardの「\*」は文字列としての「*」を表す。
|
||||||
|
// 正規表現で「*」はエスケープする必要があるので、\*が正解
|
||||||
|
assert_eq!(r"\*ho\*\*ge\*", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_pipe_pattern_wildcard_asterisk3() {
|
||||||
|
// wildcardの「\\*」は文字列としての「\」と正規表現の「.*」を表す。
|
||||||
|
// 文字列としての「\」はエスケープされるので、「\\.*」が正解
|
||||||
|
let value = PipeElement::pipe_pattern_wildcard(r"\\*ho\\*ge\\*".to_string());
|
||||||
|
assert_eq!(r"\\.*ho\\.*ge\\.*", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_pipe_pattern_wildcard_question() {
|
||||||
|
let value = PipeElement::pipe_pattern_wildcard(r"?ho?ge?".to_string());
|
||||||
|
assert_eq!(r".ho.ge.", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_pipe_pattern_wildcard_question2() {
|
||||||
|
let value = PipeElement::pipe_pattern_wildcard(r"\?ho\?ge\?".to_string());
|
||||||
|
assert_eq!(r"\?ho\?ge\?", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_pipe_pattern_wildcard_question3() {
|
||||||
|
let value = PipeElement::pipe_pattern_wildcard(r"\\?ho\\?ge\\?".to_string());
|
||||||
|
assert_eq!(r"\\.ho\\.ge\\.", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_pipe_pattern_wildcard_backshash() {
|
||||||
|
let value = PipeElement::pipe_pattern_wildcard(r"\\ho\\ge\\".to_string());
|
||||||
|
assert_eq!(r"\\\\ho\\\\ge\\\\", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_pipe_pattern_wildcard_mixed() {
|
||||||
|
let value = PipeElement::pipe_pattern_wildcard(r"\\*\****\*\\*".to_string());
|
||||||
|
assert_eq!(r"\\.*\*.*.*.*\*\\.*", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_pipe_pattern_wildcard_many_backshashs() {
|
||||||
|
let value = PipeElement::pipe_pattern_wildcard(r"\\\*ho\\\*ge\\\".to_string());
|
||||||
|
assert_eq!(r"\\\\.*ho\\\\.*ge\\\\\\", value);
|
||||||
|
}
|
||||||
|
|
||||||
fn check_aggregation_condition_ope(expr: String, cmp_num: i32) -> AggregationConditionToken {
|
fn check_aggregation_condition_ope(expr: String, cmp_num: i32) -> AggregationConditionToken {
|
||||||
let compiler = AggegationConditionCompiler::new();
|
let compiler = AggegationConditionCompiler::new();
|
||||||
let result = compiler.compile(expr);
|
let result = compiler.compile(expr);
|
||||||
|
|||||||
Reference in New Issue
Block a user