add: customize csv and display fmt from profile #165

This commit is contained in:
DastInDark
2022-07-24 19:34:02 +09:00
parent 830644acee
commit b7264082e8
5 changed files with 51 additions and 123 deletions

View File

@@ -27,41 +27,6 @@ use std::process;
use termcolor::{BufferWriter, Color, ColorChoice, ColorSpec, WriteColor};
use terminal_size::Width;
#[derive(Debug, Serialize)]
#[serde(rename_all = "PascalCase")]
pub struct CsvFormat<'a> {
timestamp: &'a str,
computer: &'a str,
channel: &'a str,
event_i_d: &'a str,
level: &'a str,
mitre_attack: &'a str,
#[serde(skip_serializing_if = "Option::is_none")]
record_i_d: Option<&'a str>,
rule_title: &'a str,
details: &'a str,
#[serde(skip_serializing_if = "Option::is_none")]
record_information: Option<&'a str>,
rule_file: &'a str,
evtx_file: &'a str,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "PascalCase")]
pub struct DisplayFormat<'a> {
timestamp: &'a str,
pub computer: &'a str,
pub channel: &'a str,
pub event_i_d: &'a str,
pub level: &'a str,
#[serde(skip_serializing_if = "Option::is_none")]
record_i_d: Option<&'a str>,
pub rule_title: &'a str,
pub details: &'a str,
#[serde(skip_serializing_if = "Option::is_none")]
pub record_information: Option<&'a str>,
}
lazy_static! {
pub static ref OUTPUT_COLOR: HashMap<String, Color> = set_output_color();
}
@@ -251,50 +216,13 @@ fn emit_csv<W: std::io::Write>(
timestamps.push(_get_timestamp(time));
for detect_info in detect_infos {
detected_record_idset.insert(format!("{}_{}", time, detect_info.eventid));
let level = detect_info.level.to_string();
let time_str = format_time(time, false);
if displayflag {
let record_id = detect_info
.record_id
.as_ref()
.map(|recinfo| _format_cellpos(recinfo, ColPos::Other));
let recinfo = detect_info
.record_information
.as_ref()
.map(|recinfo| _format_cellpos(recinfo, ColPos::Last));
let ctr_char_exclude_details = detect_info
.detail
.chars()
.filter(|&c| !c.is_control())
.collect::<String>();
let details = if ctr_char_exclude_details.is_empty() {
"-".to_string()
} else {
ctr_char_exclude_details
};
let dispformat: _ = DisplayFormat {
timestamp: &_format_cellpos(&time_str, ColPos::First),
level: &_format_cellpos(
level_abbr.get(&level).unwrap_or(&level),
ColPos::Other,
),
computer: &_format_cellpos(&detect_info.computername, ColPos::Other),
event_i_d: &_format_cellpos(&detect_info.eventid, ColPos::Other),
channel: &_format_cellpos(&detect_info.channel, ColPos::Other),
rule_title: &_format_cellpos(&detect_info.alert, ColPos::Other),
details: &_format_cellpos(&details, ColPos::Other),
record_information: recinfo.as_deref(),
record_i_d: record_id.as_deref(),
};
//ヘッダーのみを出力
if plus_header {
write_color_buffer(
&disp_wtr,
get_writable_color(None),
&_get_serialized_disp_output(None),
&_get_serialized_disp_output(PROFILES.as_ref().unwrap().clone(), true),
true,
)
.ok();
@@ -302,31 +230,18 @@ fn emit_csv<W: std::io::Write>(
}
write_color_buffer(
&disp_wtr,
get_writable_color(_get_output_color(&color_map, &detect_info.level)),
&_get_serialized_disp_output(Some(dispformat)),
get_writable_color(_get_output_color(&color_map, LEVEL_ABBR.get(&detect_info.level).unwrap_or(&String::default()))),
&_get_serialized_disp_output(detect_info.ext_field.clone(), false),
false,
)
.ok();
} else {
// csv output format
wtr.serialize(CsvFormat {
timestamp: &time_str,
level: level_abbr.get(&level).unwrap_or(&level).trim(),
computer: &detect_info.computername,
event_i_d: &detect_info.eventid,
channel: &detect_info.channel,
mitre_attack: &detect_info.tag_info,
rule_title: &detect_info.alert,
details: &detect_info.detail,
record_information: detect_info.record_information.as_deref(),
evtx_file: &detect_info.filepath,
rule_file: Path::new(&detect_info.rulepath)
.file_name()
.unwrap()
.to_str()
.unwrap(),
record_i_d: detect_info.record_id.as_deref(),
})?;
if plus_header {
wtr.write_record(detect_info.ext_field.keys())?;
plus_header = false;
}
wtr.write_record(detect_info.ext_field.values())?;
}
let level_suffix = *configs::LEVELMAP
.get(&detect_info.level.to_uppercase())
@@ -470,24 +385,24 @@ enum ColPos {
Other,
}
fn _get_serialized_disp_output(dispformat: Option<DisplayFormat>) -> String {
if dispformat.is_none() {
let mut titles = vec![
"Timestamp",
"Computer",
"Channel",
"EventID",
"Level",
"RuleTitle",
"Details",
];
if !*IS_HIDE_RECORD_ID {
titles.insert(5, "RecordID");
fn _get_serialized_disp_output(mut data: LinkedHashMap<String, String>, header: bool) -> String {
let data_length = &data.len();
let entries = data.entries();
let mut ret:Vec<String> = vec![];
if header {
entries.for_each(|entry|{
ret.push(entry.key().to_owned());
});
} else {
entries.enumerate().for_each(|(i, entry)|{
if i == 0 {
ret.push(_format_cellpos(entry.get(), ColPos::First))
} else if i == data_length - 1{
ret.push(_format_cellpos(entry.get(), ColPos::Last))
} else {
ret.push(_format_cellpos(entry.get(), ColPos::Other))
}
if configs::CONFIG.read().unwrap().args.full_data {
titles.push("RecordInformation");
}
return titles.join("|");
});
}
let mut disp_serializer = csv::WriterBuilder::new()
.double_quote(false)
@@ -496,8 +411,7 @@ fn _get_serialized_disp_output(dispformat: Option<DisplayFormat>) -> String {
.has_headers(false)
.from_writer(vec![]);
disp_serializer.serialize(dispformat.unwrap()).ok();
disp_serializer.write_record(ret).ok();
String::from_utf8(disp_serializer.into_inner().unwrap_or_default()).unwrap_or_default()
}

View File

@@ -2,12 +2,15 @@ extern crate csv;
use crate::detections::configs;
use crate::detections::utils::write_color_buffer;
use crate::options::profile;
use crate::options::profile::PROFILES;
use linked_hash_map::LinkedHashMap;
use termcolor::{BufferWriter, Color, ColorChoice};
use crate::detections::message::AlertMessage;
use crate::detections::message::DetectInfo;
use crate::detections::message::ERROR_LOG_STACK;
use crate::detections::message::{CH_CONFIG, DEFAULT_DETAILS, IS_HIDE_RECORD_ID, TAGS_CONFIG};
use crate::detections::message::{CH_CONFIG, DEFAULT_DETAILS, TAGS_CONFIG};
use crate::detections::message::{
LOGONSUMMARY_FLAG, PIVOT_KEYWORD_LIST_FLAG, QUIET_ERRORS_FLAG, STATISTICS_FLAG,
};
@@ -202,8 +205,12 @@ impl Detection {
rule
}
/// 条件に合致したレコードを表示するための関数
/// 条件に合致したレコードを格納するための関数
fn insert_message(rule: &RuleNode, record_info: &EvtxRecordInfo) {
let profile_all_alias = if PROFILES.is_some() {
PROFILES.as_ref().unwrap().values().cloned().collect::<Vec<_>>().join("|")
}
else{String::default()};
let tag_info: Vec<String> = match TAGS_CONFIG.is_empty() {
false => rule.yaml["tags"]
.as_vec()
@@ -229,7 +236,7 @@ impl Detection {
.record_information
.as_ref()
.map(|recinfo| recinfo.to_string());
let rec_id = if !*IS_HIDE_RECORD_ID {
let rec_id = if !profile_all_alias.contains("%RecordID%") {
Some(
get_serde_number_to_string(&record_info.record["Event"]["System"]["EventRecordID"])
.unwrap_or_default(),
@@ -268,6 +275,7 @@ impl Detection {
tag_info: tag_info.join(" | "),
record_information: opt_record_info,
record_id: rec_id,
ext_field: PROFILES.as_ref().unwrap().to_owned(),
};
message::insert(
&record_info.record,
@@ -311,7 +319,8 @@ impl Detection {
detail: output,
record_information: rec_info,
tag_info: tag_info.join(" : "),
record_id: rec_id,
record_id: Some("-".to_owned()),
ext_field: PROFILES.as_ref().unwrap().to_owned(),
};
message::insert_message(detect_info, agg_result.start_timedate)

View File

@@ -20,6 +20,8 @@ use std::path::Path;
use std::sync::Mutex;
use termcolor::{BufferWriter, ColorChoice};
use super::utils::format_time;
#[derive(Debug, Clone)]
pub struct DetectInfo {
pub filepath: String,
@@ -33,6 +35,7 @@ pub struct DetectInfo {
pub tag_info: String,
pub record_information: Option<String>,
pub record_id: Option<String>,
pub ext_field: LinkedHashMap<String, String>,
}
pub struct AlertMessage {}

View File

@@ -21,7 +21,7 @@ use hayabusa::detections::pivot::PivotKeyword;
use hayabusa::detections::pivot::PIVOT_KEYWORD;
use hayabusa::detections::rule::{get_detection_keys, RuleNode};
use hayabusa::omikuji::Omikuji;
use hayabusa::options::{level_tuning::LevelTuning, update_rules::UpdateRules};
use hayabusa::options::{level_tuning::LevelTuning, update_rules::UpdateRules, profile::PROFILES};
use hayabusa::{afterfact::after_fact, detections::utils};
use hayabusa::{detections::configs, timeline::timelines::Timeline};
use hayabusa::{detections::utils::write_color_buffer, filter};

View File

@@ -10,7 +10,7 @@ use std::path::Path;
use yaml_rust::{Yaml, YamlEmitter, YamlLoader};
lazy_static! {
pub static ref PROFILES: Option<HashMap<String, String>> = load_profile(
pub static ref PROFILES: Option<LinkedHashMap<String, String>> = load_profile(
check_setting_path(
&CURRENT_EXE_PATH.to_path_buf(),
"config/default_profile.txt"
@@ -46,7 +46,7 @@ fn read_profile_data(profile_path: &str) -> Result<Vec<Yaml>, String> {
pub fn load_profile(
default_profile_path: &str,
profile_path: &str,
) -> Option<HashMap<String, String>> {
) -> Option<LinkedHashMap<String, String>> {
let conf = &configs::CONFIG.read().unwrap().args;
let profile_all: Vec<Yaml> = if conf.profile.is_none() {
match read_profile_data(default_profile_path) {
@@ -71,7 +71,7 @@ pub fn load_profile(
return None;
}
let profile_data = &profile_all[0];
let mut ret: HashMap<String, String> = HashMap::new();
let mut ret: LinkedHashMap<String, String> = LinkedHashMap::new();
if let Some(profile_name) = &conf.profile {
if !profile_data[profile_name.as_str()].is_badvalue() {
profile_data[profile_name.as_str()].clone().as_hash().unwrap().into_iter().for_each(|(k, v)| {
@@ -83,8 +83,10 @@ pub fn load_profile(
None
}
} else {
AlertMessage::alert("Not specified --profile").ok();
None
profile_all[0].clone().as_hash().unwrap().into_iter().for_each(|(k, v)| {
ret.insert(k.as_str().unwrap().to_string(), v.as_str().unwrap().to_string());
});
Some(ret)
}
}