Skip to content

Commit

Permalink
Merge pull request #1533 from Yamato-Security/1447-support-temporal-o…
Browse files Browse the repository at this point in the history
…rdererd

Support Ordered Temporal Proximity correlation
  • Loading branch information
YamatoSecurity authored Dec 22, 2024
2 parents 92df533 + bc76edf commit a3c1513
Show file tree
Hide file tree
Showing 5 changed files with 101 additions and 56 deletions.
1 change: 1 addition & 0 deletions CHANGELOG-Japanese.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
- `expand`修飾子が入っているルールで使用されるプレースホルダー名を出力する`expand-list`コマンドを追加した。(#1513) (@fukuseket)
- `expand`フィールド修飾子に対応した。 (#1434) (@fukusuket)
- Temporal Proximity(`temporal`)の相関ルールに対応した。 (#1446) (@fukusuket)
- Temporal Ordered Proximity (`temporal_ordered`) の相関ルールに対応した。 (#1447) (@fukusuket)

**改善:**

Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
- New `expand-list` command to output placeholder names used for rules with the `expand` modifier. (#1513) (@fukuseket)
- Support for `expand` field modifiers. (#1434) (@fukusuket)
- Suppport for Temporal Proximity (`temporal`) correlation rules. (#1446) (@fukusuket)
- Suppport for Temporal Ordered Proximity (`temporal_ordered`) correlation rules. (#1447) (@fukusuket)

**Enhancements:**

Expand Down
95 changes: 62 additions & 33 deletions src/detections/detection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -199,25 +199,43 @@ impl Detection {

fn detect_within_timeframe(
ids: &[String],
data: &HashMap<String, Vec<AggResult>>,
temporal_ref_all_results: &HashMap<String, Vec<AggResult>>,
timeframe: Duration,
temporal_ordered: bool,
) -> Vec<AggResult> {
let mut result = Vec::new();
let key = ids[0].clone();
for y in data.get(key.as_str()).unwrap() {
let mut found = true;
for id in ids.iter().skip(1) {
if !data.get(id.as_str()).unwrap().iter().any(|t| {
(t.start_timedate >= y.start_timedate - timeframe)
&& (t.start_timedate <= y.start_timedate + timeframe)
}) {
found = false;
break;
let key = ids.first();
if let Some(key) = key {
if let Some(base_records) = temporal_ref_all_results.get(key.as_str()) {
for base in base_records {
let mut found = false;
let mut last_base = base;
for id in ids.iter().skip(1) {
found = false;
if let Some(target_records) = temporal_ref_all_results.get(id.as_str()) {
if temporal_ordered {
found = target_records.iter().any(|t| {
(t.start_timedate >= last_base.start_timedate)
&& (t.start_timedate
<= last_base.start_timedate + timeframe)
});
} else {
found = target_records.iter().any(|t| {
(t.start_timedate >= base.start_timedate - timeframe)
&& (t.start_timedate <= base.start_timedate + timeframe)
});
}
if !found {
break;
}
last_base = base;
}
}
if found {
result.push(base.clone());
}
}
}
if found {
result.push(y.clone());
}
}
result
}
Expand All @@ -230,35 +248,46 @@ impl Detection {
continue;
}
for value in rule.judge_satisfy_aggcondition(stored_static) {
if let CorrelationType::TemporalRef(_, uuid) = &rule.correlation_type {
let mut output = true;
if let CorrelationType::TemporalRef(generate, uuid) = &rule.correlation_type {
detected_temporal_refs
.entry(uuid.clone())
.or_insert_with(Vec::new)
.push(value.clone());
} else {
output = *generate;
}
if output {
ret.push(Detection::create_agg_log_record(rule, value, stored_static));
}
}
}
// temporalルールは個々ルールの判定がすべて出揃ってから判定できるため、再度rulesをループしてtemporalルールの判定を行う
for rule in self.rules.iter() {
if let CorrelationType::Temporal(ref_ids) = &rule.correlation_type {
if ref_ids
.iter()
.all(|x| detected_temporal_refs.contains_key(x))
{
let mut data = HashMap::new();
for id in ref_ids {
let entry = detected_temporal_refs.get_key_value(id);
data.insert(entry.unwrap().0.clone(), entry.unwrap().1.clone());
}
let timeframe = get_sec_timeframe(rule, stored_static);
if let Some(timeframe) = timeframe {
let duration = Duration::seconds(timeframe);
let values = Detection::detect_within_timeframe(ref_ids, &data, duration);
for v in values {
ret.push(Detection::create_agg_log_record(rule, v, stored_static));
}
let (ref_ids, temporal_ordered) = match &rule.correlation_type {
CorrelationType::Temporal(ref_ids) => (ref_ids, false),
CorrelationType::TemporalOrdered(ref_ids) => (ref_ids, true),
_ => continue,
};
if ref_ids
.iter()
.all(|x| detected_temporal_refs.contains_key(x))
{
let mut data = HashMap::new();
for id in ref_ids {
let entry = detected_temporal_refs.get_key_value(id);
data.insert(entry.unwrap().0.clone(), entry.unwrap().1.clone());
}
let timeframe = get_sec_timeframe(rule, stored_static);
if let Some(timeframe) = timeframe {
let duration = Duration::seconds(timeframe);
let results = Detection::detect_within_timeframe(
ref_ids,
&data,
duration,
temporal_ordered,
);
for res in results {
ret.push(Detection::create_agg_log_record(rule, res, stored_static));
}
}
}
Expand Down
51 changes: 30 additions & 21 deletions src/detections/rule/correlation_parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -251,8 +251,9 @@ fn merge_referenced_rule(
if rule_type != Some("event_count")
&& rule_type != Some("value_count")
&& rule_type != Some("temporal")
&& rule_type != Some("temporal_ordered")
{
let m = "The type of correlation rule only supports event_count/value_count/temporal.";
let m = "The type of correlation rule only supports event_count/value_count/temporal/temporal_ordered.";
error_log(&rule.rulepath, m, stored_static, parse_error_count);
return rule;
}
Expand All @@ -279,7 +280,7 @@ fn merge_referenced_rule(
error_log(&rule.rulepath, m, stored_static, parse_error_count);
return rule;
}
if rule_type == Some("temporal") {
if rule_type == Some("temporal") || rule_type == Some("temporal_ordered") {
return rule;
}
let (referenced_rules, name_to_selection) =
Expand Down Expand Up @@ -345,24 +346,26 @@ fn parse_temporal_rules(
let mut temporal_ref_ids: Vec<Yaml> = Vec::new();
if let Some(ref_ids) = temporal_yaml["correlation"]["rules"].as_vec() {
for ref_id in ref_ids {
for other_rule in other_rules.iter() {
if is_referenced_rule(other_rule, ref_id.as_str().unwrap_or_default()) {
let new_id = Uuid::new_v4();
temporal_ref_ids.push(Yaml::String(new_id.to_string()));
for other_rule in other_rules.iter_mut() {
let ref_id = ref_id.as_str().unwrap_or_default();
if is_referenced_rule(other_rule, ref_id) {
let generate = temporal_yaml["correlation"]["generate"]
.as_bool()
.unwrap_or_default();
let mut new_yaml = other_rule.yaml.clone();
if other_rule.correlation_type != CorrelationType::None {
other_rule.correlation_type =
CorrelationType::TemporalRef(generate, ref_id.to_string());
temporal_ref_ids.push(Yaml::String(ref_id.to_string()));
continue;
}
let new_id = Uuid::new_v4().to_string();
if let Some(hash) = new_yaml.as_mut_hash() {
hash.insert(
Yaml::String("id".to_string()),
Yaml::String(new_id.to_string()),
);
}
let generate = temporal_yaml["correlation"]["generate"]
.as_bool()
.unwrap_or_default();
if !generate {
referenced_del_ids
.insert(ref_id.as_str().unwrap_or_default().to_string());
}
let mut node = RuleNode::new(other_rule.rulepath.clone(), new_yaml);
let _ = node.init(stored_static);
node.correlation_type =
Expand All @@ -383,6 +386,10 @@ fn parse_temporal_rules(
detection.aggregation_condition = Some(agg_info);
node.detection = detection;
temporal_ref_rules.push(node);
temporal_ref_ids.push(Yaml::String(new_id.to_string()));
if !generate {
referenced_del_ids.insert(ref_id.to_string());
}
}
}
}
Expand Down Expand Up @@ -422,10 +429,12 @@ pub fn parse_correlation_rules(
let (correlation_rules, mut not_correlation_rules): (Vec<RuleNode>, Vec<RuleNode>) = rule_nodes
.into_iter()
.partition(|rule_node| !rule_node.yaml["correlation"].is_badvalue());
let (temporal_rules, not_temporal_rules): (Vec<RuleNode>, Vec<RuleNode>) = correlation_rules
.into_iter()
.partition(|rule_node| rule_node.yaml["correlation"]["type"].as_str() == Some("temporal"));
let mut correlation_parsed_rules: Vec<RuleNode> = not_temporal_rules
let (temporal_rules, not_temporal_rules): (Vec<RuleNode>, Vec<RuleNode>) =
correlation_rules.into_iter().partition(|rule_node| {
rule_node.yaml["correlation"]["type"].as_str() == Some("temporal")
|| rule_node.yaml["correlation"]["type"].as_str() == Some("temporal_ordered")
});
let mut parsed_rules: Vec<RuleNode> = not_temporal_rules
.into_iter()
.map(|correlation_rule_node| {
merge_referenced_rule(
Expand All @@ -436,11 +445,11 @@ pub fn parse_correlation_rules(
)
})
.collect();
parsed_rules.extend(not_correlation_rules);
let parsed_temporal_rules =
parse_temporal_rules(temporal_rules, &mut not_correlation_rules, stored_static);
correlation_parsed_rules.extend(not_correlation_rules);
correlation_parsed_rules.extend(parsed_temporal_rules);
correlation_parsed_rules
parse_temporal_rules(temporal_rules, &mut parsed_rules, stored_static);
parsed_rules.extend(parsed_temporal_rules);
parsed_rules
}

#[cfg(test)]
Expand Down
9 changes: 7 additions & 2 deletions src/detections/rule/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ pub enum CorrelationType {
EventCount,
ValueCount,
Temporal(Vec<String>),
TemporalOrdered(Vec<String>),
TemporalRef(bool, String),
}

Expand All @@ -44,14 +45,18 @@ impl CorrelationType {
match correlation_type {
"event_count" => CorrelationType::EventCount,
"value_count" => CorrelationType::ValueCount,
"temporal" => {
"temporal" | "temporal_ordered" => {
let rules: Vec<String> = yaml["correlation"]["rules"]
.as_vec()
.unwrap()
.iter()
.map(|rule| rule.as_str().unwrap().to_string())
.collect();
CorrelationType::Temporal(rules)
if correlation_type == "temporal" {
CorrelationType::Temporal(rules)
} else {
CorrelationType::TemporalOrdered(rules)
}
}
_ => CorrelationType::None,
}
Expand Down

0 comments on commit a3c1513

Please sign in to comment.