Merge branch 'main' into feature/fill_no_use_rules

This commit is contained in:
James Takai / hach1yon
2021-12-04 19:31:35 +09:00
committed by GitHub
23 changed files with 409 additions and 117 deletions
+169 -78
View File
@@ -72,19 +72,26 @@ pub fn after_fact() {
}
fn emit_csv<W: std::io::Write>(writer: &mut W, displayflag: bool) -> io::Result<()> {
let mut wtr = csv::WriterBuilder::new().from_writer(writer);
let mut wtr;
if displayflag {
wtr = csv::WriterBuilder::new()
.delimiter(b'|')
.from_writer(writer);
} else {
wtr = csv::WriterBuilder::new().from_writer(writer);
}
let messages = print::MESSAGES.lock().unwrap();
let mut detect_count = 0;
for (time, detect_infos) in messages.iter() {
for detect_info in detect_infos {
if displayflag {
wtr.serialize(DisplayFormat {
time: &format_time(time),
level: &detect_info.level,
computername: &detect_info.computername,
eventid: &detect_info.eventid,
alert: &detect_info.alert,
details: &detect_info.detail,
time: &format!("{} ", &format_time(time)),
level: &format!(" {} ", &detect_info.level),
computername: &format!(" {} ", &detect_info.computername),
eventid: &format!(" {} ", &detect_info.eventid),
alert: &format!(" {} ", &detect_info.alert),
details: &format!(" {}", &detect_info.detail),
})?;
} else {
// csv出力時フォーマット
@@ -131,81 +138,165 @@ where
}
}
#[test]
fn test_emit_csv() {
#[cfg(test)]
mod tests {
use crate::afterfact::emit_csv;
use crate::detections::print;
use chrono::{Local, TimeZone, Utc};
use serde_json::Value;
use std::fs::File;
use std::fs::{read_to_string, remove_file};
let testfilepath: &str = "test.evtx";
let testrulepath: &str = "test-rule.yml";
let test_title = "test_title";
let test_level = "high";
let test_computername = "testcomputer";
let test_eventid = "1111";
let output = "pokepoke";
{
let mut messages = print::MESSAGES.lock().unwrap();
use std::io;
let val = r##"
{
"Event": {
"EventData": {
"CommandRLine": "hoge"
},
"System": {
"TimeCreated_attributes": {
"SystemTime": "1996-02-27T01:05:01Z"
}
}
}
}
"##;
let event: Value = serde_json::from_str(val).unwrap();
messages.insert(
testfilepath.to_string(),
testrulepath.to_string(),
&event,
test_level.to_string(),
test_computername.to_string(),
test_eventid.to_string(),
test_title.to_string(),
output.to_string(),
);
#[test]
fn test_emit_csv() {
//テストの並列処理によって読み込みの順序が担保できずstatic変数の内容が担保が取れない為、このテストはシーケンシャルで行う
test_emit_csv_output();
test_emit_csv_output();
}
let expect_time = Utc
.datetime_from_str("1996-02-27T01:05:01Z", "%Y-%m-%dT%H:%M:%SZ")
.unwrap();
let expect_tz = expect_time.with_timezone(&Local);
let expect = "Time,Computername,Eventid,Level,Alert,Details,Rulepath,Filepath\n".to_string()
+ &expect_tz
.clone()
.format("%Y-%m-%d %H:%M:%S%.3f %:z")
.to_string()
+ ","
+ test_computername
+ ","
+ test_eventid
+ ","
+ test_level
+ ","
+ test_title
+ ","
+ output
+ ","
+ testrulepath
+ ","
+ &testfilepath.to_string()
+ "\n";
let mut file: Box<dyn io::Write> =
Box::new(File::create("./test_emit_csv.csv".to_string()).unwrap());
assert!(emit_csv(&mut file, false).is_ok());
match read_to_string("./test_emit_csv.csv") {
Err(_) => panic!("Failed to open file."),
Ok(s) => {
assert_eq!(s, expect);
fn test_emit_csv_output() {
let testfilepath: &str = "test.evtx";
let testrulepath: &str = "test-rule.yml";
let test_title = "test_title";
let test_level = "high";
let test_computername = "testcomputer";
let test_eventid = "1111";
let output = "pokepoke";
{
let mut messages = print::MESSAGES.lock().unwrap();
messages.clear();
let val = r##"
{
"Event": {
"EventData": {
"CommandRLine": "hoge"
},
"System": {
"TimeCreated_attributes": {
"SystemTime": "1996-02-27T01:05:01Z"
}
}
}
}
"##;
let event: Value = serde_json::from_str(val).unwrap();
messages.insert(
testfilepath.to_string(),
testrulepath.to_string(),
&event,
test_level.to_string(),
test_computername.to_string(),
test_eventid.to_string(),
test_title.to_string(),
output.to_string(),
);
}
};
assert!(remove_file("./test_emit_csv.csv").is_ok());
let expect_time = Utc
.datetime_from_str("1996-02-27T01:05:01Z", "%Y-%m-%dT%H:%M:%SZ")
.unwrap();
let expect_tz = expect_time.with_timezone(&Local);
let expect = "Time,Computername,Eventid,Level,Alert,Details,Rulepath,Filepath\n"
.to_string()
+ &expect_tz
.clone()
.format("%Y-%m-%d %H:%M:%S%.3f %:z")
.to_string()
+ ","
+ test_computername
+ ","
+ test_eventid
+ ","
+ test_level
+ ","
+ test_title
+ ","
+ output
+ ","
+ testrulepath
+ ","
+ &testfilepath.to_string()
+ "\n";
let mut file: Box<dyn io::Write> =
Box::new(File::create("./test_emit_csv.csv".to_string()).unwrap());
assert!(emit_csv(&mut file, false).is_ok());
match read_to_string("./test_emit_csv.csv") {
Err(_) => panic!("Failed to open file."),
Ok(s) => {
assert_eq!(s, expect);
}
};
assert!(remove_file("./test_emit_csv.csv").is_ok());
check_emit_csv_display();
}
fn check_emit_csv_display() {
let testfilepath: &str = "test2.evtx";
let testrulepath: &str = "test-rule2.yml";
let test_title = "test_title2";
let test_level = "medium";
let test_computername = "testcomputer2";
let test_eventid = "2222";
let output = "displaytest";
{
let mut messages = print::MESSAGES.lock().unwrap();
messages.clear();
let val = r##"
{
"Event": {
"EventData": {
"CommandRLine": "hoge"
},
"System": {
"TimeCreated_attributes": {
"SystemTime": "1996-02-27T01:05:01Z"
}
}
}
}
"##;
let event: Value = serde_json::from_str(val).unwrap();
messages.insert(
testfilepath.to_string(),
testrulepath.to_string(),
&event,
test_level.to_string(),
test_computername.to_string(),
test_eventid.to_string(),
test_title.to_string(),
output.to_string(),
);
messages.debug();
}
let expect_time = Utc
.datetime_from_str("1996-02-27T01:05:01Z", "%Y-%m-%dT%H:%M:%SZ")
.unwrap();
let expect_tz = expect_time.with_timezone(&Local);
let expect = "Time|Computername|Eventid|Level|Alert|Details\n".to_string()
+ &expect_tz
.clone()
.format("%Y-%m-%d %H:%M:%S%.3f %:z")
.to_string()
+ " | "
+ test_computername
+ " | "
+ test_eventid
+ " | "
+ test_level
+ " | "
+ test_title
+ " | "
+ output
+ "\n";
let mut file: Box<dyn io::Write> =
Box::new(File::create("./test_emit_csv_display.txt".to_string()).unwrap());
assert!(emit_csv(&mut file, true).is_ok());
match read_to_string("./test_emit_csv_display.txt") {
Err(_) => panic!("Failed to open file."),
Ok(s) => {
assert_eq!(s, expect);
}
};
assert!(remove_file("./test_emit_csv_display.txt").is_ok());
}
}
+5 -3
View File
@@ -157,9 +157,9 @@ impl Detection {
"Medium",
"Low",
"Informational",
"Undeifned",
"Undefined",
]);
// levclcounts is [(Undeifned), (Informational), (Low),(Medium),(High),(Critical)]
// levclcounts is [(Undefined), (Informational), (Low),(Medium),(High),(Critical)]
let mut levelcounts = Vec::from([0, 0, 0, 0, 0, 0]);
for rule in rules.into_iter() {
if rule.check_exist_countdata() {
@@ -212,7 +212,9 @@ impl Detection {
record_info.record["Event"]["System"]["Computer"]
.to_string()
.replace("\"", ""),
get_serde_number_to_string(&record_info.record["Event"]["System"]["EventID"]),
get_serde_number_to_string(&record_info.record["Event"]["System"]["EventID"])
.unwrap_or("-".to_owned())
.to_string(),
rule.yaml["title"].as_str().unwrap_or("").to_string(),
rule.yaml["output"].as_str().unwrap_or("").to_string(),
);
+98 -6
View File
@@ -30,6 +30,7 @@ pub struct AlertMessage {}
lazy_static! {
pub static ref MESSAGES: Mutex<Message> = Mutex::new(Message::new());
pub static ref ALIASREGEX: Regex = Regex::new(r"%[a-zA-Z0-9-_]+%").unwrap();
}
impl Message {
@@ -101,8 +102,7 @@ impl Message {
fn parse_message(&mut self, event_record: &Value, output: String) -> String {
let mut return_message: String = output;
let mut hash_map: HashMap<String, String> = HashMap::new();
let re = Regex::new(r"%[a-zA-Z0-9-_]+%").unwrap();
for caps in re.captures_iter(&return_message) {
for caps in ALIASREGEX.captures_iter(&return_message) {
let full_target_str = &caps[0];
let target_length = full_target_str.chars().count() - 2; // The meaning of 2 is two percent
let target_str = full_target_str
@@ -118,16 +118,20 @@ impl Message {
.get_event_key(target_str.to_string())
{
let split: Vec<&str> = array_str.split(".").collect();
let mut is_exist_event_key = false;
let mut tmp_event_record: &Value = event_record.into();
for s in split {
if let Some(record) = tmp_event_record.get(s) {
is_exist_event_key = true;
tmp_event_record = record;
}
}
hash_map.insert(
full_target_str.to_string(),
get_serde_number_to_string(tmp_event_record),
);
if is_exist_event_key {
let hash_value = get_serde_number_to_string(tmp_event_record);
if hash_value.is_some() {
hash_map.insert(full_target_str.to_string(), hash_value.unwrap());
}
}
}
}
@@ -188,6 +192,11 @@ impl Message {
return Option::Some(datetime.unwrap());
}
}
/// message内のマップをクリアする。テストする際の冪等性の担保のため作成。
pub fn clear(&mut self) {
self.map.clear();
}
}
impl AlertMessage {
@@ -327,4 +336,87 @@ mod tests {
let mut stdout = stdout.lock();
AlertMessage::alert(&mut stdout, input.to_string()).expect("[WARN] TESTWarn!");
}
#[test]
/// outputで指定されているキー(eventkey_alias.txt内で設定済み)から対象のレコード内の情報でメッセージをパースしているか確認する関数
fn test_parse_message() {
let mut message = Message::new();
let json_str = r##"
{
"Event": {
"EventData": {
"CommandLine": "parsetest1"
},
"System": {
"Computer": "testcomputer1",
"TimeCreated_attributes": {
"SystemTime": "1996-02-27T01:05:01Z"
}
}
}
}
"##;
let event_record: Value = serde_json::from_str(json_str).unwrap();
let expected = "commandline:parsetest1 computername:testcomputer1";
assert_eq!(
message.parse_message(
&event_record,
"commandline:%CommandLine% computername:%ComputerName%".to_owned()
),
expected,
);
}
#[test]
/// outputで指定されているキーが、eventkey_alias.txt内で設定されていない場合の出力テスト
fn test_parse_message_not_exist_key_in_output() {
let mut message = Message::new();
let json_str = r##"
{
"Event": {
"EventData": {
"CommandLine": "parsetest2"
},
"System": {
"TimeCreated_attributes": {
"SystemTime": "1996-02-27T01:05:01Z"
}
}
}
}
"##;
let event_record: Value = serde_json::from_str(json_str).unwrap();
let expected = "NoExistKey:%TESTNoExistKey%";
assert_eq!(
message.parse_message(&event_record, "NoExistKey:%TESTNoExistKey%".to_owned()),
expected,
);
}
#[test]
/// outputで指定されているキー(eventkey_alias.txt内で設定済み)が対象のレコード内に該当する情報がない場合の出力テスト
fn test_parse_message_not_exist_value_in_record() {
let mut message = Message::new();
let json_str = r##"
{
"Event": {
"EventData": {
"CommandLine": "parsetest3"
},
"System": {
"TimeCreated_attributes": {
"SystemTime": "1996-02-27T01:05:01Z"
}
}
}
}
"##;
let event_record: Value = serde_json::from_str(json_str).unwrap();
let expected = "commandline:parsetest3 computername:%ComputerName%";
assert_eq!(
message.parse_message(
&event_record,
"commandline:%CommandLine% computername:%ComputerName%".to_owned()
),
expected,
);
}
}
+65 -3
View File
@@ -94,11 +94,14 @@ pub fn get_event_id_key() -> String {
}
/// serde:Valueの型を確認し、文字列を返します。
pub fn get_serde_number_to_string(value: &serde_json::Value) -> String {
pub fn get_serde_number_to_string(value: &serde_json::Value) -> Option<String> {
if value.is_string() {
return value.as_str().unwrap_or("").to_string();
return Option::Some(value.as_str().unwrap_or("").to_string());
} else if value.is_object() {
// Object type is not specified record value.
return Option::None;
} else {
return value.to_string();
return Option::Some(value.to_string());
}
}
@@ -163,6 +166,7 @@ pub fn create_tokio_runtime() -> Runtime {
mod tests {
use crate::detections::utils;
use regex::Regex;
use serde_json::Value;
#[test]
fn test_check_regex() {
@@ -191,4 +195,62 @@ mod tests {
let commandline = "\"C:\\Program Files\\Google\\Update\\GoogleUpdate2.exe\"";
assert!(false == utils::check_allowlist(commandline, &allowlist));
}
#[test]
/// Serde::Valueの数値型の値を文字列として返却することを確かめるテスト
fn test_get_serde_number_to_string() {
let json_str = r##"
{
"Event": {
"System": {
"EventID": 11111
}
}
}
"##;
let event_record: Value = serde_json::from_str(json_str).unwrap();
assert_eq!(
utils::get_serde_number_to_string(&event_record["Event"]["System"]["EventID"]).unwrap(),
"11111".to_owned()
);
}
#[test]
/// Serde::Valueの文字列型の値を文字列として返却することを確かめるテスト
fn test_get_serde_number_serde_string_to_string() {
let json_str = r##"
{
"Event": {
"EventData": {
"ComputerName": "HayabusaComputer1"
}
}
}
"##;
let event_record: Value = serde_json::from_str(json_str).unwrap();
assert_eq!(
utils::get_serde_number_to_string(&event_record["Event"]["EventData"]["ComputerName"])
.unwrap(),
"HayabusaComputer1".to_owned()
);
}
#[test]
/// Serde::Valueのオブジェクト型の内容を誤って渡した際にNoneを返却することを確かめるテスト
fn test_get_serde_number_serde_object_ret_none() {
let json_str = r##"
{
"Event": {
"EventData": {
"ComputerName": "HayabusaComputer1"
}
}
}
"##;
let event_record: Value = serde_json::from_str(json_str).unwrap();
assert!(utils::get_serde_number_to_string(&event_record["Event"]["EventData"]).is_none());
}
}
+3
View File
@@ -11,6 +11,7 @@ use hayabusa::omikuji::Omikuji;
use hayabusa::{afterfact::after_fact, detections::utils};
use hayabusa::{detections::configs, timeline::timeline::Timeline};
use hhmmss::Hhmmss;
use pbr::ProgressBar;
use serde_json::Value;
use std::collections::HashSet;
use std::{
@@ -152,12 +153,14 @@ fn analysis_files(evtx_files: Vec<PathBuf>) {
configs::CONFIG.read().unwrap().args.value_of("rules"),
&fill_ids,
);
let mut pb = ProgressBar::new(evtx_files.len() as u64);
let mut detection = detection::Detection::new(rule_files);
for evtx_file in evtx_files {
if configs::CONFIG.read().unwrap().args.is_present("verbose") {
println!("Checking target evtx FilePath: {:?}", &evtx_file);
}
detection = analysis_file(evtx_file, detection);
pb.inc();
}
after_fact();
detection.print_unique_results();