Merge pull request #226 from Yamato-Security/feature/start_finish_time
指定時間範囲のイベントのみ読み込み
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
use crate::detections::print::AlertMessage;
|
||||
use crate::detections::utils;
|
||||
use chrono::{DateTime, Utc};
|
||||
use clap::{App, AppSettings, ArgMatches};
|
||||
use hashbrown::HashMap;
|
||||
use hashbrown::HashSet;
|
||||
@@ -55,6 +57,8 @@ fn build_app<'a>() -> ArgMatches<'a> {
|
||||
--rfc-2822 'Output date and time in RFC 2822 format. Example: Mon, 07 Aug 2006 12:34:56 -0600'
|
||||
--rfc-3339 'Output date and time in RFC 3339 format. Example: 2006-08-07T12:34:56.485214 -06:00'
|
||||
--verbose 'Output verbose information to target event file path and rule file'
|
||||
--starttimeline=[STARTTIMELINE] 'Start time of the event to load from event file. Example: '2018/11/28 12:00:00 +09:00''
|
||||
--endtimeline=[ENDTIMELINE]'End time of the event to load from event file. Example: '2018/11/28 12:00:00 +09:00''
|
||||
-q 'Quiet mode. Do not display the launch banner'
|
||||
-r --rules=[RULEDIRECTORY] 'Rule file directory (default: ./rules)'
|
||||
-m --min-level=[LEVEL] 'Minimum level for rules (default: informational)'
|
||||
@@ -119,6 +123,80 @@ fn load_target_ids(path: &str) -> TargetEventIds {
|
||||
return ret;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TargetEventTime {
|
||||
start_time: Option<DateTime<Utc>>,
|
||||
end_time: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
impl TargetEventTime {
|
||||
pub fn new() -> Self {
|
||||
let start_time = if let Some(s_time) = CONFIG.read().unwrap().args.value_of("starttimeline")
|
||||
{
|
||||
match DateTime::parse_from_str(s_time, "%Y-%m-%d %H:%M:%S %z") // 2014-11-28 21:00:09 +09:00
|
||||
.or_else(|_| DateTime::parse_from_str(s_time, "%Y/%m/%d %H:%M:%S %z")) // 2014/11/28 21:00:09 +09:00
|
||||
{
|
||||
Ok(dt) => Some(dt.with_timezone(&Utc)),
|
||||
Err(err) => {
|
||||
AlertMessage::alert(
|
||||
&mut std::io::stderr().lock(),
|
||||
format!("starttimeline field: {}", err),
|
||||
)
|
||||
.ok();
|
||||
None
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let end_time = if let Some(e_time) = CONFIG.read().unwrap().args.value_of("endtimeline") {
|
||||
match DateTime::parse_from_str(e_time, "%Y-%m-%d %H:%M:%S %z") // 2014-11-28 21:00:09 +09:00
|
||||
.or_else(|_| DateTime::parse_from_str(e_time, "%Y/%m/%d %H:%M:%S %z")) // 2014/11/28 21:00:09 +09:00
|
||||
{
|
||||
Ok(dt) => Some(dt.with_timezone(&Utc)),
|
||||
Err(err) => {
|
||||
AlertMessage::alert(
|
||||
&mut std::io::stderr().lock(),
|
||||
format!("endtimeline field: {}", err),
|
||||
)
|
||||
.ok();
|
||||
None
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
return Self::set(start_time, end_time);
|
||||
}
|
||||
|
||||
pub fn set(
|
||||
start_time: Option<chrono::DateTime<chrono::Utc>>,
|
||||
end_time: Option<chrono::DateTime<chrono::Utc>>,
|
||||
) -> Self {
|
||||
return Self {
|
||||
start_time: start_time,
|
||||
end_time: end_time,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn is_target(&self, eventtime: &Option<DateTime<Utc>>) -> bool {
|
||||
if eventtime.is_none() {
|
||||
return true;
|
||||
}
|
||||
if let Some(starttime) = self.start_time {
|
||||
if eventtime.unwrap() < starttime {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if let Some(endtime) = self.end_time {
|
||||
if eventtime.unwrap() > endtime {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EventKeyAliasConfig {
|
||||
key_to_eventkey: HashMap<String, String>,
|
||||
@@ -233,26 +311,51 @@ fn load_eventcode_info(path: &str) -> EventInfoConfig {
|
||||
return config;
|
||||
}
|
||||
|
||||
// #[cfg(test)]
|
||||
// mod tests {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::detections::configs;
|
||||
use chrono::{DateTime, Utc};
|
||||
|
||||
// use crate::detections::configs;
|
||||
// #[test]
|
||||
// #[ignore]
|
||||
// fn singleton_read_and_write() {
|
||||
// let message =
|
||||
// "EventKeyAliasConfig { key_to_eventkey: {\"EventID\": \"Event.System.EventID\"} }";
|
||||
// configs::EVENT_KEY_ALIAS_CONFIG =
|
||||
// configs::load_eventkey_alias("test_files/config/eventkey_alias.txt");
|
||||
// let display = format!(
|
||||
// "{}",
|
||||
// format_args!(
|
||||
// "{:?}",
|
||||
// configs::CONFIG.write().unwrap().event_key_alias_config
|
||||
// )
|
||||
// );
|
||||
// assert_eq!(message, display);
|
||||
// }
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// #[ignore]
|
||||
// fn singleton_read_and_write() {
|
||||
// let message =
|
||||
// "EventKeyAliasConfig { key_to_eventkey: {\"EventID\": \"Event.System.EventID\"} }";
|
||||
// configs::EVENT_KEY_ALIAS_CONFIG =
|
||||
// configs::load_eventkey_alias("test_files/config/eventkey_alias.txt");
|
||||
#[test]
|
||||
fn target_event_time_filter() {
|
||||
let start_time = Some("2018-02-20T12:00:09Z".parse::<DateTime<Utc>>().unwrap());
|
||||
let end_time = Some("2020-03-30T12:00:09Z".parse::<DateTime<Utc>>().unwrap());
|
||||
let time_filter = configs::TargetEventTime::set(start_time, end_time);
|
||||
|
||||
// let display = format!(
|
||||
// "{}",
|
||||
// format_args!(
|
||||
// "{:?}",
|
||||
// configs::CONFIG.write().unwrap().event_key_alias_config
|
||||
// )
|
||||
// );
|
||||
// assert_eq!(message, display);
|
||||
// }
|
||||
// }
|
||||
let out_of_range1 = Some("1999-01-01T12:00:09Z".parse::<DateTime<Utc>>().unwrap());
|
||||
let within_range = Some("2019-02-27T01:05:01Z".parse::<DateTime<Utc>>().unwrap());
|
||||
let out_of_range2 = Some("2021-02-27T01:05:01Z".parse::<DateTime<Utc>>().unwrap());
|
||||
|
||||
assert_eq!(time_filter.is_target(&out_of_range1), false);
|
||||
assert_eq!(time_filter.is_target(&within_range), true);
|
||||
assert_eq!(time_filter.is_target(&out_of_range2), false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn target_event_time_filter_containes_on_time() {
|
||||
let start_time = Some("2018-02-20T12:00:09Z".parse::<DateTime<Utc>>().unwrap());
|
||||
let end_time = Some("2020-03-30T12:00:09Z".parse::<DateTime<Utc>>().unwrap());
|
||||
let time_filter = configs::TargetEventTime::set(start_time, end_time);
|
||||
|
||||
assert_eq!(time_filter.is_target(&start_time), true);
|
||||
assert_eq!(time_filter.is_target(&end_time), true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
extern crate lazy_static;
|
||||
use crate::detections::configs;
|
||||
use crate::detections::utils;
|
||||
use crate::detections::utils::get_serde_number_to_string;
|
||||
use chrono::{DateTime, TimeZone, Utc};
|
||||
use lazy_static::lazy_static;
|
||||
@@ -169,23 +170,7 @@ impl Message {
|
||||
|
||||
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() {
|
||||
return Option::None;
|
||||
}
|
||||
|
||||
let rfc3339_time = DateTime::parse_from_rfc3339(system_time_str);
|
||||
if rfc3339_time.is_err() {
|
||||
return Option::None;
|
||||
}
|
||||
let datetime = Utc
|
||||
.from_local_datetime(&rfc3339_time.unwrap().naive_utc())
|
||||
.single();
|
||||
if datetime.is_none() {
|
||||
return Option::None;
|
||||
} else {
|
||||
return Option::Some(datetime.unwrap());
|
||||
}
|
||||
return utils::str_time_to_datetime(system_time.as_str().unwrap_or(""));
|
||||
}
|
||||
|
||||
/// message内のマップをクリアする。テストする際の冪等性の担保のため作成。
|
||||
|
||||
@@ -7,6 +7,7 @@ use crate::detections::configs;
|
||||
use tokio::runtime::Builder;
|
||||
use tokio::runtime::Runtime;
|
||||
|
||||
use chrono::{DateTime, TimeZone, Utc};
|
||||
use regex::Regex;
|
||||
use serde_json::Value;
|
||||
use std::fs::File;
|
||||
@@ -93,6 +94,29 @@ pub fn get_event_id_key() -> String {
|
||||
return "Event.System.EventID".to_string();
|
||||
}
|
||||
|
||||
pub fn get_event_time() -> String {
|
||||
return "Event.System.TimeCreated_attributes.SystemTime".to_string();
|
||||
}
|
||||
|
||||
pub fn str_time_to_datetime(system_time_str: &str) -> Option<DateTime<Utc>> {
|
||||
if system_time_str.is_empty() {
|
||||
return Option::None;
|
||||
}
|
||||
|
||||
let rfc3339_time = DateTime::parse_from_rfc3339(system_time_str);
|
||||
if rfc3339_time.is_err() {
|
||||
return Option::None;
|
||||
}
|
||||
let datetime = Utc
|
||||
.from_local_datetime(&rfc3339_time.unwrap().naive_utc())
|
||||
.single();
|
||||
if datetime.is_none() {
|
||||
return Option::None;
|
||||
} else {
|
||||
return Option::Some(datetime.unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
/// serde:Valueの型を確認し、文字列を返します。
|
||||
pub fn get_serde_number_to_string(value: &serde_json::Value) -> Option<String> {
|
||||
if value.is_string() {
|
||||
|
||||
10
src/main.rs
10
src/main.rs
@@ -165,6 +165,8 @@ fn analysis_file(
|
||||
let mut records = parser.records_json_value();
|
||||
let tokio_rt = utils::create_tokio_runtime();
|
||||
|
||||
let target_event_time = configs::TargetEventTime::new();
|
||||
|
||||
loop {
|
||||
let mut records_per_detect = vec![];
|
||||
while records_per_detect.len() < MAX_DETECT_RECORDS {
|
||||
@@ -192,6 +194,14 @@ fn analysis_file(
|
||||
continue;
|
||||
}
|
||||
|
||||
let eventtime = utils::get_event_value(&utils::get_event_time(), &data);
|
||||
if eventtime.is_some() {
|
||||
let time = utils::str_time_to_datetime(eventtime.unwrap().as_str().unwrap_or(""));
|
||||
if !target_event_time.is_target(&time) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// EvtxRecordInfo構造体に変更
|
||||
records_per_detect.push(_create_rec_info(data, &filepath_disp));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user