* Feature/call error message struct#66 (#69) * change way to use write trait #66 * change call error message struct #66 * erase finished TODO #66 * erase comment in error message format test #66 * resolve conflict #66 * Feature/call error message struct#66 (#71) * change ERROR writeln struct #66 * under constructing * add statistics template * fix * add comment * add condition impl #93 * fix erased get_descendants and remove unnecessaly struct #93 * erased finished TODO comment * erased finished TODO comment * Revert "fix erased get_descendants and remove unnecessaly struct #93" This reverts commit82e905e045. Revert "add condition impl #93" This reverts commit19ecc87377. * add doc comment to rule function * fix and add test doc commet * add doc to AggregaationParseInfo * add struct count in aggregation condition. #93 * add evaluate aggregation condition func provisional architecture. #93 * add countup function #93 * fix key to count hashmap #93 * add judge aggregation condition function #93 * fix error #93 * fix test #93 * share compile error ver * fix detection.rs compile error * fix timeframe parse * add countup process in select * fix select argument * add test countup * add test count judge #93 * add SIGMA windows count field and by keyword #93 * fix reference record in countup/judgecount #93 * add timedata in countup schema #93 * Refact: split code for matcher from rule.rs * Reafact: combine multiple declared functions * Refact: split code for SelectionNode from rule.rs * Refact: mv test code for SelectionNode from rule.rs * Refact: mv condition's code from rule.rs * add count to detection #93 * fix compile error * fix source to test ng. #93 * erase unused variable #93 * fix count architecture #93 * fix comment and compile error * erase dust (response to review) * erase dust (response to review) * reduce calling Rulenode function (response to review) * add aggregation output func * erase dust(response to review) and add agg condition String func * change error output * reduce call RuleNode function(response to review) * To reduce call RuleNode function * fix test name * fix coflicted resolve miss * add code comment in timeframe count. * add sort record timedata in timeframe(response to review) * fix unnecesasry result in ArgResult * add no field and by value count test * create count test no field and by with timeframe * erase duplicated timeframe data in RuleNode * fix test error no field and no by count with timeframe * fix test name * add test case of exist field and by count. * fix by count test and add test count othervalue in timeframe * add test * fix judge_timeframe logic when indexout * fix test name and add count test field and by with timeframe * adjust #120 * move associated count function from rulenode * fix error when resolve conflict * fix no output bug if exist output Co-authored-by: HajimeTakai <takai.wa.hajime@gmail.com> Co-authored-by: itiB <is0312vx@ed.ritsumei.ac.jp>
This commit is contained in:
@@ -24,3 +24,22 @@ SubjectUserSid,Event.EventData.SubjectUserSid
|
||||
DomainName,Event.EventData.SubjectDomainName
|
||||
TicketEncryptionType,Event.EventData.TicketEncryptionType
|
||||
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
|
||||
@@ -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<RuleNode>, records: Vec<EvtxRecordInfo>) {
|
||||
// 複数スレッドで所有権を共有するため、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<Vec<bool>> = 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<RuleNode>, records: Vec<EvtxRecordInfo>) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
// メッセージを追加する。これを上記の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 {
|
||||
// 検知ロジックを実行します。
|
||||
fn execute_rule(mut rule: RuleNode, records: Arc<Vec<EvtxRecordInfo>>) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO メッセージが多いと、rule.select()よりもこの処理の方が時間かかる。
|
||||
message.insert(
|
||||
record_info_arc.evtx_filepath.to_string(),
|
||||
&record_info_arc.record,
|
||||
let agg_results = rule.judge_satisfy_aggcondition();
|
||||
for value in agg_results {
|
||||
if agg_condition {
|
||||
Detection::insert_agg_message(&rule, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 条件に合致したレコードを表示するための関数
|
||||
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(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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(),
|
||||
)
|
||||
}
|
||||
|
||||
// fn get_event_ids(rules: &Vec<RuleNode>) -> HashSet<i64> {
|
||||
// return rules
|
||||
// .iter()
|
||||
// .map(|rule| rule.get_event_ids())
|
||||
// .flatten()
|
||||
// .collect();
|
||||
// }
|
||||
|
||||
// 配列を指定したサイズで分割する。Vector.chunksと同じ動作をするが、Vectorの関数だとinto的なことができないので自作
|
||||
fn chunks<T>(ary: Vec<T>, size: usize) -> Vec<Vec<T>> {
|
||||
let arylen = ary.len();
|
||||
let mut ite = ary.into_iter();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Utc>,
|
||||
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<DateTime<Utc>> {
|
||||
pub fn get_event_time(event_record: &Value) -> Option<DateTime<Utc>> {
|
||||
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() {
|
||||
|
||||
@@ -2,10 +2,10 @@ use regex::Regex;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AggregationParseInfo {
|
||||
_field_name: Option<String>, // countの括弧に囲まれた部分の文字
|
||||
_by_field_name: Option<String>, // count() by の後に指定される文字列
|
||||
_cmp_op: AggregationConditionToken, // (必須)<とか>とか何が指定されたのか
|
||||
_cmp_num: i32, // (必須)<とか>とかの後にある数値
|
||||
pub _field_name: Option<String>, // countの括弧に囲まれた部分の文字
|
||||
pub _by_field_name: Option<String>, // count() by の後に指定される文字列
|
||||
pub _cmp_op: AggregationConditionToken, // (必須)<とか>とか何が指定されたのか
|
||||
pub _cmp_num: i32, // (必須)<とか>とかの後にある数値
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
||||
@@ -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.");
|
||||
|
||||
802
src/detections/rule/count.rs
Normal file
802
src/detections/rule/count.rs
Normal file
@@ -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<Utc>,
|
||||
) {
|
||||
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<AggResult> {
|
||||
// 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<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 {
|
||||
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::<i64>(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// TimeFrameInfoで格納されたtimeframeの値を秒数に変換した結果を返す関数
|
||||
pub fn get_sec_timeframe(timeframe: &Option<TimeFrameInfo>) -> Option<i64> {
|
||||
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<DateTime<Utc>>,
|
||||
key: &String,
|
||||
) -> Vec<AggResult> {
|
||||
let mut ret: Vec<AggResult> = 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<AggResult> = 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<AggResult> = 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<AggResult> = 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<AggResult> = 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<AggResult> = 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<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(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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.");
|
||||
|
||||
@@ -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<DetectionNode>,
|
||||
countdata: HashMap<String, HashMap<String, Vec<DateTime<Utc>>>>,
|
||||
}
|
||||
|
||||
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<AggResult> {
|
||||
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<String, Arc<Box<dyn SelectionNode + Send + Sync>>>,
|
||||
pub condition: Option<Box<dyn SelectionNode + Send + Sync>>,
|
||||
pub aggregation_condition: Option<aggregation_parser::AggregationParseInfo>,
|
||||
pub aggregation_condition: Option<AggregationParseInfo>,
|
||||
pub timeframe: Option<TimeFrameInfo>,
|
||||
}
|
||||
|
||||
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<Utc>,
|
||||
///条件式の情報
|
||||
pub condition_op_num: String,
|
||||
}
|
||||
|
||||
impl AggResult {
|
||||
pub fn new(
|
||||
filepath: String,
|
||||
data: i32,
|
||||
key: String,
|
||||
start_timedate: DateTime<Utc>,
|
||||
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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.");
|
||||
|
||||
Reference in New Issue
Block a user