logon summary (#523)

* logon summary #110

* logon summary #110

* english update

* add sort #110

* add sort #110

* Formatting the output string

* Fixed the check process.

* added document #110

* Fixed login failure eventID.

* Fixed clipy err

* prevent rule load output with logon-summary option #110

* fixed bug of  level-tuning execute when option is -s or -L only #110

Co-authored-by: garigariganzy <tosada31@hotmail.co.jp>
Co-authored-by: Tanaka Zakku <71482215+YamatoSecurity@users.noreply.github.com>
This commit is contained in:
DustInDark
2022-05-17 09:36:45 +09:00
committed by GitHub
parent 9092cc2301
commit d654c2cb6b
12 changed files with 264 additions and 14 deletions

View File

@@ -90,6 +90,7 @@ fn build_app<'a>() -> ArgMatches<'a> {
-U --utc 'Output time in UTC format. (Default: local time)'
-t --thread-number=[NUMBER] 'Thread number. (Default: Optimal number for performance.)'
-s --statistics 'Prints statistics of event IDs.'
-L --logon-summary 'User logon and failed logon summary'
-q --quiet 'Quiet mode. Do not display the launch banner.'
-Q --quiet-errors 'Quiet errors mode. Do not save error logs.'
-p --pivot-keywords-list 'Create a list of pivot keywords.'

View File

@@ -121,11 +121,18 @@ impl Detection {
.map(|rule_file_tuple| rule::create_rule(rule_file_tuple.0, rule_file_tuple.1))
.filter_map(return_if_success)
.collect();
Detection::print_rule_load_info(
&rulefile_loader.rulecounter,
&parseerror_count,
&rulefile_loader.ignorerule_count,
);
if !configs::CONFIG
.read()
.unwrap()
.args
.is_present("logon-summary")
{
Detection::print_rule_load_info(
&rulefile_loader.rulecounter,
&parseerror_count,
&rulefile_loader.ignorerule_count,
);
}
ret
}

View File

@@ -55,6 +55,11 @@ lazy_static! {
.unwrap()
.args
.is_present("statistics");
pub static ref LOGONSUMMARY_FLAG: bool = configs::CONFIG
.read()
.unwrap()
.args
.is_present("logon-summary");
pub static ref TAGS_CONFIG: HashMap<String, String> =
Message::create_output_filter_config("config/output_tag.txt");
pub static ref CH_CONFIG: HashMap<String, String> =

View File

@@ -6,3 +6,5 @@ pub mod omikuji;
pub mod options;
pub mod timeline;
pub mod yaml;
#[macro_use]
extern crate prettytable;

View File

@@ -13,8 +13,8 @@ use hayabusa::detections::configs::load_pivot_keywords;
use hayabusa::detections::detection::{self, EvtxRecordInfo};
use hayabusa::detections::pivot::PIVOT_KEYWORD;
use hayabusa::detections::print::{
AlertMessage, ERROR_LOG_PATH, ERROR_LOG_STACK, PIVOT_KEYWORD_LIST_FLAG, QUIET_ERRORS_FLAG,
STATISTICS_FLAG,
AlertMessage, ERROR_LOG_PATH, ERROR_LOG_STACK, LOGONSUMMARY_FLAG, PIVOT_KEYWORD_LIST_FLAG,
QUIET_ERRORS_FLAG, STATISTICS_FLAG,
};
use hayabusa::detections::rule::{get_detection_keys, RuleNode};
use hayabusa::filter;
@@ -176,6 +176,10 @@ impl App {
println!("Generating Event ID Statistics");
println!();
}
if *LOGONSUMMARY_FLAG {
println!("Generating Logons Summary");
println!();
}
if configs::CONFIG
.read()
.unwrap()
@@ -229,6 +233,9 @@ impl App {
.unwrap()
.args
.is_present("level-tuning")
&& std::env::args()
.into_iter()
.any(|arg| arg.contains("level-tuning"))
{
let level_tuning_config_path = configs::CONFIG
.read()
@@ -458,7 +465,7 @@ impl App {
pb.inc();
}
detection.add_aggcondition_msges(&self.rt);
if !*STATISTICS_FLAG && !*PIVOT_KEYWORD_LIST_FLAG {
if !(*STATISTICS_FLAG || *LOGONSUMMARY_FLAG || *PIVOT_KEYWORD_LIST_FLAG) {
after_fact();
}
}
@@ -531,13 +538,14 @@ impl App {
// timeline機能の実行
tl.start(&records_per_detect);
if !*STATISTICS_FLAG {
if !(*STATISTICS_FLAG || *LOGONSUMMARY_FLAG) {
// ruleファイルの検知
detection = detection.start(&self.rt, records_per_detect);
}
}
tl.tm_stats_dsp_msg();
tl.tm_logon_stats_dsp_msg();
detection
}

View File

@@ -8,6 +8,7 @@ pub struct EventStatistics {
pub start_time: String,
pub end_time: String,
pub stats_list: HashMap<String, usize>,
pub stats_login_list: HashMap<String, [usize; 2]>,
}
/**
* Windows Event Logの統計情報を出力する
@@ -19,6 +20,7 @@ impl EventStatistics {
start_time: String,
end_time: String,
stats_list: HashMap<String, usize>,
stats_login_list: HashMap<String, [usize; 2]>,
) -> EventStatistics {
EventStatistics {
total,
@@ -26,10 +28,11 @@ impl EventStatistics {
start_time,
end_time,
stats_list,
stats_login_list,
}
}
pub fn start(&mut self, records: &[EvtxRecordInfo]) {
pub fn evt_stats_start(&mut self, records: &[EvtxRecordInfo]) {
// 引数でstatisticsオプションが指定されている時だけ、統計情報を出力する。
if !configs::CONFIG
.read()
@@ -49,6 +52,22 @@ impl EventStatistics {
self.stats_eventid(records);
}
pub fn logon_stats_start(&mut self, records: &[EvtxRecordInfo]) {
// 引数でstatisticsオプションが指定されている時だけ、統計情報を出力する。
if !configs::CONFIG
.read()
.unwrap()
.args
.is_present("logon-summary")
{
return;
}
self.stats_time_cnt(records);
self.stats_login_eventid(records);
}
fn stats_time_cnt(&mut self, records: &[EvtxRecordInfo]) {
if records.is_empty() {
return;
@@ -93,4 +112,29 @@ impl EventStatistics {
}
// return evtstat_map;
}
// Login event
fn stats_login_eventid(&mut self, records: &[EvtxRecordInfo]) {
for record in records.iter() {
let evtid = utils::get_event_value("EventID", &record.record);
if evtid.is_none() {
continue;
}
let username = utils::get_event_value("TargetUserName", &record.record);
let idnum = evtid.unwrap();
let countlist: [usize; 2] = [0, 0];
if idnum == 4624 {
let count: &mut [usize; 2] = self
.stats_login_list
.entry(username.unwrap().to_string())
.or_insert(countlist);
count[0] += 1;
} else if idnum == 4625 {
let count: &mut [usize; 2] = self
.stats_login_list
.entry(username.unwrap().to_string())
.or_insert(countlist);
count[1] += 1;
}
}
}
}

View File

@@ -1,4 +1,5 @@
use crate::detections::{configs, detection::EvtxRecordInfo};
use prettytable::{Cell, Row, Table};
use super::statistics::EventStatistics;
use hashbrown::HashMap;
@@ -21,13 +22,16 @@ impl Timeline {
let starttm = "".to_string();
let endtm = "".to_string();
let statslst = HashMap::new();
let statsloginlst = HashMap::new();
let statistic = EventStatistics::new(totalcnt, filepath, starttm, endtm, statslst);
let statistic =
EventStatistics::new(totalcnt, filepath, starttm, endtm, statslst, statsloginlst);
Timeline { stats: statistic }
}
pub fn start(&mut self, records: &[EvtxRecordInfo]) {
self.stats.start(records);
self.stats.evt_stats_start(records);
self.stats.logon_stats_start(records);
}
pub fn tm_stats_dsp_msg(&mut self) {
@@ -64,6 +68,31 @@ impl Timeline {
println!("{}", msgprint);
}
}
pub fn tm_logon_stats_dsp_msg(&mut self) {
if !configs::CONFIG
.read()
.unwrap()
.args
.is_present("logon-summary")
{
return;
}
// 出力メッセージ作成
let mut sammsges: Vec<String> = Vec::new();
sammsges.push("---------------------------------------".to_string());
sammsges.push(format!("Evtx File Path: {}", self.stats.filepath));
sammsges.push(format!("Total Event Records: {}\n", self.stats.total));
sammsges.push(format!("First Timestamp: {}", self.stats.start_time));
sammsges.push(format!("Last Timestamp: {}\n", self.stats.end_time));
sammsges.push("---------------------------------------".to_string());
for msgprint in sammsges.iter() {
println!("{}", msgprint);
}
self.tm_loginstats_tb_set_msg();
}
// イベントID毎の出力メッセージ生成
fn tm_stats_set_msg(&self, mapsorted: Vec<(&std::string::String, &usize)>) -> Vec<String> {
let mut msges: Vec<String> = Vec::new();
@@ -101,4 +130,38 @@ impl Timeline {
msges.push("---------------------------------------".to_string());
msges
}
// ユーザ毎のログイン統計情報出力メッセージ生成
fn tm_loginstats_tb_set_msg(&self) {
println!("Logon Summary");
if self.stats.stats_login_list.is_empty() {
let mut loginmsges: Vec<String> = Vec::new();
loginmsges.push("-----------------------------------------".to_string());
loginmsges.push("| No logon events were detected. |".to_string());
loginmsges.push("-----------------------------------------\n".to_string());
for msgprint in loginmsges.iter() {
println!("{}", msgprint);
}
} else {
let mut logins_stats_tb = Table::new();
logins_stats_tb.set_titles(row!["User", "Failed", "Successful"]);
// 集計件数でソート
let mut mapsorted: Vec<_> = self.stats.stats_login_list.iter().collect();
mapsorted.sort_by(|x, y| x.0.cmp(y.0));
for (key, values) in &mapsorted {
let mut username: String = key.to_string();
//key.to_string().retain(|c| c != '\"');
//key.to_string().pop();
username.pop();
username.remove(0);
logins_stats_tb.add_row(Row::new(vec![
Cell::new(&username),
Cell::new(&values[1].to_string()),
Cell::new(&values[0].to_string()),
]));
}
logins_stats_tb.printstd();
println!();
}
}
}