diff --git a/config/eventkey_alias.txt b/config/eventkey_alias.txt index c39542dd..678c9ac1 100644 --- a/config/eventkey_alias.txt +++ b/config/eventkey_alias.txt @@ -23,4 +23,23 @@ SubjectUserName,Event.EventData.SubjectUserName SubjectUserSid,Event.EventData.SubjectUserSid DomainName,Event.EventData.SubjectDomainName TicketEncryptionType,Event.EventData.TicketEncryptionType -PreAuthType,Event.EventData.PreAuthType \ No newline at end of file +PreAuthType,Event.EventData.PreAuthType +TaskName,Event.EventData.TaskName +WorkStationName,Event.EventData.WorkStationName +Workstation,Event.EventData.WorkStation +UserName,Event.EventData.UserName +ServiceFileName,Event.EventData.ServiceFileName +ComputerName,Event.System.Computer +Account_Name,Event.EventData.Account_Name +Source_Network_Address,Event.EventData.Source_Network_Address +Caller_Process_Name,Event.EventData.Caller_Process_Name +Computer,Event.System.Computer +Client_Address,Event.EventData.Client_Address +Logon_Account,Event.EventData.Logon_Account +Source_WorkStation,Event.EventData.Source_WorkStation +SourceAddress,Event.EventData.SourceAddress +SubjectLogonId,Event.EventData.SubjectLogonId +Image,Event.EventData.Image +ParentImage,Event.EventData.ParentImage +MachineName,Event.EventData.MachineName +QueryName,Event.EventData.QueryName \ No newline at end of file diff --git a/src/detections/detection.rs b/src/detections/detection.rs index 5121d1fd..157ef66b 100644 --- a/src/detections/detection.rs +++ b/src/detections/detection.rs @@ -1,7 +1,8 @@ extern crate csv; +use crate::detections::rule::AggResult; use serde_json::Value; -use tokio::{spawn, task::JoinHandle}; +use tokio::spawn; use crate::detections::print::MESSAGES; use crate::detections::rule; @@ -45,7 +46,7 @@ impl Detection { } let tokio_rt = utils::create_tokio_runtime(); - tokio_rt.block_on(self.execute_rule(rules, records)); + tokio_rt.block_on(Detection::execute_rules(rules, records)); tokio_rt.shutdown_background(); } @@ -94,82 +95,85 @@ impl Detection { .collect(); } - // 検知ロジックを実行します。 - async fn execute_rule(&mut self, rules: Vec, records: Vec) { - // 複数スレッドで所有権を共有するため、recordsをArcでwwap - let mut records_arcs = vec![]; - for record_chunk in Detection::chunks(records, num_cpus::get() * 4) { - let record_chunk_arc = Arc::new(record_chunk); - records_arcs.push(record_chunk_arc); - } - - // 複数スレッドで所有権を共有するため、rulesをArcでwwap - let rules_arc = Arc::new(rules); - - // ルール実行するスレッドを作成。 - let mut handles = vec![]; - for record_chunk_arc in &records_arcs { - let records_arc_clone = Arc::clone(&record_chunk_arc); - let rules_clones = Arc::clone(&rules_arc); - - let handle: JoinHandle> = spawn(async move { - let mut ret = vec![]; - for rule in rules_clones.iter() { - for record_info in records_arc_clone.iter() { - ret.push(rule.select(&record_info.record)); // 検知したか否かを配列に保存しておく - } - } - return ret; + async fn execute_rules(rules: Vec, records: Vec) { + let records_arc = Arc::new(records); + let traiter = rules.into_iter(); + // 各rule毎にスレッドを作成して、スレッドを起動する。 + let handles = traiter.map(|rule| { + let records_cloned = Arc::clone(&records_arc); + return spawn(async move { + Detection::execute_rule(rule, records_cloned); }); - handles.push(handle); + }); + + // 全スレッドの実行完了を待機 + for handle in handles { + handle.await.unwrap(); + } + } + + // 検知ロジックを実行します。 + fn execute_rule(mut rule: RuleNode, records: Arc>) { + let records = &*records; + let agg_condition = rule.has_agg_condition(); + for record_info in records { + let result = rule.select(&record_info.evtx_filepath, &record_info.record); + if !result { + continue; + } + // aggregation conditionが存在しない場合はそのまま出力対応を行う + if !agg_condition { + Detection::insert_message(&rule, &record_info); + return; + } } - // メッセージを追加する。これを上記のspawnの中でやると、ロックの取得で逆に時間がかかるので、外に出す - let mut message = MESSAGES.lock().unwrap(); - let mut handles_ite = handles.into_iter(); - for record_chunk_arc in &records_arcs { - let mut handles_ret_ite = handles_ite.next().unwrap().await.unwrap().into_iter(); - for rule in rules_arc.iter() { - for record_info_arc in record_chunk_arc.iter() { - if handles_ret_ite.next().unwrap() == false { - continue; - } - - // TODO メッセージが多いと、rule.select()よりもこの処理の方が時間かかる。 - message.insert( - record_info_arc.evtx_filepath.to_string(), - &record_info_arc.record, - rule.yaml["title"].as_str().unwrap_or("").to_string(), - rule.yaml["output"].as_str().unwrap_or("").to_string(), - ); - } + let agg_results = rule.judge_satisfy_aggcondition(); + for value in agg_results { + if agg_condition { + Detection::insert_agg_message(&rule, value); } } } - // fn get_event_ids(rules: &Vec) -> HashSet { - // return rules - // .iter() - // .map(|rule| rule.get_event_ids()) - // .flatten() - // .collect(); - // } + /// 条件に合致したレコードを表示するための関数 + fn insert_message(rule: &RuleNode, record_info: &EvtxRecordInfo) { + MESSAGES.lock().unwrap().insert( + record_info.evtx_filepath.to_string(), + &record_info.record, + rule.yaml["title"].as_str().unwrap_or("").to_string(), + rule.yaml["output"].as_str().unwrap_or("").to_string(), + ); + } - // 配列を指定したサイズで分割する。Vector.chunksと同じ動作をするが、Vectorの関数だとinto的なことができないので自作 - fn chunks(ary: Vec, size: usize) -> Vec> { - let arylen = ary.len(); - let mut ite = ary.into_iter(); + /// insert aggregation condition detection message to output stack + fn insert_agg_message(rule: &RuleNode, agg_result: AggResult) { + let output = Detection::create_count_output(rule, &agg_result); + MESSAGES.lock().unwrap().insert_message( + agg_result.filepath, + agg_result.start_timedate, + rule.yaml["title"].as_str().unwrap_or("").to_string(), + output.to_string(), + ) + } - let mut ret = vec![]; - for i in 0..arylen { - if i % size == 0 { - ret.push(vec![]); - ret.iter_mut().last().unwrap().push(ite.next().unwrap()); - } else { - ret.iter_mut().last().unwrap().push(ite.next().unwrap()); - } + ///aggregation conditionのcount部分の検知出力文の文字列を返す関数 + fn create_count_output(rule: &RuleNode, agg_result: &AggResult) -> String { + let mut ret: String = "count(".to_owned(); + let key: Vec<&str> = agg_result.key.split("_").collect(); + if key.len() >= 1 { + ret.push_str(key[0]); } - + ret.push_str(&") "); + if key.len() >= 2 { + ret.push_str("by "); + ret.push_str(key[1]); + } + ret.push_str(&format!( + "{} in {}.", + agg_result.condition_op_num, + rule.yaml["timeframe"].as_str().unwrap_or(""), + )); return ret; } } diff --git a/src/detections/print.rs b/src/detections/print.rs index c6ab0cd0..22640a93 100644 --- a/src/detections/print.rs +++ b/src/detections/print.rs @@ -1,4 +1,3 @@ -extern crate chrono; extern crate lazy_static; use crate::detections::configs; use chrono::{DateTime, TimeZone, Utc}; @@ -34,6 +33,31 @@ impl Message { Message { map: messages } } + /// メッセージの設定を行う関数。aggcondition対応のためrecordではなく出力をする対象時間がDatetime形式での入力としている + pub fn insert_message( + &mut self, + target_file: String, + event_time: DateTime, + event_title: String, + event_detail: String, + ) { + let detect_info = DetectInfo { + filepath: target_file, + title: event_title, + detail: event_detail, + }; + + match self.map.get_mut(&event_time) { + Some(v) => { + v.push(detect_info); + } + None => { + let m = vec![detect_info; 1]; + self.map.insert(event_time, m); + } + } + } + /// メッセージを設定 pub fn insert( &mut self, @@ -42,28 +66,10 @@ impl Message { event_title: String, output: String, ) { - if output.is_empty() { - return; - } - let message = &self.parse_message(event_record, output); let default_time = Utc.ymd(1970, 1, 1).and_hms(0, 0, 0); let time = Message::get_event_time(event_record).unwrap_or(default_time); - let detect_info = DetectInfo { - filepath: target_file, - title: event_title, - detail: message.to_string(), - }; - - match self.map.get_mut(&time) { - Some(v) => { - v.push(detect_info); - } - None => { - let m = vec![detect_info; 1]; - self.map.insert(time, m); - } - } + self.insert_message(target_file, time, event_title, message.to_string()) } fn parse_message(&mut self, event_record: &Value, output: String) -> String { @@ -132,7 +138,7 @@ impl Message { &self.map } - fn get_event_time(event_record: &Value) -> Option> { + pub fn get_event_time(event_record: &Value) -> Option> { let system_time = &event_record["Event"]["System"]["TimeCreated_attributes"]["SystemTime"]; let system_time_str = system_time.as_str().unwrap_or(""); if system_time_str.is_empty() { diff --git a/src/detections/rule/aggregation_parser.rs b/src/detections/rule/aggregation_parser.rs index efca1d53..bcc84bf1 100644 --- a/src/detections/rule/aggregation_parser.rs +++ b/src/detections/rule/aggregation_parser.rs @@ -2,10 +2,10 @@ use regex::Regex; #[derive(Debug)] pub struct AggregationParseInfo { - _field_name: Option, // countの括弧に囲まれた部分の文字 - _by_field_name: Option, // count() by の後に指定される文字列 - _cmp_op: AggregationConditionToken, // (必須)<とか>とか何が指定されたのか - _cmp_num: i32, // (必須)<とか>とかの後にある数値 + pub _field_name: Option, // countの括弧に囲まれた部分の文字 + pub _by_field_name: Option, // count() by の後に指定される文字列 + pub _cmp_op: AggregationConditionToken, // (必須)<とか>とか何が指定されたのか + pub _cmp_num: i32, // (必須)<とか>とかの後にある数値 } #[derive(Debug)] diff --git a/src/detections/rule/condition_parser.rs b/src/detections/rule/condition_parser.rs index d36f1dfa..3e17fec2 100644 --- a/src/detections/rule/condition_parser.rs +++ b/src/detections/rule/condition_parser.rs @@ -534,10 +534,13 @@ mod tests { } fn check_select(rule_str: &str, record_str: &str, expect_select: bool) { - let rule_node = parse_rule_from_str(rule_str); + let mut rule_node = parse_rule_from_str(rule_str); match serde_json::from_str(record_str) { Ok(record) => { - assert_eq!(rule_node.select(&record), expect_select); + assert_eq!( + rule_node.select(&"testpath".to_owned(), &record), + expect_select + ); } Err(_rec) => { assert!(false, "failed to parse json record."); @@ -575,10 +578,10 @@ mod tests { } }"#; - let rule_node = parse_rule_from_str(rule_str); + let mut 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); + assert_eq!(rule_node.select(&"testpath".to_owned(), &record), true); } Err(_rec) => { assert!(false, "failed to parse json record."); @@ -617,10 +620,10 @@ mod tests { } }"#; - let rule_node = parse_rule_from_str(rule_str); + let mut 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); + assert_eq!(rule_node.select(&"testpath".to_owned(), &record), false); } Err(_rec) => { assert!(false, "failed to parse json record."); diff --git a/src/detections/rule/count.rs b/src/detections/rule/count.rs new file mode 100644 index 00000000..da984fda --- /dev/null +++ b/src/detections/rule/count.rs @@ -0,0 +1,802 @@ +use crate::detections::print::AlertMessage; +use crate::detections::rule::AggResult; +use crate::detections::rule::AggregationParseInfo; +use crate::detections::rule::Message; +use crate::detections::rule::RuleNode; +use chrono::{DateTime, TimeZone, Utc}; +use serde_json::Value; +use std::collections::HashMap; +use std::num::ParseIntError; + +use crate::detections::rule::aggregation_parser::AggregationConditionToken; + +use crate::detections::utils; + +/// 検知された際にカウント情報を投入する関数 +pub fn count(rule: &mut RuleNode, filepath: &String, record: &Value) { + let key = create_count_key(&rule, record); + let default_time = Utc.ymd(1977, 1, 1).and_hms(0, 0, 0); + countup( + rule, + filepath, + &key, + Message::get_event_time(record).unwrap_or(default_time), + ); +} + +///count byの条件に合致する検知済みレコードの数を増やすための関数 +pub fn countup( + rule: &mut RuleNode, + filepath: &String, + key: &str, + record_time_value: DateTime, +) { + rule.countdata + .entry(filepath.to_string()) + .or_insert(HashMap::new()); + let value_map = rule.countdata.get_mut(filepath).unwrap(); + value_map.entry(key.to_string()).or_insert(Vec::new()); + let mut prev_value = value_map[key].clone(); + prev_value.push(record_time_value); + value_map.insert(key.to_string(), prev_value); +} + +/// countでgroupbyなどの情報を区分するためのハッシュマップのキーを作成する関数 +pub fn create_count_key(rule: &RuleNode, record: &Value) -> String { + if rule + .detection + .as_ref() + .unwrap() + .aggregation_condition + .as_ref() + .is_none() + { + return "_".to_string(); + } + let aggcondition = rule + .detection + .as_ref() + .unwrap() + .aggregation_condition + .as_ref() + .unwrap(); + // recordでaliasが登録されている前提とする + let mut key = "".to_string(); + if aggcondition._field_name.is_some() { + let field_value = aggcondition._field_name.as_ref().unwrap(); + match utils::get_event_value(field_value, record) { + Some(value) => { + key.push_str(&value.to_string().replace("\"", "")); + } + None => { + let stdout = std::io::stdout(); + let mut stdout = stdout.lock(); + AlertMessage::alert( + &mut stdout, + format!("field_value alias not found.value:{}", field_value), + ); + } + }; + } + key.push_str("_"); + if aggcondition._by_field_name.is_some() { + let by_field_value = aggcondition._by_field_name.as_ref().unwrap(); + match utils::get_event_value(by_field_value, record) { + Some(value) => { + key.push_str(&value.to_string().replace("\"", "")); + } + None => { + let stdout = std::io::stdout(); + let mut stdout = stdout.lock(); + AlertMessage::alert( + &mut stdout, + format!("by_field_value alias not found.value:{}", by_field_value), + ); + } + } + } + return key; +} + +///現状のレコードの状態から条件式に一致しているかを判定する関数 +pub fn aggregation_condition_select(rule: &RuleNode, filepath: &String) -> Vec { + // recordでaliasが登録されている前提とする + let value_map = rule.countdata.get(filepath).unwrap(); + let mut ret = Vec::new(); + for (key, value) in value_map { + ret.append(&mut judge_timeframe( + &rule, + &filepath.to_string(), + value, + &key.to_string(), + )); + } + return ret; +} + +/// aggregation condition内での条件式を文字として返す関数 +pub fn get_str_agg_eq(rule: &RuleNode) -> String { + //この関数はaggregation ruleのパースが正常終了した後に呼ばれる想定のためOptionの判定は行わない + let agg_condition = rule + .detection + .as_ref() + .unwrap() + .aggregation_condition + .as_ref() + .unwrap(); + let mut ret: String = "".to_owned(); + match agg_condition._cmp_op { + AggregationConditionToken::EQ => { + ret.push_str("== "); + } + AggregationConditionToken::GE => { + ret.push_str(">= "); + } + AggregationConditionToken::LE => { + ret.push_str("<= "); + } + AggregationConditionToken::GT => { + ret.push_str("> "); + } + AggregationConditionToken::LT => { + ret.push_str("< "); + } + _ => { + //想定しない演算子のため、空白文字で対応するものがない + return "".to_string(); + } + } + ret.push_str(&agg_condition._cmp_num.to_string()); + return ret; +} + +#[derive(Debug)] +/// timeframeに設定された情報。SIGMAルール上timeframeで複数の単位(日、時、分、秒)が複合で記載されているルールがなかったためタイプと数値のみを格納する構造体 +pub struct TimeFrameInfo { + pub timetype: String, + pub timenum: Result, +} + +impl TimeFrameInfo { + /// timeframeの文字列をパースし、構造体を返す関数 + pub fn parse_tframe(value: String) -> TimeFrameInfo { + let mut ttype: String = "".to_string(); + let mut tnum = value.clone(); + if value.contains("s") { + ttype = "s".to_owned(); + tnum.retain(|c| c != 's'); + } else if value.contains("m") { + ttype = "m".to_owned(); + tnum.retain(|c| c != 'm'); + } else if value.contains("h") { + ttype = "h".to_owned(); + tnum.retain(|c| c != 'h'); + } else if value.contains("d") { + ttype = "d".to_owned(); + tnum.retain(|c| c != 'd'); + } else { + let stdout = std::io::stdout(); + let mut stdout = stdout.lock(); + AlertMessage::alert( + &mut stdout, + format!("timeframe is invalid.input value:{}", value), + ); + } + return TimeFrameInfo { + timetype: ttype, + timenum: tnum.parse::(), + }; + } +} + +/// TimeFrameInfoで格納されたtimeframeの値を秒数に変換した結果を返す関数 +pub fn get_sec_timeframe(timeframe: &Option) -> Option { + if timeframe.is_none() { + return Option::None; + } + let tfi = timeframe.as_ref().unwrap(); + match &tfi.timenum { + Ok(n) => { + if tfi.timetype == "d" { + return Some(n * 86400); + } else if tfi.timetype == "h" { + return Some(n * 3600); + } else if tfi.timetype == "m" { + return Some(n * 60); + } else { + return Some(*n); + } + } + Err(err) => { + let stdout = std::io::stdout(); + let mut stdout = stdout.lock(); + AlertMessage::alert( + &mut stdout, + format!("timeframe num is invalid. timeframe.{}", err), + ); + return Option::None; + } + } +} +/// conditionのパイプ以降の処理をAggregationParseInfoを参照し、conditionの条件を満たすか判定するための関数 +pub fn select_aggcon(cnt: i32, aggcondition: &AggregationParseInfo) -> bool { + match aggcondition._cmp_op { + AggregationConditionToken::EQ => { + if cnt == aggcondition._cmp_num { + return true; + } else { + return false; + } + } + AggregationConditionToken::GE => { + if cnt >= aggcondition._cmp_num { + return true; + } else { + return false; + } + } + AggregationConditionToken::GT => { + if cnt > aggcondition._cmp_num { + return true; + } else { + return false; + } + } + AggregationConditionToken::LE => { + if cnt <= aggcondition._cmp_num { + return true; + } else { + return false; + } + } + AggregationConditionToken::LT => { + if cnt < aggcondition._cmp_num { + return true; + } else { + return false; + } + } + _ => { + return false; + } + } +} + +/// count済みデータ内でタイムフレーム内に存在するselectの条件を満たすレコードが、timeframe単位でcountの条件を満たしているAggResultを配列として返却する関数 +pub fn judge_timeframe( + rule: &RuleNode, + filepath: &String, + time_datas: &Vec>, + key: &String, +) -> Vec { + let mut ret: Vec = Vec::new(); + let mut time_data = time_datas.clone(); + time_data.sort(); + let aggcondition = rule + .detection + .as_ref() + .unwrap() + .aggregation_condition + .as_ref() + .unwrap(); + let mut start_point = 0; + // 最初はcountの条件として記載されている分のレコードを取得するためのindex指定 + let mut check_point = start_point + aggcondition._cmp_num - 1; + // timeframeで指定された基準の値を秒数として保持 + let judge_sec_frame = get_sec_timeframe(&rule.detection.as_ref().unwrap().timeframe); + loop { + // 基準となるレコードもしくはcountを最低限満たす対象のレコードのindexが配列の領域を超えていた場合 + if start_point as usize >= time_data.len() || check_point as usize >= time_data.len() { + // 最終のレコードを対象として時刻を確認する + let check_point_date = time_data[time_data.len() - 1]; + let diff = check_point_date.timestamp() - time_data[start_point as usize].timestamp(); + // 対象のレコード数を基準となるindexから計算 + let mut count_set_cnt = time_data.len() - (start_point as usize); + if judge_sec_frame.is_some() && diff > judge_sec_frame.unwrap() { + //すでにcountを満たしている状態で1つずつdiffを確認している場合は適正な個数指定となり、もともとcountの条件が残りデータ個数より多い場合は-1したことによってcountの判定でもfalseになるため + count_set_cnt -= count_set_cnt - 1; + } + + // timeframe内に入っている場合があるため判定を行う + let judge = select_aggcon(count_set_cnt as i32, &aggcondition); + if judge { + ret.push(AggResult::new( + filepath.to_string(), + count_set_cnt as i32, + key.to_string(), + time_data[start_point as usize], + get_str_agg_eq(rule), + )); + } + break; + } + // 基準となるレコードと時刻比較を行う対象のレコード時刻情報を取得する + let check_point_date = time_data[check_point as usize]; + let diff = check_point_date.timestamp() - time_data[start_point as usize].timestamp(); + // timeframeで指定した情報と比較して時刻差がtimeframeの枠を超えていた場合(timeframeの属性を記載していない場合はこの処理を行わない) + if judge_sec_frame.is_some() && diff > judge_sec_frame.unwrap() { + let count_set_cnt = check_point - start_point; + let judge = select_aggcon(count_set_cnt, &aggcondition); + // timeframe内の対象のレコード数がcountの条件を満たさなかった場合、基準となるレコードを1つずらし、countの判定基準分のindexを設定して、次のレコードから始まるtimeframeの判定を行う + if !judge { + start_point += 1; + check_point = start_point + aggcondition._cmp_num - 1; + + continue; + } + //timeframe内の対象のレコード数がcountの条件を満たした場合は返却用の変数に結果を投入する + ret.push(AggResult::new( + filepath.to_string(), + count_set_cnt, + key.to_string(), + time_data[start_point as usize], + get_str_agg_eq(rule), + )); + // timeframe投入内の対象レコード数がcountの条件を満たした場合は、すでに判定済みのtimeframe内では同様に検知を行うことになり、過検知となってしまうため、今回timeframe内と判定された最後のレコードの次のレコードを次の基準として参照するようにindexを設定する + start_point = check_point; + check_point = start_point + aggcondition._cmp_num - 1; + } else { + // timeframeで指定した情報と比較して。時刻差がtimeframeの枠を超えていない場合は次のレコード時刻情報を参照して、timeframe内であるかを判定するため + check_point += 1; + } + } + return ret; +} + +#[cfg(test)] +mod tests { + use crate::detections::rule::create_rule; + use crate::detections::rule::AggResult; + use std::collections::HashMap; + + use chrono::{TimeZone, Utc}; + use yaml_rust::YamlLoader; + + const SIMPLE_RECORD_STR: &str = r#" + { + "Event": { + "System": { + "EventID": 7040, + "Channel": "System" + }, + "EventData": { + "param1": "Windows Event Log", + "param2": "auto start" + } + }, + "Event_attributes": { + "xmlns": "http://schemas.microsoft.com/win/2004/08/events/event" + } + }"#; + + #[test] + /// countのカッコ内の記載及びcount byの記載がない場合(timeframeなし)にruleで検知ができることのテスト + fn test_count_no_field_and_by() { + let record_str: &str = r#" + { + "Event": { + "System": { + "EventID": 7040, + "Channel": "System", + "TimeCreated_attributes": { + "SystemTime": "1996-02-27T01:05:01Z" + } + }, + "EventData": { + "param1": "Windows Event Log", + "param2": "auto start" + } + }, + "Event_attributes": { + "xmlns": "http://schemas.microsoft.com/win/2004/08/events/event" + } + }"#; + let rule_str = r#" + enabled: true + detection: + selection1: + Channel: 'System' + selection2: + EventID: 7040 + selection3: + param1: 'Windows Event Log' + condition: selection1 and selection2 and selection3 | count() >= 1 + output: 'Service name : %param1%¥nMessage : Event Log Service Stopped¥nResults: Selective event log manipulation may follow this event.' + "#; + let default_time = Utc.ymd(1977, 1, 1).and_hms(0, 0, 0); + let mut expected_count = HashMap::new(); + expected_count.insert("_".to_owned(), 2); + let expected_agg_result: Vec = vec![AggResult::new( + "testpath".to_string(), + 2, + "_".to_string(), + default_time, + ">= 1".to_string(), + )]; + check_count( + rule_str, + vec![SIMPLE_RECORD_STR, record_str], + expected_count, + expected_agg_result, + ); + } + + #[test] + /// countのカッコ内の記載及びcount byの記載がない場合(timeframeあり)にruleで検知ができることのテスト + fn test_count_no_field_and_by_with_timeframe() { + let record_str: &str = r#" + { + "Event": { + "System": { + "EventID": 7040, + "Channel": "System", + "TimeCreated_attributes": { + "SystemTime": "1996-02-27T01:05:01Z" + } + }, + "EventData": { + "param1": "Windows Event Log", + "param2": "auto start" + } + }, + "Event_attributes": { + "xmlns": "http://schemas.microsoft.com/win/2004/08/events/event" + } + }"#; + let rule_str = r#" + enabled: true + detection: + selection1: + Channel: 'System' + selection2: + EventID: 7040 + selection3: + param1: 'Windows Event Log' + condition: selection1 and selection2 and selection3 | count() >= 1 + timeframe: 15m + output: 'Service name : %param1%¥nMessage : Event Log Service Stopped¥nResults: Selective event log manipulation may follow this event.' + "#; + let default_time = Utc.ymd(1977, 1, 1).and_hms(0, 0, 0); + let record_time = Utc.ymd(1996, 2, 27).and_hms(1, 5, 1); + let mut expected_count = HashMap::new(); + expected_count.insert("_".to_owned(), 2); + let mut expected_agg_result: Vec = Vec::new(); + expected_agg_result.push(AggResult::new( + "testpath".to_string(), + 1, + "_".to_string(), + default_time, + ">= 1".to_string(), + )); + expected_agg_result.push(AggResult::new( + "testpath".to_string(), + 1, + "_".to_string(), + record_time, + ">= 1".to_string(), + )); + check_count( + rule_str, + vec![SIMPLE_RECORD_STR, record_str], + expected_count, + expected_agg_result, + ); + } + + #[test] + /// countでカッコ内の記載がある場合にruleでcountの検知ができることを確認する + fn test_count_exist_field() { + let rule_str = r#" + enabled: true + detection: + selection1: + Channel: 'System' + selection2: + EventID: 7040 + selection3: + param1: 'Windows Event Log' + condition: selection1 and selection2 and selection3 | count(Channel) >= 1 + output: 'Service name : %param1%¥nMessage : Event Log Service Stopped¥nResults: Selective event log manipulation may follow this event.' + "#; + let default_time = Utc.ymd(1977, 1, 1).and_hms(0, 0, 0); + let mut expected_count = HashMap::new(); + expected_count.insert("System_".to_owned(), 1); + let expected_agg_result = AggResult::new( + "testpath".to_string(), + 1, + "System_".to_string(), + default_time, + ">= 1".to_string(), + ); + check_count( + rule_str, + vec![SIMPLE_RECORD_STR], + expected_count, + vec![expected_agg_result], + ); + } + + #[test] + /// countでカッコ内の記載、byの記載両方がある場合にruleでcountの検知ができることを確認する + fn test_count_exist_field_and_by() { + let record_str: &str = r#" + { + "Event": { + "System": { + "EventID": 9999, + "Channel": "Test", + "TimeCreated_attributes": { + "SystemTime": "1996-02-27T01:05:01Z" + } + }, + "EventData": { + "param1": "Windows Event Log", + "param2": "auto start" + } + }, + "Event_attributes": { + "xmlns": "http://schemas.microsoft.com/win/2004/08/events/event" + } + }"#; + let rule_str = r#" + enabled: true + detection: + selection1: + param1: 'Windows Event Log' + condition: selection1 | count(EventID) by Channel >= 1 + output: 'Service name : %param1%¥nMessage : Event Log Service Stopped¥nResults: Selective event log manipulation may follow this event.' + "#; + + let default_time = Utc.ymd(1977, 1, 1).and_hms(0, 0, 0); + let record_time = Utc.ymd(1996, 2, 27).and_hms(1, 5, 1); + let mut expected_count = HashMap::new(); + expected_count.insert("7040_System".to_owned(), 1); + expected_count.insert("9999_Test".to_owned(), 1); + let mut expected_agg_result: Vec = Vec::new(); + expected_agg_result.push(AggResult::new( + "testpath".to_string(), + 1, + "7040_System".to_owned(), + default_time, + ">= 1".to_string(), + )); + expected_agg_result.push(AggResult::new( + "testpath".to_string(), + 1, + "9999_Test".to_owned(), + record_time, + ">= 1".to_string(), + )); + check_count( + rule_str, + vec![SIMPLE_RECORD_STR, record_str], + expected_count, + expected_agg_result, + ); + } + + #[test] + /// countでカッコ内の記載、byの記載両方がある場合(複数レコードでカッコ内の指定する値が異なる場合)に値の組み合わせごとに分けてcountが実行していることを確認する + fn test_count_exist_field_and_by_with_othervalue_in_timeframe() { + let record_str: &str = r#" + { + "Event": { + "System": { + "EventID": 9999, + "Channel": "System", + "TimeCreated_attributes": { + "SystemTime": "1977-01-01T00:05:00Z" + } + }, + "EventData": { + "param1": "Test", + "param2": "auto start" + } + }, + "Event_attributes": { + "xmlns": "http://schemas.microsoft.com/win/2004/08/events/event" + } + }"#; + let rule_str = r#" + enabled: true + detection: + selection1: + Channel: 'System' + condition: selection1 | count(EventID) by param1 >= 1 + timeframe: 1h + output: 'Service name : %param1%¥nMessage : Event Log Service Stopped¥nResults: Selective event log manipulation may follow this event.' + "#; + let default_time = Utc.ymd(1977, 1, 1).and_hms(0, 0, 0); + let record_time = Utc.ymd(1977, 1, 1).and_hms(0, 5, 0); + let mut expected_count = HashMap::new(); + expected_count.insert("7040_Windows Event Log".to_owned(), 1); + expected_count.insert("9999_Test".to_owned(), 1); + let mut expected_agg_result: Vec = Vec::new(); + expected_agg_result.push(AggResult::new( + "testpath".to_string(), + 1, + "7040_Windows Event Log".to_owned(), + default_time, + ">= 1".to_string(), + )); + expected_agg_result.push(AggResult::new( + "testpath".to_string(), + 1, + "9999_Test".to_owned(), + record_time, + ">= 1".to_string(), + )); + check_count( + rule_str, + vec![SIMPLE_RECORD_STR, record_str], + expected_count, + expected_agg_result, + ); + } + + #[test] + /// countでtimeframeの条件によってruleのcountの条件を満たさない場合に空の配列を返すことを確認する + fn test_count_not_satisfy_in_timeframe() { + let record_str: &str = r#" + { + "Event": { + "System": { + "EventID": 7040, + "Channel": "System", + "TimeCreated_attributes": { + "SystemTime": "1977-01-01T01:05:00Z" + } + } + }, + "Event_attributes": { + "xmlns": "http://schemas.microsoft.com/win/2004/08/events/event" + } + }"#; + let rule_str = r#" + enabled: true + detection: + selection1: + Channel: 'System' + condition: selection1 | count(EventID) >= 2 + timeframe: 1h + output: 'Service name : %param1%¥nMessage : Event Log Service Stopped¥nResults: Selective event log manipulation may follow this event.' + "#; + let mut rule_yaml = YamlLoader::load_from_str(rule_str).unwrap().into_iter(); + let test = rule_yaml.next().unwrap(); + let mut rule_node = create_rule(test); + let init_result = rule_node.init(); + assert_eq!(init_result.is_ok(), true); + let target = vec![SIMPLE_RECORD_STR, record_str]; + for record in target { + match serde_json::from_str(record) { + Ok(rec) => { + let _result = rule_node.select(&"testpath".to_string(), &rec); + } + Err(_rec) => { + assert!(false, "failed to parse json record."); + } + } + } + //countupの関数が機能しているかを確認 + assert_eq!( + *&rule_node + .countdata + .get("testpath") + .unwrap() + .get(&"7040_".to_owned()) + .unwrap() + .len() as i32, + 2 + ); + let judge_result = rule_node.judge_satisfy_aggcondition(); + assert_eq!(judge_result.len(), 0); + } + #[test] + /// countでカッコ内の記載、byの記載両方がありtimeframe内に存在する場合にruleでcountの検知ができることを確認する + fn test_count_exist_field_and_by_with_timeframe() { + let record_str: &str = r#" + { + "Event": { + "System": { + "EventID": 7040, + "Channel": "System", + "TimeCreated_attributes": { + "SystemTime": "1977-01-01T00:05:00Z" + } + }, + "EventData": { + "param1": "Windows Event Log", + "param2": "auto start" + } + }, + "Event_attributes": { + "xmlns": "http://schemas.microsoft.com/win/2004/08/events/event" + } + }"#; + let rule_str = r#" + enabled: true + detection: + selection1: + param1: 'Windows Event Log' + condition: selection1 | count(EventID) by Channel >= 2 + timeframe: 30m + output: 'Service name : %param1%¥nMessage : Event Log Service Stopped¥nResults: Selective event log manipulation may follow this event.' + "#; + + let default_time = Utc.ymd(1977, 1, 1).and_hms(0, 0, 0); + let mut expected_count = HashMap::new(); + expected_count.insert("7040_System".to_owned(), 2); + let mut expected_agg_result: Vec = Vec::new(); + expected_agg_result.push(AggResult::new( + "testpath".to_string(), + 2, + "7040_System".to_owned(), + default_time, + ">= 2".to_string(), + )); + check_count( + rule_str, + vec![SIMPLE_RECORD_STR, record_str], + expected_count, + expected_agg_result, + ); + } + /// countで対象の数値確認を行うためのテスト用関数 + fn check_count( + rule_str: &str, + records_str: Vec<&str>, + expected_counts: HashMap, + expect_agg_results: Vec, + ) { + let mut rule_yaml = YamlLoader::load_from_str(rule_str).unwrap().into_iter(); + let test = rule_yaml.next().unwrap(); + let mut rule_node = create_rule(test); + rule_node.init(); + for record_str in records_str { + match serde_json::from_str(record_str) { + Ok(record) => { + let result = &rule_node.select(&"testpath".to_owned(), &record); + assert_eq!(result, &true); + } + Err(_rec) => { + assert!(false, "failed to parse json record."); + } + } + } + let agg_results = &rule_node.judge_satisfy_aggcondition(); + let mut expect_filepath = vec![]; + let mut expect_data = vec![]; + let mut expect_key = vec![]; + let mut expect_start_timedate = vec![]; + let mut expect_condition_op_num = vec![]; + for expect_agg in expect_agg_results { + let expect_count = expected_counts.get(&expect_agg.key).unwrap_or(&-1); + //countupの関数が機能しているかを確認 + assert_eq!( + *&rule_node + .countdata + .get("testpath") + .unwrap() + .get(&expect_agg.key) + .unwrap() + .len() as i32, + *expect_count + ); + expect_filepath.push(expect_agg.filepath); + expect_data.push(expect_agg.data); + expect_key.push(expect_agg.key); + expect_start_timedate.push(expect_agg.start_timedate); + expect_condition_op_num.push(expect_agg.condition_op_num); + } + for agg_result in agg_results { + //ここですでにstart_timedateの格納を確認済み + let index = expect_start_timedate + .binary_search(&agg_result.start_timedate) + .unwrap(); + assert_eq!(agg_result.filepath, expect_filepath[index]); + assert_eq!(agg_result.data, expect_data[index]); + assert_eq!(agg_result.key, expect_key[index]); + assert_eq!(agg_result.condition_op_num, expect_condition_op_num[index]); + } + } +} diff --git a/src/detections/rule/matchers.rs b/src/detections/rule/matchers.rs index 26fe5fa9..fc4909b8 100644 --- a/src/detections/rule/matchers.rs +++ b/src/detections/rule/matchers.rs @@ -532,7 +532,7 @@ mod tests { creation_date: 2020/11/8 updated_date: 2020/11/8 "#; - let rule_node = parse_rule_from_str(rule_str); + let mut rule_node = parse_rule_from_str(rule_str); let selection_node = &rule_node.detection.unwrap().name_to_selection["selection"]; // Root @@ -732,11 +732,11 @@ mod tests { "Event_attributes": {"xmlns": "http://schemas.microsoft.com/win/2004/08/events/event"} }"#; - let rule_node = parse_rule_from_str(rule_str); + let mut 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); + assert_eq!(rule_node.select(&"testpath".to_owned(), &record), false); } Err(_) => { assert!(false, "failed to parse json record."); @@ -761,10 +761,10 @@ mod tests { "Event_attributes": {"xmlns": "http://schemas.microsoft.com/win/2004/08/events/event"} }"#; - let rule_node = parse_rule_from_str(rule_str); + let mut 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); + assert_eq!(rule_node.select(&"testpath".to_owned(), &record), false); } Err(_) => { assert!(false, "failed to parse json record."); @@ -789,10 +789,10 @@ mod tests { "Event_attributes": {"xmlns": "http://schemas.microsoft.com/win/2004/08/events/event"} }"#; - let rule_node = parse_rule_from_str(rule_str); + let mut 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); + assert_eq!(rule_node.select(&"testpath".to_owned(), &record), true); } Err(_) => { assert!(false, "failed to parse json record."); @@ -818,10 +818,10 @@ mod tests { "Event_attributes": {"xmlns": "http://schemas.microsoft.com/win/2004/08/events/event"} }"#; - let rule_node = parse_rule_from_str(rule_str); + let mut 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); + assert_eq!(rule_node.select(&"testpath".to_owned(), &record), false); } Err(_) => { assert!(false, "failed to parse json record."); @@ -847,10 +847,10 @@ mod tests { "Event_attributes": {"xmlns": "http://schemas.microsoft.com/win/2004/08/events/event"} }"#; - let rule_node = parse_rule_from_str(rule_str); + let mut 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); + assert_eq!(rule_node.select(&"testpath".to_owned(), &record), false); } Err(_) => { assert!(false, "failed to parse json record."); @@ -875,10 +875,10 @@ mod tests { "Event_attributes": {"xmlns": "http://schemas.microsoft.com/win/2004/08/events/event"} }"#; - let rule_node = parse_rule_from_str(rule_str); + let mut 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); + assert_eq!(rule_node.select(&"testpath".to_owned(), &record), true); } Err(_) => { assert!(false, "failed to parse json record."); @@ -903,10 +903,10 @@ mod tests { "Event_attributes": {"xmlns": "http://schemas.microsoft.com/win/2004/08/events/event"} }"#; - let rule_node = parse_rule_from_str(rule_str); + let mut 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); + assert_eq!(rule_node.select(&"testpath".to_owned(), &record), false); } Err(_) => { assert!(false, "failed to parse json record."); @@ -932,10 +932,10 @@ mod tests { "Event_attributes": {"xmlns": "http://schemas.microsoft.com/win/2004/08/events/event"} }"#; - let rule_node = parse_rule_from_str(rule_str); + let mut 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); + assert_eq!(rule_node.select(&"testpath".to_owned(), &record), false); } Err(_) => { assert!(false, "failed to parse json record."); @@ -961,10 +961,10 @@ mod tests { "Event_attributes": {"xmlns": "http://schemas.microsoft.com/win/2004/08/events/event"} }"#; - let rule_node = parse_rule_from_str(rule_str); + let mut 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); + assert_eq!(rule_node.select(&"testpath".to_owned(), &record), true); } Err(_) => { assert!(false, "failed to parse json record."); @@ -990,10 +990,10 @@ mod tests { "Event_attributes": {"xmlns": "http://schemas.microsoft.com/win/2004/08/events/event"} }"#; - let rule_node = parse_rule_from_str(rule_str); + let mut 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); + assert_eq!(rule_node.select(&"testpath".to_owned(), &record), true); } Err(_) => { assert!(false, "failed to parse json record."); @@ -1019,10 +1019,10 @@ mod tests { "Event_attributes": {"xmlns": "http://schemas.microsoft.com/win/2004/08/events/event"} }"#; - let rule_node = parse_rule_from_str(rule_str); + let mut 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); + assert_eq!(rule_node.select(&"testpath".to_owned(), &record), true); } Err(_) => { assert!(false, "failed to parse json record."); @@ -1048,10 +1048,10 @@ mod tests { "Event_attributes": {"xmlns": "http://schemas.microsoft.com/win/2004/08/events/event"} }"#; - let rule_node = parse_rule_from_str(rule_str); + let mut 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); + assert_eq!(rule_node.select(&"testpath".to_owned(), &record), false); } Err(_) => { assert!(false, "failed to parse json record."); @@ -1076,10 +1076,10 @@ mod tests { "Event_attributes": {"xmlns": "http://schemas.microsoft.com/win/2004/08/events/event"} }"#; - let rule_node = parse_rule_from_str(rule_str); + let mut 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); + assert_eq!(rule_node.select(&"testpath".to_owned(), &record), true); } Err(_) => { assert!(false, "failed to parse json record."); @@ -1108,10 +1108,10 @@ mod tests { "Event_attributes": {"xmlns": "http://schemas.microsoft.com/win/2004/08/events/event"} }"#; - let rule_node = parse_rule_from_str(rule_str); + let mut 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); + assert_eq!(rule_node.select(&"testpath".to_owned(), &record), false); } Err(_) => { assert!(false, "failed to parse json record."); @@ -1140,10 +1140,10 @@ mod tests { "Event_attributes": {"xmlns": "http://schemas.microsoft.com/win/2004/08/events/event"} }"#; - let rule_node = parse_rule_from_str(rule_str); + let mut 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); + assert_eq!(rule_node.select(&"testpath".to_owned(), &record), false); } Err(_) => { assert!(false, "failed to parse json record."); @@ -1171,10 +1171,10 @@ mod tests { "Event_attributes": {"xmlns": "http://schemas.microsoft.com/win/2004/08/events/event"} }"#; - let rule_node = parse_rule_from_str(rule_str); + let mut 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); + assert_eq!(rule_node.select(&"testpath".to_owned(), &record), false); } Err(_) => { assert!(false, "failed to parse json record."); @@ -1211,10 +1211,10 @@ mod tests { } }"#; - let rule_node = parse_rule_from_str(rule_str); + let mut 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); + assert_eq!(rule_node.select(&"testpath".to_owned(), &record), true); } Err(_rec) => { assert!(false, "failed to parse json record."); @@ -1251,10 +1251,10 @@ mod tests { } }"#; - let rule_node = parse_rule_from_str(rule_str); + let mut 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); + assert_eq!(rule_node.select(&"testpath".to_owned(), &record), false); } Err(_rec) => { assert!(false, "failed to parse json record."); @@ -1291,10 +1291,10 @@ mod tests { } }"#; - let rule_node = parse_rule_from_str(rule_str); + let mut 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); + assert_eq!(rule_node.select(&"testpath".to_owned(), &record), true); } Err(_rec) => { assert!(false, "failed to parse json record."); @@ -1331,10 +1331,10 @@ mod tests { } }"#; - let rule_node = parse_rule_from_str(rule_str); + let mut 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); + assert_eq!(rule_node.select(&"testpath".to_owned(), &record), false); } Err(_rec) => { assert!(false, "failed to parse json record."); @@ -1371,10 +1371,10 @@ mod tests { } }"#; - let rule_node = parse_rule_from_str(rule_str); + let mut 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); + assert_eq!(rule_node.select(&"testpath".to_owned(), &record), true); } Err(_rec) => { assert!(false, "failed to parse json record."); @@ -1411,10 +1411,10 @@ mod tests { } }"#; - let rule_node = parse_rule_from_str(rule_str); + let mut 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); + assert_eq!(rule_node.select(&"testpath".to_owned(), &record), false); } Err(_rec) => { assert!(false, "failed to parse json record."); @@ -1439,10 +1439,10 @@ mod tests { "Event_attributes": {"xmlns": "http://schemas.microsoft.com/win/2004/08/events/event"} }"#; - let rule_node = parse_rule_from_str(rule_str); + let mut 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); + assert_eq!(rule_node.select(&"testpath".to_owned(), &record), true); } Err(_) => { assert!(false, "failed to parse json record."); @@ -1467,10 +1467,10 @@ mod tests { "Event_attributes": {"xmlns": "http://schemas.microsoft.com/win/2004/08/events/event"} }"#; - let rule_node = parse_rule_from_str(rule_str); + let mut 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); + assert_eq!(rule_node.select(&"testpath".to_owned(), &record), false); } Err(_) => { assert!(false, "failed to parse json record."); @@ -1495,10 +1495,10 @@ mod tests { "Event_attributes": {"xmlns": "http://schemas.microsoft.com/win/2004/08/events/event"} }"#; - let rule_node = parse_rule_from_str(rule_str); + let mut 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); + assert_eq!(rule_node.select(&"testpath".to_owned(), &record), true); } Err(_) => { assert!(false, "failed to parse json record."); diff --git a/src/detections/rule/mod.rs b/src/detections/rule/mod.rs index 89e14da2..f6a1ec56 100644 --- a/src/detections/rule/mod.rs +++ b/src/detections/rule/mod.rs @@ -1,4 +1,8 @@ extern crate regex; +use crate::detections::print::Message; + +use chrono::{DateTime, TimeZone, Utc}; + use std::{collections::HashMap, sync::Arc, vec}; use serde_json::Value; @@ -8,7 +12,11 @@ mod matchers; mod selectionnodes; use self::selectionnodes::SelectionNode; mod aggregation_parser; +use self::aggregation_parser::AggregationParseInfo; + mod condition_parser; +mod count; +use self::count::TimeFrameInfo; pub fn create_rule(yaml: Yaml) -> RuleNode { return RuleNode::new(yaml); @@ -18,6 +26,7 @@ pub fn create_rule(yaml: Yaml) -> RuleNode { pub struct RuleNode { pub yaml: Yaml, detection: Option, + countdata: HashMap>>>, } unsafe impl Sync for RuleNode {} @@ -27,6 +36,7 @@ impl RuleNode { return RuleNode { yaml: yaml, detection: Option::None, + countdata: HashMap::new(), }; } @@ -53,12 +63,35 @@ impl RuleNode { } } - pub fn select(&self, event_record: &Value) -> bool { + pub fn select(&mut self, filepath: &String, event_record: &Value) -> bool { if self.detection.is_none() { return false; } - - return self.detection.as_ref().unwrap().select(event_record); + let result = self.detection.as_ref().unwrap().select(event_record); + if result { + count::count(self, filepath, event_record); + } + return result; + } + /// aggregation conditionが存在するかを返す関数 + pub fn has_agg_condition(&self) -> bool { + return self + .detection + .as_ref() + .unwrap() + .aggregation_condition + .is_some(); + } + /// Aggregation Conditionの結果を配列で返却する関数 + pub fn judge_satisfy_aggcondition(&self) -> Vec { + let mut ret = Vec::new(); + if !self.has_agg_condition() { + return ret; + } + for filepath in self.countdata.keys() { + ret.append(&mut count::aggregation_condition_select(&self, &filepath)); + } + return ret; } } @@ -66,7 +99,8 @@ impl RuleNode { struct DetectionNode { pub name_to_selection: HashMap>>, pub condition: Option>, - pub aggregation_condition: Option, + pub aggregation_condition: Option, + pub timeframe: Option, } impl DetectionNode { @@ -75,6 +109,7 @@ impl DetectionNode { name_to_selection: HashMap::new(), condition: Option::None, aggregation_condition: Option::None, + timeframe: Option::None, }; } @@ -82,6 +117,12 @@ impl DetectionNode { // selection nodeの初期化 self.parse_name_to_selection(detection_yaml)?; + //timeframeに指定されている値を取得 + let timeframe = &detection_yaml["timeframe"].as_str(); + if timeframe.is_some() { + self.timeframe = Some(TimeFrameInfo::parse_tframe(timeframe.unwrap().to_string())); + } + // conditionに指定されている式を取得 let condition = &detection_yaml["condition"].as_str(); let condition_str = if let Some(cond_str) = condition { @@ -151,7 +192,7 @@ impl DetectionNode { continue; } // condition等、特殊なキーワードを無視する。 - if name == "condition" { + if name == "condition" || name == "timeframe" { continue; } @@ -229,6 +270,39 @@ impl DetectionNode { } } +#[derive(Debug)] +/// countなどのaggregationの結果を出力する構造体 +pub struct AggResult { + /// evtx file path + pub filepath: String, + /// countなどの値 + pub data: i32, + /// (countの括弧内の記載)_(count byで指定された条件)で設定されたキー + pub key: String, + ///検知したブロックの最初のレコードの時間 + pub start_timedate: DateTime, + ///条件式の情報 + pub condition_op_num: String, +} + +impl AggResult { + pub fn new( + filepath: String, + data: i32, + key: String, + start_timedate: DateTime, + condition_op_num: String, + ) -> AggResult { + return AggResult { + filepath: filepath, + data: data, + key: key, + start_timedate: start_timedate, + condition_op_num: condition_op_num, + }; + } +} + #[cfg(test)] mod tests { use crate::detections::rule::create_rule; @@ -263,10 +337,10 @@ mod tests { "Event_attributes": {"xmlns": "http://schemas.microsoft.com/win/2004/08/events/event"} }"#; - let rule_node = parse_rule_from_str(rule_str); + let mut 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); + assert_eq!(rule_node.select(&"testpath".to_owned(), &record), true); } Err(_) => { assert!(false, "failed to parse json record."); @@ -291,10 +365,10 @@ mod tests { "Event_attributes": {"xmlns": "http://schemas.microsoft.com/win/2004/08/events/event"} }"#; - let rule_node = parse_rule_from_str(rule_str); + let mut 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); + assert_eq!(rule_node.select(&"testpath".to_owned(), &record), false); } Err(_) => { assert!(false, "failed to parse json record."); @@ -319,10 +393,10 @@ mod tests { "Event_attributes": {"xmlns": "http://schemas.microsoft.com/win/2004/08/events/event"} }"#; - let rule_node = parse_rule_from_str(rule_str); + let mut 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); + assert_eq!(rule_node.select(&"testpath".to_owned(), &record), false); } Err(_) => { assert!(false, "failed to parse json record."); @@ -400,10 +474,10 @@ mod tests { } }"#; - let rule_node = parse_rule_from_str(rule_str); + let mut 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); + assert_eq!(rule_node.select(&"testpath".to_owned(), &record), true); } Err(_) => { assert!(false, "failed to parse json record."); @@ -457,10 +531,10 @@ mod tests { } }"#; - let rule_node = parse_rule_from_str(rule_str); + let mut 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); + assert_eq!(rule_node.select(&"testpath".to_owned(), &record), false); } Err(_) => { assert!(false, "failed to parse json record."); @@ -521,10 +595,10 @@ mod tests { } "#; - let rule_node = parse_rule_from_str(rule_str); + let mut 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); + assert_eq!(rule_node.select(&"testpath".to_owned(), &record), true); } Err(_) => { assert!(false, "failed to parse json record."); @@ -563,10 +637,10 @@ mod tests { } "#; - let rule_node = parse_rule_from_str(rule_str); + let mut 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); + assert_eq!(rule_node.select(&"testpath".to_owned(), &record), true); } Err(_) => { assert!(false, "failed to parse json record."); @@ -606,10 +680,10 @@ mod tests { } "#; - let rule_node = parse_rule_from_str(rule_str); + let mut 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); + assert_eq!(rule_node.select(&"testpath".to_owned(), &record), false); } Err(_) => { assert!(false, "failed to parse json record."); @@ -668,10 +742,10 @@ mod tests { } "#; - let rule_node = parse_rule_from_str(rule_str); + let mut 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); + assert_eq!(rule_node.select(&"testpath".to_owned(), &record), true); } Err(_) => { assert!(false, "failed to parse json record."); @@ -730,10 +804,10 @@ mod tests { } "#; - let rule_node = parse_rule_from_str(rule_str); + let mut 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); + assert_eq!(rule_node.select(&"testpath".to_owned(), &record), false); } Err(_) => { assert!(false, "failed to parse json record."); @@ -774,10 +848,10 @@ mod tests { } }"#; - let rule_node = parse_rule_from_str(rule_str); + let mut 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); + assert_eq!(rule_node.select(&"testpath".to_owned(), &record), true); } Err(_rec) => { assert!(false, "failed to parse json record."); @@ -824,4 +898,35 @@ mod tests { Err(vec!["not found detection node".to_string()]) ); } + + /// countで対象の数値確認を行うためのテスト用関数 + fn check_count(rule_str: &str, record_str: &str, key: &str, expect_count: i32) { + let mut rule_yaml = YamlLoader::load_from_str(rule_str).unwrap().into_iter(); + let test = rule_yaml.next().unwrap(); + let mut rule_node = create_rule(test); + let _init = rule_node.init(); + match serde_json::from_str(record_str) { + Ok(record) => { + let result = rule_node.select(&"testpath".to_string(), &record); + assert_eq!( + rule_node.detection.unwrap().aggregation_condition.is_some(), + true + ); + assert_eq!(result, true); + assert_eq!( + *&rule_node + .countdata + .get("testpath") + .unwrap() + .get(key) + .unwrap() + .len() as i32, + expect_count + ); + } + Err(_rec) => { + assert!(false, "failed to parse json record."); + } + } + } } diff --git a/src/detections/rule/selectionnodes.rs b/src/detections/rule/selectionnodes.rs index 352742b6..df1e320f 100644 --- a/src/detections/rule/selectionnodes.rs +++ b/src/detections/rule/selectionnodes.rs @@ -421,10 +421,10 @@ mod tests { "Event_attributes": {"xmlns": "http://schemas.microsoft.com/win/2004/08/events/event"} }"#; - let rule_node = parse_rule_from_str(rule_str); + let mut 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); + assert_eq!(rule_node.select(&"testpath".to_owned(), &record), true); } Err(_) => { assert!(false, "failed to parse json record."); @@ -452,10 +452,10 @@ mod tests { "Event_attributes": {"xmlns": "http://schemas.microsoft.com/win/2004/08/events/event"} }"#; - let rule_node = parse_rule_from_str(rule_str); + let mut 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); + assert_eq!(rule_node.select(&"testpath".to_owned(), &record), false); } Err(_) => { assert!(false, "failed to parse json record."); @@ -482,10 +482,10 @@ mod tests { "Event_attributes": {"xmlns": "http://schemas.microsoft.com/win/2004/08/events/event"} }"#; - let rule_node = parse_rule_from_str(rule_str); + let mut 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); + assert_eq!(rule_node.select(&"testpath".to_owned(), &record), true); } Err(_) => { assert!(false, "failed to parse json record."); @@ -512,10 +512,10 @@ mod tests { "Event_attributes": {"xmlns": "http://schemas.microsoft.com/win/2004/08/events/event"} }"#; - let rule_node = parse_rule_from_str(rule_str); + let mut 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); + assert_eq!(rule_node.select(&"testpath".to_owned(), &record), true); } Err(_) => { assert!(false, "failed to parse json record."); @@ -542,10 +542,10 @@ mod tests { "Event_attributes": {"xmlns": "http://schemas.microsoft.com/win/2004/08/events/event"} }"#; - let rule_node = parse_rule_from_str(rule_str); + let mut 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); + assert_eq!(rule_node.select(&"testpath".to_owned(), &record), false); } Err(_) => { assert!(false, "failed to parse json record.");