Merge pull request #598 from Yamato-Security/596-new-feature-exclude-status
new feature exclude status
This commit is contained in:
@@ -1,10 +1,11 @@
|
||||
# 変更点
|
||||
|
||||
## v1.4 [2022/XX/XX]
|
||||
## v1.4.0 [2022/XX/XX]
|
||||
|
||||
**新機能:**
|
||||
|
||||
- `--target-file-ext` オプションの追加。evtx以外の拡張子を指定する事ができます。ただし、ファイルの中身の形式はevtxファイル形式である必要があります。 (#586) (@hitenkoku)
|
||||
- `--exclude-status` オプションの追加。ルール内の`status`フィールドをもとに、読み込み対象から除外するフィルタを利用することができます。 (#596) (@hitenkoku)
|
||||
|
||||
**改善:**
|
||||
|
||||
@@ -36,6 +37,7 @@
|
||||
- `--rfc-3339` オプションの時刻表示形式を変更した。 (#574) (@hitenkoku)
|
||||
- `-R/ --display-record-id`オプションを`-R/ --hide-record-id`に変更。レコードIDはデフォルトで出力するようにして`-R`オプションを付けた際に表示しないように変更した。(#579) (@hitenkoku)
|
||||
- ルール読み込み時のメッセージを追加した。 (#583) (@hitenkoku)
|
||||
- `rules/tools/sigmac/testfiles`内のテスト用のymlファイルを読み込まないようにした. (#602) (@hitenkoku)
|
||||
|
||||
**バグ修正:**
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
# Changes
|
||||
|
||||
## v1.4 [2022/XX/XX]
|
||||
## v1.4.0 [2022/XX/XX]
|
||||
|
||||
**New Features:**
|
||||
|
||||
- Added `--target-file-ext` option. You can specify additional file extensions to scan in addtition to the default `.evtx` files. For example, `--target-file-ext evtx_data` or multiple extensions with `--target-file-ext evtx1 evtx2`. (#586) (@hitenkoku)
|
||||
- Added `--exclude-status` option: You can ignore rules based on their `status`. (#596) (@hitenkoku)
|
||||
|
||||
**Enhancements:**
|
||||
|
||||
@@ -12,6 +13,7 @@
|
||||
- Updated the default usage and help menu. (#387) (@hitenkoku)
|
||||
- Added default details output based on `rules/config/default_details.txt` when no `details` field in a rule is specified. (i.e. Sigma rules) (#359) (@hitenkoku)
|
||||
- Added saved file size output when `output` is specified. (#595) (@hitenkoku)
|
||||
- Ignored loading yml file in `rules/tools/sigmac/testfiles`. (#602) (@hitenkoku)
|
||||
|
||||
**Bug Fixes:**
|
||||
|
||||
|
||||
@@ -335,6 +335,7 @@ OPTIONS:
|
||||
-d, --directory <DIRECTORY> .evtxファイルを持つディレクトリのパス
|
||||
-D, --enable-deprecated-rules Deprecatedルールを有効にする
|
||||
--end-timeline <END_TIMELINE> 解析対象とするイベントログの終了時刻 (例: "2022-02-22 23:59:59 +09:00")
|
||||
--exclude-status <EXCLUDE_STATUS>... 読み込み対象外とするルール内でのステータス (ex: experimental) (ex: stable test)
|
||||
-f, --filepath <FILE_PATH> 1つの.evtxファイルに対して解析を行う
|
||||
-F, --full-data 全てのフィールド情報を出力する
|
||||
-h, --help ヘルプ情報を表示する
|
||||
|
||||
@@ -331,6 +331,7 @@ OPTIONS:
|
||||
-d, --directory <DIRECTORY> Directory of multiple .evtx files
|
||||
-D, --enable-deprecated-rules Enable rules marked as deprecated
|
||||
--end-timeline <END_TIMELINE> End time of the event logs to load (ex: "2022-02-22 23:59:59 +09:00")
|
||||
--exclude-status <EXCLUDE_STATUS>... Ignore rules according to status (ex: experimental) (ex: stable test)
|
||||
-f, --filepath <FILE_PATH> File path to one .evtx file
|
||||
-F, --full-data Print all field information
|
||||
-h, --help Print help information
|
||||
|
||||
@@ -32,6 +32,8 @@ lazy_static! {
|
||||
pub static ref TERM_SIZE: Option<(Width, Height)> = terminal_size();
|
||||
pub static ref TARGET_EXTENSIONS: HashSet<String> =
|
||||
get_target_extensions(CONFIG.read().unwrap().args.evtx_file_ext.as_ref());
|
||||
pub static ref EXCLUDE_STATUS: HashSet<String> =
|
||||
convert_option_vecs_to_hs(CONFIG.read().unwrap().args.exclude_status.as_ref());
|
||||
}
|
||||
|
||||
pub struct ConfigReader<'a> {
|
||||
@@ -211,6 +213,10 @@ pub struct Config {
|
||||
/// Specify additional target file extensions (ex: evtx_data) (ex: evtx1 evtx2)
|
||||
#[clap(long = "target-file-ext", multiple_values = true)]
|
||||
pub evtx_file_ext: Option<Vec<String>>,
|
||||
|
||||
/// Ignore rules according to status (ex: experimental) (ex: stable test)
|
||||
#[clap(long = "exclude-status", multiple_values = true)]
|
||||
pub exclude_status: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
impl ConfigReader<'_> {
|
||||
@@ -461,12 +467,17 @@ pub fn load_pivot_keywords(path: &str) {
|
||||
|
||||
/// --target-file-extで追加された拡張子から、調査対象ファイルの拡張子セットを返す関数
|
||||
pub fn get_target_extensions(arg: Option<&Vec<String>>) -> HashSet<String> {
|
||||
let mut target_file_extensions: HashSet<String> =
|
||||
arg.unwrap_or(&Vec::new()).iter().cloned().collect();
|
||||
let mut target_file_extensions: HashSet<String> = convert_option_vecs_to_hs(arg);
|
||||
target_file_extensions.insert(String::from("evtx"));
|
||||
target_file_extensions
|
||||
}
|
||||
|
||||
/// Option<Vec<String>>の内容をHashSetに変換する関数
|
||||
pub fn convert_option_vecs_to_hs(arg: Option<&Vec<String>>) -> HashSet<String> {
|
||||
let ret: HashSet<String> = arg.unwrap_or(&Vec::new()).iter().cloned().collect();
|
||||
ret
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EventInfo {
|
||||
pub evttitle: String,
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
extern crate csv;
|
||||
|
||||
use crate::detections::configs;
|
||||
use crate::detections::utils::write_color_buffer;
|
||||
use termcolor::{BufferWriter, Color, ColorChoice};
|
||||
|
||||
use crate::detections::pivot::insert_pivot_keyword;
|
||||
use crate::detections::print::AlertMessage;
|
||||
use crate::detections::print::DetectInfo;
|
||||
@@ -119,13 +122,11 @@ impl Detection {
|
||||
.filter_map(return_if_success)
|
||||
.collect();
|
||||
if !*LOGONSUMMARY_FLAG {
|
||||
let _ = &rulefile_loader
|
||||
.rule_load_cnt
|
||||
.insert(String::from("rule parsing error"), parseerror_count);
|
||||
Detection::print_rule_load_info(
|
||||
&rulefile_loader.rulecounter,
|
||||
&rulefile_loader.rule_load_cnt,
|
||||
&rulefile_loader.rule_status_cnt,
|
||||
&parseerror_count,
|
||||
);
|
||||
}
|
||||
ret
|
||||
@@ -363,46 +364,80 @@ impl Detection {
|
||||
rc: &HashMap<String, u128>,
|
||||
ld_rc: &HashMap<String, u128>,
|
||||
st_rc: &HashMap<String, u128>,
|
||||
err_rc: &u128,
|
||||
) {
|
||||
if *STATISTICS_FLAG {
|
||||
return;
|
||||
}
|
||||
let mut sorted_ld_rc: Vec<(&String, &u128)> = ld_rc.iter().collect();
|
||||
sorted_ld_rc.sort_by(|a, b| a.0.cmp(b.0));
|
||||
let args = &configs::CONFIG.read().unwrap().args;
|
||||
|
||||
sorted_ld_rc.into_iter().for_each(|(key, value)| {
|
||||
//タイトルに利用するものはascii文字であることを前提として1文字目を大文字にするように変更する
|
||||
println!(
|
||||
"{} rules: {}",
|
||||
make_ascii_titlecase(key.clone().as_mut()),
|
||||
value,
|
||||
);
|
||||
if value != &0_u128 {
|
||||
let disable_flag = if key == "noisy" && !args.enable_noisy_rules {
|
||||
" (Disabled)"
|
||||
} else {
|
||||
""
|
||||
};
|
||||
//タイトルに利用するものはascii文字であることを前提として1文字目を大文字にするように変更する
|
||||
println!(
|
||||
"{} rules: {}{}",
|
||||
make_ascii_titlecase(key.clone().as_mut()),
|
||||
value,
|
||||
disable_flag,
|
||||
);
|
||||
}
|
||||
});
|
||||
if err_rc != &0_u128 {
|
||||
write_color_buffer(
|
||||
&BufferWriter::stdout(ColorChoice::Always),
|
||||
Some(Color::Red),
|
||||
&format!("Rule parsing errors: {}", err_rc),
|
||||
)
|
||||
.ok();
|
||||
}
|
||||
println!();
|
||||
|
||||
let mut sorted_st_rc: Vec<(&String, &u128)> = st_rc.iter().collect();
|
||||
let total_loaded_rule_cnt: u128 = sorted_st_rc.iter().map(|(_, v)| v.to_owned()).sum();
|
||||
sorted_st_rc.sort_by(|a, b| a.0.cmp(b.0));
|
||||
sorted_st_rc.into_iter().for_each(|(key, value)| {
|
||||
let rate = if value == &0_u128 {
|
||||
0 as f64
|
||||
} else {
|
||||
(*value as f64) / (total_loaded_rule_cnt as f64) * 100.0
|
||||
};
|
||||
//タイトルに利用するものはascii文字であることを前提として1文字目を大文字にするように変更する
|
||||
println!(
|
||||
"{} rules: {} ({:.2}%)",
|
||||
make_ascii_titlecase(key.clone().as_mut()),
|
||||
value,
|
||||
rate
|
||||
);
|
||||
if value != &0_u128 {
|
||||
let rate = (*value as f64) / (total_loaded_rule_cnt as f64) * 100.0;
|
||||
let deprecated_flag = if key == "deprecated" && !args.enable_deprecated_rules {
|
||||
" (Disabled)"
|
||||
} else {
|
||||
""
|
||||
};
|
||||
//タイトルに利用するものはascii文字であることを前提として1文字目を大文字にするように変更する
|
||||
write_color_buffer(
|
||||
&BufferWriter::stdout(ColorChoice::Always),
|
||||
None,
|
||||
&format!(
|
||||
"{} rules: {} ({:.2}%){}",
|
||||
make_ascii_titlecase(key.clone().as_mut()),
|
||||
value,
|
||||
rate,
|
||||
deprecated_flag
|
||||
),
|
||||
)
|
||||
.ok();
|
||||
}
|
||||
});
|
||||
println!();
|
||||
|
||||
let mut sorted_rc: Vec<(&String, &u128)> = rc.iter().collect();
|
||||
sorted_rc.sort_by(|a, b| a.0.cmp(b.0));
|
||||
sorted_rc.into_iter().for_each(|(key, value)| {
|
||||
println!("{} rules: {}", key, value);
|
||||
write_color_buffer(
|
||||
&BufferWriter::stdout(ColorChoice::Always),
|
||||
None,
|
||||
&format!("{} rules: {}", key, value),
|
||||
)
|
||||
.ok();
|
||||
});
|
||||
|
||||
println!("Total enabled detection rules: {}", total_loaded_rule_cnt);
|
||||
println!();
|
||||
}
|
||||
|
||||
@@ -29,18 +29,16 @@ impl RuleExclude {
|
||||
pub fn exclude_ids() -> RuleExclude {
|
||||
let mut exclude_ids = RuleExclude::default();
|
||||
|
||||
if !configs::CONFIG.read().unwrap().args.enable_noisy_rules {
|
||||
exclude_ids.insert_ids(&format!(
|
||||
"{}/noisy_rules.txt",
|
||||
configs::CONFIG
|
||||
.read()
|
||||
.unwrap()
|
||||
.args
|
||||
.config
|
||||
.as_path()
|
||||
.display()
|
||||
));
|
||||
};
|
||||
exclude_ids.insert_ids(&format!(
|
||||
"{}/noisy_rules.txt",
|
||||
configs::CONFIG
|
||||
.read()
|
||||
.unwrap()
|
||||
.args
|
||||
.config
|
||||
.as_path()
|
||||
.display()
|
||||
));
|
||||
|
||||
exclude_ids.insert_ids(&format!(
|
||||
"{}/exclude_rules.txt",
|
||||
|
||||
55
src/yaml.rs
55
src/yaml.rs
@@ -2,9 +2,9 @@ extern crate serde_derive;
|
||||
extern crate yaml_rust;
|
||||
|
||||
use crate::detections::configs;
|
||||
use crate::detections::configs::EXCLUDE_STATUS;
|
||||
use crate::detections::print::AlertMessage;
|
||||
use crate::detections::print::ERROR_LOG_STACK;
|
||||
use crate::detections::print::QUIET_ERRORS_FLAG;
|
||||
use crate::detections::print::{ERROR_LOG_STACK, QUIET_ERRORS_FLAG};
|
||||
use crate::filter::RuleExclude;
|
||||
use hashbrown::HashMap;
|
||||
use std::ffi::OsStr;
|
||||
@@ -165,6 +165,19 @@ impl ParseYaml {
|
||||
return io::Result::Ok(ret);
|
||||
}
|
||||
|
||||
// ignore if tool test yml file in hayabusa-rules.
|
||||
if path
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.contains("rules/tools/sigmac/test_files")
|
||||
|| path
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.contains("rules\\tools\\sigmac\\test_files")
|
||||
{
|
||||
return io::Result::Ok(ret);
|
||||
}
|
||||
|
||||
// 個別のファイルの読み込みは即終了としない。
|
||||
let read_content = self.read_file(path);
|
||||
if read_content.is_err() {
|
||||
@@ -231,7 +244,28 @@ impl ParseYaml {
|
||||
} else {
|
||||
"noisy"
|
||||
};
|
||||
let entry = self.rule_load_cnt.entry(entry_key.to_string()).or_insert(0);
|
||||
// テスト用のルール(ID:000...0)の場合はexcluded ruleのカウントから除外するようにする
|
||||
if v != "00000000-0000-0000-0000-000000000000" {
|
||||
let entry =
|
||||
self.rule_load_cnt.entry(entry_key.to_string()).or_insert(0);
|
||||
*entry += 1;
|
||||
}
|
||||
if entry_key == "excluded"
|
||||
|| (entry_key == "noisy"
|
||||
&& !configs::CONFIG.read().unwrap().args.enable_noisy_rules)
|
||||
{
|
||||
return Option::None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let status = &yaml_doc["status"].as_str();
|
||||
if let Some(s) = status {
|
||||
if EXCLUDE_STATUS.contains(&s.to_string()) {
|
||||
let entry = self
|
||||
.rule_load_cnt
|
||||
.entry("excluded".to_string())
|
||||
.or_insert(0);
|
||||
*entry += 1;
|
||||
return Option::None;
|
||||
}
|
||||
@@ -271,19 +305,6 @@ impl ParseYaml {
|
||||
if doc_level_num < args_level_num {
|
||||
return Option::None;
|
||||
}
|
||||
|
||||
if !configs::CONFIG.read().unwrap().args.enable_deprecated_rules {
|
||||
let rule_status = &yaml_doc["status"].as_str().unwrap_or_default();
|
||||
if *rule_status == "deprecated" {
|
||||
let entry = self
|
||||
.rule_status_cnt
|
||||
.entry(rule_status.to_string())
|
||||
.or_insert(0);
|
||||
*entry += 1;
|
||||
return Option::None;
|
||||
}
|
||||
}
|
||||
|
||||
Option::Some((filepath, yaml_doc))
|
||||
})
|
||||
.collect();
|
||||
@@ -439,7 +460,7 @@ mod tests {
|
||||
yaml.read_dir(path, "", &exclude_ids).unwrap();
|
||||
assert_eq!(
|
||||
yaml.rule_status_cnt.get("deprecated").unwrap().to_owned(),
|
||||
2
|
||||
1
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user