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:
@@ -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.'
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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> =
|
||||
|
||||
@@ -6,3 +6,5 @@ pub mod omikuji;
|
||||
pub mod options;
|
||||
pub mod timeline;
|
||||
pub mod yaml;
|
||||
#[macro_use]
|
||||
extern crate prettytable;
|
||||
|
||||
16
src/main.rs
16
src/main.rs
@@ -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
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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!();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user