Add: sigma rules (#175)

This commit is contained in:
itiB
2021-11-22 08:45:44 +09:00
committed by GitHub
parent b53342218c
commit 034f9c0957
1086 changed files with 40715 additions and 192 deletions

View File

@@ -21,13 +21,15 @@ const DIRPATH_RULES: &str = "rules";
pub struct EvtxRecordInfo {
pub evtx_filepath: String, // イベントファイルのファイルパス ログで出力するときに使う
pub record: Value, // 1レコード分のデータをJSON形式にシリアライズしたもの
pub data_string: String,
}
impl EvtxRecordInfo {
pub fn new(evtx_filepath: String, record: Value) -> EvtxRecordInfo {
pub fn new(evtx_filepath: String, record: Value, data_string: String) -> EvtxRecordInfo {
return EvtxRecordInfo {
evtx_filepath: evtx_filepath,
record: record,
data_string,
};
}
}
@@ -144,7 +146,7 @@ impl Detection {
let records = &*records;
let agg_condition = rule.has_agg_condition();
for record_info in records {
let result = rule.select(&record_info.evtx_filepath, &record_info.record);
let result = rule.select(&record_info.evtx_filepath, &record_info);
if !result {
continue;
}

View File

@@ -506,6 +506,7 @@ impl ConditionCompiler {
mod tests {
use yaml_rust::YamlLoader;
use crate::detections::detection::EvtxRecordInfo;
use crate::detections::rule::create_rule;
use crate::detections::rule::tests::parse_rule_from_str;
@@ -537,8 +538,13 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_str) {
Ok(record) => {
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: String::default(),
};
assert_eq!(
rule_node.select(&"testpath".to_owned(), &record),
rule_node.select(&"testpath".to_owned(), &recinfo),
expect_select
);
}
@@ -581,7 +587,12 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
assert_eq!(rule_node.select(&"testpath".to_owned(), &record), true);
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: String::default(),
};
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), true);
}
Err(_rec) => {
assert!(false, "failed to parse json record.");
@@ -623,7 +634,12 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
assert_eq!(rule_node.select(&"testpath".to_owned(), &record), false);
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: String::default(),
};
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), false);
}
Err(_rec) => {
assert!(false, "failed to parse json record.");

View File

@@ -341,6 +341,7 @@ pub fn judge_timeframe(
#[cfg(test)]
mod tests {
use crate::detections::detection::EvtxRecordInfo;
use crate::detections::rule::create_rule;
use crate::detections::rule::AggResult;
use std::collections::HashMap;
@@ -666,7 +667,12 @@ mod tests {
for record in target {
match serde_json::from_str(record) {
Ok(rec) => {
let _result = rule_node.select(&"testpath".to_string(), &rec);
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: rec,
data_string: String::default(),
};
let _result = rule_node.select(&"testpath".to_string(), &recinfo);
}
Err(_rec) => {
assert!(false, "failed to parse json record.");
@@ -754,7 +760,12 @@ mod tests {
for record_str in records_str {
match serde_json::from_str(record_str) {
Ok(record) => {
let result = &rule_node.select(&"testpath".to_owned(), &record);
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: String::default(),
};
let result = &rule_node.select(&"testpath".to_owned(), &recinfo);
assert_eq!(result, &true);
}
Err(_rec) => {

View File

@@ -3,7 +3,7 @@ use serde_json::Value;
use std::collections::VecDeque;
use yaml_rust::Yaml;
use crate::detections::utils;
use crate::detections::{detection::EvtxRecordInfo, utils};
use mopa::mopafy;
// 末端ードがEventLogの値を比較するロジックを表す。
@@ -18,7 +18,7 @@ pub trait LeafMatcher: mopa::Any {
/// 引数に指定されたJSON形式のデータがマッチするかどうか判定する。
/// main.rsでWindows Event LogをJSON形式に変換していて、そのJSON形式のWindowsのイベントログデータがここには来る
/// 例えば正規表現でマッチするロジックなら、ここに正規表現でマッチさせる処理を書く。
fn is_match(&self, event_value: Option<&Value>) -> bool;
fn is_match(&self, event_value: Option<&Value>, recinfo: &EvtxRecordInfo) -> bool;
/// 初期化ロジックをここに記載します。
/// ルールファイルの書き方が間違っている等の原因により、正しくルールファイルからパースできない場合、戻り値のResult型でエラーを返してください。
@@ -60,7 +60,7 @@ impl LeafMatcher for MinlengthMatcher {
return Result::Ok(());
}
fn is_match(&self, event_value: Option<&Value>) -> bool {
fn is_match(&self, event_value: Option<&Value>, _recinfo: &EvtxRecordInfo) -> bool {
return match event_value.unwrap_or(&Value::Null) {
Value::String(s) => s.len() as i64 >= self.min_len,
Value::Number(n) => n.to_string().len() as i64 >= self.min_len,
@@ -118,7 +118,7 @@ impl LeafMatcher for RegexesFileMatcher {
return Result::Ok(());
}
fn is_match(&self, event_value: Option<&Value>) -> bool {
fn is_match(&self, event_value: Option<&Value>, _recinfo: &EvtxRecordInfo) -> bool {
//TODO Wildcardの場合、CaseInsensitiveなので、ToLowerする。
return match event_value.unwrap_or(&Value::Null) {
Value::String(s) => !utils::check_regex(s, &self.regexes),
@@ -177,7 +177,7 @@ impl LeafMatcher for AllowlistFileMatcher {
return Result::Ok(());
}
fn is_match(&self, event_value: Option<&Value>) -> bool {
fn is_match(&self, event_value: Option<&Value>, _recinfo: &EvtxRecordInfo) -> bool {
return match event_value.unwrap_or(&Value::Null) {
Value::String(s) => !utils::check_allowlist(s, &self.regexes),
Value::Number(n) => !utils::check_allowlist(&n.to_string(), &self.regexes),
@@ -192,6 +192,7 @@ impl LeafMatcher for AllowlistFileMatcher {
pub struct DefaultMatcher {
re: Option<Regex>,
pipes: Vec<PipeElement>,
key_list: Vec<String>,
}
impl DefaultMatcher {
@@ -199,20 +200,21 @@ impl DefaultMatcher {
return DefaultMatcher {
re: Option::None,
pipes: Vec::new(),
key_list: Vec::new(),
};
}
/// このmatcherの正規表現とマッチするかどうか判定します。
/// 判定対象の文字列とこのmatcherが保持する正規表現が完全にマッチした場合のTRUEを返します。
/// 例えば、判定対象文字列が"abc"で、正規表現が"ab"の場合、正規表現は判定対象文字列の一部分にしか一致していないので、この関数はfalseを返します。
fn is_regex_fullmatch(&self, value: String) -> bool {
fn is_regex_fullmatch(&self, value: &String) -> bool {
return self
.re
.as_ref()
.unwrap()
.find_iter(&value)
.any(|match_obj| {
return match_obj.as_str().to_string() == value;
return match_obj.as_str() == value;
});
}
@@ -228,7 +230,7 @@ impl DefaultMatcher {
impl LeafMatcher for DefaultMatcher {
fn is_target_key(&self, key_list: &Vec<String>) -> bool {
if key_list.len() == 1 {
if key_list.len() <= 1 {
return true;
}
@@ -236,6 +238,7 @@ impl LeafMatcher for DefaultMatcher {
}
fn init(&mut self, key_list: &Vec<String>, select_value: &Yaml) -> Result<(), Vec<String>> {
self.key_list = key_list.to_vec();
if select_value.is_null() {
return Result::Ok(());
}
@@ -258,7 +261,8 @@ impl LeafMatcher for DefaultMatcher {
let pattern = yaml_value.unwrap();
// Pipeが指定されていればパースする
let mut keys: VecDeque<&str> = key_list.get(0).unwrap().split("|").collect(); // key_listが空はあり得ない
let emp = String::default();
let mut keys: VecDeque<&str> = key_list.get(0).unwrap_or(&emp).split("|").collect(); // key_listが空はあり得ない
keys.pop_front();
while !keys.is_empty() {
let key = keys.pop_front().unwrap();
@@ -316,7 +320,7 @@ impl LeafMatcher for DefaultMatcher {
return Result::Ok(());
}
fn is_match(&self, event_value: Option<&Value>) -> bool {
fn is_match(&self, event_value: Option<&Value>, recinfo: &EvtxRecordInfo) -> bool {
// unwrap_orの引数に""ではなく" "を指定しているのは、
// event_valueが文字列じゃない場合にis_event_value_nullの値がfalseになるように、len() == 0とならない値を指定している。
let is_event_value_null = event_value.is_none()
@@ -324,16 +328,22 @@ impl LeafMatcher for DefaultMatcher {
|| event_value.unwrap().as_str().unwrap_or(" ").len() == 0;
// yamlにnullが設定されていた場合
if self.re.is_none() {
// keylistが空(==JSONのgrep検索)の場合、無視する。
if !self.key_list.is_empty() && self.re.is_none() {
return is_event_value_null;
}
// JSON形式のEventLogデータをstringに変換
let event_value_str = match event_value.unwrap_or(&Value::Null) {
Value::Bool(b) => Option::Some(b.to_string()),
Value::String(s) => Option::Some(s.to_owned()),
Value::Number(n) => Option::Some(n.to_string()),
_ => Option::None,
let event_value_str: Option<String> = if self.key_list.is_empty() {
Option::Some(recinfo.data_string.to_string())
} else {
let value = match event_value.unwrap_or(&Value::Null) {
Value::Bool(b) => Option::Some(b.to_string()),
Value::String(s) => Option::Some(s.to_string()),
Value::Number(n) => Option::Some(n.to_string()),
_ => Option::None,
};
value
};
if event_value_str.is_none() {
return false;
@@ -346,7 +356,13 @@ impl LeafMatcher for DefaultMatcher {
return pipe.pipe_eventlog_data(acc);
});
return self.is_regex_fullmatch(event_value_str);
if self.key_list.is_empty() {
// この場合ただのgrep検索なので、ただ正規表現に一致するかどうか調べればよいだけ
return self.re.as_ref().unwrap().is_match(&event_value_str);
} else {
// 通常の検索はこっち
return self.is_regex_fullmatch(&event_value_str);
}
}
}
@@ -363,7 +379,7 @@ impl PipeElement {
/// WindowsEventLogのJSONデータに対してパイプ処理します。
fn pipe_eventlog_data(&self, pattern: String) -> String {
return match self {
// wildcardはcase sensetiveなので、全て小文字にして比較する。
// wildcardはcase insensetiveなので、全て小文字にして比較する。
PipeElement::Wildcard => pattern.to_lowercase(),
_ => pattern,
};
@@ -498,7 +514,9 @@ mod tests {
use super::super::selectionnodes::{
AndSelectionNode, LeafSelectionNode, OrSelectionNode, SelectionNode,
};
use crate::detections::detection::EvtxRecordInfo;
use crate::detections::rule::tests::parse_rule_from_str;
use serde_json::Value;
#[test]
fn test_rule_parse() {
@@ -720,7 +738,12 @@ mod tests {
match serde_json::from_str(record_json_str) {
Ok(record) => {
assert_eq!(rule_node.select(&"testpath".to_owned(), &record), false);
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: String::default(),
};
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), false);
}
Err(_) => {
assert!(false, "failed to parse json record.");
@@ -748,7 +771,12 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
assert_eq!(rule_node.select(&"testpath".to_owned(), &record), false);
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: String::default(),
};
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), false);
}
Err(_) => {
assert!(false, "failed to parse json record.");
@@ -776,7 +804,12 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
assert_eq!(rule_node.select(&"testpath".to_owned(), &record), true);
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: String::default(),
};
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), true);
}
Err(_) => {
assert!(false, "failed to parse json record.");
@@ -805,7 +838,12 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
assert_eq!(rule_node.select(&"testpath".to_owned(), &record), false);
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: String::default(),
};
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), false);
}
Err(_) => {
assert!(false, "failed to parse json record.");
@@ -834,7 +872,12 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
assert_eq!(rule_node.select(&"testpath".to_owned(), &record), false);
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: String::default(),
};
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), false);
}
Err(_) => {
assert!(false, "failed to parse json record.");
@@ -862,7 +905,12 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
assert_eq!(rule_node.select(&"testpath".to_owned(), &record), true);
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: String::default(),
};
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), true);
}
Err(_) => {
assert!(false, "failed to parse json record.");
@@ -890,7 +938,12 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
assert_eq!(rule_node.select(&"testpath".to_owned(), &record), false);
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: String::default(),
};
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), false);
}
Err(_) => {
assert!(false, "failed to parse json record.");
@@ -919,7 +972,12 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
assert_eq!(rule_node.select(&"testpath".to_owned(), &record), false);
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: String::default(),
};
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), false);
}
Err(_) => {
assert!(false, "failed to parse json record.");
@@ -948,7 +1006,12 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
assert_eq!(rule_node.select(&"testpath".to_owned(), &record), true);
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: String::default(),
};
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), true);
}
Err(_) => {
assert!(false, "failed to parse json record.");
@@ -977,7 +1040,12 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
assert_eq!(rule_node.select(&"testpath".to_owned(), &record), true);
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: String::default(),
};
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), true);
}
Err(_) => {
assert!(false, "failed to parse json record.");
@@ -1006,7 +1074,12 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
assert_eq!(rule_node.select(&"testpath".to_owned(), &record), true);
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: String::default(),
};
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), true);
}
Err(_) => {
assert!(false, "failed to parse json record.");
@@ -1035,7 +1108,12 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
assert_eq!(rule_node.select(&"testpath".to_owned(), &record), false);
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: String::default(),
};
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), false);
}
Err(_) => {
assert!(false, "failed to parse json record.");
@@ -1063,7 +1141,12 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
assert_eq!(rule_node.select(&"testpath".to_owned(), &record), true);
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: String::default(),
};
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), true);
}
Err(_) => {
assert!(false, "failed to parse json record.");
@@ -1095,7 +1178,12 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
assert_eq!(rule_node.select(&"testpath".to_owned(), &record), false);
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: String::default(),
};
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), false);
}
Err(_) => {
assert!(false, "failed to parse json record.");
@@ -1127,7 +1215,12 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
assert_eq!(rule_node.select(&"testpath".to_owned(), &record), false);
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: String::default(),
};
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), false);
}
Err(_) => {
assert!(false, "failed to parse json record.");
@@ -1158,7 +1251,12 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
assert_eq!(rule_node.select(&"testpath".to_owned(), &record), false);
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: String::default(),
};
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), false);
}
Err(_) => {
assert!(false, "failed to parse json record.");
@@ -1198,7 +1296,12 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
assert_eq!(rule_node.select(&"testpath".to_owned(), &record), true);
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: String::default(),
};
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), true);
}
Err(_rec) => {
assert!(false, "failed to parse json record.");
@@ -1238,7 +1341,12 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
assert_eq!(rule_node.select(&"testpath".to_owned(), &record), false);
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: String::default(),
};
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), false);
}
Err(_rec) => {
assert!(false, "failed to parse json record.");
@@ -1278,7 +1386,12 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
assert_eq!(rule_node.select(&"testpath".to_owned(), &record), true);
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: String::default(),
};
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), true);
}
Err(_rec) => {
assert!(false, "failed to parse json record.");
@@ -1318,7 +1431,12 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
assert_eq!(rule_node.select(&"testpath".to_owned(), &record), false);
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: String::default(),
};
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), false);
}
Err(_rec) => {
assert!(false, "failed to parse json record.");
@@ -1358,7 +1476,12 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
assert_eq!(rule_node.select(&"testpath".to_owned(), &record), true);
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: String::default(),
};
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), true);
}
Err(_rec) => {
assert!(false, "failed to parse json record.");
@@ -1398,7 +1521,12 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
assert_eq!(rule_node.select(&"testpath".to_owned(), &record), false);
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: String::default(),
};
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), false);
}
Err(_rec) => {
assert!(false, "failed to parse json record.");
@@ -1426,7 +1554,12 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
assert_eq!(rule_node.select(&"testpath".to_owned(), &record), true);
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: String::default(),
};
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), true);
}
Err(_) => {
assert!(false, "failed to parse json record.");
@@ -1454,7 +1587,12 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
assert_eq!(rule_node.select(&"testpath".to_owned(), &record), false);
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: String::default(),
};
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), false);
}
Err(_) => {
assert!(false, "failed to parse json record.");
@@ -1482,7 +1620,12 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
assert_eq!(rule_node.select(&"testpath".to_owned(), &record), true);
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: String::default(),
};
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), true);
}
Err(_) => {
assert!(false, "failed to parse json record.");
@@ -1548,6 +1691,76 @@ mod tests {
assert_eq!(r"\\\\.*ho\\\\.*ge\\\\\\", value);
}
#[test]
fn test_grep_match() {
// wildcardは大文字小文字関係なくマッチする。
let rule_str = r#"
enabled: true
detection:
selection:
- 4103
output: 'command=%CommandLine%'
"#;
let record_json_str = r#"
{
"Event": {"System": {"EventID": 4103, "Channel": "security", "Computer":"DESKTOP-ICHIICHI"}},
"Event_attributes": {"xmlns": "http://schemas.microsoft.com/win/2004/08/events/event"}
}"#;
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(rec) => {
let rec: Value = rec;
let recstr = rec.to_string();
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: rec,
data_string: recstr,
};
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), true);
}
Err(_) => {
assert!(false, "failed to parse json record.");
}
}
}
#[test]
fn test_grep_not_match() {
// wildcardは大文字小文字関係なくマッチする。
let rule_str = r#"
enabled: true
detection:
selection:
- 4104
output: 'command=%CommandLine%'
"#;
let record_json_str = r#"
{
"Event": {"System": {"EventID": 4103, "Channel": "security", "Computer":"DESKTOP-ICHIICHI"}},
"Event_attributes": {"xmlns": "http://schemas.microsoft.com/win/2004/08/events/event"}
}"#;
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
let rec: Value = record;
let recstr = rec.to_string();
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: rec,
data_string: recstr,
};
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), false);
}
Err(_) => {
assert!(false, "failed to parse json record.");
}
}
}
#[test]
fn test_detect_value_keyword() {
// 文字列っぽいデータでも確認
@@ -1570,7 +1783,12 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
assert_eq!(rule_node.select(&"testpath".to_owned(), &record), true);
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: String::default(),
};
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), true);
}
Err(_) => {
assert!(false, "failed to parse json record.");
@@ -1600,7 +1818,12 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
assert_eq!(rule_node.select(&"testpath".to_owned(), &record), false);
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: String::default(),
};
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), false);
}
Err(_) => {
assert!(false, "failed to parse json record.");

View File

@@ -5,7 +5,6 @@ use chrono::{DateTime, Utc};
use std::{collections::HashMap, fmt::Debug, sync::Arc, vec};
use serde_json::Value;
use yaml_rust::Yaml;
mod matchers;
@@ -18,6 +17,8 @@ mod condition_parser;
mod count;
use self::count::TimeFrameInfo;
use super::detection::EvtxRecordInfo;
pub fn create_rule(rulepath: String, yaml: Yaml) -> RuleNode {
return RuleNode::new(rulepath, yaml);
}
@@ -71,13 +72,13 @@ impl RuleNode {
}
}
pub fn select(&mut self, filepath: &String, event_record: &Value) -> bool {
pub fn select(&mut self, filepath: &String, event_record: &EvtxRecordInfo) -> bool {
if self.detection.is_none() {
return false;
}
let result = self.detection.as_ref().unwrap().select(event_record);
if result {
count::count(self, filepath, event_record);
count::count(self, filepath, &event_record.record);
}
return result;
}
@@ -174,7 +175,7 @@ impl DetectionNode {
}
}
pub fn select(&self, event_record: &Value) -> bool {
pub fn select(&self, event_record: &EvtxRecordInfo) -> bool {
if self.condition.is_none() {
return false;
}
@@ -313,7 +314,7 @@ impl AggResult {
#[cfg(test)]
mod tests {
use crate::detections::rule::create_rule;
use crate::detections::{detection::EvtxRecordInfo, rule::create_rule};
use yaml_rust::YamlLoader;
use super::RuleNode;
@@ -348,7 +349,12 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
assert_eq!(rule_node.select(&"testpath".to_owned(), &record), true);
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: String::default(),
};
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), true);
}
Err(_) => {
assert!(false, "failed to parse json record.");
@@ -376,7 +382,12 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
assert_eq!(rule_node.select(&"testpath".to_owned(), &record), false);
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: String::default(),
};
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), false);
}
Err(_) => {
assert!(false, "failed to parse json record.");
@@ -404,7 +415,12 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
assert_eq!(rule_node.select(&"testpath".to_owned(), &record), false);
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: String::default(),
};
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), false);
}
Err(_) => {
assert!(false, "failed to parse json record.");
@@ -485,7 +501,12 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
assert_eq!(rule_node.select(&"testpath".to_owned(), &record), true);
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: String::default(),
};
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), true);
}
Err(_) => {
assert!(false, "failed to parse json record.");
@@ -542,7 +563,12 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
assert_eq!(rule_node.select(&"testpath".to_owned(), &record), false);
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: String::default(),
};
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), false);
}
Err(_) => {
assert!(false, "failed to parse json record.");
@@ -606,7 +632,12 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
assert_eq!(rule_node.select(&"testpath".to_owned(), &record), true);
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: String::default(),
};
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), true);
}
Err(_) => {
assert!(false, "failed to parse json record.");
@@ -648,7 +679,12 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
assert_eq!(rule_node.select(&"testpath".to_owned(), &record), true);
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: String::default(),
};
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), true);
}
Err(_) => {
assert!(false, "failed to parse json record.");
@@ -691,7 +727,12 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
assert_eq!(rule_node.select(&"testpath".to_owned(), &record), false);
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: String::default(),
};
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), false);
}
Err(_) => {
assert!(false, "failed to parse json record.");
@@ -753,7 +794,12 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
assert_eq!(rule_node.select(&"testpath".to_owned(), &record), true);
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: String::default(),
};
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), true);
}
Err(_) => {
assert!(false, "failed to parse json record.");
@@ -815,7 +861,12 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
assert_eq!(rule_node.select(&"testpath".to_owned(), &record), false);
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: String::default(),
};
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), false);
}
Err(_) => {
assert!(false, "failed to parse json record.");
@@ -859,7 +910,12 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
assert_eq!(rule_node.select(&"testpath".to_owned(), &record), true);
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: String::default(),
};
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), true);
}
Err(_rec) => {
assert!(false, "failed to parse json record.");
@@ -915,7 +971,12 @@ mod tests {
let _init = rule_node.init();
match serde_json::from_str(record_str) {
Ok(record) => {
let result = rule_node.select(&"testpath".to_string(), &record);
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: String::default(),
};
let result = rule_node.select(&"testpath".to_string(), &recinfo);
assert_eq!(
rule_node.detection.unwrap().aggregation_condition.is_some(),
true

View File

@@ -1,4 +1,4 @@
use crate::detections::utils;
use crate::detections::{detection::EvtxRecordInfo, utils};
use mopa::mopafy;
use serde_json::Value;
use std::{sync::Arc, vec};
@@ -10,7 +10,7 @@ use super::matchers;
pub trait SelectionNode: mopa::Any {
// 引数で指定されるイベントログのレコードが、条件に一致するかどうかを判定する
// このトレイトを実装する構造体毎に適切な判定処理を書く必要がある。
fn select(&self, event_record: &Value) -> bool;
fn select(&self, event_record: &EvtxRecordInfo) -> bool;
// 初期化処理を行う
// 戻り値としてエラーを返却できるようになっているので、Ruleファイルが間違っていて、SelectionNodeを構成出来ない時はここでエラーを出す
@@ -43,7 +43,7 @@ impl AndSelectionNode {
}
impl SelectionNode for AndSelectionNode {
fn select(&self, event_record: &Value) -> bool {
fn select(&self, event_record: &EvtxRecordInfo) -> bool {
return self.child_nodes.iter().all(|child_node| {
return child_node.select(event_record);
});
@@ -119,7 +119,7 @@ impl OrSelectionNode {
}
impl SelectionNode for OrSelectionNode {
fn select(&self, event_record: &Value) -> bool {
fn select(&self, event_record: &EvtxRecordInfo) -> bool {
return self.child_nodes.iter().any(|child_node| {
return child_node.select(event_record);
});
@@ -193,7 +193,7 @@ impl NotSelectionNode {
}
impl SelectionNode for NotSelectionNode {
fn select(&self, event_record: &Value) -> bool {
fn select(&self, event_record: &EvtxRecordInfo) -> bool {
return !self.node.select(event_record);
}
@@ -230,7 +230,7 @@ impl RefSelectionNode {
}
impl SelectionNode for RefSelectionNode {
fn select(&self, event_record: &Value) -> bool {
fn select(&self, event_record: &EvtxRecordInfo) -> bool {
return self.selection_node.select(event_record);
}
@@ -278,8 +278,9 @@ impl LeafSelectionNode {
/// JSON形式のEventJSONから値を取得する関数 aliasも考慮されている。
fn get_event_value<'a>(&self, event_value: &'a Value) -> Option<&'a Value> {
// keyが指定されたいない場合は
if self.key_list.is_empty() {
return Option::None;
return Option::Some(event_value);
}
return utils::get_event_value(&self.get_key(), event_value);
@@ -298,7 +299,7 @@ impl LeafSelectionNode {
}
impl SelectionNode for LeafSelectionNode {
fn select(&self, event_record: &Value) -> bool {
fn select(&self, event_record: &EvtxRecordInfo) -> bool {
if self.matcher.is_none() {
return false;
}
@@ -324,9 +325,14 @@ impl SelectionNode for LeafSelectionNode {
}
*/
if self.get_key() == "EventData" {
let values = utils::get_event_value(&"Event.EventData.Data".to_string(), event_record);
let values =
utils::get_event_value(&"Event.EventData.Data".to_string(), &event_record.record);
if values.is_none() {
return self.matcher.as_ref().unwrap().is_match(Option::None);
return self
.matcher
.as_ref()
.unwrap()
.is_match(Option::None, event_record);
}
// 配列じゃなくて、文字列や数値等の場合は普通通りに比較する。
@@ -337,7 +343,7 @@ impl SelectionNode for LeafSelectionNode {
.matcher
.as_ref()
.unwrap()
.is_match(Option::Some(eventdata_data));
.is_match(Option::Some(eventdata_data), event_record);
}
// 配列の場合は配列の要素のどれか一つでもルールに合致すれば条件に一致したことにする。
if eventdata_data.is_array() {
@@ -350,15 +356,23 @@ impl SelectionNode for LeafSelectionNode {
.matcher
.as_ref()
.unwrap()
.is_match(Option::Some(ary_element));
.is_match(Option::Some(ary_element), event_record);
});
} else {
return self.matcher.as_ref().unwrap().is_match(Option::None);
return self
.matcher
.as_ref()
.unwrap()
.is_match(Option::None, event_record);
}
}
let event_value = self.get_event_value(event_record);
return self.matcher.as_ref().unwrap().is_match(event_value);
let event_value = self.get_event_value(&event_record.record);
return self
.matcher
.as_ref()
.unwrap()
.is_match(event_value, event_record);
}
fn init(&mut self) -> Result<(), Vec<String>> {
@@ -401,7 +415,7 @@ impl SelectionNode for LeafSelectionNode {
#[cfg(test)]
mod tests {
use crate::detections::rule::tests::parse_rule_from_str;
use crate::detections::{detection::EvtxRecordInfo, rule::tests::parse_rule_from_str};
#[test]
fn test_detect_mutiple_regex_and() {
@@ -424,7 +438,12 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
assert_eq!(rule_node.select(&"testpath".to_owned(), &record), true);
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: String::default(),
};
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), true);
}
Err(_) => {
assert!(false, "failed to parse json record.");
@@ -455,7 +474,12 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
assert_eq!(rule_node.select(&"testpath".to_owned(), &record), false);
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: String::default(),
};
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), false);
}
Err(_) => {
assert!(false, "failed to parse json record.");
@@ -485,7 +509,12 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
assert_eq!(rule_node.select(&"testpath".to_owned(), &record), true);
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: String::default(),
};
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), true);
}
Err(_) => {
assert!(false, "failed to parse json record.");
@@ -515,7 +544,12 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
assert_eq!(rule_node.select(&"testpath".to_owned(), &record), true);
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: String::default(),
};
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), true);
}
Err(_) => {
assert!(false, "failed to parse json record.");
@@ -545,7 +579,12 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
assert_eq!(rule_node.select(&"testpath".to_owned(), &record), false);
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: String::default(),
};
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), false);
}
Err(_) => {
assert!(false, "failed to parse json record.");