diff --git a/CHANGELOG.md b/CHANGELOG.md index ca8a308..5842855 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,12 +14,35 @@ Versioning](https://semver.org/spec/v2.0.0.html). - Added kind keywords for detailed searching of `RepeatedHttpSessions`, `NonBrowser`,`ExternalDdos`, `CryptocurrencyMiningPool`, `BlockListDceRpc` detection events. +- Added triage functionality for scoring with attributes of each raw event. + - Introduced a new crate attrievent. The kinds of raw events and their + attributes change as our software evolves. The purpose of attrievent is to + provide a comprehensive list of attributes for both review and the UI + simultaneously. + - Added a new enum type `AttrValue`. This type is used to convert the + attribute value of each raw event to its corresponding type to perform + comparison operations. + - Added the `to_attr_value` to the `Match` trait to generate an `AttrValue` + from the field in all detection event. + - Implemented `score_by_attr` under `Match` trait. ### Changed - The APIs taking an ip2location database non longer requires a mutable reference to the database. This change improves the ergonomics of the API and removes the need for locking the database. +- Removed the `tor` module file. The structures (`HttpEventFields`, + `TorConnection`) and implementations within that module have been moved to + `crate::event::http`. +- Modified the `ValueKind` enum to support different types of input for packet + attribute triage. +- Changed the type of fields in the detection event structure for some raw + event. This change allows users to see meaningful values directly without + having to do any special conversion for that field. + - `post_body` inside structs of `event::http`: `Vec` to `String`. + - `chaddr` inside structs of `event::bootp`: `Vec` to `String`. + - `class_id`, `client_id` inside structs of `event::dhcp`: `Vec` to + `String`. ## [0.33.1] - 2024-12-20 diff --git a/Cargo.toml b/Cargo.toml index 9207553..146a77b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,6 +52,7 @@ tokio = { version = "1", features = ["macros"] } tokio-postgres-rustls = "0.13" toml = "0.8.13" tracing = "0.1" +attrievent = { git = "https://github.com/aicers/attrievent.git", tag = "0.1.0" } [dev-dependencies] tempfile = "3" diff --git a/src/event.rs b/src/event.rs index 3b5e599..c0767ce 100644 --- a/src/event.rs +++ b/src/event.rs @@ -20,7 +20,6 @@ mod smtp; mod ssh; mod sysmon; mod tls; -mod tor; use std::{ collections::HashMap, @@ -56,8 +55,9 @@ pub use self::{ }, ftp::{BlockListFtp, FtpBruteForce, FtpBruteForceFields, FtpEventFields, FtpPlainText}, http::{ - BlockListHttp, BlockListHttpFields, DgaFields, DomainGenerationAlgorithm, HttpThreat, - HttpThreatFields, NonBrowser, RepeatedHttpSessions, RepeatedHttpSessionsFields, + BlockListHttp, BlockListHttpFields, DgaFields, DomainGenerationAlgorithm, HttpEventFields, + HttpThreat, HttpThreatFields, NonBrowser, RepeatedHttpSessions, RepeatedHttpSessionsFields, + TorConnection, }, kerberos::{BlockListKerberos, BlockListKerberosFields}, ldap::{BlockListLdap, LdapBruteForce, LdapBruteForceFields, LdapEventFields, LdapPlainText}, @@ -72,7 +72,6 @@ pub use self::{ ssh::{BlockListSsh, BlockListSshFields}, sysmon::WindowsThreat, tls::{BlockListTls, BlockListTlsFields, SuspiciousTlsTraffic}, - tor::{HttpEventFields, TorConnection}, }; use super::{ types::{Endpoint, HostNetworkGroup}, @@ -3404,7 +3403,7 @@ mod tests { message: "message".to_string(), renewal_time: 100, rebinding_time: 200, - class_id: vec![4, 5, 6], + class_id: "MSFT 5.0".to_string().into_bytes(), client_id_type: 1, client_id: vec![7, 8, 9], category: EventCategory::InitialAccess, @@ -3423,7 +3422,7 @@ mod tests { let syslog_message = message.to_string(); assert_eq!( &syslog_message, - r#"time="1970-01-01T01:01:01+00:00" event_kind="BlockListDhcp" category="InitialAccess" sensor="collector1" src_addr="127.0.0.1" src_port="68" dst_addr="127.0.0.2" dst_port="67" proto="17" last_time="100" msg_type="1" ciaddr="127.0.0.5" yiaddr="127.0.0.6" siaddr="127.0.0.7" giaddr="127.0.0.8" subnet_mask="255.255.255.0" router="127.0.0.1" domain_name_server="127.0.0.1" req_ip_addr="127.0.0.100" lease_time="100" server_id="127.0.0.1" param_req_list="1,2,3" message="message" renewal_time="100" rebinding_time="200" class_id="04:05:06" client_id_type="1" client_id="07:08:09""#, + r#"time="1970-01-01T01:01:01+00:00" event_kind="BlockListDhcp" category="InitialAccess" sensor="collector1" src_addr="127.0.0.1" src_port="68" dst_addr="127.0.0.2" dst_port="67" proto="17" last_time="100" msg_type="1" ciaddr="127.0.0.5" yiaddr="127.0.0.6" siaddr="127.0.0.7" giaddr="127.0.0.8" subnet_mask="255.255.255.0" router="127.0.0.1" domain_name_server="127.0.0.1" req_ip_addr="127.0.0.100" lease_time="100" server_id="127.0.0.1" param_req_list="1,2,3" message="message" renewal_time="100" rebinding_time="200" class_id="MSFT 5.0" client_id_type="1" client_id="07:08:09""#, ); let block_list_dhcp = Event::BlockList(RecordType::Dhcp(BlockListDhcp::new( @@ -3434,7 +3433,7 @@ mod tests { assert_eq!( &block_list_dhcp, - r#"time="1970-01-01T01:01:01+00:00" event_kind="BlockListDhcp" category="InitialAccess" sensor="collector1" src_addr="127.0.0.1" src_port="68" dst_addr="127.0.0.2" dst_port="67" proto="17" last_time="100" msg_type="1" ciaddr="127.0.0.5" yiaddr="127.0.0.6" siaddr="127.0.0.7" giaddr="127.0.0.8" subnet_mask="255.255.255.0" router="127.0.0.1" domain_name_server="127.0.0.1" req_ip_addr="127.0.0.100" lease_time="100" server_id="127.0.0.1" param_req_list="1,2,3" message="message" renewal_time="100" rebinding_time="200" class_id="04:05:06" client_id_type="1" client_id="07:08:09" triage_scores="""# + r#"time="1970-01-01T01:01:01+00:00" event_kind="BlockListDhcp" category="InitialAccess" sensor="collector1" src_addr="127.0.0.1" src_port="68" dst_addr="127.0.0.2" dst_port="67" proto="17" last_time="100" msg_type="1" ciaddr="127.0.0.5" yiaddr="127.0.0.6" siaddr="127.0.0.7" giaddr="127.0.0.8" subnet_mask="255.255.255.0" router="127.0.0.1" domain_name_server="127.0.0.1" req_ip_addr="127.0.0.100" lease_time="100" server_id="127.0.0.1" param_req_list="1,2,3" message="message" renewal_time="100" rebinding_time="200" class_id="MSFT 5.0" client_id_type="1" client_id="07:08:09" triage_scores="""# ); } diff --git a/src/event/bootp.rs b/src/event/bootp.rs index 17aaf37..99f4ad9 100644 --- a/src/event/bootp.rs +++ b/src/event/bootp.rs @@ -1,10 +1,39 @@ use std::{fmt, net::IpAddr, num::NonZeroU8}; +use attrievent::attribute::{BootpAttr, RawEventAttrKind}; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; -use super::{common::Match, EventCategory, TriagePolicy, TriageScore, MEDIUM}; -use crate::event::common::{to_hardware_address, triage_scores_to_string}; +use super::{common::Match, EventCategory, TriageScore, MEDIUM}; +use crate::event::common::{to_hardware_address, triage_scores_to_string, AttrValue}; + +macro_rules! bootp_target_attr { + ($event: expr, $raw_event_attr: expr) => {{ + if let RawEventAttrKind::Bootp(attr) = $raw_event_attr { + let target_value = match attr { + BootpAttr::SrcAddr => AttrValue::Addr($event.src_addr), + BootpAttr::SrcPort => AttrValue::UInt($event.src_port.into()), + BootpAttr::DstAddr => AttrValue::Addr($event.dst_addr), + BootpAttr::DstPort => AttrValue::UInt($event.dst_port.into()), + BootpAttr::Proto => AttrValue::UInt($event.proto.into()), + BootpAttr::Op => AttrValue::UInt($event.op.into()), + BootpAttr::Htype => AttrValue::UInt($event.htype.into()), + BootpAttr::Hops => AttrValue::UInt($event.hops.into()), + BootpAttr::Xid => AttrValue::UInt($event.xid.into()), + BootpAttr::CiAddr => AttrValue::Addr($event.ciaddr), + BootpAttr::YiAddr => AttrValue::Addr($event.yiaddr), + BootpAttr::SiAddr => AttrValue::Addr($event.siaddr), + BootpAttr::GiAddr => AttrValue::Addr($event.giaddr), + BootpAttr::ChAddr => AttrValue::String(&$event.chaddr), + BootpAttr::SName => AttrValue::String(&$event.sname), + BootpAttr::File => AttrValue::String(&$event.file), + }; + Some(target_value) + } else { + None + } + }}; +} #[derive(Serialize, Deserialize)] pub struct BlockListBootpFields { @@ -73,7 +102,7 @@ pub struct BlockListBootp { pub yiaddr: IpAddr, pub siaddr: IpAddr, pub giaddr: IpAddr, - pub chaddr: Vec, + pub chaddr: String, pub sname: String, pub file: String, pub category: EventCategory, @@ -99,7 +128,7 @@ impl fmt::Display for BlockListBootp { self.yiaddr.to_string(), self.siaddr.to_string(), self.giaddr.to_string(), - to_hardware_address(&self.chaddr), + self.chaddr, self.sname.to_string(), self.file.to_string(), triage_scores_to_string(self.triage_scores.as_ref()) @@ -126,7 +155,7 @@ impl BlockListBootp { yiaddr: fields.yiaddr, siaddr: fields.siaddr, giaddr: fields.giaddr, - chaddr: fields.chaddr, + chaddr: to_hardware_address(&fields.chaddr), sname: fields.sname, file: fields.file, category: fields.category, @@ -176,7 +205,7 @@ impl Match for BlockListBootp { None } - fn score_by_packet_attr(&self, _triage: &TriagePolicy) -> f64 { - 0.0 + fn to_attr_value(&self, raw_event_attr: RawEventAttrKind) -> Option { + bootp_target_attr!(self, raw_event_attr) } } diff --git a/src/event/common.rs b/src/event/common.rs index 425231a..9d75d9d 100644 --- a/src/event/common.rs +++ b/src/event/common.rs @@ -5,13 +5,15 @@ use std::{ }; use anyhow::Result; +use attrievent::attribute::RawEventAttrKind; +use bincode::Options; use num_traits::ToPrimitive; use serde::{Deserialize, Serialize}; use super::{ eq_ip_country, EventCategory, EventFilter, FlowKind, LearningMethod, TrafficDirection, - TriagePolicy, }; +use crate::{AttrCmpKind, Confidence, PacketAttr, Ti}; // TODO: Make new Match trait to support Windows Events @@ -29,14 +31,24 @@ pub(super) trait Match { fn kind(&self) -> &str; fn sensor(&self) -> &str; fn confidence(&self) -> Option; - - /// Calculates a score based on packet attributes according to the triage policy. - /// - /// Note: This method is currently unused. All implementations return 0.0. - /// It's retained for future use as planned by @syncpark. - /// For more details, see: - /// - fn score_by_packet_attr(&self, triage: &TriagePolicy) -> f64; + fn to_attr_value(&self, raw_event_attr: RawEventAttrKind) -> Option; + fn score_by_attr(&self, packet_attr: &[PacketAttr]) -> f64 { + let mut total_score = 0.0; + for attr in packet_attr { + let Ok(raw_event_attr) = + RawEventAttrKind::from_kind_and_attr_name(&attr.raw_event_kind, &attr.attr_name) + else { + continue; + }; + let Some(target_attr_val) = self.to_attr_value(raw_event_attr) else { + continue; + }; + if process_attr_compare(target_attr_val, attr) { + total_score += attr.weight.unwrap_or_default(); + } + } + (total_score * 100.0).trunc() / 100.0 + } /// Returns whether the event matches the filter and the triage scores. The triage scores are /// only returned if the event matches the filter. @@ -193,9 +205,9 @@ pub(super) trait Match { let triage_scores = triage_policies .iter() .filter_map(|triage| { - let score = self.score_by_ti_db(triage) - + self.score_by_packet_attr(triage) - + self.score_by_confidence(triage); + let score = self.score_by_ti_db(&triage.ti_db) + + self.score_by_attr(&triage.packet_attr) + + self.score_by_confidence(&triage.confidence); if triage.response.iter().any(|r| score >= r.minimum_score) { Some(TriageScore { policy_id: triage.id, @@ -216,13 +228,13 @@ pub(super) trait Match { Ok((true, None)) } - fn score_by_ti_db(&self, _triage: &TriagePolicy) -> f64 { + fn score_by_ti_db(&self, _ti_db: &[Ti]) -> f64 { // TODO: implement 0.0 } - fn score_by_confidence(&self, triage: &TriagePolicy) -> f64 { - triage.confidence.iter().fold(0.0, |score, conf| { + fn score_by_confidence(&self, confidence: &[Confidence]) -> f64 { + confidence.iter().fold(0.0, |score, conf| { if conf.threat_category == self.category() && conf.threat_kind.to_lowercase() == self.kind().to_lowercase() && self.confidence().map_or(true, |c| { @@ -291,7 +303,194 @@ pub fn to_hardware_address(chaddr: &[u8]) -> String { ) } +pub enum AttrValue<'a> { + Addr(IpAddr), + Bool(bool), + #[allow(dead_code)] + Float(f64), + SInt(i64), + UInt(u64), + String(&'a str), + VecAddr(&'a [IpAddr]), + #[allow(dead_code)] + VecFloat(Vec), + VecSInt(Vec), + VecUInt(Vec), + VecString(&'a [String]), +} + +fn check_all_or_any<'a, T, F>(attr_val: &'a [T], packet_attr: &'a PacketAttr, compare_fn: F) -> bool +where + F: Fn(&'a T, &'a PacketAttr) -> bool, +{ + match packet_attr.cmp_kind { + AttrCmpKind::NotEqual | AttrCmpKind::NotContain => { + attr_val.iter().all(|x| compare_fn(x, packet_attr)) + } + _ => attr_val.iter().any(|x| compare_fn(x, packet_attr)), + } +} + +fn process_attr_compare(target_value: AttrValue, attr: &PacketAttr) -> bool { + match target_value { + AttrValue::Addr(ip_addr) => compare_addr_attribute(&ip_addr, attr), + AttrValue::Bool(bool_val) => compare_bool_attribute(bool_val, attr), + AttrValue::Float(float_val) => compare_number_attribute::<_, f64>(&float_val, attr), + AttrValue::SInt(signed_int_val) => { + compare_number_attribute::<_, i64>(&signed_int_val, attr) + } + AttrValue::UInt(unsigned_int_val) => { + compare_number_attribute::<_, u64>(&unsigned_int_val, attr) + } + AttrValue::String(str_val) => compare_string_attribute(str_val, attr), + AttrValue::VecAddr(vec_addr_val) => { + check_all_or_any(vec_addr_val, attr, compare_addr_attribute) + } + AttrValue::VecFloat(vec_float_val) => { + check_all_or_any(&vec_float_val, attr, compare_number_attribute::<_, f64>) + } + AttrValue::VecSInt(vec_sint_val) => { + check_all_or_any(&vec_sint_val, attr, compare_number_attribute::<_, i64>) + } + AttrValue::VecUInt(vec_uint_val) => { + check_all_or_any(&vec_uint_val, attr, compare_number_attribute::<_, u64>) + } + AttrValue::VecString(vec_str_val) => check_all_or_any(vec_str_val, attr, |val, attr| { + compare_string_attribute(val.as_str(), attr) + }), + } +} + +fn deserialize<'de, T>(value: &'de [u8]) -> Option +where + T: Deserialize<'de>, +{ + bincode::DefaultOptions::new().deserialize::(value).ok() +} + +fn check_second_value<'de, T, K>(kind: AttrCmpKind, value: Option<&'de Vec>) -> Option +where + T: TryFrom + std::cmp::PartialOrd, + K: Deserialize<'de>, +{ + match kind { + AttrCmpKind::OpenRange + | AttrCmpKind::CloseRange + | AttrCmpKind::LeftOpenRange + | AttrCmpKind::RightOpenRange + | AttrCmpKind::NotOpenRange + | AttrCmpKind::NotCloseRange + | AttrCmpKind::NotLeftOpenRange + | AttrCmpKind::NotRightOpenRange => { + let value = value.as_ref()?; + let de_second_value: K = deserialize(value)?; + T::try_from(de_second_value).ok() + } + _ => None, + } +} + +fn compare_all_attr_cmp_kind( + cmp_kind: AttrCmpKind, + attr_val: &T, + first_val: &T, + second_val: Option, +) -> bool +where + T: PartialOrd, +{ + match (cmp_kind, second_val) { + (AttrCmpKind::Less, _) => attr_val < first_val, + (AttrCmpKind::LessOrEqual, _) => attr_val <= first_val, + (AttrCmpKind::Equal, _) => attr_val == first_val, + (AttrCmpKind::NotEqual, _) => attr_val != first_val, + (AttrCmpKind::Greater, _) => attr_val > first_val, + (AttrCmpKind::GreaterOrEqual, _) => attr_val >= first_val, + (AttrCmpKind::OpenRange, Some(second_val)) => { + (first_val < attr_val) && (second_val > *attr_val) + } + (AttrCmpKind::CloseRange, Some(second_val)) => { + (first_val <= attr_val) && (second_val >= *attr_val) + } + (AttrCmpKind::LeftOpenRange, Some(second_val)) => { + (first_val < attr_val) && (second_val >= *attr_val) + } + (AttrCmpKind::RightOpenRange, Some(second_val)) => { + (first_val <= attr_val) && (second_val > *attr_val) + } + (AttrCmpKind::NotOpenRange, Some(second_val)) => { + !((first_val < attr_val) && (second_val > *attr_val)) + } + (AttrCmpKind::NotCloseRange, Some(second_val)) => { + !((first_val <= attr_val) && (second_val >= *attr_val)) + } + (AttrCmpKind::NotLeftOpenRange, Some(second_val)) => { + !((first_val < attr_val) && (second_val >= *attr_val)) + } + (AttrCmpKind::NotRightOpenRange, Some(second_val)) => { + !((first_val <= attr_val) && (second_val > *attr_val)) + } + _ => false, + } +} + +fn compare_bool_attribute(attr_val: bool, packet_attr: &PacketAttr) -> bool { + deserialize::(&packet_attr.first_value).is_some_and(|compare_val| { + match packet_attr.cmp_kind { + AttrCmpKind::Equal => attr_val == compare_val, + AttrCmpKind::NotEqual => attr_val != compare_val, + _ => false, + } + }) +} + +fn compare_string_attribute(attr_val: &str, packet_attr: &PacketAttr) -> bool { + deserialize::(&packet_attr.first_value).is_some_and(|compare_val| { + let cmp_result = attr_val.contains(&compare_val); + match packet_attr.cmp_kind { + AttrCmpKind::Contain => cmp_result, + AttrCmpKind::NotContain => !cmp_result, + _ => false, + } + }) +} + +fn compare_addr_attribute(attr_val: &IpAddr, packet_attr: &PacketAttr) -> bool { + if let Some(first_val) = deserialize::(&packet_attr.first_value) { + let second_val = packet_attr + .second_value + .as_ref() + .and_then(|serde_val| deserialize::(serde_val)); + return compare_all_attr_cmp_kind(packet_attr.cmp_kind, attr_val, &first_val, second_val); + } + false +} + +fn compare_number_attribute<'de, T, K>(attr_val: &T, packet_attr: &'de PacketAttr) -> bool +where + T: TryFrom + PartialOrd, + K: Deserialize<'de>, +{ + if let Some(first_val) = deserialize::(&packet_attr.first_value) { + if let Ok(first_val) = T::try_from(first_val) { + let second_val = + check_second_value::(packet_attr.cmp_kind, packet_attr.second_value.as_ref()); + return compare_all_attr_cmp_kind( + packet_attr.cmp_kind, + attr_val, + &first_val, + second_val, + ); + } + }; + false +} + mod tests { + + use bincode::Options; + use serde::Serialize; + #[test] fn empty_byte_slice_to_colon_separated_string() { assert_eq!(super::to_hardware_address(&[]), ""); @@ -304,4 +503,232 @@ mod tests { "12:34:56:78:9a:bc" ); } + + #[allow(dead_code)] + fn serialize(v: &T) -> Option> + where + T: Serialize, + { + bincode::DefaultOptions::new().serialize(v).ok() + } + + #[test] + fn compare_attribute() { + use std::{ + cmp::Ordering, + net::{IpAddr, Ipv4Addr}, + }; + + use attrievent::attribute::{DhcpAttr, DnsAttr, HttpAttr, RawEventKind}; + use chrono::{TimeZone, Utc}; + + use crate::{ + event::common::Match, AttrCmpKind, BlockListDhcp, BlockListDhcpFields, DgaFields, + DnsCovertChannel, DnsEventFields, DomainGenerationAlgorithm, EventCategory, PacketAttr, + ValueKind, + }; + + let time = Utc.with_ymd_and_hms(1970, 1, 1, 0, 1, 1).unwrap(); + + // Compare `Addr`, `String`, `UInt`, `VecString` type + let fields = DgaFields { + sensor: "sensor".to_string(), + src_addr: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 5)), + src_port: 10000, + dst_addr: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 6)), + dst_port: 80, + proto: 6, + duration: 1000, + method: "GET".to_string(), + host: "example.com".to_string(), + uri: "/uri/path".to_string(), + referer: "-".to_string(), + version: "1.1".to_string(), + user_agent: "browser".to_string(), + request_len: 100, + response_len: 100, + status_code: 200, + status_msg: "-".to_string(), + username: "-".to_string(), + password: "-".to_string(), + cookie: "cookie".to_string(), + content_encoding: "encoding type".to_string(), + content_type: "content type".to_string(), + cache_control: "no cache".to_string(), + orig_filenames: vec!["a1".to_string(), "a2".to_string()], + orig_mime_types: Vec::new(), + resp_filenames: Vec::new(), + resp_mime_types: vec!["b1".to_string(), "b2".to_string()], + post_body: "12345678901234567890".to_string().into_bytes(), + state: String::new(), + confidence: 0.8, + category: EventCategory::CommandAndControl, + }; + + let http_event = DomainGenerationAlgorithm::new(time, fields); + let success_packet_attr = vec![ + PacketAttr { + raw_event_kind: RawEventKind::Http, + attr_name: HttpAttr::SrcAddr.to_string(), + value_kind: ValueKind::IpAddr, + cmp_kind: AttrCmpKind::CloseRange, + first_value: serialize(&IpAddr::V4(Ipv4Addr::new(127, 0, 0, 5))).unwrap(), + second_value: serialize(&IpAddr::V4(Ipv4Addr::new(127, 0, 0, 6))), + weight: Some(0.1), + }, + PacketAttr { + raw_event_kind: RawEventKind::Http, + attr_name: HttpAttr::Uri.to_string(), + value_kind: ValueKind::String, + cmp_kind: AttrCmpKind::Contain, + first_value: serialize(&"path").unwrap(), + second_value: None, + weight: Some(0.2), + }, + ]; + let score_result = http_event.score_by_attr(&success_packet_attr); + assert_eq!(score_result.partial_cmp(&0.3), Some(Ordering::Equal)); + + let fail_packet_attr = vec![ + PacketAttr { + raw_event_kind: RawEventKind::Http, + attr_name: HttpAttr::DstPort.to_string(), + value_kind: ValueKind::UInteger, + cmp_kind: AttrCmpKind::OpenRange, + first_value: serialize(&80_u64).unwrap(), + second_value: serialize(&82_u64), + weight: Some(0.1), + }, + PacketAttr { + raw_event_kind: RawEventKind::Http, + attr_name: HttpAttr::RespMimeTypes.to_string(), + value_kind: ValueKind::String, + cmp_kind: AttrCmpKind::NotContain, + first_value: serialize(&"b1").unwrap(), + second_value: None, + weight: Some(0.1), + }, + ]; + let score_result = http_event.score_by_attr(&fail_packet_attr); + assert_eq!(score_result.partial_cmp(&0.0), Some(Ordering::Equal)); + + // Compare `Bool`, `SInt`, `VecSInt` + let fields = DnsEventFields { + sensor: "sensor".to_string(), + session_end_time: Utc::now(), + src_addr: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), + src_port: 10000, + dst_addr: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 2)), + dst_port: 53, + proto: 17, + query: "foo.com".to_string(), + answer: vec!["1.1.1.1".to_string()], + trans_id: 1, + rtt: 5, + qclass: 0, + qtype: 0, + rcode: 0, + aa_flag: false, + tc_flag: false, + rd_flag: false, + ra_flag: false, + ttl: vec![1, 3, 5, 7], + confidence: 0.8, + category: EventCategory::CommandAndControl, + }; + let dns_event = DnsCovertChannel::new(time, fields); + let success_packet_attr = vec![ + PacketAttr { + raw_event_kind: RawEventKind::Dns, + attr_name: DnsAttr::Rtt.to_string(), + value_kind: ValueKind::Integer, + cmp_kind: AttrCmpKind::Less, + first_value: serialize(&6_i64).unwrap(), + second_value: None, + weight: Some(0.3), + }, + PacketAttr { + raw_event_kind: RawEventKind::Dns, + attr_name: DnsAttr::Ttl.to_string(), + value_kind: ValueKind::Integer, + cmp_kind: AttrCmpKind::NotEqual, + first_value: serialize(&9_i64).unwrap(), + second_value: None, + weight: Some(0.5), + }, + ]; + let score_result = dns_event.score_by_attr(&success_packet_attr); + assert_eq!(score_result.partial_cmp(&0.8), Some(Ordering::Equal)); + + let fail_packet_attr = vec![PacketAttr { + raw_event_kind: RawEventKind::Dns, + attr_name: DnsAttr::AA.to_string(), + value_kind: ValueKind::Bool, + cmp_kind: AttrCmpKind::Equal, + first_value: serialize(&true).unwrap(), + second_value: None, + weight: Some(0.2), + }]; + let score_result = dns_event.score_by_attr(&fail_packet_attr); + assert_eq!(score_result.partial_cmp(&0.0), Some(Ordering::Equal)); + + // Compare `VecAddr`, `VecUInt` + let fields = BlockListDhcpFields { + sensor: "sensor".to_string(), + src_addr: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), + src_port: 68, + dst_addr: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 2)), + dst_port: 67, + proto: 17, + last_time: 100, + msg_type: 1, + ciaddr: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 5)), + yiaddr: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 6)), + siaddr: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 7)), + giaddr: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 8)), + subnet_mask: IpAddr::V4(Ipv4Addr::new(255, 255, 255, 255)), + router: vec![ + IpAddr::V4(Ipv4Addr::new(127, 0, 0, 7)), + IpAddr::V4(Ipv4Addr::new(127, 0, 0, 8)), + IpAddr::V4(Ipv4Addr::new(127, 0, 0, 9)), + ], + domain_name_server: vec![IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))], + req_ip_addr: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 100)), + lease_time: 100, + server_id: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), + param_req_list: vec![2, 3, 4], + message: "message".to_string(), + renewal_time: 100, + rebinding_time: 200, + class_id: "MSFT 5.0".to_string().into_bytes(), + client_id_type: 1, + client_id: vec![7, 8, 9], + category: EventCategory::InitialAccess, + }; + + let dhcp_event = BlockListDhcp::new(time, fields); + let success_packet_attr = vec![PacketAttr { + raw_event_kind: RawEventKind::Dhcp, + attr_name: DhcpAttr::Router.to_string(), + value_kind: ValueKind::IpAddr, + cmp_kind: AttrCmpKind::LeftOpenRange, + first_value: serialize(&IpAddr::V4(Ipv4Addr::new(127, 0, 0, 8))).unwrap(), + second_value: serialize(&IpAddr::V4(Ipv4Addr::new(127, 0, 0, 9))), + weight: Some(0.15), + }]; + let score_result = dhcp_event.score_by_attr(&success_packet_attr); + assert_eq!(score_result.partial_cmp(&0.15), Some(Ordering::Equal)); + + let fail_packet_attr = vec![PacketAttr { + raw_event_kind: RawEventKind::Dhcp, + attr_name: DhcpAttr::ParamReqList.to_string(), + value_kind: ValueKind::UInteger, + cmp_kind: AttrCmpKind::RightOpenRange, + first_value: serialize(&1_u64).unwrap(), + second_value: serialize(&2_u64), + weight: Some(0.35), + }]; + let score_result = dhcp_event.score_by_attr(&fail_packet_attr); + assert_eq!(score_result.partial_cmp(&0.0), Some(Ordering::Equal)); + } } diff --git a/src/event/conn.rs b/src/event/conn.rs index a79eac9..7d22241 100644 --- a/src/event/conn.rs +++ b/src/event/conn.rs @@ -4,11 +4,38 @@ use std::{ num::NonZeroU8, }; +use attrievent::attribute::{ConnAttr, RawEventAttrKind}; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; -use super::{common::Match, EventCategory, TriagePolicy, TriageScore, MEDIUM}; -use crate::event::common::{triage_scores_to_string, vector_to_string}; +use super::{common::Match, EventCategory, TriageScore, MEDIUM}; +use crate::event::common::{triage_scores_to_string, vector_to_string, AttrValue}; + +macro_rules! conn_target_attr { + ($event: expr, $raw_event_attr: expr) => {{ + if let RawEventAttrKind::Conn(attr) = $raw_event_attr { + let target_value = match attr { + ConnAttr::SrcAddr => AttrValue::Addr($event.src_addr), + ConnAttr::SrcPort => AttrValue::UInt($event.src_port.into()), + ConnAttr::DstAddr => AttrValue::Addr($event.dst_addr), + ConnAttr::DstPort => AttrValue::UInt($event.dst_port.into()), + ConnAttr::Proto => AttrValue::UInt($event.proto.into()), + ConnAttr::ConnState => AttrValue::String(&$event.conn_state), + ConnAttr::Duration => AttrValue::SInt($event.duration), + ConnAttr::Service => AttrValue::String(&$event.service), + ConnAttr::OrigBytes => AttrValue::UInt($event.orig_bytes), + ConnAttr::RespBytes => AttrValue::UInt($event.resp_bytes), + ConnAttr::OrigPkts => AttrValue::UInt($event.orig_pkts), + ConnAttr::RespPkts => AttrValue::UInt($event.resp_pkts), + ConnAttr::OrigL2Bytes => AttrValue::UInt($event.orig_l2_bytes), + ConnAttr::RespL2Bytes => AttrValue::UInt($event.resp_l2_bytes), + }; + Some(target_value) + } else { + None + } + }}; +} #[derive(Serialize, Deserialize)] pub struct PortScanFields { @@ -122,8 +149,20 @@ impl Match for PortScan { None } - fn score_by_packet_attr(&self, _triage: &TriagePolicy) -> f64 { - 0.0 + fn to_attr_value(&self, raw_event_attr: RawEventAttrKind) -> Option { + if let RawEventAttrKind::Conn(attr) = raw_event_attr { + match attr { + ConnAttr::SrcAddr => Some(AttrValue::Addr(self.src_addr)), + ConnAttr::DstAddr => Some(AttrValue::Addr(self.dst_addr)), + ConnAttr::DstPort => Some(AttrValue::VecUInt( + self.dst_ports.iter().map(|val| u64::from(*val)).collect(), + )), + ConnAttr::Proto => Some(AttrValue::UInt(self.proto.into())), + _ => None, + } + } else { + None + } } } @@ -239,8 +278,18 @@ impl Match for MultiHostPortScan { None } - fn score_by_packet_attr(&self, _triage: &TriagePolicy) -> f64 { - 0.0 + fn to_attr_value(&self, raw_event_attr: RawEventAttrKind) -> Option { + if let RawEventAttrKind::Conn(attr) = raw_event_attr { + match attr { + ConnAttr::SrcAddr => Some(AttrValue::Addr(self.src_addr)), + ConnAttr::DstPort => Some(AttrValue::UInt(self.dst_port.into())), + ConnAttr::DstAddr => Some(AttrValue::VecAddr(&self.dst_addrs)), + ConnAttr::Proto => Some(AttrValue::UInt(self.proto.into())), + _ => None, + } + } else { + None + } } } @@ -351,8 +400,17 @@ impl Match for ExternalDdos { None } - fn score_by_packet_attr(&self, _triage: &TriagePolicy) -> f64 { - 0.0 + fn to_attr_value(&self, raw_event_attr: RawEventAttrKind) -> Option { + if let RawEventAttrKind::Conn(attr) = raw_event_attr { + match attr { + ConnAttr::SrcAddr => Some(AttrValue::VecAddr(&self.src_addrs)), + ConnAttr::DstAddr => Some(AttrValue::Addr(self.dst_addr)), + ConnAttr::Proto => Some(AttrValue::UInt(self.proto.into())), + _ => None, + } + } else { + None + } } } @@ -513,7 +571,7 @@ impl Match for BlockListConn { None } - fn score_by_packet_attr(&self, _triage: &TriagePolicy) -> f64 { - 0.0 + fn to_attr_value(&self, raw_event_attr: RawEventAttrKind) -> Option { + conn_target_attr!(self, raw_event_attr) } } diff --git a/src/event/dcerpc.rs b/src/event/dcerpc.rs index 1ad74e8..d85c5ff 100644 --- a/src/event/dcerpc.rs +++ b/src/event/dcerpc.rs @@ -1,10 +1,11 @@ use std::{fmt, net::IpAddr, num::NonZeroU8}; +use attrievent::attribute::RawEventAttrKind; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; -use super::{common::Match, EventCategory, TriagePolicy, TriageScore, MEDIUM}; -use crate::event::common::triage_scores_to_string; +use super::{common::Match, EventCategory, TriageScore, MEDIUM}; +use crate::event::common::{triage_scores_to_string, AttrValue}; #[derive(Serialize, Deserialize)] pub struct BlockListDceRpcFields { @@ -142,7 +143,10 @@ impl Match for BlockListDceRpc { None } - fn score_by_packet_attr(&self, _triage: &TriagePolicy) -> f64 { - 0.0 + // Since `dcerpc` is not currently an event type collected by Feature Sensor, and as a result, + // the notation for each attribute of `dcerpc` has not been finalized. Therefore, we will + // proceed with this part after the collection and notation of dcerpc events is finalized. + fn to_attr_value(&self, _raw_event_attr: RawEventAttrKind) -> Option { + None } } diff --git a/src/event/dhcp.rs b/src/event/dhcp.rs index 4fbe79d..b0ddc54 100644 --- a/src/event/dhcp.rs +++ b/src/event/dhcp.rs @@ -1,11 +1,56 @@ use std::{fmt, net::IpAddr, num::NonZeroU8}; +use attrievent::attribute::{DhcpAttr, RawEventAttrKind}; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; -use super::{common::Match, EventCategory, TriagePolicy, TriageScore, MEDIUM}; +use super::{ + common::{AttrValue, Match}, + EventCategory, TriageScore, MEDIUM, +}; use crate::event::common::{to_hardware_address, triage_scores_to_string, vector_to_string}; +macro_rules! dhcp_target_attr { + ($event: expr, $raw_event_attr: expr) => {{ + if let RawEventAttrKind::Dhcp(attr) = $raw_event_attr { + let target_value = match attr { + DhcpAttr::SrcAddr => AttrValue::Addr($event.src_addr), + DhcpAttr::SrcPort => AttrValue::UInt($event.src_port.into()), + DhcpAttr::DstAddr => AttrValue::Addr($event.dst_addr), + DhcpAttr::DstPort => AttrValue::UInt($event.dst_port.into()), + DhcpAttr::Proto => AttrValue::UInt($event.proto.into()), + DhcpAttr::MgsType => todo!(), + DhcpAttr::CiAddr => AttrValue::Addr($event.ciaddr), + DhcpAttr::YiAddr => AttrValue::Addr($event.yiaddr), + DhcpAttr::SiAddr => AttrValue::Addr($event.siaddr), + DhcpAttr::GiAddr => AttrValue::Addr($event.giaddr), + DhcpAttr::SubNetMask => AttrValue::Addr($event.subnet_mask), + DhcpAttr::Router => AttrValue::VecAddr(&$event.router), + DhcpAttr::DomainNameServer => AttrValue::VecAddr(&$event.domain_name_server), + DhcpAttr::ReqIpAddr => AttrValue::Addr($event.req_ip_addr), + DhcpAttr::LeaseTime => AttrValue::UInt($event.lease_time.into()), + DhcpAttr::ServerId => AttrValue::Addr($event.server_id), + DhcpAttr::ParamReqList => AttrValue::VecUInt( + $event + .param_req_list + .iter() + .map(|val| u64::from(*val)) + .collect(), + ), + DhcpAttr::Message => AttrValue::String(&$event.message), + DhcpAttr::RenewalTime => AttrValue::UInt($event.renewal_time.into()), + DhcpAttr::RebindingTime => AttrValue::UInt($event.rebinding_time.into()), + DhcpAttr::ClassId => AttrValue::String(&$event.class_id), + DhcpAttr::ClientIdType => AttrValue::UInt($event.client_id_type.into()), + DhcpAttr::ClientId => AttrValue::String(&$event.client_id), + }; + Some(target_value) + } else { + None + } + }}; +} + #[derive(Serialize, Deserialize)] pub struct BlockListDhcpFields { pub sensor: String, @@ -68,7 +113,7 @@ impl fmt::Display for BlockListDhcpFields { self.message.to_string(), self.renewal_time.to_string(), self.rebinding_time.to_string(), - to_hardware_address(&self.class_id), + std::str::from_utf8(&self.class_id).unwrap_or_default().to_string(), self.client_id_type.to_string(), to_hardware_address(&self.client_id), ) @@ -100,9 +145,9 @@ pub struct BlockListDhcp { pub message: String, pub renewal_time: u32, pub rebinding_time: u32, - pub class_id: Vec, + pub class_id: String, pub client_id_type: u8, - pub client_id: Vec, + pub client_id: String, pub category: EventCategory, pub triage_scores: Option>, } @@ -133,9 +178,9 @@ impl fmt::Display for BlockListDhcp { self.message.to_string(), self.renewal_time.to_string(), self.rebinding_time.to_string(), - to_hardware_address(&self.class_id), + self.class_id, self.client_id_type.to_string(), - to_hardware_address(&self.client_id), + self.client_id, triage_scores_to_string(self.triage_scores.as_ref()) ) } @@ -167,9 +212,11 @@ impl BlockListDhcp { message: fields.message, renewal_time: fields.renewal_time, rebinding_time: fields.rebinding_time, - class_id: fields.class_id, + class_id: std::str::from_utf8(&fields.class_id) + .unwrap_or_default() + .to_string(), client_id_type: fields.client_id_type, - client_id: fields.client_id, + client_id: to_hardware_address(&fields.client_id), category: fields.category, triage_scores: None, } @@ -217,7 +264,7 @@ impl Match for BlockListDhcp { None } - fn score_by_packet_attr(&self, _triage: &TriagePolicy) -> f64 { - 0.0 + fn to_attr_value(&self, raw_event_attr: RawEventAttrKind) -> Option { + dhcp_target_attr!(self, raw_event_attr) } } diff --git a/src/event/dns.rs b/src/event/dns.rs index b5bc13a..e7564c7 100644 --- a/src/event/dns.rs +++ b/src/event/dns.rs @@ -1,11 +1,43 @@ #![allow(clippy::module_name_repetitions, clippy::struct_excessive_bools)] use std::{fmt, net::IpAddr, num::NonZeroU8}; +use attrievent::attribute::{DnsAttr, RawEventAttrKind}; use chrono::{serde::ts_nanoseconds, DateTime, Utc}; use serde::{Deserialize, Serialize}; -use super::{common::Match, EventCategory, TriagePolicy, TriageScore, HIGH, MEDIUM}; -use crate::event::common::{triage_scores_to_string, vector_to_string}; +use super::{common::Match, EventCategory, TriageScore, HIGH, MEDIUM}; +use crate::event::common::{triage_scores_to_string, vector_to_string, AttrValue}; + +macro_rules! dns_target_attr { + ($event: expr, $raw_event_attr: expr) => {{ + if let RawEventAttrKind::Dns(attr) = $raw_event_attr { + let target_value = match attr { + DnsAttr::SrcAddr => AttrValue::Addr($event.src_addr), + DnsAttr::SrcPort => AttrValue::UInt($event.src_port.into()), + DnsAttr::DstAddr => AttrValue::Addr($event.dst_addr), + DnsAttr::DstPort => AttrValue::UInt($event.dst_port.into()), + DnsAttr::Proto => AttrValue::UInt($event.proto.into()), + DnsAttr::Query => AttrValue::String(&$event.query), + DnsAttr::Answer => AttrValue::VecString(&$event.answer), + DnsAttr::TransId => AttrValue::UInt($event.trans_id.into()), + DnsAttr::Rtt => AttrValue::SInt($event.rtt.into()), + DnsAttr::QClass => AttrValue::UInt($event.qclass.into()), + DnsAttr::QType => AttrValue::UInt($event.qtype.into()), + DnsAttr::RCode => AttrValue::UInt($event.rcode.into()), + DnsAttr::AA => AttrValue::Bool($event.aa_flag), + DnsAttr::TC => AttrValue::Bool($event.tc_flag), + DnsAttr::RD => AttrValue::Bool($event.rd_flag), + DnsAttr::RA => AttrValue::Bool($event.ra_flag), + DnsAttr::Ttl => { + AttrValue::VecSInt($event.ttl.iter().map(|val| i64::from(*val)).collect()) + } + }; + Some(target_value) + } else { + None + } + }}; +} #[derive(Deserialize, Serialize)] pub struct DnsEventFields { @@ -189,8 +221,8 @@ impl Match for DnsCovertChannel { Some(self.confidence) } - fn score_by_packet_attr(&self, _triage: &TriagePolicy) -> f64 { - 0.0 + fn to_attr_value(&self, raw_event_attr: RawEventAttrKind) -> Option { + dns_target_attr!(self, raw_event_attr) } } @@ -322,8 +354,8 @@ impl Match for LockyRansomware { Some(self.confidence) } - fn score_by_packet_attr(&self, _triage: &TriagePolicy) -> f64 { - 0.0 + fn to_attr_value(&self, raw_event_attr: RawEventAttrKind) -> Option { + dns_target_attr!(self, raw_event_attr) } } @@ -509,8 +541,8 @@ impl Match for CryptocurrencyMiningPool { None } - fn score_by_packet_attr(&self, _triage: &TriagePolicy) -> f64 { - 0.0 + fn to_attr_value(&self, raw_event_attr: RawEventAttrKind) -> Option { + dns_target_attr!(self, raw_event_attr) } } @@ -690,7 +722,7 @@ impl Match for BlockListDns { None } - fn score_by_packet_attr(&self, _triage: &TriagePolicy) -> f64 { - 0.0 + fn to_attr_value(&self, raw_event_attr: RawEventAttrKind) -> Option { + dns_target_attr!(self, raw_event_attr) } } diff --git a/src/event/ftp.rs b/src/event/ftp.rs index 0acaff3..3464716 100644 --- a/src/event/ftp.rs +++ b/src/event/ftp.rs @@ -1,11 +1,41 @@ #![allow(clippy::module_name_repetitions)] use std::{fmt, net::IpAddr, num::NonZeroU8}; +use attrievent::attribute::{FtpAttr, RawEventAttrKind}; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; -use super::{common::Match, EventCategory, TriagePolicy, TriageScore, MEDIUM}; -use crate::event::common::triage_scores_to_string; +use super::{common::Match, EventCategory, TriageScore, MEDIUM}; +use crate::event::common::{triage_scores_to_string, AttrValue}; + +macro_rules! ftp_target_attr { + ($event: expr, $raw_event_attr: expr) => {{ + if let RawEventAttrKind::Ftp(attr) = $raw_event_attr { + let target_value = match attr { + FtpAttr::SrcAddr => AttrValue::Addr($event.src_addr), + FtpAttr::SrcPort => AttrValue::UInt($event.src_port.into()), + FtpAttr::DstAddr => AttrValue::Addr($event.dst_addr), + FtpAttr::DstPort => AttrValue::UInt($event.dst_port.into()), + FtpAttr::Proto => AttrValue::UInt($event.proto.into()), + FtpAttr::User => AttrValue::String(&$event.user), + FtpAttr::Password => AttrValue::String(&$event.password), + FtpAttr::Command => AttrValue::String(&$event.command), + FtpAttr::ReplyCode => AttrValue::String(&$event.reply_code), + FtpAttr::ReplyMsg => AttrValue::String(&$event.reply_msg), + FtpAttr::DataPassive => AttrValue::Bool($event.data_passive), + FtpAttr::DataOrigAddr => AttrValue::Addr($event.data_orig_addr), + FtpAttr::DataRespAddr => AttrValue::Addr($event.data_resp_addr), + FtpAttr::DataRespPort => AttrValue::UInt($event.data_resp_port.into()), + FtpAttr::File => AttrValue::String(&$event.file), + FtpAttr::FileSize => AttrValue::UInt($event.file_size), + FtpAttr::FileId => AttrValue::String(&$event.file_id), + }; + Some(target_value) + } else { + None + } + }}; +} #[derive(Serialize, Deserialize)] pub struct FtpBruteForceFields { @@ -128,8 +158,19 @@ impl Match for FtpBruteForce { None } - fn score_by_packet_attr(&self, _triage: &TriagePolicy) -> f64 { - 0.0 + fn to_attr_value(&self, raw_event_attr: RawEventAttrKind) -> Option { + if let RawEventAttrKind::Ftp(attr) = raw_event_attr { + match attr { + FtpAttr::SrcAddr => Some(AttrValue::Addr(self.src_addr)), + FtpAttr::DstAddr => Some(AttrValue::Addr(self.dst_addr)), + FtpAttr::DstPort => Some(AttrValue::UInt(self.dst_port.into())), + FtpAttr::Proto => Some(AttrValue::UInt(self.proto.into())), + FtpAttr::User => Some(AttrValue::VecString(&self.user_list)), + _ => None, + } + } else { + None + } } } @@ -310,8 +351,8 @@ impl Match for FtpPlainText { None } - fn score_by_packet_attr(&self, _triage: &TriagePolicy) -> f64 { - 0.0 + fn to_attr_value(&self, raw_event_attr: RawEventAttrKind) -> Option { + ftp_target_attr!(self, raw_event_attr) } } @@ -440,7 +481,7 @@ impl Match for BlockListFtp { None } - fn score_by_packet_attr(&self, _triage: &TriagePolicy) -> f64 { - 0.0 + fn to_attr_value(&self, raw_event_attr: RawEventAttrKind) -> Option { + ftp_target_attr!(self, raw_event_attr) } } diff --git a/src/event/http.rs b/src/event/http.rs index 4ae1f3f..1e7f7c0 100644 --- a/src/event/http.rs +++ b/src/event/http.rs @@ -1,14 +1,52 @@ use std::{fmt, net::IpAddr, num::NonZeroU8}; use aho_corasick::AhoCorasickBuilder; +use attrievent::attribute::{HttpAttr, RawEventAttrKind}; use chrono::{serde::ts_nanoseconds, DateTime, Utc}; use serde::{Deserialize, Serialize}; -use super::{ - common::Match, EventCategory, EventFilter, HttpEventFields, TriagePolicy, TriageScore, LOW, - MEDIUM, -}; -use crate::event::common::triage_scores_to_string; +use super::{common::Match, EventCategory, EventFilter, TriageScore, LOW, MEDIUM}; +use crate::event::common::{triage_scores_to_string, AttrValue}; + +macro_rules! http_target_attr { + ($event: expr, $raw_event_attr: expr, $field: ident) => { + if let RawEventAttrKind::Http(attr) = $raw_event_attr { + match attr { + HttpAttr::SrcAddr => Some(AttrValue::Addr($event.src_addr)), + HttpAttr::SrcPort => Some(AttrValue::UInt($event.src_port.into())), + HttpAttr::DstAddr => Some(AttrValue::Addr($event.dst_addr)), + HttpAttr::DstPort => Some(AttrValue::UInt($event.dst_port.into())), + HttpAttr::Proto => Some(AttrValue::UInt($event.proto.into())), + HttpAttr::Method => Some(AttrValue::String(&$event.method)), + HttpAttr::Host => Some(AttrValue::String(&$event.host)), + HttpAttr::Uri => Some(AttrValue::String(&$event.uri)), + HttpAttr::Referrer => Some(AttrValue::String(&$event.$field)), + HttpAttr::Version => Some(AttrValue::String(&$event.version)), + HttpAttr::UserAgent => Some(AttrValue::String(&$event.user_agent)), + HttpAttr::RequestLen => u64::try_from($event.request_len).ok().map(AttrValue::UInt), + HttpAttr::ResponseLen => { + u64::try_from($event.response_len).ok().map(AttrValue::UInt) + } + HttpAttr::StatusCode => Some(AttrValue::UInt($event.status_code.into())), + HttpAttr::StatusMsg => Some(AttrValue::String(&$event.status_msg)), + HttpAttr::Username => Some(AttrValue::String(&$event.username)), + HttpAttr::Password => Some(AttrValue::String(&$event.password)), + HttpAttr::Cookie => Some(AttrValue::String(&$event.cookie)), + HttpAttr::ContentEncoding => Some(AttrValue::String(&$event.content_encoding)), + HttpAttr::ContentType => Some(AttrValue::String(&$event.content_type)), + HttpAttr::CacheControl => Some(AttrValue::String(&$event.cache_control)), + HttpAttr::OrigFilenames => Some(AttrValue::VecString(&$event.orig_filenames)), + HttpAttr::OrigMimeTypes => Some(AttrValue::VecString(&$event.orig_mime_types)), + HttpAttr::RespFilenames => Some(AttrValue::VecString(&$event.resp_filenames)), + HttpAttr::RespMimeTypes => Some(AttrValue::VecString(&$event.resp_mime_types)), + HttpAttr::PostBody => Some(AttrValue::String(&$event.post_body)), + HttpAttr::State => Some(AttrValue::String(&$event.state)), + } + } else { + None + } + }; +} #[derive(Serialize, Deserialize)] pub struct RepeatedHttpSessionsFields { @@ -121,9 +159,19 @@ impl Match for RepeatedHttpSessions { None } - fn score_by_packet_attr(&self, _triage: &TriagePolicy) -> f64 { - // TODO: implement - 0.0 + fn to_attr_value(&self, raw_event_attr: RawEventAttrKind) -> Option { + if let RawEventAttrKind::Http(attr) = raw_event_attr { + match attr { + HttpAttr::SrcAddr => Some(AttrValue::Addr(self.src_addr)), + HttpAttr::SrcPort => Some(AttrValue::UInt(self.src_port.into())), + HttpAttr::DstAddr => Some(AttrValue::Addr(self.dst_addr)), + HttpAttr::DstPort => Some(AttrValue::UInt(self.dst_port.into())), + HttpAttr::Proto => Some(AttrValue::UInt(self.proto.into())), + _ => None, + } + } else { + None + } } } @@ -261,7 +309,7 @@ pub struct HttpThreat { pub orig_mime_types: Vec, pub resp_filenames: Vec, pub resp_mime_types: Vec, - pub post_body: Vec, + pub post_body: String, pub state: String, pub db_name: String, pub rule_id: u32, @@ -305,7 +353,7 @@ impl fmt::Display for HttpThreat { self.orig_mime_types.join(","), self.resp_filenames.join(","), self.resp_mime_types.join(","), - get_post_body(&self.post_body), + self.post_body, self.state, self.db_name, self.rule_id.to_string(), @@ -349,7 +397,7 @@ impl HttpThreat { orig_mime_types: fields.orig_mime_types, resp_filenames: fields.resp_filenames, resp_mime_types: fields.resp_mime_types, - post_body: fields.post_body, + post_body: get_post_body(&fields.post_body), state: fields.state, db_name: fields.db_name, rule_id: fields.rule_id, @@ -404,8 +452,8 @@ impl Match for HttpThreat { Some(self.confidence) } - fn score_by_packet_attr(&self, _triage: &TriagePolicy) -> f64 { - 0.0 + fn to_attr_value(&self, raw_event_attr: RawEventAttrKind) -> Option { + http_target_attr!(self, raw_event_attr, referer) } fn kind_matches(&self, filter: &EventFilter) -> bool { @@ -537,7 +585,7 @@ pub struct DomainGenerationAlgorithm { pub orig_mime_types: Vec, pub resp_filenames: Vec, pub resp_mime_types: Vec, - pub post_body: Vec, + pub post_body: String, pub state: String, pub confidence: f32, pub category: EventCategory, @@ -576,7 +624,7 @@ impl fmt::Display for DomainGenerationAlgorithm { self.orig_mime_types.join(","), self.resp_filenames.join(","), self.resp_mime_types.join(","), - get_post_body(&self.post_body), + self.post_body, self.state, self.confidence.to_string(), triage_scores_to_string(self.triage_scores.as_ref()), @@ -615,7 +663,7 @@ impl DomainGenerationAlgorithm { orig_mime_types: fields.orig_mime_types, resp_filenames: fields.resp_filenames, resp_mime_types: fields.resp_mime_types, - post_body: fields.post_body, + post_body: get_post_body(&fields.post_body), state: fields.state, confidence: fields.confidence, category: fields.category, @@ -665,8 +713,82 @@ impl Match for DomainGenerationAlgorithm { Some(self.confidence) } - fn score_by_packet_attr(&self, _triage: &TriagePolicy) -> f64 { - 0.0 + fn to_attr_value(&self, raw_event_attr: RawEventAttrKind) -> Option { + http_target_attr!(self, raw_event_attr, referer) + } +} + +#[derive(Deserialize, Serialize)] +#[allow(clippy::module_name_repetitions)] +pub struct HttpEventFields { + pub sensor: String, + #[serde(with = "ts_nanoseconds")] + pub session_end_time: DateTime, + pub src_addr: IpAddr, + pub src_port: u16, + pub dst_addr: IpAddr, + pub dst_port: u16, + pub proto: u8, + pub method: String, + pub host: String, + pub uri: String, + pub referrer: String, + pub version: String, + pub user_agent: String, + pub request_len: usize, + pub response_len: usize, + pub status_code: u16, + pub status_msg: String, + pub username: String, + pub password: String, + pub cookie: String, + pub content_encoding: String, + pub content_type: String, + pub cache_control: String, + pub orig_filenames: Vec, + pub orig_mime_types: Vec, + pub resp_filenames: Vec, + pub resp_mime_types: Vec, + pub post_body: Vec, + pub state: String, + pub category: EventCategory, +} + +impl fmt::Display for HttpEventFields { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "sensor={:?} src_addr={:?} src_port={:?} dst_addr={:?} dst_port={:?} proto={:?} session_end_time={:?} method={:?} host={:?} uri={:?} referrer={:?} version={:?} user_agent={:?} request_len={:?} response_len={:?} status_code={:?} status_msg={:?} username={:?} password={:?} cookie={:?} content_encoding={:?} content_type={:?} cache_control={:?} orig_filenames={:?} orig_mime_types={:?} resp_filenames={:?} resp_mime_types={:?} post_body={:?} state={:?}", + self.sensor, + self.src_addr.to_string(), + self.src_port.to_string(), + self.dst_addr.to_string(), + self.dst_port.to_string(), + self.proto.to_string(), + self.session_end_time.to_rfc3339(), + self.method, + self.host, + self.uri, + self.referrer, + self.version, + self.user_agent, + self.request_len.to_string(), + self.response_len.to_string(), + self.status_code.to_string(), + self.status_msg, + self.username, + self.password, + self.cookie, + self.content_encoding, + self.content_type, + self.cache_control, + self.orig_filenames.join(","), + self.orig_mime_types.join(","), + self.resp_filenames.join(","), + self.resp_mime_types.join(","), + get_post_body(&self.post_body), + self.state + ) } } @@ -700,7 +822,7 @@ pub struct NonBrowser { pub orig_mime_types: Vec, pub resp_filenames: Vec, pub resp_mime_types: Vec, - pub post_body: Vec, + pub post_body: String, pub state: String, pub category: EventCategory, pub triage_scores: Option>, @@ -738,7 +860,7 @@ impl fmt::Display for NonBrowser { self.orig_mime_types.join(","), self.resp_filenames.join(","), self.resp_mime_types.join(","), - get_post_body(&self.post_body), + self.post_body, self.state, triage_scores_to_string(self.triage_scores.as_ref()), ) @@ -776,7 +898,7 @@ impl NonBrowser { orig_mime_types: fields.orig_mime_types.clone(), resp_filenames: fields.resp_filenames.clone(), resp_mime_types: fields.resp_mime_types.clone(), - post_body: fields.post_body.clone(), + post_body: get_post_body(&fields.post_body), state: fields.state.clone(), category: fields.category, triage_scores: None, @@ -825,8 +947,168 @@ impl Match for NonBrowser { None } - fn score_by_packet_attr(&self, _triage: &TriagePolicy) -> f64 { - 0.0 + fn to_attr_value(&self, raw_event_attr: RawEventAttrKind) -> Option { + http_target_attr!(self, raw_event_attr, referrer) + } +} + +#[allow(clippy::module_name_repetitions)] +pub struct TorConnection { + pub time: DateTime, + pub sensor: String, + pub session_end_time: DateTime, + pub src_addr: IpAddr, + pub src_port: u16, + pub dst_addr: IpAddr, + pub dst_port: u16, + pub proto: u8, + pub method: String, + pub host: String, + pub uri: String, + pub referrer: String, + pub version: String, + pub user_agent: String, + pub request_len: usize, + pub response_len: usize, + pub status_code: u16, + pub status_msg: String, + pub username: String, + pub password: String, + pub cookie: String, + pub content_encoding: String, + pub content_type: String, + pub cache_control: String, + pub orig_filenames: Vec, + pub orig_mime_types: Vec, + pub resp_filenames: Vec, + pub resp_mime_types: Vec, + pub post_body: String, + pub state: String, + pub category: EventCategory, + pub triage_scores: Option>, +} + +impl fmt::Display for TorConnection { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "sensor={:?} src_addr={:?} src_port={:?} dst_addr={:?} dst_port={:?} proto={:?} session_end_time={:?} method={:?} host={:?} uri={:?} referrer={:?} version={:?} user_agent={:?} request_len={:?} response_len={:?} status_code={:?} status_msg={:?} username={:?} password={:?} cookie={:?} content_encoding={:?} content_type={:?} cache_control={:?} orig_filenames={:?} orig_mime_types={:?} resp_filenames={:?} resp_mime_types={:?} post_body={:?} state={:?} triage_scores={:?}", + self.sensor, + self.src_addr.to_string(), + self.src_port.to_string(), + self.dst_addr.to_string(), + self.dst_port.to_string(), + self.proto.to_string(), + self.session_end_time.to_rfc3339(), + self.method, + self.host, + self.uri, + self.referrer, + self.version, + self.user_agent, + self.request_len.to_string(), + self.response_len.to_string(), + self.status_code.to_string(), + self.status_msg, + self.username, + self.password, + self.cookie, + self.content_encoding, + self.content_type, + self.cache_control, + self.orig_filenames.join(","), + self.orig_mime_types.join(","), + self.resp_filenames.join(","), + self.resp_mime_types.join(","), + self.post_body, + self.state, + triage_scores_to_string(self.triage_scores.as_ref()) + ) + } +} + +impl TorConnection { + pub(super) fn new(time: DateTime, fields: &HttpEventFields) -> Self { + TorConnection { + time, + sensor: fields.sensor.clone(), + session_end_time: fields.session_end_time, + src_addr: fields.src_addr, + src_port: fields.src_port, + dst_addr: fields.dst_addr, + dst_port: fields.dst_port, + proto: fields.proto, + method: fields.method.clone(), + host: fields.host.clone(), + uri: fields.uri.clone(), + referrer: fields.referrer.clone(), + version: fields.version.clone(), + user_agent: fields.user_agent.clone(), + request_len: fields.request_len, + response_len: fields.response_len, + status_code: fields.status_code, + status_msg: fields.status_msg.clone(), + username: fields.username.clone(), + password: fields.password.clone(), + cookie: fields.cookie.clone(), + content_encoding: fields.content_encoding.clone(), + content_type: fields.content_type.clone(), + cache_control: fields.cache_control.clone(), + orig_filenames: fields.orig_filenames.clone(), + orig_mime_types: fields.orig_mime_types.clone(), + resp_filenames: fields.resp_filenames.clone(), + resp_mime_types: fields.resp_mime_types.clone(), + post_body: get_post_body(&fields.post_body), + state: fields.state.clone(), + category: fields.category, + triage_scores: None, + } + } +} + +impl Match for TorConnection { + fn src_addr(&self) -> IpAddr { + self.src_addr + } + + fn src_port(&self) -> u16 { + self.src_port + } + + fn dst_addr(&self) -> IpAddr { + self.dst_addr + } + + fn dst_port(&self) -> u16 { + self.dst_port + } + + fn proto(&self) -> u8 { + self.proto + } + + fn category(&self) -> EventCategory { + self.category + } + + fn level(&self) -> NonZeroU8 { + MEDIUM + } + + fn kind(&self) -> &str { + "tor exit nodes" + } + + fn sensor(&self) -> &str { + self.sensor.as_str() + } + + fn confidence(&self) -> Option { + None + } + + fn to_attr_value(&self, raw_event_attr: RawEventAttrKind) -> Option { + http_target_attr!(self, raw_event_attr, referrer) } } @@ -933,7 +1215,7 @@ pub struct BlockListHttp { pub orig_mime_types: Vec, pub resp_filenames: Vec, pub resp_mime_types: Vec, - pub post_body: Vec, + pub post_body: String, pub state: String, pub category: EventCategory, pub triage_scores: Option>, @@ -971,7 +1253,7 @@ impl fmt::Display for BlockListHttp { self.orig_mime_types.join(","), self.resp_filenames.join(","), self.resp_mime_types.join(","), - get_post_body(&self.post_body), + self.post_body, self.state, triage_scores_to_string(self.triage_scores.as_ref()), ) @@ -1009,7 +1291,7 @@ impl BlockListHttp { orig_mime_types: fields.orig_mime_types.clone(), resp_filenames: fields.resp_filenames.clone(), resp_mime_types: fields.resp_mime_types.clone(), - post_body: fields.post_body.clone(), + post_body: get_post_body(&fields.post_body), state: fields.state.clone(), category: fields.category, triage_scores: None, @@ -1058,7 +1340,7 @@ impl Match for BlockListHttp { None } - fn score_by_packet_attr(&self, _triage: &TriagePolicy) -> f64 { - 0.0 + fn to_attr_value(&self, raw_event_attr: RawEventAttrKind) -> Option { + http_target_attr!(self, raw_event_attr, referrer) } } diff --git a/src/event/kerberos.rs b/src/event/kerberos.rs index f585bf9..4dba1e1 100644 --- a/src/event/kerberos.rs +++ b/src/event/kerberos.rs @@ -1,10 +1,37 @@ use std::{fmt, net::IpAddr, num::NonZeroU8}; +use attrievent::attribute::{KerberosAttr, RawEventAttrKind}; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; -use super::{common::Match, EventCategory, TriagePolicy, TriageScore, MEDIUM}; -use crate::event::common::triage_scores_to_string; +use super::{common::Match, EventCategory, TriageScore, MEDIUM}; +use crate::event::common::{triage_scores_to_string, AttrValue}; + +macro_rules! kerberos_target_attr { + ($event: expr, $raw_event_attr: expr) => {{ + if let RawEventAttrKind::Kerberos(attr) = $raw_event_attr { + let target_value = match attr { + KerberosAttr::SrcAddr => AttrValue::Addr($event.src_addr), + KerberosAttr::SrcPort => AttrValue::UInt($event.src_port.into()), + KerberosAttr::DstAddr => AttrValue::Addr($event.dst_addr), + KerberosAttr::DstPort => AttrValue::UInt($event.dst_port.into()), + KerberosAttr::Proto => AttrValue::UInt($event.proto.into()), + KerberosAttr::ClientTime => AttrValue::SInt($event.client_time), + KerberosAttr::ServerTime => AttrValue::SInt($event.server_time), + KerberosAttr::ErrorCode => AttrValue::UInt($event.error_code.into()), + KerberosAttr::ClientRealm => AttrValue::String(&$event.client_realm), + KerberosAttr::CnameType => AttrValue::UInt($event.cname_type.into()), + KerberosAttr::ClientName => AttrValue::VecString(&$event.client_name), + KerberosAttr::Realm => AttrValue::String(&$event.realm), + KerberosAttr::SnameType => AttrValue::UInt($event.sname_type.into()), + KerberosAttr::ServiceName => AttrValue::VecString(&$event.service_name), + }; + Some(target_value) + } else { + None + } + }}; +} #[derive(Serialize, Deserialize)] pub struct BlockListKerberosFields { @@ -168,7 +195,7 @@ impl Match for BlockListKerberos { None } - fn score_by_packet_attr(&self, _triage: &TriagePolicy) -> f64 { - 0.0 + fn to_attr_value(&self, raw_event_attr: RawEventAttrKind) -> Option { + kerberos_target_attr!(self, raw_event_attr) } } diff --git a/src/event/ldap.rs b/src/event/ldap.rs index 6a3625c..733c641 100644 --- a/src/event/ldap.rs +++ b/src/event/ldap.rs @@ -1,11 +1,36 @@ #![allow(clippy::module_name_repetitions)] use std::{fmt, net::IpAddr, num::NonZeroU8}; +use attrievent::attribute::{LdapAttr, RawEventAttrKind}; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; -use super::{common::Match, EventCategory, TriagePolicy, TriageScore, MEDIUM}; -use crate::event::common::triage_scores_to_string; +use super::{common::Match, EventCategory, TriageScore, MEDIUM}; +use crate::event::common::{triage_scores_to_string, AttrValue}; + +macro_rules! ldap_target_attr { + ($event: expr, $raw_event_attr: expr) => {{ + if let RawEventAttrKind::Ldap(attr) = $raw_event_attr { + let target_value = match attr { + LdapAttr::SrcAddr => AttrValue::Addr($event.src_addr), + LdapAttr::SrcPort => AttrValue::UInt($event.src_port.into()), + LdapAttr::DstAddr => AttrValue::Addr($event.dst_addr), + LdapAttr::DstPort => AttrValue::UInt($event.dst_port.into()), + LdapAttr::Proto => AttrValue::UInt($event.proto.into()), + LdapAttr::MessageId => AttrValue::UInt($event.message_id.into()), + LdapAttr::Version => AttrValue::UInt($event.version.into()), + LdapAttr::Opcode => AttrValue::VecString(&$event.opcode), + LdapAttr::Result => AttrValue::VecString(&$event.result), + LdapAttr::DiagnosticMessage => AttrValue::VecString(&$event.diagnostic_message), + LdapAttr::Object => AttrValue::VecString(&$event.object), + LdapAttr::Argument => AttrValue::VecString(&$event.argument), + }; + Some(target_value) + } else { + None + } + }}; +} #[derive(Serialize, Deserialize)] pub struct LdapBruteForceFields { @@ -135,8 +160,18 @@ impl Match for LdapBruteForce { None } - fn score_by_packet_attr(&self, _triage: &TriagePolicy) -> f64 { - 0.0 + fn to_attr_value(&self, raw_event_attr: RawEventAttrKind) -> Option { + if let RawEventAttrKind::Ldap(attr) = raw_event_attr { + match attr { + LdapAttr::SrcAddr => Some(AttrValue::Addr(self.src_addr)), + LdapAttr::DstAddr => Some(AttrValue::Addr(self.dst_addr)), + LdapAttr::DstPort => Some(AttrValue::UInt(self.dst_port.into())), + LdapAttr::Proto => Some(AttrValue::UInt(self.proto.into())), + _ => None, + } + } else { + None + } } } @@ -292,8 +327,8 @@ impl Match for LdapPlainText { None } - fn score_by_packet_attr(&self, _triage: &TriagePolicy) -> f64 { - 0.0 + fn to_attr_value(&self, raw_event_attr: RawEventAttrKind) -> Option { + ldap_target_attr!(self, raw_event_attr) } } @@ -407,7 +442,7 @@ impl Match for BlockListLdap { None } - fn score_by_packet_attr(&self, _triage: &TriagePolicy) -> f64 { - 0.0 + fn to_attr_value(&self, raw_event_attr: RawEventAttrKind) -> Option { + ldap_target_attr!(self, raw_event_attr) } } diff --git a/src/event/log.rs b/src/event/log.rs index 78b0de0..0fc5d5b 100644 --- a/src/event/log.rs +++ b/src/event/log.rs @@ -5,11 +5,12 @@ use std::{ num::NonZeroU8, }; +use attrievent::attribute::{LogAttr, RawEventAttrKind}; use chrono::{serde::ts_nanoseconds, DateTime, Utc}; use serde::{Deserialize, Serialize}; -use super::{common::Match, EventCategory, TriagePolicy, TriageScore, MEDIUM}; -use crate::event::common::triage_scores_to_string; +use super::{common::Match, EventCategory, TriageScore, MEDIUM}; +use crate::event::common::{triage_scores_to_string, AttrValue}; #[derive(Serialize, Deserialize)] pub struct ExtraThreat { @@ -87,7 +88,10 @@ impl Match for ExtraThreat { Some(self.confidence) } - fn score_by_packet_attr(&self, _triage: &TriagePolicy) -> f64 { - 0.0 + fn to_attr_value(&self, raw_event_attr: RawEventAttrKind) -> Option { + if let RawEventAttrKind::Log(LogAttr::Content) = raw_event_attr { + return Some(AttrValue::String(&self.content)); + } + None } } diff --git a/src/event/mqtt.rs b/src/event/mqtt.rs index 915fc00..7778674 100644 --- a/src/event/mqtt.rs +++ b/src/event/mqtt.rs @@ -1,10 +1,40 @@ use std::{fmt, net::IpAddr, num::NonZeroU8}; +use attrievent::attribute::{MqttAttr, RawEventAttrKind}; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; -use super::{common::Match, EventCategory, TriagePolicy, TriageScore, MEDIUM}; -use crate::event::common::triage_scores_to_string; +use super::{common::Match, EventCategory, TriageScore, MEDIUM}; +use crate::event::common::{triage_scores_to_string, AttrValue}; + +macro_rules! mqtt_target_attr { + ($event: expr, $raw_event_attr: expr) => {{ + if let RawEventAttrKind::Mqtt(attr) = $raw_event_attr { + let target_value = match attr { + MqttAttr::SrcAddr => AttrValue::Addr($event.src_addr), + MqttAttr::SrcPort => AttrValue::UInt($event.src_port.into()), + MqttAttr::DstAddr => AttrValue::Addr($event.dst_addr), + MqttAttr::DstPort => AttrValue::UInt($event.dst_port.into()), + MqttAttr::Proto => AttrValue::UInt($event.proto.into()), + MqttAttr::Protocol => AttrValue::String(&$event.protocol), + MqttAttr::Version => AttrValue::UInt($event.version.into()), + MqttAttr::ClientId => AttrValue::String(&$event.client_id), + MqttAttr::ConnackReason => AttrValue::UInt($event.connack_reason.into()), + MqttAttr::Subscribe => AttrValue::VecString(&$event.subscribe), + MqttAttr::SubackReason => AttrValue::VecUInt( + $event + .suback_reason + .iter() + .map(|val| u64::from(*val)) + .collect(), + ), + }; + Some(target_value) + } else { + None + } + }}; +} #[derive(Serialize, Deserialize)] pub struct BlockListMqttFields { @@ -152,7 +182,7 @@ impl Match for BlockListMqtt { None } - fn score_by_packet_attr(&self, _triage: &TriagePolicy) -> f64 { - 0.0 + fn to_attr_value(&self, raw_event_attr: RawEventAttrKind) -> Option { + mqtt_target_attr!(self, raw_event_attr) } } diff --git a/src/event/network.rs b/src/event/network.rs index 32108c8..40978e3 100644 --- a/src/event/network.rs +++ b/src/event/network.rs @@ -1,11 +1,32 @@ #![allow(clippy::module_name_repetitions)] use std::{fmt, net::IpAddr, num::NonZeroU8}; +use attrievent::attribute::{NetworkAttr, RawEventAttrKind}; use chrono::{serde::ts_nanoseconds, DateTime, Utc}; use serde::{Deserialize, Serialize}; -use super::{common::Match, EventCategory, TriagePolicy, TriageScore, MEDIUM}; -use crate::event::common::triage_scores_to_string; +use super::{common::Match, EventCategory, TriageScore, MEDIUM}; +use crate::event::common::{triage_scores_to_string, AttrValue}; + +// TODO: We plan to implement the triage feature after detection events from other network +// protocols are consolidated into `NetworkThreat` events. +macro_rules! network_target_attr { + ($event: expr, $raw_event_attr: expr) => { + if let RawEventAttrKind::Network(attr) = $raw_event_attr { + let target_value = match attr { + NetworkAttr::SrcAddr => AttrValue::Addr($event.orig_addr), + NetworkAttr::SrcPort => AttrValue::UInt($event.orig_port.into()), + NetworkAttr::DstAddr => AttrValue::Addr($event.resp_addr), + NetworkAttr::DstPort => AttrValue::UInt($event.resp_port.into()), + NetworkAttr::Proto => AttrValue::UInt($event.proto.into()), + NetworkAttr::Content => AttrValue::String(&$event.content), + }; + Some(target_value) + } else { + None + } + }; +} #[derive(Serialize, Deserialize)] pub struct NetworkThreat { @@ -96,7 +117,7 @@ impl Match for NetworkThreat { Some(self.confidence) } - fn score_by_packet_attr(&self, _triage: &TriagePolicy) -> f64 { - 0.0 + fn to_attr_value(&self, raw_event_attr: RawEventAttrKind) -> Option { + network_target_attr!(self, raw_event_attr) } } diff --git a/src/event/nfs.rs b/src/event/nfs.rs index c37ec1c..f9000d5 100644 --- a/src/event/nfs.rs +++ b/src/event/nfs.rs @@ -1,10 +1,30 @@ use std::{fmt, net::IpAddr, num::NonZeroU8}; +use attrievent::attribute::{NfsAttr, RawEventAttrKind}; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; -use super::{common::Match, EventCategory, TriagePolicy, TriageScore, MEDIUM}; -use crate::event::common::triage_scores_to_string; +use super::{common::Match, EventCategory, TriageScore, MEDIUM}; +use crate::event::common::{triage_scores_to_string, AttrValue}; + +macro_rules! nfs_target_attr { + ($event: expr, $raw_event_attr: expr) => {{ + if let RawEventAttrKind::Nfs(attr) = $raw_event_attr { + let target_value = match attr { + NfsAttr::SrcAddr => AttrValue::Addr($event.src_addr), + NfsAttr::SrcPort => AttrValue::UInt($event.src_port.into()), + NfsAttr::DstAddr => AttrValue::Addr($event.dst_addr), + NfsAttr::DstPort => AttrValue::UInt($event.dst_port.into()), + NfsAttr::Proto => AttrValue::UInt($event.proto.into()), + NfsAttr::ReadFiles => AttrValue::VecString(&$event.read_files), + NfsAttr::WriteFiles => AttrValue::VecString(&$event.write_files), + }; + Some(target_value) + } else { + None + } + }}; +} #[derive(Serialize, Deserialize)] pub struct BlockListNfsFields { @@ -131,7 +151,7 @@ impl Match for BlockListNfs { None } - fn score_by_packet_attr(&self, _triage: &TriagePolicy) -> f64 { - 0.0 + fn to_attr_value(&self, raw_event_attr: RawEventAttrKind) -> Option { + nfs_target_attr!(self, raw_event_attr) } } diff --git a/src/event/ntlm.rs b/src/event/ntlm.rs index e609bb6..821779e 100644 --- a/src/event/ntlm.rs +++ b/src/event/ntlm.rs @@ -1,10 +1,33 @@ use std::{fmt, net::IpAddr, num::NonZeroU8}; +use attrievent::attribute::{NtlmAttr, RawEventAttrKind}; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; -use super::{common::Match, EventCategory, TriagePolicy, TriageScore, MEDIUM}; -use crate::event::common::triage_scores_to_string; +use super::{common::Match, EventCategory, TriageScore, MEDIUM}; +use crate::event::common::{triage_scores_to_string, AttrValue}; + +macro_rules! ntlm_target_attr { + ($event: expr, $raw_event_attr: expr) => {{ + if let RawEventAttrKind::Ntlm(attr) = $raw_event_attr { + let target_value = match attr { + NtlmAttr::SrcAddr => AttrValue::Addr($event.src_addr), + NtlmAttr::SrcPort => AttrValue::UInt($event.src_port.into()), + NtlmAttr::DstAddr => AttrValue::Addr($event.dst_addr), + NtlmAttr::DstPort => AttrValue::UInt($event.dst_port.into()), + NtlmAttr::Proto => AttrValue::UInt($event.proto.into()), + NtlmAttr::Protocol => AttrValue::String(&$event.protocol), + NtlmAttr::Username => AttrValue::String(&$event.username), + NtlmAttr::Hostname => AttrValue::String(&$event.hostname), + NtlmAttr::Domainname => AttrValue::String(&$event.domainname), + NtlmAttr::Success => AttrValue::String(&$event.success), + }; + Some(target_value) + } else { + None + } + }}; +} #[derive(Serialize, Deserialize)] pub struct BlockListNtlmFields { @@ -144,7 +167,7 @@ impl Match for BlockListNtlm { None } - fn score_by_packet_attr(&self, _triage: &TriagePolicy) -> f64 { - 0.0 + fn to_attr_value(&self, raw_event_attr: RawEventAttrKind) -> Option { + ntlm_target_attr!(self, raw_event_attr) } } diff --git a/src/event/rdp.rs b/src/event/rdp.rs index 9d1e31f..e6c85a6 100644 --- a/src/event/rdp.rs +++ b/src/event/rdp.rs @@ -6,15 +6,33 @@ use std::{ num::NonZeroU8, }; +use attrievent::attribute::{RawEventAttrKind, RdpAttr}; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use super::{ common::{vector_to_string, Match}, - EventCategory, TriagePolicy, TriageScore, MEDIUM, + EventCategory, TriageScore, MEDIUM, }; -use crate::event::common::triage_scores_to_string; - +use crate::event::common::{triage_scores_to_string, AttrValue}; + +macro_rules! rdp_target_attr { + ($event: expr, $raw_event_attr: expr) => {{ + if let RawEventAttrKind::Rdp(attr) = $raw_event_attr { + let target_value = match attr { + RdpAttr::SrcAddr => AttrValue::Addr($event.src_addr), + RdpAttr::SrcPort => AttrValue::UInt($event.src_port.into()), + RdpAttr::DstAddr => AttrValue::Addr($event.dst_addr), + RdpAttr::DstPort => AttrValue::UInt($event.dst_port.into()), + RdpAttr::Proto => AttrValue::UInt($event.proto.into()), + RdpAttr::Cookie => AttrValue::String(&$event.cookie), + }; + Some(target_value) + } else { + None + } + }}; +} #[derive(Serialize, Deserialize)] pub struct RdpBruteForceFields { pub src_addr: IpAddr, @@ -121,8 +139,17 @@ impl Match for RdpBruteForce { None } - fn score_by_packet_attr(&self, _triage: &TriagePolicy) -> f64 { - 0.0 + fn to_attr_value(&self, raw_event_attr: RawEventAttrKind) -> Option { + if let RawEventAttrKind::Rdp(attr) = raw_event_attr { + match attr { + RdpAttr::SrcAddr => Some(AttrValue::Addr(self.src_addr)), + RdpAttr::DstAddr => Some(AttrValue::VecAddr(&self.dst_addrs)), + RdpAttr::Proto => Some(AttrValue::UInt(self.proto.into())), + _ => None, + } + } else { + None + } } } @@ -245,7 +272,7 @@ impl Match for BlockListRdp { None } - fn score_by_packet_attr(&self, _triage: &TriagePolicy) -> f64 { - 0.0 + fn to_attr_value(&self, raw_event_attr: RawEventAttrKind) -> Option { + rdp_target_attr!(self, raw_event_attr) } } diff --git a/src/event/smb.rs b/src/event/smb.rs index 2fed020..7f34715 100644 --- a/src/event/smb.rs +++ b/src/event/smb.rs @@ -1,10 +1,39 @@ use std::{fmt, net::IpAddr, num::NonZeroU8}; +use attrievent::attribute::{RawEventAttrKind, SmbAttr}; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; -use super::{common::Match, EventCategory, TriagePolicy, TriageScore, MEDIUM}; -use crate::event::common::triage_scores_to_string; +use super::{common::Match, EventCategory, TriageScore, MEDIUM}; +use crate::event::common::{triage_scores_to_string, AttrValue}; + +macro_rules! smb_target_attr { + ($event: expr, $raw_event_attr: expr) => {{ + if let RawEventAttrKind::Smb(attr) = $raw_event_attr { + let target_value = match attr { + SmbAttr::SrcAddr => AttrValue::Addr($event.src_addr), + SmbAttr::SrcPort => AttrValue::UInt($event.src_port.into()), + SmbAttr::DstAddr => AttrValue::Addr($event.dst_addr), + SmbAttr::DstPort => AttrValue::UInt($event.dst_port.into()), + SmbAttr::Proto => AttrValue::UInt($event.proto.into()), + SmbAttr::Command => AttrValue::UInt($event.command.into()), + SmbAttr::Path => AttrValue::String(&$event.path), + SmbAttr::Service => AttrValue::String(&$event.service), + SmbAttr::FileName => AttrValue::String(&$event.file_name), + SmbAttr::FileSize => AttrValue::UInt($event.file_size), + SmbAttr::ResourceType => AttrValue::UInt($event.resource_type.into()), + SmbAttr::Fid => AttrValue::UInt($event.fid.into()), + SmbAttr::CreateTime => AttrValue::SInt($event.create_time), + SmbAttr::AccessTime => AttrValue::SInt($event.access_time), + SmbAttr::WriteTime => AttrValue::SInt($event.write_time), + SmbAttr::ChangeTime => AttrValue::SInt($event.change_time), + }; + Some(target_value) + } else { + None + } + }}; +} #[derive(Serialize, Deserialize)] pub struct BlockListSmbFields { @@ -175,7 +204,7 @@ impl Match for BlockListSmb { None } - fn score_by_packet_attr(&self, _triage: &TriagePolicy) -> f64 { - 0.0 + fn to_attr_value(&self, raw_event_attr: RawEventAttrKind) -> Option { + smb_target_attr!(self, raw_event_attr) } } diff --git a/src/event/smtp.rs b/src/event/smtp.rs index 0b5489b..82b29f6 100644 --- a/src/event/smtp.rs +++ b/src/event/smtp.rs @@ -1,10 +1,35 @@ use std::{fmt, net::IpAddr, num::NonZeroU8}; +use attrievent::attribute::{RawEventAttrKind, SmtpAttr}; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; -use super::{common::Match, EventCategory, TriagePolicy, TriageScore, MEDIUM}; -use crate::event::common::triage_scores_to_string; +use super::{common::Match, EventCategory, TriageScore, MEDIUM}; +use crate::event::common::{triage_scores_to_string, AttrValue}; + +macro_rules! smtp_target_attr { + ($event: expr, $raw_event_attr: expr) => {{ + if let RawEventAttrKind::Smtp(attr) = $raw_event_attr { + let target_value = match attr { + SmtpAttr::SrcAddr => AttrValue::Addr($event.src_addr), + SmtpAttr::SrcPort => AttrValue::UInt($event.src_port.into()), + SmtpAttr::DstAddr => AttrValue::Addr($event.dst_addr), + SmtpAttr::DstPort => AttrValue::UInt($event.dst_port.into()), + SmtpAttr::Proto => AttrValue::UInt($event.proto.into()), + SmtpAttr::MailFrom => AttrValue::String(&$event.mailfrom), + SmtpAttr::Date => AttrValue::String(&$event.date), + SmtpAttr::From => AttrValue::String(&$event.from), + SmtpAttr::To => AttrValue::String(&$event.to), + SmtpAttr::Subject => AttrValue::String(&$event.subject), + SmtpAttr::Agent => AttrValue::String(&$event.agent), + SmtpAttr::State => AttrValue::String(&$event.state), + }; + Some(target_value) + } else { + None + } + }}; +} #[derive(Serialize, Deserialize)] pub struct BlockListSmtpFields { @@ -157,7 +182,7 @@ impl Match for BlockListSmtp { None } - fn score_by_packet_attr(&self, _triage: &TriagePolicy) -> f64 { - 0.0 + fn to_attr_value(&self, raw_event_attr: RawEventAttrKind) -> Option { + smtp_target_attr!(self, raw_event_attr) } } diff --git a/src/event/ssh.rs b/src/event/ssh.rs index 967c0d1..36e208e 100644 --- a/src/event/ssh.rs +++ b/src/event/ssh.rs @@ -1,10 +1,43 @@ use std::{fmt, net::IpAddr, num::NonZeroU8}; +use attrievent::attribute::{RawEventAttrKind, SshAttr}; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; -use super::{common::Match, EventCategory, TriagePolicy, TriageScore, MEDIUM}; -use crate::event::common::triage_scores_to_string; +use super::{common::Match, EventCategory, TriageScore, MEDIUM}; +use crate::event::common::{triage_scores_to_string, AttrValue}; + +macro_rules! ssh_target_attr { + ($event: expr, $raw_event_attr: expr) => {{ + if let RawEventAttrKind::Ssh(attr) = $raw_event_attr { + let target_value = match attr { + SshAttr::SrcAddr => AttrValue::Addr($event.src_addr), + SshAttr::SrcPort => AttrValue::UInt($event.src_port.into()), + SshAttr::DstAddr => AttrValue::Addr($event.dst_addr), + SshAttr::DstPort => AttrValue::UInt($event.dst_port.into()), + SshAttr::Proto => AttrValue::UInt($event.proto.into()), + SshAttr::Client => AttrValue::String(&$event.client), + SshAttr::Server => AttrValue::String(&$event.server), + SshAttr::CipherAlg => AttrValue::String(&$event.cipher_alg), + SshAttr::MacAlg => AttrValue::String(&$event.mac_alg), + SshAttr::CompressionAlg => AttrValue::String(&$event.compression_alg), + SshAttr::KexAlg => AttrValue::String(&$event.kex_alg), + SshAttr::HostKeyAlg => AttrValue::String(&$event.host_key_alg), + SshAttr::HasshAlgorithms => AttrValue::String(&$event.hassh_algorithms), + SshAttr::Hassh => AttrValue::String(&$event.hassh), + SshAttr::HasshServerAlgorithms => { + AttrValue::String(&$event.hassh_server_algorithms) + } + SshAttr::HasshServer => AttrValue::String(&$event.hassh_server), + SshAttr::ClientShka => AttrValue::String(&$event.client_shka), + SshAttr::ServerShka => AttrValue::String(&$event.server_shka), + }; + Some(target_value) + } else { + None + } + }}; +} #[derive(Serialize, Deserialize)] pub struct BlockListSshFields { @@ -186,7 +219,7 @@ impl Match for BlockListSsh { None } - fn score_by_packet_attr(&self, _triage: &TriagePolicy) -> f64 { - 0.0 + fn to_attr_value(&self, raw_event_attr: RawEventAttrKind) -> Option { + ssh_target_attr!(self, raw_event_attr) } } diff --git a/src/event/sysmon.rs b/src/event/sysmon.rs index 9b4f3f0..2414fae 100644 --- a/src/event/sysmon.rs +++ b/src/event/sysmon.rs @@ -5,11 +5,34 @@ use std::{ num::NonZeroU8, }; +use attrievent::attribute::{RawEventAttrKind, WindowAttr}; use chrono::{serde::ts_nanoseconds, DateTime, Utc}; use serde::{Deserialize, Serialize}; -use super::{common::Match, EventCategory, TriagePolicy, TriageScore, MEDIUM}; -use crate::event::common::triage_scores_to_string; +use super::{common::Match, EventCategory, TriageScore, MEDIUM}; +use crate::event::common::{triage_scores_to_string, AttrValue}; + +// TODO: We plan to implement the triage feature only after we have cleaned up the range of +// values for the properties for each sysmon service. +macro_rules! window_target_attr { + ($event: expr, $raw_event_attr: expr) => { + if let RawEventAttrKind::Window(attr) = $raw_event_attr { + let target_value = match attr { + WindowAttr::Service => AttrValue::String(&$event.service), + WindowAttr::AgentName => AttrValue::String(&$event.agent_name), + WindowAttr::AgentId => AttrValue::String(&$event.agent_id), + WindowAttr::ProcessGuid => AttrValue::String(&$event.process_guid), + WindowAttr::ProcessId => AttrValue::UInt($event.process_id.into()), + WindowAttr::Image => AttrValue::String(&$event.image), + WindowAttr::User => AttrValue::String(&$event.user), + WindowAttr::Content => AttrValue::String(&$event.content), + }; + Some(target_value) + } else { + None + } + }; +} #[derive(Serialize, Deserialize)] pub struct WindowsThreat { @@ -102,7 +125,7 @@ impl Match for WindowsThreat { None } - fn score_by_packet_attr(&self, _triage: &TriagePolicy) -> f64 { - 0.0 + fn to_attr_value(&self, raw_event_attr: RawEventAttrKind) -> Option { + window_target_attr!(self, raw_event_attr) } } diff --git a/src/event/tls.rs b/src/event/tls.rs index d434415..af9ef4b 100644 --- a/src/event/tls.rs +++ b/src/event/tls.rs @@ -1,10 +1,67 @@ use std::{fmt, net::IpAddr, num::NonZeroU8}; +use attrievent::attribute::{RawEventAttrKind, TlsAttr}; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; -use super::{common::Match, EventCategory, TriagePolicy, TriageScore, MEDIUM}; -use crate::event::common::{triage_scores_to_string, vector_to_string}; +use super::{common::Match, EventCategory, TriageScore, MEDIUM}; +use crate::event::common::{triage_scores_to_string, vector_to_string, AttrValue}; + +macro_rules! tls_target_attr { + ($event: expr, $raw_event_attr: expr) => {{ + if let RawEventAttrKind::Tls(attr) = $raw_event_attr { + let target_value = match attr { + TlsAttr::SrcAddr => AttrValue::Addr($event.src_addr), + TlsAttr::SrcPort => AttrValue::UInt($event.src_port.into()), + TlsAttr::DstAddr => AttrValue::Addr($event.dst_addr), + TlsAttr::DstPort => AttrValue::UInt($event.dst_port.into()), + TlsAttr::Proto => AttrValue::UInt($event.proto.into()), + TlsAttr::ServerName => AttrValue::String(&$event.server_name), + TlsAttr::AlpnProtocol => AttrValue::String(&$event.server_name), + TlsAttr::Ja3 => AttrValue::String(&$event.ja3), + TlsAttr::Version => AttrValue::String(&$event.version), + TlsAttr::ClientCipherSuites => AttrValue::VecUInt( + $event + .client_cipher_suites + .iter() + .map(|val| u64::from(*val)) + .collect(), + ), + TlsAttr::ClientExtensions => AttrValue::VecUInt( + $event + .client_extensions + .iter() + .map(|val| u64::from(*val)) + .collect(), + ), + TlsAttr::Cipher => AttrValue::UInt($event.cipher.into()), + TlsAttr::Extensions => AttrValue::VecUInt( + $event + .extensions + .iter() + .map(|val| u64::from(*val)) + .collect(), + ), + TlsAttr::Ja3s => AttrValue::String(&$event.ja3s), + TlsAttr::Serial => AttrValue::String(&$event.serial), + TlsAttr::SubjectCountry => AttrValue::String(&$event.subject_country), + TlsAttr::SubjectOrgName => AttrValue::String(&$event.subject_org_name), + TlsAttr::SubjectCommonName => AttrValue::String(&$event.subject_common_name), + TlsAttr::ValidityNotBefore => AttrValue::SInt($event.validity_not_before.into()), + TlsAttr::ValidityNotAfter => AttrValue::SInt($event.validity_not_after.into()), + TlsAttr::SubjectAltName => AttrValue::String(&$event.subject_alt_name), + TlsAttr::IssuerCountry => AttrValue::String(&$event.issuer_country), + TlsAttr::IssuerOrgName => AttrValue::String(&$event.issuer_org_name), + TlsAttr::IssuerOrgUnitName => AttrValue::String(&$event.issuer_org_unit_name), + TlsAttr::IssuerCommonName => AttrValue::String(&$event.issuer_common_name), + TlsAttr::LastAlert => AttrValue::UInt($event.last_alert.into()), + }; + Some(target_value) + } else { + None + } + }}; +} #[derive(Serialize, Deserialize)] pub struct BlockListTlsFields { @@ -227,8 +284,8 @@ impl Match for BlockListTls { None } - fn score_by_packet_attr(&self, _triage: &TriagePolicy) -> f64 { - 0.0 + fn to_attr_value(&self, raw_event_attr: RawEventAttrKind) -> Option { + tls_target_attr!(self, raw_event_attr) } } @@ -382,7 +439,7 @@ impl Match for SuspiciousTlsTraffic { None } - fn score_by_packet_attr(&self, _triage: &TriagePolicy) -> f64 { - 0.0 + fn to_attr_value(&self, raw_event_attr: RawEventAttrKind) -> Option { + tls_target_attr!(self, raw_event_attr) } } diff --git a/src/event/tor.rs b/src/event/tor.rs deleted file mode 100644 index 90950f6..0000000 --- a/src/event/tor.rs +++ /dev/null @@ -1,241 +0,0 @@ -use std::{fmt, net::IpAddr, num::NonZeroU8}; - -use chrono::{serde::ts_nanoseconds, DateTime, Utc}; -use serde::{Deserialize, Serialize}; - -use super::{common::Match, EventCategory, TriagePolicy, TriageScore, MEDIUM}; -use crate::event::{common::triage_scores_to_string, http::get_post_body}; - -#[derive(Deserialize, Serialize)] -#[allow(clippy::module_name_repetitions)] -pub struct HttpEventFields { - pub sensor: String, - #[serde(with = "ts_nanoseconds")] - pub session_end_time: DateTime, - pub src_addr: IpAddr, - pub src_port: u16, - pub dst_addr: IpAddr, - pub dst_port: u16, - pub proto: u8, - pub method: String, - pub host: String, - pub uri: String, - pub referrer: String, - pub version: String, - pub user_agent: String, - pub request_len: usize, - pub response_len: usize, - pub status_code: u16, - pub status_msg: String, - pub username: String, - pub password: String, - pub cookie: String, - pub content_encoding: String, - pub content_type: String, - pub cache_control: String, - pub orig_filenames: Vec, - pub orig_mime_types: Vec, - pub resp_filenames: Vec, - pub resp_mime_types: Vec, - pub post_body: Vec, - pub state: String, - pub category: EventCategory, -} - -impl fmt::Display for HttpEventFields { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "sensor={:?} src_addr={:?} src_port={:?} dst_addr={:?} dst_port={:?} proto={:?} session_end_time={:?} method={:?} host={:?} uri={:?} referrer={:?} version={:?} user_agent={:?} request_len={:?} response_len={:?} status_code={:?} status_msg={:?} username={:?} password={:?} cookie={:?} content_encoding={:?} content_type={:?} cache_control={:?} orig_filenames={:?} orig_mime_types={:?} resp_filenames={:?} resp_mime_types={:?} post_body={:?} state={:?}", - self.sensor, - self.src_addr.to_string(), - self.src_port.to_string(), - self.dst_addr.to_string(), - self.dst_port.to_string(), - self.proto.to_string(), - self.session_end_time.to_rfc3339(), - self.method, - self.host, - self.uri, - self.referrer, - self.version, - self.user_agent, - self.request_len.to_string(), - self.response_len.to_string(), - self.status_code.to_string(), - self.status_msg, - self.username, - self.password, - self.cookie, - self.content_encoding, - self.content_type, - self.cache_control, - self.orig_filenames.join(","), - self.orig_mime_types.join(","), - self.resp_filenames.join(","), - self.resp_mime_types.join(","), - get_post_body(&self.post_body), - self.state - ) - } -} - -#[allow(clippy::module_name_repetitions)] -pub struct TorConnection { - pub time: DateTime, - pub sensor: String, - pub session_end_time: DateTime, - pub src_addr: IpAddr, - pub src_port: u16, - pub dst_addr: IpAddr, - pub dst_port: u16, - pub proto: u8, - pub method: String, - pub host: String, - pub uri: String, - pub referrer: String, - pub version: String, - pub user_agent: String, - pub request_len: usize, - pub response_len: usize, - pub status_code: u16, - pub status_msg: String, - pub username: String, - pub password: String, - pub cookie: String, - pub content_encoding: String, - pub content_type: String, - pub cache_control: String, - pub orig_filenames: Vec, - pub orig_mime_types: Vec, - pub resp_filenames: Vec, - pub resp_mime_types: Vec, - pub post_body: Vec, - pub state: String, - pub category: EventCategory, - pub triage_scores: Option>, -} - -impl fmt::Display for TorConnection { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "sensor={:?} src_addr={:?} src_port={:?} dst_addr={:?} dst_port={:?} proto={:?} session_end_time={:?} method={:?} host={:?} uri={:?} referrer={:?} version={:?} user_agent={:?} request_len={:?} response_len={:?} status_code={:?} status_msg={:?} username={:?} password={:?} cookie={:?} content_encoding={:?} content_type={:?} cache_control={:?} orig_filenames={:?} orig_mime_types={:?} resp_filenames={:?} resp_mime_types={:?} post_body={:?} state={:?} triage_scores={:?}", - self.sensor, - self.src_addr.to_string(), - self.src_port.to_string(), - self.dst_addr.to_string(), - self.dst_port.to_string(), - self.proto.to_string(), - self.session_end_time.to_rfc3339(), - self.method, - self.host, - self.uri, - self.referrer, - self.version, - self.user_agent, - self.request_len.to_string(), - self.response_len.to_string(), - self.status_code.to_string(), - self.status_msg, - self.username, - self.password, - self.cookie, - self.content_encoding, - self.content_type, - self.cache_control, - self.orig_filenames.join(","), - self.orig_mime_types.join(","), - self.resp_filenames.join(","), - self.resp_mime_types.join(","), - get_post_body(&self.post_body), - self.state, - triage_scores_to_string(self.triage_scores.as_ref()) - ) - } -} - -impl TorConnection { - pub(super) fn new(time: DateTime, fields: &HttpEventFields) -> Self { - TorConnection { - time, - sensor: fields.sensor.clone(), - session_end_time: fields.session_end_time, - src_addr: fields.src_addr, - src_port: fields.src_port, - dst_addr: fields.dst_addr, - dst_port: fields.dst_port, - proto: fields.proto, - method: fields.method.clone(), - host: fields.host.clone(), - uri: fields.uri.clone(), - referrer: fields.referrer.clone(), - version: fields.version.clone(), - user_agent: fields.user_agent.clone(), - request_len: fields.request_len, - response_len: fields.response_len, - status_code: fields.status_code, - status_msg: fields.status_msg.clone(), - username: fields.username.clone(), - password: fields.password.clone(), - cookie: fields.cookie.clone(), - content_encoding: fields.content_encoding.clone(), - content_type: fields.content_type.clone(), - cache_control: fields.cache_control.clone(), - orig_filenames: fields.orig_filenames.clone(), - orig_mime_types: fields.orig_mime_types.clone(), - resp_filenames: fields.resp_filenames.clone(), - resp_mime_types: fields.resp_mime_types.clone(), - post_body: fields.post_body.clone(), - state: fields.state.clone(), - category: fields.category, - triage_scores: None, - } - } -} - -impl Match for TorConnection { - fn src_addr(&self) -> IpAddr { - self.src_addr - } - - fn src_port(&self) -> u16 { - self.src_port - } - - fn dst_addr(&self) -> IpAddr { - self.dst_addr - } - - fn dst_port(&self) -> u16 { - self.dst_port - } - - fn proto(&self) -> u8 { - self.proto - } - - fn category(&self) -> EventCategory { - self.category - } - - fn level(&self) -> NonZeroU8 { - MEDIUM - } - - fn kind(&self) -> &str { - "tor exit nodes" - } - - fn sensor(&self) -> &str { - self.sensor.as_str() - } - - fn confidence(&self) -> Option { - None - } - - fn score_by_packet_attr(&self, _triage: &TriagePolicy) -> f64 { - 0.0 - } -} diff --git a/src/tables/triage_policy.rs b/src/tables/triage_policy.rs index 0c17419..4cccbd7 100644 --- a/src/tables/triage_policy.rs +++ b/src/tables/triage_policy.rs @@ -3,6 +3,7 @@ use std::{borrow::Cow, cmp::Ordering}; use anyhow::Result; +use attrievent::attribute::RawEventKind; use chrono::{DateTime, Utc}; use rocksdb::OptimisticTransactionDB; use serde::{Deserialize, Serialize}; @@ -68,8 +69,11 @@ pub enum TiCmpKind { #[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Deserialize, Serialize)] pub enum ValueKind { String, - Integer, + Integer, // range: i64::MAX + UInteger, // range: u64::MAX Float, + IpAddr, + Bool, } #[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Deserialize, Serialize)] @@ -135,6 +139,7 @@ impl Ord for Ti { #[derive(Clone, PartialEq, Deserialize, Serialize)] pub struct PacketAttr { + pub raw_event_kind: RawEventKind, pub attr_name: String, pub value_kind: ValueKind, pub cmp_kind: AttrCmpKind,