1641 lines
59 KiB
Rust
1641 lines
59 KiB
Rust
use crate::detections::print::AlertMessage;
|
|
use crate::detections::rule::AggResult;
|
|
use crate::detections::rule::Message;
|
|
use crate::detections::rule::RuleNode;
|
|
use chrono::{DateTime, TimeZone, Utc};
|
|
use hashbrown::HashMap;
|
|
use serde_json::Value;
|
|
use std::num::ParseIntError;
|
|
use std::path::Path;
|
|
|
|
use crate::detections::rule::aggregation_parser::AggregationConditionToken;
|
|
|
|
use crate::detections::utils;
|
|
|
|
/// 検知された際にカウント情報を投入する関数
|
|
pub fn count(rule: &mut RuleNode, record: &Value) {
|
|
let key = create_count_key(&rule, record);
|
|
let field_name: String = match rule.get_agg_condition() {
|
|
None => String::default(),
|
|
Some(aggcondition) => aggcondition
|
|
._field_name
|
|
.as_ref()
|
|
.unwrap_or(&String::default())
|
|
.to_owned(),
|
|
};
|
|
let field_value =
|
|
get_alias_value_in_record(rule, &field_name, record, false).unwrap_or(String::default());
|
|
let default_time = Utc.ymd(1977, 1, 1).and_hms(0, 0, 0);
|
|
countup(
|
|
rule,
|
|
key,
|
|
field_value,
|
|
Message::get_event_time(record).unwrap_or(default_time),
|
|
);
|
|
}
|
|
|
|
///count byの条件に合致する検知済みレコードの数を増やすための関数
|
|
pub fn countup(
|
|
rule: &mut RuleNode,
|
|
key: String,
|
|
field_value: String,
|
|
record_time_value: DateTime<Utc>,
|
|
) {
|
|
let value_map = rule.countdata.entry(key).or_insert(Vec::new());
|
|
value_map.push(AggRecordTimeInfo {
|
|
field_record_value: field_value,
|
|
record_time: record_time_value,
|
|
});
|
|
}
|
|
|
|
/// 与えられたエイリアスから対象レコード内の値を取得してダブルクオーテーションを外す関数。
|
|
/// ダブルクオーテーションを外す理由は結果表示の際に余計なダブルクオーテーションが入るのを防ぐため
|
|
/// is_by_aliasはこの関数を呼び出す際はcountのbyの値もしくはfieldの値のどちらかであるためboolとした
|
|
fn get_alias_value_in_record(
|
|
rule: &RuleNode,
|
|
alias: &String,
|
|
record: &Value,
|
|
is_by_alias: bool,
|
|
) -> Option<String> {
|
|
if alias == "" {
|
|
return None;
|
|
}
|
|
match utils::get_event_value(alias, record) {
|
|
Some(value) => {
|
|
return Some(value.to_string().replace("\"", ""));
|
|
}
|
|
None => {
|
|
AlertMessage::alert(
|
|
&mut std::io::stderr().lock(),
|
|
match is_by_alias {
|
|
true => format!(
|
|
"count by clause alias value not found in count process. rule file:{} EventID:{}",
|
|
Path::new(&rule.rulepath)
|
|
.file_name()
|
|
.unwrap()
|
|
.to_str()
|
|
.unwrap(),
|
|
utils::get_event_value(&utils::get_event_id_key(), record).unwrap()
|
|
),
|
|
false => format!(
|
|
"count field clause alias value not found in count process. rule file:{} EventID:{}",
|
|
Path::new(&rule.rulepath)
|
|
.file_name()
|
|
.unwrap()
|
|
.to_str()
|
|
.unwrap(),
|
|
utils::get_event_value(&utils::get_event_id_key(), record).unwrap()
|
|
),
|
|
},
|
|
)
|
|
.ok();
|
|
return None;
|
|
}
|
|
};
|
|
}
|
|
|
|
/// countでgroupbyなどの情報を区分するためのハッシュマップのキーを作成する関数。
|
|
/// 以下の場合は空文字を返却
|
|
/// groupbyの指定がない、groubpbyで指定したエイリアスがレコードに存在しない場合は_のみとする。空文字ではキーを指定してデータを取得することができなかった
|
|
pub fn create_count_key(rule: &RuleNode, record: &Value) -> String {
|
|
let agg_condition = rule.get_agg_condition().unwrap();
|
|
if agg_condition._by_field_name.is_some() {
|
|
let by_field_key = agg_condition._by_field_name.as_ref().unwrap();
|
|
return get_alias_value_in_record(rule, by_field_key, record, true)
|
|
.unwrap_or("_".to_string());
|
|
} else {
|
|
return "_".to_string();
|
|
}
|
|
}
|
|
|
|
///現状のレコードの状態から条件式に一致しているかを判定する関数
|
|
pub fn aggregation_condition_select(rule: &RuleNode) -> Vec<AggResult> {
|
|
// recordでaliasが登録されている前提とする
|
|
let value_map = &rule.countdata;
|
|
let mut ret = Vec::new();
|
|
for (key, value) in value_map {
|
|
ret.append(&mut judge_timeframe(&rule, &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.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(Clone, Debug)]
|
|
/// countの括弧内の情報とレコードの情報を所持する構造体
|
|
pub struct AggRecordTimeInfo {
|
|
pub field_record_value: String,
|
|
pub record_time: DateTime<Utc>,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
/// timeframeに設定された情報。SIGMAルール上timeframeで複数の単位(日、時、分、秒)が複合で記載されているルールがなかったためタイプと数値のみを格納する構造体
|
|
pub struct TimeFrameInfo {
|
|
pub timetype: String,
|
|
pub timenum: Result<i64, ParseIntError>,
|
|
}
|
|
|
|
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 {
|
|
AlertMessage::alert(
|
|
&mut std::io::stderr().lock(),
|
|
format!("Timeframe is invalid. Input value:{}", value),
|
|
)
|
|
.ok();
|
|
}
|
|
return TimeFrameInfo {
|
|
timetype: ttype,
|
|
timenum: tnum.parse::<i64>(),
|
|
};
|
|
}
|
|
}
|
|
|
|
/// TimeFrameInfoで格納されたtimeframeの値を秒数に変換した結果を返す関数
|
|
pub fn get_sec_timeframe(rule: &RuleNode) -> Option<i64> {
|
|
let timeframe = rule.detection.timeframe.as_ref();
|
|
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) => {
|
|
AlertMessage::alert(
|
|
&mut std::io::stderr().lock(),
|
|
format!("Timeframe number is invalid. timeframe.{}", err),
|
|
)
|
|
.ok();
|
|
return Option::None;
|
|
}
|
|
}
|
|
}
|
|
/// conditionのパイプ以降の処理をAggregationParseInfoを参照し、conditionの条件を満たすか判定するための関数
|
|
pub fn select_aggcon(cnt: i64, rule: &RuleNode) -> bool {
|
|
let agg_condition = rule.detection.aggregation_condition.as_ref();
|
|
if agg_condition.is_none() {
|
|
return false;
|
|
}
|
|
|
|
let agg_condition = agg_condition.unwrap();
|
|
match agg_condition._cmp_op {
|
|
AggregationConditionToken::EQ => {
|
|
if cnt == agg_condition._cmp_num {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
AggregationConditionToken::GE => {
|
|
if cnt >= agg_condition._cmp_num {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
AggregationConditionToken::GT => {
|
|
if cnt > agg_condition._cmp_num {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
AggregationConditionToken::LE => {
|
|
if cnt <= agg_condition._cmp_num {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
AggregationConditionToken::LT => {
|
|
if cnt < agg_condition._cmp_num {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
_ => {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// condtionの分岐によって同じ型を返すif-letのジェネリクス
|
|
fn _if_condition_fn_caller<T: FnMut() -> S, S, U: FnMut() -> S>(
|
|
condition: bool,
|
|
mut process_true: T,
|
|
mut process_false: U,
|
|
) -> S {
|
|
if condition {
|
|
process_true()
|
|
} else {
|
|
process_false()
|
|
}
|
|
}
|
|
|
|
/**
|
|
* count()の数え方の違いを吸収するtrait
|
|
*/
|
|
trait CountStrategy {
|
|
/**
|
|
* datas[idx]のデータをtimeframeに追加します
|
|
*/
|
|
fn add_data(&mut self, idx: i64, datas: &Vec<AggRecordTimeInfo>, rule: &RuleNode);
|
|
/**
|
|
* datas[idx]のデータをtimeframeから削除します。
|
|
*/
|
|
fn remove_data(&mut self, idx: i64, datas: &Vec<AggRecordTimeInfo>, rule: &RuleNode);
|
|
/**
|
|
* count()の値を返します。
|
|
*/
|
|
fn count(&mut self) -> i64;
|
|
/**
|
|
* AggResultを作成します。
|
|
*/
|
|
fn create_agg_result(
|
|
&mut self,
|
|
left: i64,
|
|
datas: &Vec<AggRecordTimeInfo>,
|
|
cnt: i64,
|
|
key: &String,
|
|
rule: &RuleNode,
|
|
) -> AggResult;
|
|
}
|
|
|
|
/**
|
|
* countにfieldが指定されている場合のjudgeの計算方法を表す構造体
|
|
*/
|
|
struct FieldStrategy {
|
|
value_2_cnt: HashMap<String, i64>,
|
|
}
|
|
|
|
impl CountStrategy for FieldStrategy {
|
|
fn add_data(&mut self, idx: i64, datas: &Vec<AggRecordTimeInfo>, _rule: &RuleNode) {
|
|
if idx >= datas.len() as i64 || idx < 0 {
|
|
return;
|
|
}
|
|
|
|
let value = &datas[idx as usize].field_record_value;
|
|
let key_val = self.value_2_cnt.get_key_value_mut(value);
|
|
if key_val.is_none() {
|
|
self.value_2_cnt.insert(value.to_string(), 1);
|
|
} else {
|
|
let (_, val) = key_val.unwrap();
|
|
*val += 1;
|
|
}
|
|
}
|
|
|
|
fn remove_data(&mut self, idx: i64, datas: &Vec<AggRecordTimeInfo>, _rule: &RuleNode) {
|
|
if idx >= datas.len() as i64 || idx < 0 {
|
|
return;
|
|
}
|
|
|
|
let record_value = &datas[idx as usize].field_record_value;
|
|
let key_val = self.value_2_cnt.get_key_value_mut(record_value);
|
|
if key_val.is_none() {
|
|
return;
|
|
}
|
|
|
|
let val: &mut i64 = key_val.unwrap().1;
|
|
if val <= &mut 1 {
|
|
// 0になる場合はキー自体削除する
|
|
self.value_2_cnt.remove(record_value);
|
|
} else {
|
|
*val += -1; // 個数を減らす
|
|
}
|
|
}
|
|
|
|
fn count(&mut self) -> i64 {
|
|
return self.value_2_cnt.keys().len() as i64;
|
|
}
|
|
|
|
fn create_agg_result(
|
|
&mut self,
|
|
left: i64,
|
|
datas: &Vec<AggRecordTimeInfo>,
|
|
_cnt: i64,
|
|
key: &String,
|
|
rule: &RuleNode,
|
|
) -> AggResult {
|
|
let values: Vec<String> = self.value_2_cnt.drain().map(|(key, _)| key).collect(); // drainで初期化
|
|
return AggResult::new(
|
|
values.len() as i64,
|
|
key.to_string(),
|
|
values,
|
|
datas[left as usize].record_time,
|
|
get_str_agg_eq(rule),
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* countにfieldが指定されていない場合のjudgeの計算方法を表す構造体
|
|
*/
|
|
struct NoFieldStrategy {
|
|
cnt: i64,
|
|
}
|
|
|
|
impl CountStrategy for NoFieldStrategy {
|
|
fn add_data(&mut self, idx: i64, datas: &Vec<AggRecordTimeInfo>, _rule: &RuleNode) {
|
|
if idx >= datas.len() as i64 || idx < 0 {
|
|
return;
|
|
}
|
|
|
|
self.cnt += 1;
|
|
}
|
|
|
|
fn remove_data(&mut self, idx: i64, datas: &Vec<AggRecordTimeInfo>, _rule: &RuleNode) {
|
|
if idx >= datas.len() as i64 || idx < 0 {
|
|
return;
|
|
}
|
|
|
|
self.cnt += -1;
|
|
}
|
|
|
|
fn count(&mut self) -> i64 {
|
|
return self.cnt;
|
|
}
|
|
|
|
fn create_agg_result(
|
|
&mut self,
|
|
left: i64,
|
|
datas: &Vec<AggRecordTimeInfo>,
|
|
cnt: i64,
|
|
key: &String,
|
|
rule: &RuleNode,
|
|
) -> AggResult {
|
|
let ret = AggResult::new(
|
|
cnt as i64,
|
|
key.to_string(),
|
|
vec![],
|
|
datas[left as usize].record_time,
|
|
get_str_agg_eq(rule),
|
|
);
|
|
self.cnt = 0; //cntを初期化
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
fn _create_counter(rule: &RuleNode) -> Box<dyn CountStrategy> {
|
|
let agg_cond = rule.get_agg_condition().unwrap();
|
|
if agg_cond._field_name.is_some() {
|
|
return Box::new(FieldStrategy {
|
|
value_2_cnt: HashMap::new(),
|
|
});
|
|
} else {
|
|
return Box::new(NoFieldStrategy { cnt: 0 });
|
|
}
|
|
}
|
|
|
|
fn _get_timestamp(idx: i64, datas: &Vec<AggRecordTimeInfo>) -> i64 {
|
|
return datas[idx as usize].record_time.timestamp();
|
|
}
|
|
|
|
fn _get_timestamp_subsec_nano(idx: i64, datas: &Vec<AggRecordTimeInfo>) -> u32 {
|
|
return datas[idx as usize].record_time.timestamp_subsec_nanos();
|
|
}
|
|
|
|
// data[left]からdata[right-1]までのデータがtimeframeに収まっているか判定する
|
|
fn _is_in_timeframe(left: i64, right: i64, frame: i64, datas: &Vec<AggRecordTimeInfo>) -> bool {
|
|
let left_time = _get_timestamp(left, datas);
|
|
let left_time_nano = _get_timestamp_subsec_nano(left, datas);
|
|
// evtxのSystemTimeは小数点7桁秒まで記録されているので、それを考慮する
|
|
let mut right_time = _get_timestamp(right, datas);
|
|
let right_time_nano = _get_timestamp_subsec_nano(right, datas);
|
|
if right_time_nano > left_time_nano {
|
|
right_time += 1;
|
|
}
|
|
return right_time - left_time <= frame;
|
|
}
|
|
|
|
/// count済みデータ内でタイムフレーム内に存在するselectの条件を満たすレコードが、timeframe単位でcountの条件を満たしているAggResultを配列として返却する関数
|
|
pub fn judge_timeframe(
|
|
rule: &RuleNode,
|
|
time_datas: &Vec<AggRecordTimeInfo>,
|
|
key: &String,
|
|
) -> Vec<AggResult> {
|
|
let mut ret: Vec<AggResult> = Vec::new();
|
|
if time_datas.is_empty() {
|
|
return ret;
|
|
}
|
|
|
|
// AggRecordTimeInfoを時間順がソートされている前提で処理を進める
|
|
let mut datas = time_datas.clone();
|
|
datas.sort_by(|a, b| a.record_time.cmp(&b.record_time));
|
|
|
|
// timeframeの設定がルールにない時は最初と最後の要素の時間差をtimeframeに設定する。
|
|
let def_frame = &datas.last().unwrap().record_time.timestamp()
|
|
- &datas.first().unwrap().record_time.timestamp();
|
|
let frame = get_sec_timeframe(rule).unwrap_or(def_frame);
|
|
|
|
// left <= i < rightの範囲にあるdata[i]がtimeframe内にあるデータであると考える
|
|
let mut left: i64 = 0;
|
|
let mut right: i64 = 0;
|
|
let mut counter = _create_counter(rule);
|
|
let data_len = datas.len() as i64;
|
|
// rightは開区間なので+1
|
|
while left < data_len && right < data_len + 1 {
|
|
// timeframeの範囲にある限りrightをincrement
|
|
while right < data_len && _is_in_timeframe(left, right, frame, &datas) {
|
|
counter.add_data(right, &datas, rule);
|
|
right = right + 1;
|
|
}
|
|
|
|
let cnt = counter.count();
|
|
if select_aggcon(cnt as i64, rule) {
|
|
// 条件を満たすtimeframeが見つかった
|
|
ret.push(counter.create_agg_result(left, &datas, cnt, key, rule));
|
|
left = right;
|
|
} else {
|
|
// 条件を満たさなかったので、rightとleftを+1ずらす
|
|
counter.add_data(right, &datas, rule);
|
|
right += 1;
|
|
counter.remove_data(left, &datas, rule);
|
|
left += 1;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use crate::detections;
|
|
use crate::detections::rule::create_rule;
|
|
use crate::detections::rule::AggResult;
|
|
use crate::detections::utils;
|
|
use hashbrown::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 mut expected_count = HashMap::new();
|
|
expected_count.insert("_".to_owned(), 2);
|
|
let expected_agg_result: Vec<AggResult> = vec![AggResult::new(
|
|
2,
|
|
"_".to_string(),
|
|
vec![],
|
|
Utc.ymd(1977, 1, 1).and_hms(0, 0, 0),
|
|
">= 1".to_string(),
|
|
)];
|
|
check_count(
|
|
rule_str,
|
|
&vec![SIMPLE_RECORD_STR.to_string(), record_str.to_string()],
|
|
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 mut expected_count = HashMap::new();
|
|
expected_count.insert("_".to_owned(), 2);
|
|
let mut expected_agg_result: Vec<AggResult> = Vec::new();
|
|
expected_agg_result.push(AggResult::new(
|
|
1,
|
|
"_".to_string(),
|
|
vec![],
|
|
Utc.ymd(1977, 1, 1).and_hms(0, 0, 0),
|
|
">= 1".to_string(),
|
|
));
|
|
expected_agg_result.push(AggResult::new(
|
|
1,
|
|
"_".to_string(),
|
|
vec![],
|
|
Utc.ymd(1996, 2, 27).and_hms(1, 5, 1),
|
|
">= 1".to_string(),
|
|
));
|
|
check_count(
|
|
rule_str,
|
|
&vec![SIMPLE_RECORD_STR.to_string(), record_str.to_string()],
|
|
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 mut expected_count = HashMap::new();
|
|
expected_count.insert("_".to_owned(), 1);
|
|
let expected_agg_result = AggResult::new(
|
|
1,
|
|
"_".to_string(),
|
|
vec!["System".to_owned()],
|
|
Utc.ymd(1977, 1, 1).and_hms(0, 0, 0),
|
|
">= 1".to_string(),
|
|
);
|
|
check_count(
|
|
rule_str,
|
|
&vec![SIMPLE_RECORD_STR.to_string()],
|
|
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 mut expected_count = HashMap::new();
|
|
expected_count.insert("System".to_owned(), 1);
|
|
expected_count.insert("Test".to_owned(), 1);
|
|
let mut expected_agg_result: Vec<AggResult> = Vec::new();
|
|
expected_agg_result.push(AggResult::new(
|
|
1,
|
|
"System".to_owned(),
|
|
vec!["7040".to_owned()],
|
|
Utc.ymd(1977, 1, 1).and_hms(0, 0, 0),
|
|
">= 1".to_string(),
|
|
));
|
|
expected_agg_result.push(AggResult::new(
|
|
1,
|
|
"Test".to_owned(),
|
|
vec!["9999".to_owned()],
|
|
Utc.ymd(1996, 2, 27).and_hms(1, 5, 1),
|
|
">= 1".to_string(),
|
|
));
|
|
check_count(
|
|
rule_str,
|
|
&vec![SIMPLE_RECORD_STR.to_string(), record_str.to_string()],
|
|
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 mut expected_count = HashMap::new();
|
|
expected_count.insert("Windows Event Log".to_owned(), 1);
|
|
expected_count.insert("Test".to_owned(), 1);
|
|
let mut expected_agg_result: Vec<AggResult> = Vec::new();
|
|
expected_agg_result.push(AggResult::new(
|
|
1,
|
|
"Windows Event Log".to_owned(),
|
|
vec!["7040".to_owned()],
|
|
Utc.ymd(1977, 1, 1).and_hms(0, 0, 0),
|
|
">= 1".to_string(),
|
|
));
|
|
expected_agg_result.push(AggResult::new(
|
|
1,
|
|
"Test".to_owned(),
|
|
vec!["9999".to_owned()],
|
|
Utc.ymd(1977, 1, 1).and_hms(0, 5, 0),
|
|
">= 1".to_string(),
|
|
));
|
|
check_count(
|
|
rule_str,
|
|
&vec![SIMPLE_RECORD_STR.to_string(), record_str.to_string()],
|
|
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("testpath".to_string(), test);
|
|
let init_result = rule_node.init();
|
|
assert!(init_result.is_ok());
|
|
let target = vec![SIMPLE_RECORD_STR, record_str];
|
|
for record in target {
|
|
match serde_json::from_str(record) {
|
|
Ok(rec) => {
|
|
let keys = detections::rule::get_detection_keys(&rule_node);
|
|
let recinfo = utils::create_rec_info(rec, "testpath".to_owned(), &keys);
|
|
let _result = rule_node.select(&recinfo);
|
|
}
|
|
Err(_rec) => {
|
|
assert!(false, "failed to parse json record.");
|
|
}
|
|
}
|
|
}
|
|
//countupの関数が機能しているかを確認
|
|
assert_eq!(
|
|
*&rule_node.countdata.get(&"_".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": 9999,
|
|
"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 mut expected_count = HashMap::new();
|
|
expected_count.insert("System".to_owned(), 2);
|
|
let mut expected_agg_result: Vec<AggResult> = Vec::new();
|
|
expected_agg_result.push(AggResult::new(
|
|
2,
|
|
"System".to_owned(),
|
|
vec!["7040".to_owned(), "9999".to_owned()],
|
|
Utc.ymd(1977, 1, 1).and_hms(0, 0, 0),
|
|
">= 2".to_string(),
|
|
));
|
|
check_count(
|
|
rule_str,
|
|
&vec![SIMPLE_RECORD_STR.to_string(), record_str.to_string()],
|
|
expected_count,
|
|
expected_agg_result,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
/// countで括弧内の記載、byの記載両方がありtimeframe内に存在する場合にruleでcountの検知ができることを確認する(countの括弧内の項目が異なる場合)
|
|
fn test_count_exist_field_and_by_with_timeframe_other_field_value() {
|
|
let record_str: &str = r#"
|
|
{
|
|
"Event": {
|
|
"System": {
|
|
"EventID": 9999,
|
|
"Channel": "System",
|
|
"TimeCreated_attributes": {
|
|
"SystemTime": "1977-01-01T00:30: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 >= 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 mut expected_count = HashMap::new();
|
|
expected_count.insert("System".to_owned(), 2);
|
|
let mut expected_agg_result: Vec<AggResult> = Vec::new();
|
|
expected_agg_result.push(AggResult::new(
|
|
2,
|
|
"System".to_owned(),
|
|
vec!["7040".to_owned(), "9999".to_owned()],
|
|
default_time,
|
|
">= 1".to_string(),
|
|
));
|
|
check_count(
|
|
rule_str,
|
|
&vec![SIMPLE_RECORD_STR.to_string(), record_str.to_string()],
|
|
expected_count,
|
|
expected_agg_result,
|
|
);
|
|
}
|
|
|
|
// timeframeのsecondsが動くことを確認
|
|
#[test]
|
|
fn test_count_timeframe_seconds() {
|
|
let recs = vec![
|
|
test_create_recstr_std("1", "1977-01-09T00:30:00Z"),
|
|
test_create_recstr_std("2", "1977-01-09T00:30:10Z"),
|
|
test_create_recstr_std("3", "1977-01-09T00:30:20Z"),
|
|
];
|
|
|
|
// timeframe=20sはギリギリHit
|
|
{
|
|
let rule_str = create_std_rule("count(EventID) >= 3", "20s");
|
|
let default_time = Utc.ymd(1977, 1, 9).and_hms(0, 30, 0);
|
|
let mut expected_count = HashMap::new();
|
|
expected_count.insert("_".to_owned(), 3);
|
|
let mut expected_agg_result: Vec<AggResult> = Vec::new();
|
|
expected_agg_result.push(AggResult::new(
|
|
3,
|
|
"_".to_owned(),
|
|
vec!["1".to_owned(), "2".to_owned(), "3".to_owned()],
|
|
default_time,
|
|
">= 3".to_string(),
|
|
));
|
|
check_count(&rule_str, &recs, expected_count, expected_agg_result);
|
|
}
|
|
|
|
// timeframe=19sはギリギリHitしない
|
|
{
|
|
let rule_str = create_std_rule("count(EventID) >= 3", "19s");
|
|
let mut expected_count = HashMap::new();
|
|
expected_count.insert("_".to_owned(), 3);
|
|
check_count(&rule_str, &recs, expected_count, Vec::new());
|
|
}
|
|
}
|
|
|
|
// timeframeのminitutesが動くことを確認
|
|
#[test]
|
|
fn test_count_timeframe_minitues() {
|
|
let recs = vec![
|
|
test_create_recstr_std("1", "1977-01-09T00:30:00Z"),
|
|
test_create_recstr_std("2", "1977-01-09T00:40:00Z"),
|
|
test_create_recstr_std("3", "1977-01-09T00:50:00Z"),
|
|
];
|
|
|
|
// timeframe=20mはギリギリHit
|
|
{
|
|
let rule_str = create_std_rule("count(EventID) >= 3", "20m");
|
|
let default_time = Utc.ymd(1977, 1, 9).and_hms(0, 30, 0);
|
|
let mut expected_count = HashMap::new();
|
|
expected_count.insert("_".to_owned(), 3);
|
|
let mut expected_agg_result: Vec<AggResult> = Vec::new();
|
|
expected_agg_result.push(AggResult::new(
|
|
3,
|
|
"_".to_owned(),
|
|
vec!["1".to_owned(), "2".to_owned(), "3".to_owned()],
|
|
default_time,
|
|
">= 3".to_string(),
|
|
));
|
|
check_count(&rule_str, &recs, expected_count, expected_agg_result);
|
|
}
|
|
|
|
// timeframe=19mはギリギリHitしない
|
|
{
|
|
let rule_str = create_std_rule("count(EventID) >= 3", "19m");
|
|
let mut expected_count = HashMap::new();
|
|
expected_count.insert("_".to_owned(), 3);
|
|
check_count(&rule_str, &recs, expected_count, Vec::new());
|
|
}
|
|
}
|
|
|
|
// timeframeのhourが動くことを確認
|
|
#[test]
|
|
fn test_count_timeframe_hour() {
|
|
let recs = vec![
|
|
test_create_recstr_std("1", "1977-01-09T00:30:00Z"),
|
|
test_create_recstr_std("2", "1977-01-09T01:30:00Z"),
|
|
test_create_recstr_std("3", "1977-01-09T02:30:00Z"),
|
|
];
|
|
|
|
// timeframe=3hはHit
|
|
{
|
|
let rule_str = create_std_rule("count(EventID) >= 3", "3h");
|
|
let default_time = Utc.ymd(1977, 1, 9).and_hms(0, 30, 0);
|
|
let mut expected_count = HashMap::new();
|
|
expected_count.insert("_".to_owned(), 3);
|
|
let mut expected_agg_result: Vec<AggResult> = Vec::new();
|
|
expected_agg_result.push(AggResult::new(
|
|
3,
|
|
"_".to_owned(),
|
|
vec!["1".to_owned(), "2".to_owned(), "3".to_owned()],
|
|
default_time,
|
|
">= 3".to_string(),
|
|
));
|
|
check_count(&rule_str, &recs, expected_count, expected_agg_result);
|
|
}
|
|
|
|
// timeframe=2hはギリギリHit
|
|
{
|
|
let rule_str = create_std_rule("count(EventID) >= 3", "2h");
|
|
let default_time = Utc.ymd(1977, 1, 9).and_hms(0, 30, 0);
|
|
let mut expected_count = HashMap::new();
|
|
expected_count.insert("_".to_owned(), 3);
|
|
let mut expected_agg_result: Vec<AggResult> = Vec::new();
|
|
expected_agg_result.push(AggResult::new(
|
|
3,
|
|
"_".to_owned(),
|
|
vec!["1".to_owned(), "2".to_owned(), "3".to_owned()],
|
|
default_time,
|
|
">= 3".to_string(),
|
|
));
|
|
check_count(&rule_str, &recs, expected_count, expected_agg_result);
|
|
}
|
|
|
|
// timeframe=1hはギリギリHitしない
|
|
{
|
|
let rule_str = create_std_rule("count(EventID) >= 3", "1h");
|
|
let mut expected_count = HashMap::new();
|
|
expected_count.insert("_".to_owned(), 3);
|
|
check_count(&rule_str, &recs, expected_count, Vec::new());
|
|
}
|
|
|
|
// timeframe=120minはギリギリHit
|
|
{
|
|
let rule_str = create_std_rule("count(EventID) >= 3", "120m");
|
|
let default_time = Utc.ymd(1977, 1, 9).and_hms(0, 30, 0);
|
|
let mut expected_count = HashMap::new();
|
|
expected_count.insert("_".to_owned(), 3);
|
|
let mut expected_agg_result: Vec<AggResult> = Vec::new();
|
|
expected_agg_result.push(AggResult::new(
|
|
3,
|
|
"_".to_owned(),
|
|
vec!["1".to_owned(), "2".to_owned(), "3".to_owned()],
|
|
default_time,
|
|
">= 3".to_string(),
|
|
));
|
|
check_count(&rule_str, &recs, expected_count, expected_agg_result);
|
|
}
|
|
|
|
// timeframe=119minはギリギリHitしない
|
|
{
|
|
let rule_str = create_std_rule("count(EventID) >= 3", "119m");
|
|
let mut expected_count = HashMap::new();
|
|
expected_count.insert("_".to_owned(), 3);
|
|
check_count(&rule_str, &recs, expected_count, Vec::new());
|
|
}
|
|
}
|
|
|
|
// timeframeのdayが動くことを確認
|
|
#[test]
|
|
fn test_count_timeframe_day() {
|
|
let recs = vec![
|
|
test_create_recstr_std("1", "1977-01-09T00:30:00Z"),
|
|
test_create_recstr_std("2", "1977-01-13T00:30:00Z"),
|
|
test_create_recstr_std("3", "1977-01-20T00:30:00Z"),
|
|
];
|
|
|
|
// timeframe=11dはギリギリHit
|
|
{
|
|
let rule_str = create_std_rule("count(EventID) >= 3", "11d");
|
|
let default_time = Utc.ymd(1977, 1, 9).and_hms(0, 30, 0);
|
|
let mut expected_count = HashMap::new();
|
|
expected_count.insert("_".to_owned(), 3);
|
|
let mut expected_agg_result: Vec<AggResult> = Vec::new();
|
|
expected_agg_result.push(AggResult::new(
|
|
3,
|
|
"_".to_owned(),
|
|
vec!["1".to_owned(), "2".to_owned(), "3".to_owned()],
|
|
default_time,
|
|
">= 3".to_string(),
|
|
));
|
|
check_count(&rule_str, &recs, expected_count, expected_agg_result);
|
|
}
|
|
|
|
// timeframe=10dはギリギリHitしない
|
|
{
|
|
let rule_str = create_std_rule("count(EventID) >= 3", "10d");
|
|
let mut expected_count = HashMap::new();
|
|
expected_count.insert("_".to_owned(), 3);
|
|
check_count(&rule_str, &recs, expected_count, Vec::new());
|
|
}
|
|
}
|
|
|
|
// evtx的には小数点の秒数が指定されうるので、それが正しく制御できることを確認
|
|
#[test]
|
|
fn test_count_timeframe_milsecs() {
|
|
let recs = vec![
|
|
test_create_recstr_std("1", "2021-12-21T10:40:00.0000000Z"),
|
|
test_create_recstr_std("2", "2021-12-21T10:40:05.0000000Z"),
|
|
test_create_recstr_std("3", "2021-12-21T10:40:10.0003000Z"),
|
|
];
|
|
|
|
// timeframe=11secはギリギリHit
|
|
{
|
|
let rule_str = create_std_rule("count(EventID) >= 3", "11s");
|
|
let default_time = Utc.ymd(2021, 12, 21).and_hms(10, 40, 0);
|
|
let mut expected_count = HashMap::new();
|
|
expected_count.insert("_".to_owned(), 3);
|
|
let mut expected_agg_result: Vec<AggResult> = Vec::new();
|
|
expected_agg_result.push(AggResult::new(
|
|
3,
|
|
"_".to_owned(),
|
|
vec!["1".to_owned(), "2".to_owned(), "3".to_owned()],
|
|
default_time,
|
|
">= 3".to_string(),
|
|
));
|
|
check_count(&rule_str, &recs, expected_count, expected_agg_result);
|
|
}
|
|
|
|
// timeframe=10dはギリギリHitしない
|
|
{
|
|
let rule_str = create_std_rule("count(EventID) >= 3", "10s");
|
|
let mut expected_count = HashMap::new();
|
|
expected_count.insert("_".to_owned(), 3);
|
|
check_count(&rule_str, &recs, expected_count, Vec::new());
|
|
}
|
|
}
|
|
|
|
// evtx的には小数点の秒数が指定されうるので、それが正しく制御できることを確認
|
|
#[test]
|
|
fn test_count_timeframe_milsecs2() {
|
|
let recs = vec![
|
|
test_create_recstr_std("1", "2021-12-21T10:40:00.0500000Z"),
|
|
test_create_recstr_std("2", "2021-12-21T10:40:05.0000000Z"),
|
|
test_create_recstr_std("3", "2021-12-21T10:40:10.0400000Z"),
|
|
];
|
|
|
|
// timeframe=10secはギリギリHit
|
|
{
|
|
let rule_str = create_std_rule("count(EventID) >= 3", "10s");
|
|
let default_time = Utc.ymd(2021, 12, 21).and_hms_milli(10, 40, 0, 50);
|
|
let mut expected_count = HashMap::new();
|
|
expected_count.insert("_".to_owned(), 3);
|
|
let mut expected_agg_result: Vec<AggResult> = Vec::new();
|
|
expected_agg_result.push(AggResult::new(
|
|
3,
|
|
"_".to_owned(),
|
|
vec!["1".to_owned(), "2".to_owned(), "3".to_owned()],
|
|
default_time,
|
|
">= 3".to_string(),
|
|
));
|
|
check_count(&rule_str, &recs, expected_count, expected_agg_result);
|
|
}
|
|
|
|
// timeframe=10dはギリギリHitしない
|
|
{
|
|
let rule_str = create_std_rule("count(EventID) >= 3", "9s");
|
|
let mut expected_count = HashMap::new();
|
|
expected_count.insert("_".to_owned(), 3);
|
|
check_count(&rule_str, &recs, expected_count, Vec::new());
|
|
}
|
|
}
|
|
|
|
// evtx的には小数点の秒数が指定されうるので、それが正しく制御できることを確認
|
|
#[test]
|
|
fn test_count_timeframe_milsecs3() {
|
|
let recs = vec![
|
|
test_create_recstr_std("1", "2021-12-21T10:40:00.0500000Z"),
|
|
test_create_recstr_std("2", "2021-12-21T10:40:05.0000000Z"),
|
|
test_create_recstr_std("3", "2021-12-21T10:40:10.0600000Z"),
|
|
];
|
|
|
|
// timeframe=11secはギリギリHit
|
|
{
|
|
let rule_str = create_std_rule("count(EventID) >= 3", "11s");
|
|
let default_time = Utc.ymd(2021, 12, 21).and_hms_milli(10, 40, 0, 50);
|
|
let mut expected_count = HashMap::new();
|
|
expected_count.insert("_".to_owned(), 3);
|
|
let mut expected_agg_result: Vec<AggResult> = Vec::new();
|
|
expected_agg_result.push(AggResult::new(
|
|
3,
|
|
"_".to_owned(),
|
|
vec!["1".to_owned(), "2".to_owned(), "3".to_owned()],
|
|
default_time,
|
|
">= 3".to_string(),
|
|
));
|
|
check_count(&rule_str, &recs, expected_count, expected_agg_result);
|
|
}
|
|
|
|
// timeframe=10dはギリギリHitしない
|
|
{
|
|
let rule_str = create_std_rule("count(EventID) >= 3", "10s");
|
|
let mut expected_count = HashMap::new();
|
|
expected_count.insert("_".to_owned(), 3);
|
|
check_count(&rule_str, &recs, expected_count, Vec::new());
|
|
}
|
|
}
|
|
|
|
// Hitしたレコードがない時のテスト
|
|
#[test]
|
|
fn test_count_norecord() {
|
|
let recs = vec![];
|
|
|
|
{
|
|
let rule_str = create_std_rule("count(EventID) >= 3", "10s");
|
|
let expected_count = HashMap::new();
|
|
check_count(&rule_str, &recs, expected_count, Vec::new());
|
|
}
|
|
}
|
|
|
|
// 1レコードで正しく検知できることを確認
|
|
#[test]
|
|
fn test_count_onerecord() {
|
|
let recs = vec![test_create_recstr_std("1", "2021-12-21T10:40:00.0000000Z")];
|
|
|
|
// byない
|
|
{
|
|
let rule_str = create_std_rule("count(EventID) >= 1", "1s");
|
|
let default_time = Utc.ymd(2021, 12, 21).and_hms_milli(10, 40, 0, 0);
|
|
let mut expected_count = HashMap::new();
|
|
expected_count.insert("_".to_owned(), 1);
|
|
let mut expected_agg_result: Vec<AggResult> = Vec::new();
|
|
expected_agg_result.push(AggResult::new(
|
|
1,
|
|
"_".to_owned(),
|
|
vec!["1".to_owned()],
|
|
default_time,
|
|
">= 1".to_string(),
|
|
));
|
|
check_count(&rule_str, &recs, expected_count, expected_agg_result);
|
|
}
|
|
|
|
// byある
|
|
{
|
|
let rule_str = create_std_rule("count(EventID) by param1>= 1", "1s");
|
|
let default_time = Utc.ymd(2021, 12, 21).and_hms_milli(10, 40, 0, 0);
|
|
let mut expected_count = HashMap::new();
|
|
expected_count.insert("Windows Event Log".to_owned(), 1);
|
|
let mut expected_agg_result: Vec<AggResult> = Vec::new();
|
|
expected_agg_result.push(AggResult::new(
|
|
1,
|
|
"Windows Event Log".to_owned(),
|
|
vec!["1".to_owned()],
|
|
default_time,
|
|
">= 1".to_string(),
|
|
));
|
|
check_count(&rule_str, &recs, expected_count, expected_agg_result);
|
|
}
|
|
}
|
|
|
|
// timeframeの検査
|
|
// timeframe=2hで、パイプ以降はcount(EventID) >= 3とする。
|
|
//
|
|
// このとき先頭の3行だと検知しないが、2行目から4行目は検知するはず
|
|
// このように先頭行ではなく、途中から数えて検知するパターンをチェックする。
|
|
// 0:30 EventID=1
|
|
// 1:30 EventID=1
|
|
// 2:30 EventID=2
|
|
// 3:30 EventID=3
|
|
// 10:30 EventID=4
|
|
// 11:30 EventID=5
|
|
// 12:30 EventID=4
|
|
#[test]
|
|
fn test_count_timeframe1() {
|
|
let recs = vec![
|
|
test_create_recstr_std("1", "1977-01-09T00:30:00Z"),
|
|
test_create_recstr_std("1", "1977-01-09T01:30:00Z"),
|
|
test_create_recstr_std("2", "1977-01-09T02:30:00Z"),
|
|
test_create_recstr_std("3", "1977-01-09T03:30:00Z"),
|
|
test_create_recstr_std("4", "1977-01-09T10:30:00Z"),
|
|
test_create_recstr_std("5", "1977-01-09T11:30:00Z"),
|
|
test_create_recstr_std("4", "1977-01-09T12:30:00Z"),
|
|
];
|
|
let rule_str = create_std_rule("count(EventID) >= 3", "2h");
|
|
|
|
let mut expected_count = HashMap::new();
|
|
expected_count.insert("_".to_owned(), 7);
|
|
let mut expected_agg_result: Vec<AggResult> = Vec::new();
|
|
expected_agg_result.push(AggResult::new(
|
|
3,
|
|
"_".to_owned(),
|
|
vec!["1".to_owned(), "2".to_owned(), "3".to_owned()],
|
|
Utc.ymd(1977, 1, 9).and_hms(1, 30, 0),
|
|
">= 3".to_string(),
|
|
));
|
|
check_count(&rule_str, &recs, expected_count, expected_agg_result);
|
|
}
|
|
|
|
// ずっと微妙に検知しない
|
|
#[test]
|
|
fn test_count_timeframe2() {
|
|
let recs = vec![
|
|
test_create_recstr_std("2", "1977-01-09T01:30:00Z"),
|
|
test_create_recstr_std("2", "1977-01-09T02:30:00Z"),
|
|
test_create_recstr_std("3", "1977-01-09T03:30:00Z"),
|
|
test_create_recstr_std("3", "1977-01-09T04:30:00Z"),
|
|
test_create_recstr_std("1", "1977-01-09T05:30:00Z"),
|
|
test_create_recstr_std("1", "1977-01-09T06:30:00Z"),
|
|
test_create_recstr_std("2", "1977-01-09T07:30:00Z"),
|
|
];
|
|
|
|
{
|
|
let rule_str = create_std_rule("count(EventID) >= 3", "2h");
|
|
let mut expected_count = HashMap::new();
|
|
expected_count.insert("_".to_owned(), 7);
|
|
check_count(&rule_str, &recs, expected_count, Vec::new());
|
|
}
|
|
}
|
|
|
|
// 同じ時刻のレコードがあっても正しくcount出来る
|
|
#[test]
|
|
fn test_count_sametime() {
|
|
let recs = vec![
|
|
test_create_recstr_std("1", "1977-01-09T01:30:00Z"),
|
|
test_create_recstr_std("2", "1977-01-09T01:30:00Z"),
|
|
test_create_recstr_std("3", "1977-01-09T02:30:00Z"),
|
|
test_create_recstr_std("4", "1977-01-09T02:30:00Z"),
|
|
];
|
|
|
|
{
|
|
let rule_str = create_std_rule("count(EventID) >= 4", "1h");
|
|
let mut expected_count = HashMap::new();
|
|
expected_count.insert("_".to_owned(), 4);
|
|
|
|
let mut expected_agg_result: Vec<AggResult> = Vec::new();
|
|
expected_agg_result.push(AggResult::new(
|
|
4,
|
|
"_".to_owned(),
|
|
vec![
|
|
"1".to_owned(),
|
|
"2".to_owned(),
|
|
"3".to_owned(),
|
|
"4".to_owned(),
|
|
],
|
|
Utc.ymd(1977, 1, 9).and_hms(1, 30, 0),
|
|
">= 4".to_string(),
|
|
));
|
|
|
|
check_count(&rule_str, &recs, expected_count, expected_agg_result);
|
|
}
|
|
}
|
|
|
|
// countの実装で番兵をおいてないので、それで正しく動くかチェック
|
|
// Hitした全レコードのtimeframeが条件のtimeframeよりも狭い場合にエラーがでないかチェック
|
|
#[test]
|
|
fn test_count_sentinel() {
|
|
let recs = vec![
|
|
test_create_recstr_std("1", "1977-01-09T01:30:00Z"),
|
|
test_create_recstr_std("2", "1977-01-09T02:30:00Z"),
|
|
test_create_recstr_std("3", "1977-01-09T03:30:00Z"),
|
|
];
|
|
|
|
// Hitするパターン
|
|
{
|
|
let rule_str = create_std_rule("count(EventID) >= 3", "1d");
|
|
let mut expected_count = HashMap::new();
|
|
expected_count.insert("_".to_owned(), 3);
|
|
|
|
let mut expected_agg_result: Vec<AggResult> = Vec::new();
|
|
expected_agg_result.push(AggResult::new(
|
|
3,
|
|
"_".to_owned(),
|
|
vec!["1".to_owned(), "2".to_owned(), "3".to_owned()],
|
|
Utc.ymd(1977, 1, 9).and_hms(1, 30, 0),
|
|
">= 3".to_string(),
|
|
));
|
|
|
|
check_count(&rule_str, &recs, expected_count, expected_agg_result);
|
|
}
|
|
// Hitしないパターン
|
|
{
|
|
let rule_str = create_std_rule("count(EventID) >= 4", "1d");
|
|
let mut expected_count = HashMap::new();
|
|
expected_count.insert("_".to_owned(), 3);
|
|
|
|
check_count(&rule_str, &recs, expected_count, Vec::new());
|
|
}
|
|
}
|
|
|
|
// 1:30-4:30までEventIDが4種類あって、2:30-5:30までEventIDが4種類あるが、
|
|
// 1:30-4:30までで4種類あったら、今度は5:30から数え始めていることを確認
|
|
#[test]
|
|
fn test_count_timeframe_reset() {
|
|
let recs = vec![
|
|
test_create_recstr_std("1", "1977-01-09T01:30:00Z"),
|
|
test_create_recstr_std("2", "1977-01-09T02:30:00Z"),
|
|
test_create_recstr_std("3", "1977-01-09T03:30:00Z"),
|
|
test_create_recstr_std("4", "1977-01-09T04:30:00Z"),
|
|
test_create_recstr_std("1", "1977-01-09T05:30:00Z"),
|
|
test_create_recstr_std("2", "1977-01-09T06:30:00Z"),
|
|
test_create_recstr_std("3", "1977-01-09T07:30:00Z"),
|
|
test_create_recstr_std("4", "1977-01-09T08:30:00Z"),
|
|
test_create_recstr_std("1", "1977-01-09T09:30:00Z"),
|
|
test_create_recstr_std("2", "1977-01-09T10:30:00Z"),
|
|
test_create_recstr_std("3", "1977-01-09T11:30:00Z"),
|
|
test_create_recstr_std("4", "1977-01-09T12:30:00Z"),
|
|
];
|
|
|
|
{
|
|
let rule_str = create_std_rule("count(EventID) >= 4", "3h");
|
|
let mut expected_count = HashMap::new();
|
|
expected_count.insert("_".to_owned(), recs.len() as i32);
|
|
|
|
let mut expected_agg_result: Vec<AggResult> = Vec::new();
|
|
expected_agg_result.push(AggResult::new(
|
|
4,
|
|
"_".to_owned(),
|
|
vec![
|
|
"1".to_owned(),
|
|
"2".to_owned(),
|
|
"3".to_owned(),
|
|
"4".to_owned(),
|
|
],
|
|
Utc.ymd(1977, 1, 9).and_hms(1, 30, 0),
|
|
">= 4".to_string(),
|
|
));
|
|
expected_agg_result.push(AggResult::new(
|
|
4,
|
|
"_".to_owned(),
|
|
vec![
|
|
"1".to_owned(),
|
|
"2".to_owned(),
|
|
"3".to_owned(),
|
|
"4".to_owned(),
|
|
],
|
|
Utc.ymd(1977, 1, 9).and_hms(5, 30, 0),
|
|
">= 4".to_string(),
|
|
));
|
|
expected_agg_result.push(AggResult::new(
|
|
4,
|
|
"_".to_owned(),
|
|
vec![
|
|
"1".to_owned(),
|
|
"2".to_owned(),
|
|
"3".to_owned(),
|
|
"4".to_owned(),
|
|
],
|
|
Utc.ymd(1977, 1, 9).and_hms(9, 30, 0),
|
|
">= 4".to_string(),
|
|
));
|
|
|
|
check_count(&rule_str, &recs, expected_count, expected_agg_result);
|
|
}
|
|
}
|
|
|
|
// timeframeの検査
|
|
// timeframe=2hで、パイプ以降はcount(EventID) >= 3とする。
|
|
//
|
|
// test_count_timeframe()のパターンが2回続く場合
|
|
#[test]
|
|
fn test_count_timeframe_twice() {
|
|
let recs = vec![
|
|
test_create_recstr_std("1", "1977-01-09T00:30:00Z"),
|
|
test_create_recstr_std("1", "1977-01-09T01:30:00Z"),
|
|
test_create_recstr_std("2", "1977-01-09T02:30:00Z"),
|
|
test_create_recstr_std("2", "1977-01-09T03:30:00Z"),
|
|
test_create_recstr_std("3", "1977-01-09T04:30:00Z"),
|
|
test_create_recstr_std("4", "1977-01-09T05:30:00Z"),
|
|
test_create_recstr_std("1", "1977-01-09T19:00:00Z"),
|
|
test_create_recstr_std("1", "1977-01-09T20:00:00Z"),
|
|
test_create_recstr_std("3", "1977-01-09T21:00:00Z"),
|
|
test_create_recstr_std("4", "1977-01-09T21:30:00Z"),
|
|
test_create_recstr_std("5", "1977-01-09T22:00:00Z"),
|
|
];
|
|
|
|
let rule_str = create_std_rule("count(EventID) >= 3", "2h");
|
|
|
|
let mut expected_count = HashMap::new();
|
|
expected_count.insert("_".to_owned(), 11);
|
|
let mut expected_agg_result: Vec<AggResult> = Vec::new();
|
|
expected_agg_result.push(AggResult::new(
|
|
3,
|
|
"_".to_owned(),
|
|
vec!["2".to_owned(), "3".to_owned(), "4".to_owned()],
|
|
Utc.ymd(1977, 1, 9).and_hms(3, 30, 0),
|
|
">= 3".to_string(),
|
|
));
|
|
|
|
expected_agg_result.push(AggResult::new(
|
|
4,
|
|
"_".to_owned(),
|
|
vec![
|
|
"1".to_owned(),
|
|
"3".to_owned(),
|
|
"4".to_owned(),
|
|
"5".to_owned(),
|
|
],
|
|
Utc.ymd(1977, 1, 9).and_hms(20, 00, 0),
|
|
">= 3".to_string(),
|
|
));
|
|
check_count(&rule_str, &recs, expected_count, expected_agg_result);
|
|
}
|
|
|
|
fn test_create_recstr_std(event_id: &str, time: &str) -> String {
|
|
return test_create_recstr(event_id, time, "Windows Event Log");
|
|
}
|
|
|
|
fn test_create_recstr(event_id: &str, time: &str, param1: &str) -> String {
|
|
let template: &str = r#"
|
|
{
|
|
"Event": {
|
|
"System": {
|
|
"EventID": ${EVENT_ID},
|
|
"TimeCreated_attributes": {
|
|
"SystemTime": "${TIME}"
|
|
}
|
|
},
|
|
"EventData": {
|
|
"param1": "${PARAM1}"
|
|
}
|
|
}
|
|
}"#;
|
|
return template
|
|
.replace("${EVENT_ID}", event_id)
|
|
.replace("${TIME}", time)
|
|
.replace("${PARAM1}", param1);
|
|
}
|
|
|
|
fn create_std_rule(count: &str, timeframe: &str) -> String {
|
|
let template = r#"
|
|
enabled: true
|
|
detection:
|
|
selection1:
|
|
param1: 'Windows Event Log'
|
|
condition: selection1 | ${COUNT}
|
|
timeframe: ${TIME_FRAME}
|
|
output: 'Service name : %param1%¥nMessage : Event Log Service Stopped¥nResults: Selective event log manipulation may follow this event.'
|
|
"#;
|
|
return template
|
|
.replace("${COUNT}", count)
|
|
.replace("${TIME_FRAME}", timeframe);
|
|
}
|
|
|
|
/// countで対象の数値確認を行うためのテスト用関数
|
|
fn check_count(
|
|
rule_str: &str,
|
|
records_str: &Vec<String>,
|
|
expected_counts: HashMap<String, i32>,
|
|
expect_agg_results: Vec<AggResult>,
|
|
) {
|
|
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("testpath".to_string(), test);
|
|
let error_checker = rule_node.init();
|
|
if error_checker.is_err() {
|
|
assert!(false, "Failed to init rulenode");
|
|
}
|
|
for record_str in records_str {
|
|
match serde_json::from_str(&record_str) {
|
|
Ok(record) => {
|
|
let keys = detections::rule::get_detection_keys(&rule_node);
|
|
let recinfo = utils::create_rec_info(record, "testpath".to_owned(), &keys);
|
|
let result = &rule_node.select(&recinfo);
|
|
assert_eq!(result, &true);
|
|
}
|
|
Err(_rec) => {
|
|
assert!(false, "Failed to parse json record.");
|
|
}
|
|
}
|
|
}
|
|
let agg_results = &rule_node.judge_satisfy_aggcondition();
|
|
assert_eq!(agg_results.len(), expect_agg_results.len());
|
|
|
|
let mut expect_data = vec![];
|
|
let mut expect_key = vec![];
|
|
let mut expect_field_values = 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(&expect_agg.key).unwrap().len() as i32,
|
|
*expect_count
|
|
);
|
|
expect_data.push(expect_agg.data);
|
|
expect_key.push(expect_agg.key);
|
|
expect_field_values.push(expect_agg.field_values);
|
|
expect_start_timedate.push(expect_agg.start_timedate);
|
|
expect_condition_op_num.push(expect_agg.condition_op_num);
|
|
}
|
|
for agg_result in agg_results {
|
|
println!("{}", &agg_result.start_timedate);
|
|
//ここですでにstart_timedateの格納を確認済み
|
|
let index = expect_start_timedate
|
|
.binary_search(&agg_result.start_timedate)
|
|
.unwrap();
|
|
assert_eq!(agg_result.data, expect_data[index]);
|
|
assert_eq!(agg_result.key, expect_key[index]);
|
|
assert!(agg_result.field_values.len() == expect_field_values[index].len());
|
|
for expect_field_value in &expect_field_values[index] {
|
|
// テストによってはtimeframeの値と各fieldの値で配列の順番が想定したものと変化してしまう可能性があるため配列の長さを確認したうえで期待した各要素が存在するかを確認する。
|
|
// field`要素の順番については以降の処理で関連しない
|
|
assert!(agg_result.field_values.contains(&expect_field_value));
|
|
}
|
|
assert_eq!(agg_result.condition_op_num, expect_condition_op_num[index]);
|
|
}
|
|
}
|
|
}
|