add equalsfield pipe (#467)

This commit is contained in:
James / hach1yon
2022-03-30 11:49:20 +09:00
committed by GitHub
parent 7861174a93
commit bca578b89e
4 changed files with 253 additions and 74 deletions

92
Cargo.lock generated
View File

@@ -48,9 +48,9 @@ dependencies = [
[[package]]
name = "anyhow"
version = "1.0.55"
version = "1.0.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "159bb86af3a200e19a068f4224eae4c8bb2d0fa054c7e5d1cacd5cef95e684cd"
checksum = "4361135be9122e0870de935d7c439aef945b9f9ddd4199a553b5270b49c82a27"
[[package]]
name = "atty"
@@ -359,12 +359,12 @@ dependencies = [
[[package]]
name = "crossbeam-channel"
version = "0.5.2"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e54ea8bc3fb1ee042f5aace6e3c6e025d3874866da222930f70ce62aceba0bfa"
checksum = "5aaa7bd5fb665c6864b5f963dd9097905c54125909c7aa94c9e18507cdbe6c53"
dependencies = [
"cfg-if 1.0.0",
"crossbeam-utils 0.8.7",
"crossbeam-utils 0.8.8",
]
[[package]]
@@ -385,8 +385,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e"
dependencies = [
"cfg-if 1.0.0",
"crossbeam-epoch 0.9.7",
"crossbeam-utils 0.8.7",
"crossbeam-epoch 0.9.8",
"crossbeam-utils 0.8.8",
]
[[package]]
@@ -406,12 +406,13 @@ dependencies = [
[[package]]
name = "crossbeam-epoch"
version = "0.9.7"
version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c00d6d2ea26e8b151d99093005cb442fb9a37aeaca582a03ec70946f49ab5ed9"
checksum = "1145cf131a2c6ba0615079ab6a638f7e1973ac9c2634fcbeaaad6114246efe8c"
dependencies = [
"autocfg 1.1.0",
"cfg-if 1.0.0",
"crossbeam-utils 0.8.7",
"crossbeam-utils 0.8.8",
"lazy_static",
"memoffset 0.6.5",
"scopeguard",
@@ -441,9 +442,9 @@ dependencies = [
[[package]]
name = "crossbeam-utils"
version = "0.8.7"
version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5e5bed1f1c269533fa816a0a5492b3545209a205ca1a54842be180eb63a16a6"
checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38"
dependencies = [
"cfg-if 1.0.0",
"lazy_static",
@@ -776,7 +777,7 @@ checksum = "d39cd93900197114fa1fcb7ae84ca742095eed9442088988ae74fa744e930e77"
dependencies = [
"cfg-if 1.0.0",
"libc",
"wasi",
"wasi 0.10.0+wasi-snapshot-preview1",
]
[[package]]
@@ -1113,9 +1114,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.119"
version = "0.2.121"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4"
checksum = "efaa7b300f3b5fe8eb6bf21ce3895e1751d9665086af2d64b42f19701015ff4f"
[[package]]
name = "libgit2-sys"
@@ -1147,9 +1148,9 @@ dependencies = [
[[package]]
name = "libz-sys"
version = "1.1.3"
version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de5435b8549c16d423ed0c03dbaafe57cf6c3344744f1242520d59c9d8ecec66"
checksum = "6f35facd4a5673cb5a48822be2be1d4236c1c99cb4113cab7061ac720d5bf859"
dependencies = [
"cc",
"libc",
@@ -1183,9 +1184,9 @@ dependencies = [
[[package]]
name = "log"
version = "0.4.14"
version = "0.4.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
checksum = "6389c490849ff5bc16be905ae24bc913a9c8892e19b2341dbc175e14c341c2b8"
dependencies = [
"cfg-if 1.0.0",
]
@@ -1273,14 +1274,15 @@ dependencies = [
[[package]]
name = "mio"
version = "0.8.0"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba272f85fa0b41fc91872be579b3bbe0f56b792aa361a380eb669469f68dafb2"
checksum = "52da4364ffb0e4fe33a9841a98a3f3014fb964045ce4f7a45a398243c8d6b0c9"
dependencies = [
"libc",
"log",
"miow 0.3.7",
"ntapi",
"wasi 0.11.0+wasi-snapshot-preview1",
"winapi 0.3.9",
]
@@ -1394,9 +1396,9 @@ dependencies = [
[[package]]
name = "once_cell"
version = "1.9.0"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5"
checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9"
[[package]]
name = "openssl"
@@ -1420,9 +1422,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
[[package]]
name = "openssl-src"
version = "111.17.0+1.1.1m"
version = "111.18.0+1.1.1n"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05d6a336abd10814198f66e2a91ccd7336611f30334119ca8ce300536666fcf4"
checksum = "7897a926e1e8d00219127dc020130eca4292e5ca666dd592480d72c3eca2ff6c"
dependencies = [
"cc",
]
@@ -1485,7 +1487,7 @@ checksum = "28141e0cc4143da2443301914478dc976a61ffdb3f043058310c70df2fed8954"
dependencies = [
"cfg-if 1.0.0",
"libc",
"redox_syscall 0.2.11",
"redox_syscall 0.2.12",
"smallvec 1.8.0",
"windows-sys",
]
@@ -1580,9 +1582,9 @@ dependencies = [
[[package]]
name = "quote"
version = "1.0.15"
version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145"
checksum = "632d02bff7f874a36f33ea8bb416cd484b90cc66c1194b1a1110d067a7013f58"
dependencies = [
"proc-macro2",
]
@@ -1713,7 +1715,7 @@ checksum = "d78120e2c850279833f1dd3582f730c4ab53ed95aeaaaa862a2a5c71b1656d8e"
dependencies = [
"crossbeam-channel",
"crossbeam-deque 0.8.1",
"crossbeam-utils 0.8.7",
"crossbeam-utils 0.8.8",
"lazy_static",
"num_cpus",
]
@@ -1735,18 +1737,18 @@ checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
[[package]]
name = "redox_syscall"
version = "0.2.11"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8380fe0152551244f0747b1bf41737e0f8a74f97a14ccefd1148187271634f3c"
checksum = "8ae183fc1b06c149f0c1793e1eb447c8b04bfe46d48e9e48bfb8d2d7ed64ecf0"
dependencies = [
"bitflags",
]
[[package]]
name = "regex"
version = "1.5.4"
version = "1.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286"
dependencies = [
"aho-corasick",
"memchr",
@@ -2142,9 +2144,9 @@ checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
[[package]]
name = "syn"
version = "1.0.86"
version = "1.0.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b"
checksum = "ea297be220d52398dcc07ce15a209fce436d361735ac1db700cab3b6cdfb9f54"
dependencies = [
"proc-macro2",
"quote",
@@ -2172,16 +2174,16 @@ dependencies = [
"cfg-if 1.0.0",
"fastrand",
"libc",
"redox_syscall 0.2.11",
"redox_syscall 0.2.12",
"remove_dir_all",
"winapi 0.3.9",
]
[[package]]
name = "termcolor"
version = "1.1.2"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4"
checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
dependencies = [
"winapi-util",
]
@@ -2232,7 +2234,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
dependencies = [
"libc",
"wasi",
"wasi 0.10.0+wasi-snapshot-preview1",
"winapi 0.3.9",
]
@@ -2317,7 +2319,7 @@ dependencies = [
"bytes 1.1.0",
"libc",
"memchr",
"mio 0.8.0",
"mio 0.8.2",
"num_cpus",
"once_cell",
"parking_lot 0.12.0",
@@ -2598,6 +2600,12 @@ version = "0.10.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasm-bindgen"
version = "0.2.79"
@@ -2786,6 +2794,6 @@ dependencies = [
[[package]]
name = "zeroize"
version = "1.5.3"
version = "1.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50344758e2f40e3a1fcfc8f6f91aa57b5f8ebd8d27919fe6451f15aaaf9ee608"
checksum = "7eb5728b8afd3f280a869ce1d4c554ffaed35f45c231fc41bfbd0381bef50317"

View File

@@ -1,5 +1,5 @@
use regex::Regex;
use std::collections::VecDeque;
use std::{cmp::Ordering, collections::VecDeque};
use yaml_rust::Yaml;
use crate::detections::{detection::EvtxRecordInfo, utils};
@@ -192,6 +192,7 @@ pub struct DefaultMatcher {
re: Option<Regex>,
pipes: Vec<PipeElement>,
key_list: Vec<String>,
eqfield_key: Option<String>,
}
impl DefaultMatcher {
@@ -200,9 +201,14 @@ impl DefaultMatcher {
re: Option::None,
pipes: Vec::new(),
key_list: Vec::new(),
eqfield_key: Option::None,
}
}
pub fn get_eqfield_key(&self) -> Option<&String> {
self.eqfield_key.as_ref()
}
/// このmatcherの正規表現とマッチするかどうか判定します。
/// 判定対象の文字列とこのmatcherが保持する正規表現が完全にマッチした場合のTRUEを返します。
/// 例えば、判定対象文字列が"abc"で、正規表現が"ab"の場合、正規表現は判定対象文字列の一部分にしか一致していないので、この関数はfalseを返します。
@@ -265,6 +271,7 @@ impl LeafMatcher for DefaultMatcher {
"endswith" => Option::Some(PipeElement::Endswith),
"contains" => Option::Some(PipeElement::Contains),
"re" => Option::Some(PipeElement::Re),
"equalsfield" => Option::Some(PipeElement::EqualsField),
_ => Option::None,
};
if pipe_element.is_none() {
@@ -285,34 +292,54 @@ impl LeafMatcher for DefaultMatcher {
);
return Result::Err(vec![errmsg]);
}
let is_re = &self
let is_eqfield = self
.pipes
.iter()
.any(|pipe_element| matches!(pipe_element, PipeElement::Re));
// 正規表現ではない場合、ワイルドカードであることを表す。
// ワイルドカードは正規表現でマッチングするので、ワイルドカードを正規表現に変換するPipeを内部的に追加することにする。
if !is_re {
self.pipes.push(PipeElement::Wildcard);
}
.any(|pipe_element| matches!(pipe_element, PipeElement::EqualsField));
if is_eqfield {
// PipeElement::EqualsFieldは特別
self.eqfield_key = Option::Some(pattern);
} else {
// 正規表現ではない場合、ワイルドカードであることを表す。
// ワイルドカードは正規表現でマッチングするので、ワイルドカードを正規表現に変換するPipeを内部的に追加することにする。
let is_re = self
.pipes
.iter()
.any(|pipe_element| matches!(pipe_element, PipeElement::Re));
if !is_re {
self.pipes.push(PipeElement::Wildcard);
}
// パターンをPipeで処理する。
let pattern = DefaultMatcher::from_pattern_to_regex_str(pattern, &self.pipes);
// Pipeで処理されたパターンを正規表現に変換
let re_result = Regex::new(&pattern);
if re_result.is_err() {
let errmsg = format!(
"Cannot parse regex. [regex:{}, key:{}]",
pattern,
utils::concat_selection_key(key_list)
);
return Result::Err(vec![errmsg]);
let pattern = DefaultMatcher::from_pattern_to_regex_str(pattern, &self.pipes);
// Pipeで処理されたパターンを正規表現に変換
let re_result = Regex::new(&pattern);
if re_result.is_err() {
let errmsg = format!(
"Cannot parse regex. [regex:{}, key:{}]",
pattern,
utils::concat_selection_key(key_list)
);
return Result::Err(vec![errmsg]);
}
self.re = re_result.ok();
}
self.re = re_result.ok();
Result::Ok(())
}
fn is_match(&self, event_value: Option<&String>, _recinfo: &EvtxRecordInfo) -> bool {
fn is_match(&self, event_value: Option<&String>, recinfo: &EvtxRecordInfo) -> bool {
// PipeElement::EqualsFieldが設定されていた場合
if let Some(eqfield_key) = &self.eqfield_key {
let another_value = recinfo.get_value(eqfield_key);
// Evtxのレコードに存在しないeventkeyを指定された場合はfalseにする
if event_value.is_none() || another_value.is_none() {
return false;
}
return another_value.unwrap().cmp(event_value.unwrap()) == Ordering::Equal;
}
// yamlにnullが設定されていた場合
// keylistが空(==JSONのgrep検索)の場合、無視する。
if self.key_list.is_empty() && self.re.is_none() {
@@ -341,6 +368,7 @@ enum PipeElement {
Contains,
Re,
Wildcard,
EqualsField,
}
impl PipeElement {
@@ -377,10 +405,9 @@ impl PipeElement {
PipeElement::Endswith => fn_add_asterisk_begin(pattern),
// containsの場合はpatternの前後にwildcardを足すことで対応する
PipeElement::Contains => fn_add_asterisk_end(fn_add_asterisk_begin(pattern)),
// 正規表現の場合は特に処理する必要無い
PipeElement::Re => pattern,
// WildCardは正規表現に変換する。
PipeElement::Wildcard => PipeElement::pipe_pattern_wildcard(pattern),
_ => pattern,
}
}
@@ -1707,4 +1734,128 @@ mod tests {
}
}
}
#[test]
fn test_eq_field() {
// equalsfieldsで正しく検知できることを確認
let rule_str = r#"
detection:
selection:
Channel|equalsfield: Computer
details: 'command=%CommandLine%'
"#;
let record_json_str = r#"
{
"Event": {"System": {"EventID": 4103, "Channel": "Security", "Computer": "Security" }},
"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!(rule_node.select(&recinfo));
}
Err(_) => {
panic!("Failed to parse json record.");
}
}
}
#[test]
fn test_eq_field_notdetect() {
// equalsfieldsの検知できないパターン
// equalsfieldsで正しく検知できることを確認
let rule_str = r#"
detection:
selection:
Channel|equalsfield: Computer
details: 'command=%CommandLine%'
"#;
let record_json_str = r#"
{
"Event": {"System": {"EventID": 4103, "Channel": "Security", "Computer": "Powershell" }},
"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!(!rule_node.select(&recinfo));
}
Err(_) => {
panic!("Failed to parse json record.");
}
}
}
#[test]
fn test_eq_field_emptyfield() {
// 存在しないフィールドを指定した場合は検知しない
let rule_str = r#"
detection:
selection:
Channel|equalsfield: NoField
details: 'command=%CommandLine%'
"#;
let record_json_str = r#"
{
"Event": {"System": {"EventID": 4103, "Channel": "Security", "Computer": "Securiti" }},
"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!(!rule_node.select(&recinfo));
}
Err(_) => {
panic!("Failed to parse json record.");
}
}
let rule_str = r#"
detection:
selection:
NoField|equalsfield: Channel
details: 'command=%CommandLine%'
"#;
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!(!rule_node.select(&recinfo));
}
Err(_) => {
panic!("Failed to parse json record.");
}
}
let rule_str = r#"
detection:
selection:
NoField|equalsfield: NoField1
details: 'command=%CommandLine%'
"#;
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!(!rule_node.select(&recinfo));
}
Err(_) => {
panic!("Failed to parse json record.");
}
}
}
}

View File

@@ -106,19 +106,21 @@ pub fn get_detection_keys(node: &RuleNode) -> Vec<String> {
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| {
desc.iter().for_each(|node| {
if !node.is::<LeafSelectionNode>() {
return Option::None;
return;
}
let node = node.downcast_ref::<LeafSelectionNode>().unwrap();
let key = node.get_key();
if key.is_empty() {
return Option::None;
}
Option::Some(key.to_string())
let keys = node.get_keys();
let keys = keys.iter().filter_map(|key| {
if key.is_empty() {
return None;
}
Some(key.to_string())
});
ret.extend(keys);
});
ret.extend(keys);
}
ret

View File

@@ -3,7 +3,7 @@ use downcast_rs::Downcast;
use std::{sync::Arc, vec};
use yaml_rust::Yaml;
use super::matchers;
use super::matchers::{self, DefaultMatcher};
// Ruleファイルの detection- selection配下のードはこのtraitを実装する。
pub trait SelectionNode: Downcast {
@@ -250,6 +250,24 @@ impl LeafSelectionNode {
&self.key
}
pub fn get_keys(&self) -> Vec<&String> {
let mut keys = vec![];
if !self.key.is_empty() {
keys.push(&self.key);
}
if let Some(matcher) = &self.matcher {
let matcher = matcher.downcast_ref::<DefaultMatcher>();
if let Some(matcher) = matcher {
if let Some(eq_key) = matcher.get_eqfield_key() {
keys.push(eq_key);
}
}
}
keys
}
fn _create_key(&self) -> String {
if self.key_list.is_empty() {
return String::default();