Files
hayabusa/src/detections/rule/mod.rs
T

976 lines
35 KiB
Rust

extern crate regex;
use crate::detections::print::Message;
use chrono::{DateTime, Utc};
use std::{collections::HashMap, fmt::Debug, sync::Arc, vec};
use yaml_rust::Yaml;
mod matchers;
mod selectionnodes;
use self::selectionnodes::{LeafSelectionNode, SelectionNode};
mod aggregation_parser;
use self::aggregation_parser::AggregationParseInfo;
mod condition_parser;
mod count;
use self::count::{AggRecordTimeInfo, TimeFrameInfo};
use super::detection::EvtxRecordInfo;
pub fn create_rule(rulepath: String, yaml: Yaml) -> RuleNode {
return RuleNode::new(rulepath, yaml);
}
/// Ruleファイルを表すノード
pub struct RuleNode {
pub rulepath: String,
pub yaml: Yaml,
detection: DetectionNode,
countdata: HashMap<String, Vec<AggRecordTimeInfo>>,
}
impl Debug for RuleNode {
fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
return Result::Ok(());
}
}
unsafe impl Sync for RuleNode {}
unsafe impl Send for RuleNode {}
impl RuleNode {
pub fn new(rulepath: String, yaml: Yaml) -> RuleNode {
return RuleNode {
rulepath: rulepath,
yaml: yaml,
detection: DetectionNode::new(),
countdata: HashMap::new(),
};
}
pub fn init(&mut self) -> Result<(), Vec<String>> {
let mut errmsgs: Vec<String> = vec![];
// detection node initialization
let detection_result = self.detection.init(&self.yaml["detection"]);
if detection_result.is_err() {
errmsgs.extend(detection_result.unwrap_err());
}
if errmsgs.is_empty() {
return Result::Ok(());
} else {
return Result::Err(errmsgs);
}
}
pub fn select(&mut self, event_record: &EvtxRecordInfo) -> bool {
let result = self.detection.select(event_record);
if result && self.has_agg_condition() {
count::count(self, &event_record.record);
}
return result;
}
/// aggregation conditionが存在するかを返す関数
pub fn has_agg_condition(&self) -> bool {
return self.detection.aggregation_condition.is_some();
}
/// Aggregation Conditionの結果を配列で返却する関数
pub fn judge_satisfy_aggcondition(&self) -> Vec<AggResult> {
let mut ret = Vec::new();
if !self.has_agg_condition() {
return ret;
}
ret.append(&mut count::aggregation_condition_select(&self));
return ret;
}
pub fn check_exist_countdata(&self) -> bool {
self.countdata.len() > 0
}
/// ルール内のAggregationParseInfo(Aggregation Condition)を取得する関数
pub fn get_agg_condition(&self) -> Option<&AggregationParseInfo> {
match self.detection.aggregation_condition.as_ref() {
None => {
return None;
}
Some(agg_parse_info) => {
return Some(agg_parse_info);
}
}
}
}
// RuleNodeのdetectionに定義されているキーの一覧を取得する。
pub fn get_detection_keys(node: &RuleNode) -> Vec<String> {
let mut ret = vec![];
let detection = &node.detection;
for key in detection.name_to_selection.keys() {
let selection = &detection.name_to_selection[key];
let desc = selection.get_descendants();
let keys = desc.iter().filter_map(|node| {
if !node.is::<LeafSelectionNode>() {
return Option::None;
}
let node = node.downcast_ref::<LeafSelectionNode>().unwrap();
let key = node.get_key();
if key.is_empty() {
return Option::None;
}
return Option::Some(key.to_string());
});
ret.extend(keys);
}
return ret;
}
/// Ruleファイルのdetectionを表すノード
struct DetectionNode {
pub name_to_selection: HashMap<String, Arc<Box<dyn SelectionNode>>>,
pub condition: Option<Box<dyn SelectionNode>>,
pub aggregation_condition: Option<AggregationParseInfo>,
pub timeframe: Option<TimeFrameInfo>,
}
impl DetectionNode {
fn new() -> DetectionNode {
return DetectionNode {
name_to_selection: HashMap::new(),
condition: Option::None,
aggregation_condition: Option::None,
timeframe: Option::None,
};
}
fn init(&mut self, detection_yaml: &Yaml) -> Result<(), Vec<String>> {
// selection nodeの初期化
self.parse_name_to_selection(detection_yaml)?;
//timeframeに指定されている値を取得
let timeframe = &detection_yaml["timeframe"].as_str();
if timeframe.is_some() {
self.timeframe = Some(TimeFrameInfo::parse_tframe(timeframe.unwrap().to_string()));
}
// conditionに指定されている式を取得
let condition = &detection_yaml["condition"].as_str();
let condition_str = if let Some(cond_str) = condition {
cond_str.to_string()
} else {
// conditionが指定されていない場合、selectionが一つだけならそのselectionを採用することにする。
let mut keys = self.name_to_selection.keys().clone();
if keys.len() >= 2 {
return Result::Err(vec![
"There is no condition node under detection.".to_string()
]);
}
keys.nth(0).unwrap().to_string()
};
// conditionをパースして、SelectionNodeに変換する
let mut err_msgs = vec![];
let compiler = condition_parser::ConditionCompiler::new();
let compile_result =
compiler.compile_condition(condition_str.clone(), &self.name_to_selection);
if let Result::Err(err_msg) = compile_result {
err_msgs.extend(vec![err_msg]);
} else {
self.condition = Option::Some(compile_result.unwrap());
}
// aggregation condition(conditionのパイプ以降の部分)をパース
let agg_compiler = aggregation_parser::AggegationConditionCompiler::new();
let compile_result = agg_compiler.compile(condition_str);
if let Result::Err(err_msg) = compile_result {
err_msgs.push(err_msg);
} else if let Result::Ok(info) = compile_result {
self.aggregation_condition = info;
}
if err_msgs.is_empty() {
return Result::Ok(());
} else {
return Result::Err(err_msgs);
}
}
pub fn select(&self, event_record: &EvtxRecordInfo) -> bool {
if self.condition.is_none() {
return false;
}
let condition = &self.condition.as_ref().unwrap();
return condition.select(event_record);
}
/// selectionノードをパースします。
fn parse_name_to_selection(&mut self, detection_yaml: &Yaml) -> Result<(), Vec<String>> {
let detection_hash = detection_yaml.as_hash();
if detection_hash.is_none() {
return Result::Err(vec!["Detection node was not found.".to_string()]);
}
// selectionをパースする。
let detection_hash = detection_hash.unwrap();
let keys = detection_hash.keys();
let mut err_msgs = vec![];
for key in keys {
let name = key.as_str().unwrap_or("");
if name.len() == 0 {
continue;
}
// condition等、特殊なキーワードを無視する。
if name == "condition" || name == "timeframe" {
continue;
}
// パースして、エラーメッセージがあれば配列にためて、戻り値で返す。
let selection_node = self.parse_selection(&detection_hash[key]);
if selection_node.is_some() {
let mut selection_node = selection_node.unwrap();
let init_result = selection_node.init();
if init_result.is_err() {
err_msgs.extend(init_result.unwrap_err());
} else {
let rc_selection = Arc::new(selection_node);
self.name_to_selection
.insert(name.to_string(), rc_selection);
}
}
}
if !err_msgs.is_empty() {
return Result::Err(err_msgs);
}
// selectionノードが無いのはエラー
if self.name_to_selection.len() == 0 {
return Result::Err(vec![
"There is no selection node under detection.".to_string()
]);
}
return Result::Ok(());
}
/// selectionをパースします。
fn parse_selection(&self, selection_yaml: &Yaml) -> Option<Box<dyn SelectionNode>> {
return Option::Some(self.parse_selection_recursively(vec![], selection_yaml));
}
/// selectionをパースします。
fn parse_selection_recursively(
&self,
key_list: Vec<String>,
yaml: &Yaml,
) -> Box<dyn SelectionNode> {
if yaml.as_hash().is_some() {
// 連想配列はAND条件と解釈する
let yaml_hash = yaml.as_hash().unwrap();
let mut and_node = selectionnodes::AndSelectionNode::new();
yaml_hash.keys().for_each(|hash_key| {
let child_yaml = yaml_hash.get(hash_key).unwrap();
let mut child_key_list = key_list.clone();
child_key_list.push(hash_key.as_str().unwrap().to_string());
let child_node = self.parse_selection_recursively(child_key_list, child_yaml);
and_node.child_nodes.push(child_node);
});
return Box::new(and_node);
} else if yaml.as_vec().is_some() {
// 配列はOR条件と解釈する。
let mut or_node = selectionnodes::OrSelectionNode::new();
yaml.as_vec().unwrap().iter().for_each(|child_yaml| {
let child_node = self.parse_selection_recursively(key_list.clone(), child_yaml);
or_node.child_nodes.push(child_node);
});
return Box::new(or_node);
} else {
// 連想配列と配列以外は末端ノード
return Box::new(selectionnodes::LeafSelectionNode::new(
key_list,
yaml.clone(),
));
}
}
}
#[derive(Debug)]
/// countなどのaggregationの結果を出力する構造体
pub struct AggResult {
/// countなどの値
pub data: i64,
/// count byで指定された条件のレコード内での値
pub key: String,
/// countの括弧内指定された項目の検知されたレコード内での値の配列。括弧内で指定がなかった場合は長さ0の配列となる
pub field_values: Vec<String>,
///検知したブロックの最初のレコードの時間
pub start_timedate: DateTime<Utc>,
///条件式の情報
pub condition_op_num: String,
}
impl AggResult {
pub fn new(
data: i64,
key: String,
field_values: Vec<String>,
start_timedate: DateTime<Utc>,
condition_op_num: String,
) -> AggResult {
return AggResult {
data: data,
key: key,
field_values: field_values,
start_timedate: start_timedate,
condition_op_num: condition_op_num,
};
}
}
#[cfg(test)]
mod tests {
use super::RuleNode;
use crate::detections::{self, rule::create_rule, utils};
use yaml_rust::YamlLoader;
pub fn parse_rule_from_str(rule_str: &str) -> RuleNode {
let rule_yaml = YamlLoader::load_from_str(rule_str);
assert_eq!(rule_yaml.is_ok(), true);
let rule_yamls = rule_yaml.unwrap();
let mut rule_yaml = rule_yamls.into_iter();
let mut rule_node = create_rule("testpath".to_string(), rule_yaml.next().unwrap());
assert_eq!(rule_node.init().is_ok(), true);
return rule_node;
}
#[test]
fn test_detect_dotkey() {
// aliasじゃなくて、.区切りでつなげるケースが正しく検知できる。
let rule_str = r#"
enabled: true
detection:
selection:
Event.System.Computer: DESKTOP-ICHIICHI
details: '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 keys = detections::rule::get_detection_keys(&rule_node);
let recinfo = utils::create_rec_info(record, "testpath".to_owned(), &keys);
assert_eq!(rule_node.select(&recinfo), true);
}
Err(_) => {
assert!(false, "Failed to parse json record.");
}
}
}
#[test]
fn test_notdetect_dotkey() {
// aliasじゃなくて、.区切りでつなげるケースで、検知しないはずのケースで検知しないことを確かめる。
let rule_str = r#"
enabled: true
detection:
selection:
Event.System.Computer: DESKTOP-ICHIICHIN
details: '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 keys = detections::rule::get_detection_keys(&rule_node);
let recinfo = utils::create_rec_info(record, "testpath".to_owned(), &keys);
assert_eq!(rule_node.select(&recinfo), false);
}
Err(_) => {
assert!(false, "Failed to parse json record.");
}
}
}
#[test]
fn test_notdetect_differentkey() {
// aliasじゃなくて、.区切りでつなげるケースで、検知しないはずのケースで検知しないことを確かめる。
let rule_str = r#"
enabled: true
detection:
selection:
Channel: NOTDETECT
details: '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 keys = detections::rule::get_detection_keys(&rule_node);
let recinfo = utils::create_rec_info(record, "testpath".to_owned(), &keys);
assert_eq!(rule_node.select(&recinfo), false);
}
Err(_) => {
assert!(false, "Failed to parse json record.");
}
}
}
#[test]
fn test_detect_attribute() {
// XMLのタグのattributionの部分に値がある場合、JSONが特殊な感じでパースされるのでそのテスト
// 元のXMLは下記のような感じで、Providerタグの部分のNameとかGuidを検知するテスト
/* - <Event xmlns="http://schemas.microsoft.com/win/2004/08/events/event">
- <System>
<Provider Name="Microsoft-Windows-Security-Auditing" Guid="{54849625-5478-4994-a5ba-3e3b0328c30d}" />
<EventID>4672</EventID>
<Version>0</Version>
<Level>0</Level>
<Task>12548</Task>
<Opcode>0</Opcode>
<Keywords>0x8020000000000000</Keywords>
<TimeCreated SystemTime="2021-05-12T13:33:08.0144343Z" />
<EventRecordID>244666</EventRecordID>
<Correlation ActivityID="{0188dd7a-447d-000c-82dd-88017d44d701}" />
<Execution ProcessID="1172" ThreadID="22352" />
<Channel>Security</Channel>
<Security />
</System>
- <EventData>
<Data Name="SubjectUserName">SYSTEM</Data>
<Data Name="SubjectDomainName">NT AUTHORITY</Data>
<Data Name="PrivilegeList">SeAssignPrimaryTokenPrivilege SeTcbPrivilege SeSecurityPrivilege SeTakeOwnershipPrivilege SeLoadDriverPrivilege SeBackupPrivilege SeRestorePrivilege SeDebugPrivilege SeAuditPrivilege SeSystemEnvironmentPrivilege SeImpersonatePrivilege SeDelegateSessionUserImpersonatePrivilege</Data>
</EventData>
</Event> */
let rule_str = r#"
enabled: true
detection:
selection:
EventID: 4797
Event.System.Provider_attributes.Guid: 54849625-5478-4994-A5BA-3E3B0328C30D
details: 'command=%CommandLine%'
"#;
let record_json_str = r#"
{
"Event": {
"System": {
"Channel": "Security",
"Correlation_attributes": {
"ActivityID": "0188DD7A-447D-000C-82DD-88017D44D701"
},
"EventID": 4797,
"EventRecordID": 239219,
"Execution_attributes": {
"ProcessID": 1172,
"ThreadID": 23236
},
"Keywords": "0x8020000000000000",
"Level": 0,
"Opcode": 0,
"Provider_attributes": {
"Guid": "54849625-5478-4994-A5BA-3E3B0328C30D",
"Name": "Microsoft-Windows-Security-Auditing"
},
"Security": null,
"Task": 13824,
"TimeCreated_attributes": {
"SystemTime": "2021-05-12T09:39:19.828403Z"
},
"Version": 0
}
},
"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 keys = detections::rule::get_detection_keys(&rule_node);
let recinfo = utils::create_rec_info(record, "testpath".to_owned(), &keys);
assert_eq!(rule_node.select(&recinfo), true);
}
Err(_) => {
assert!(false, "Failed to parse json record.");
}
}
}
#[test]
fn test_notdetect_attribute() {
// XMLのタグのattributionの検知しないケースを確認
let rule_str = r#"
enabled: true
detection:
selection:
EventID: 4797
Event.System.Provider_attributes.Guid: 54849625-5478-4994-A5BA-3E3B0328C30DSS
details: 'command=%CommandLine%'
"#;
let record_json_str = r#"
{
"Event": {
"System": {
"Channel": "Security",
"Correlation_attributes": {
"ActivityID": "0188DD7A-447D-000C-82DD-88017D44D701"
},
"EventID": 4797,
"EventRecordID": 239219,
"Execution_attributes": {
"ProcessID": 1172,
"ThreadID": 23236
},
"Keywords": "0x8020000000000000",
"Level": 0,
"Opcode": 0,
"Provider_attributes": {
"Guid": "54849625-5478-4994-A5BA-3E3B0328C30D",
"Name": "Microsoft-Windows-Security-Auditing"
},
"Security": null,
"Task": 13824,
"TimeCreated_attributes": {
"SystemTime": "2021-05-12T09:39:19.828403Z"
},
"Version": 0
}
},
"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 keys = detections::rule::get_detection_keys(&rule_node);
let recinfo = utils::create_rec_info(record, "testpath".to_owned(), &keys);
assert_eq!(rule_node.select(&recinfo), false);
}
Err(_) => {
assert!(false, "Failed to parse json record.");
}
}
}
#[test]
fn test_detect_eventdata() {
// XML形式の特殊なパターンでEventDataというタグあって、Name=の部分にキー的なものが来る。
/* - <EventData>
<Data Name="SubjectUserSid">S-1-5-21-2673273881-979819022-3746999991-1001</Data>
<Data Name="SubjectUserName">takai</Data>
<Data Name="SubjectDomainName">DESKTOP-ICHIICH</Data>
<Data Name="SubjectLogonId">0x312cd</Data>
<Data Name="Workstation">DESKTOP-ICHIICH</Data>
<Data Name="TargetUserName">Administrator</Data>
<Data Name="TargetDomainName">DESKTOP-ICHIICH</Data>
</EventData> */
// その場合、イベントパーサーのJSONは下記のような感じになるので、それで正しく検知出来ることをテスト。
/* {
"Event": {
"EventData": {
"TargetDomainName": "TEST-DOMAIN",
"Workstation": "TEST WorkStation"
"TargetUserName": "ichiichi11",
},
}
} */
let rule_str = r#"
enabled: true
detection:
selection:
Event.EventData.Workstation: 'TEST WorkStation'
Event.EventData.TargetUserName: ichiichi11
details: 'command=%CommandLine%'
"#;
let record_json_str = r#"
{
"Event": {
"EventData": {
"Workstation": "TEST WorkStation",
"TargetUserName": "ichiichi11"
},
"System": {
"Channel": "Security",
"EventID": 4103,
"EventRecordID": 239219,
"Security": null
}
},
"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 keys = detections::rule::get_detection_keys(&rule_node);
let recinfo = utils::create_rec_info(record, "testpath".to_owned(), &keys);
assert_eq!(rule_node.select(&recinfo), true);
}
Err(_) => {
assert!(false, "Failed to parse json record.");
}
}
}
#[test]
fn test_detect_eventdata2() {
let rule_str = r#"
enabled: true
detection:
selection:
EventID: 4103
TargetUserName: ichiichi11
details: 'command=%CommandLine%'
"#;
let record_json_str = r#"
{
"Event": {
"EventData": {
"Workstation": "TEST WorkStation",
"TargetUserName": "ichiichi11"
},
"System": {
"Channel": "Security",
"EventID": 4103,
"EventRecordID": 239219,
"Security": null
}
},
"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 keys = detections::rule::get_detection_keys(&rule_node);
let recinfo = utils::create_rec_info(record, "testpath".to_owned(), &keys);
assert_eq!(rule_node.select(&recinfo), true);
}
Err(_) => {
assert!(false, "Failed to parse json record.");
}
}
}
#[test]
fn test_notdetect_eventdata() {
// EventDataの検知しないパターン
let rule_str = r#"
enabled: true
detection:
selection:
EventID: 4103
TargetUserName: ichiichi12
details: 'command=%CommandLine%'
"#;
let record_json_str = r#"
{
"Event": {
"EventData": {
"Workstation": "TEST WorkStation",
"TargetUserName": "ichiichi11"
},
"System": {
"Channel": "Security",
"EventID": 4103,
"EventRecordID": 239219,
"Security": null
}
},
"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 keys = detections::rule::get_detection_keys(&rule_node);
let recinfo = utils::create_rec_info(record, "testpath".to_owned(), &keys);
assert_eq!(rule_node.select(&recinfo), false);
}
Err(_) => {
assert!(false, "Failed to parse json record.");
}
}
}
#[test]
fn test_detect_special_eventdata() {
// 上記テストケースのEventDataの更に特殊ケースで下記のようにDataタグの中にNameキーがないケースがある。
// そのためにruleファイルでEventDataというキーだけ特別対応している。
// 現状、downgrade_attack.ymlというルールの場合だけで確認出来ているケース
let rule_str = r#"
enabled: true
detection:
selection:
EventID: 403
EventData|re: '[\s\S]*EngineVersion=2\.0[\s\S]*'
details: 'command=%CommandLine%'
"#;
let record_json_str = r#"
{
"Event": {
"EventData": {
"Binary": null,
"Data": [
"Stopped",
"Available",
"\tNewEngineState=Stopped\n\tPreviousEngineState=Available\n\n\tSequenceNumber=10\n\n\tHostName=ConsoleHost\n\tHostVersion=2.0\n\tHostId=5cbb33bf-acf7-47cc-9242-141cd0ba9f0c\n\tEngineVersion=2.0\n\tRunspaceId=c6e94dca-0daf-418c-860a-f751a9f2cbe1\n\tPipelineId=\n\tCommandName=\n\tCommandType=\n\tScriptName=\n\tCommandPath=\n\tCommandLine="
]
},
"System": {
"Channel": "Windows PowerShell",
"Computer": "DESKTOP-ST69BPO",
"EventID": 403,
"EventID_attributes": {
"Qualifiers": 0
},
"EventRecordID": 730,
"Keywords": "0x80000000000000",
"Level": 4,
"Provider_attributes": {
"Name": "PowerShell"
},
"Security": null,
"Task": 4,
"TimeCreated_attributes": {
"SystemTime": "2021-01-28T10:40:54.946866Z"
}
}
},
"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 keys = detections::rule::get_detection_keys(&rule_node);
let recinfo = utils::create_rec_info(record, "testpath".to_owned(), &keys);
assert_eq!(rule_node.select(&recinfo), true);
}
Err(_) => {
assert!(false, "Failed to parse json record.");
}
}
}
#[test]
fn test_notdetect_special_eventdata() {
// 上記テストケースのEventDataの更に特殊ケースで下記のようにDataタグの中にNameキーがないケースがある。
// そのためにruleファイルでEventDataというキーだけ特別対応している。
// 現状、downgrade_attack.ymlというルールの場合だけで確認出来ているケース
let rule_str = r#"
enabled: true
detection:
selection:
EventID: 403
EventData: '[\s\S]*EngineVersion=3.0[\s\S]*'
details: 'command=%CommandLine%'
"#;
let record_json_str = r#"
{
"Event": {
"EventData": {
"Binary": null,
"Data": [
"Stopped",
"Available",
"\tNewEngineState=Stopped\n\tPreviousEngineState=Available\n\n\tSequenceNumber=10\n\n\tHostName=ConsoleHost\n\tHostVersion=2.0\n\tHostId=5cbb33bf-acf7-47cc-9242-141cd0ba9f0c\n\tEngineVersion=2.0\n\tRunspaceId=c6e94dca-0daf-418c-860a-f751a9f2cbe1\n\tPipelineId=\n\tCommandName=\n\tCommandType=\n\tScriptName=\n\tCommandPath=\n\tCommandLine="
]
},
"System": {
"Channel": "Windows PowerShell",
"Computer": "DESKTOP-ST69BPO",
"EventID": 403,
"EventID_attributes": {
"Qualifiers": 0
},
"EventRecordID": 730,
"Keywords": "0x80000000000000",
"Level": 4,
"Provider_attributes": {
"Name": "PowerShell"
},
"Security": null,
"Task": 4,
"TimeCreated_attributes": {
"SystemTime": "2021-01-28T10:40:54.946866Z"
}
}
},
"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 keys = detections::rule::get_detection_keys(&rule_node);
let recinfo = utils::create_rec_info(record, "testpath".to_owned(), &keys);
assert_eq!(rule_node.select(&recinfo), false);
}
Err(_) => {
assert!(false, "Failed to parse json record.");
}
}
}
#[test]
fn test_use_strfeature_in_or_node() {
// orNodeの中でもstartswithが使えるかのテスト
let rule_str = r#"
enabled: true
detection:
selection:
Channel: 'System'
EventID: 7040
param1: 'Windows Event Log'
param2|startswith:
- "disa"
- "aut"
details: 'Service name : %param1%¥nMessage : Event Log Service Stopped¥nResults: Selective event log manipulation may follow this event.'
"#;
let record_json_str = r#"
{
"Event": {
"System": {
"EventID": 7040,
"Channel": "System"
},
"EventData": {
"param1": "Windows Event Log",
"param2": "auto start"
}
},
"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 keys = detections::rule::get_detection_keys(&rule_node);
let recinfo = utils::create_rec_info(record, "testpath".to_owned(), &keys);
assert_eq!(rule_node.select(&recinfo), true);
}
Err(_rec) => {
assert!(false, "Failed to parse json record.");
}
}
}
#[test]
fn test_detect_undefined_rule_option() {
// 不明な文字列オプションがルールに書かれていたら警告するテスト
let rule_str = r#"
enabled: true
detection:
selection:
Channel|failed: Security
EventID: 0
details: 'Rule parse test'
"#;
let mut rule_yaml = YamlLoader::load_from_str(rule_str).unwrap().into_iter();
let mut rule_node = create_rule("testpath".to_string(), rule_yaml.next().unwrap());
assert_eq!(
rule_node.init(),
Err(vec![
"An unknown pipe element was specified. key:detection -> selection -> Channel|failed"
.to_string()
])
);
}
#[test]
fn test_detect_not_defined_selection() {
// 不明な文字列オプションがルールに書かれていたら警告するテスト
let rule_str = r#"
enabled: true
detection:
details: 'Rule parse test'
"#;
let mut rule_yaml = YamlLoader::load_from_str(rule_str).unwrap().into_iter();
let mut rule_node = create_rule("testpath".to_string(), rule_yaml.next().unwrap());
assert_eq!(
rule_node.init(),
Err(vec!["Detection node was not found.".to_string()])
);
}
/// countで対象の数値確認を行うためのテスト用関数
fn _check_count(rule_str: &str, record_str: &str, key: &str, expect_count: i32) {
let mut rule_yaml = YamlLoader::load_from_str(rule_str).unwrap().into_iter();
let test = rule_yaml.next().unwrap();
let mut rule_node = create_rule("testpath".to_string(), test);
let _init = rule_node.init();
match serde_json::from_str(record_str) {
Ok(record) => {
let keys = detections::rule::get_detection_keys(&rule_node);
let recinfo = utils::create_rec_info(record, "testpath".to_owned(), &keys);
let result = rule_node.select(&recinfo);
assert_eq!(rule_node.detection.aggregation_condition.is_some(), true);
assert_eq!(result, true);
assert_eq!(
*&rule_node.countdata.get(key).unwrap().len() as i32,
expect_count
);
}
Err(_rec) => {
assert!(false, "Failed to parse json record.");
}
}
}
}