diff --git a/filters/audits/sophos.md b/filters/audits/sophos.md new file mode 100644 index 000000000..bd685c843 --- /dev/null +++ b/filters/audits/sophos.md @@ -0,0 +1,40 @@ +# Sophos normalization and rule review + +Normalize Central/XG outcomes; explicit firewall denial wins over HTTP block-page success; fix command and IP fields. + +This draft targets UTMStack `v11`. It contains 2 filter changes +and 0 rule changes for this technology only. Review covered +2 filter configurations and 28 matching shipped rule files. +Unchanged rules are listed in the regression manifest; they are not duplicated in the diff. + +## Contract and validation + +- Compared exact standard names/types with go-sdk v1.1.31 and the supplied UTMStack dictionaries. +- Checked documented pipeline ordering, rename/move behavior, open vendor log fields, + event-side versus alert-side fields, and surviving fields used by affected rule predicates/history/grouping. +- Strict SDK configuration decoding and actual CEL compilation pass for this scope. +- 7 synthetic normalization cases pass, including SDK Event conversion and any + trigger predicate assertions recorded in the manifest. +- The scoped alerts module tests and `git diff --check` pass with the shared contract runner applied. + +The shared alert-contract PR supplies the reusable Go runner for the manifest in +`plugins/alerts/testdata/filter-contracts/sophos.json`. Apply that support before running `go test ./...` in `plugins/alerts`. + +The model starts from synthetic extraction results. It does not run complex grok, +JSON/KV/XML/CSV extraction, time conversion, dynamic plugins, historical OpenSearch +queries, or the closed EventProcessor. Raw vendor logs and resulting alerts must +still be checked in staging before rollout. No customer false-positive reduction +has been measured and no production rollout is included. + + + +## References + +- [SDK schema](https://github.com/threatwinds/go-sdk/blob/v1.1.31/plugins/plugins.proto) +- [Filter steps](https://github.com/threatwinds/go-sdk/wiki/Filter-Steps-Reference) +- [Standard event schema](https://github.com/threatwinds/go-sdk/wiki/Standard-Event-Schema) +- [Rule implementation](https://github.com/threatwinds/go-sdk/wiki/Implementing-Rules) + +`afterEvents`, empty noncapturing grok names, supported numeric strings, and custom +`log.*` fields are accepted. Existing textual protocol casing and vendor action names +are preserved unless a concrete consumer mismatch requires correction. diff --git a/filters/sophos/sophos_central.yml b/filters/sophos/sophos_central.yml index d915b2fc5..d1b0272e4 100755 --- a/filters/sophos/sophos_central.yml +++ b/filters/sophos/sophos_central.yml @@ -55,33 +55,33 @@ pipeline: function: string params: key: actionResult - value: "blocked" - where: 'exists("log.action") && equals("log.action", "blocked")' + value: denied + where: exists("log.action") && equals("log.action", "blocked") - add: function: string params: key: actionResult - value: "blocked" - where: 'exists("log.type") && contains("log.type", "Prevented") && !exists("actionResult")' + value: denied + where: exists("log.type") && contains("log.type", "Prevented") && !exists("actionResult") - add: function: string params: key: actionResult - value: "blocked" - where: 'exists("log.type") && oneOf("log.type", ["Event::Endpoint::HmpaApplicationHijacking", "Event::Endpoint::HmpaPrivGuard"]) && !exists("actionResult")' + value: denied + where: exists("log.type") && oneOf("log.type", ["Event::Endpoint::HmpaApplicationHijacking", "Event::Endpoint::HmpaPrivGuard"]) && !exists("actionResult") - add: function: string params: key: actionResult - value: "blocked" - where: 'exists("log.type") && contains("log.type", "HmpaCred") && !exists("actionResult")' + value: denied + where: exists("log.type") && contains("log.type", "HmpaCred") && !exists("actionResult") - add: function: string params: key: actionResult - value: "failed" - where: 'exists("log.type") && contains("log.type", "AuthenticationFailure") && !exists("actionResult")' \ No newline at end of file + value: failure + where: exists("log.type") && contains("log.type", "AuthenticationFailure") && !exists("actionResult") diff --git a/filters/sophos/sophos_xg_firewall.yml b/filters/sophos/sophos_xg_firewall.yml index fbd6d1ff1..968365aee 100644 --- a/filters/sophos/sophos_xg_firewall.yml +++ b/filters/sophos/sophos_xg_firewall.yml @@ -423,8 +423,7 @@ pipeline: - rename: from: - log.ftpcommand - to: command - + to: origin.command - rename: from: - log.FTPurl @@ -546,7 +545,7 @@ pipeline: - log.outInterface - log.vLanId - log.deviceType - - command + - origin.command - log.clientHostName - log.ipAddress - log.clientPhysicalAddress @@ -604,7 +603,7 @@ pipeline: - log.outInterface - log.vLanId - log.deviceType - - command + - origin.command - log.clientHostName - log.ipAddress - log.clientPhysicalAddress @@ -684,19 +683,7 @@ pipeline: to: float # Adding actionResult based on log.subtype value - - add: - function: 'string' - params: - key: actionResult - value: 'denied' - where: exists("log.subType") && regexMatch("log.subType", "(?i)\bdenied\b") - - add: - function: 'string' - params: - key: actionResult - value: 'accepted' - where: exists("log.subType") && oneOf("log.subType", ["Accepted", "Allowed"]) # Renaming "log.statusCode" to "statusCode" to add it to the event structure - rename: @@ -705,22 +692,6 @@ pipeline: to: statusCode where: exists("log.statusCode") - # Adding actionResult - # denied by default - - add: - function: 'string' - params: - key: actionResult - value: 'denied' - where: exists("statusCode") - - - add: - function: 'string' - params: - key: actionResult - value: 'accepted' - where: exists("statusCode") && ((greaterOrEqual("statusCode", 200) && lessOrEqual("statusCode", 299)) || (greaterOrEqual("statusCode", 300) && lessOrEqual("statusCode", 399) && greaterThan("origin.bytesReceived", 0))) - # Removing unused fields - delete: fields: @@ -756,4 +727,101 @@ pipeline: - log.logcomponent - log.logsubtype - log.name - - log.logtype \ No newline at end of file + - log.logtype + + # Normalize explicit outcomes; an unknown outcome remains unset. + - add: + function: string + params: + key: actionResult + value: denied + where: regexMatch("log.subType", "(?i)^(denied|blocked|dropped)$") || regexMatch("log.status", "(?i)^(deny|denied|block|blocked|drop|dropped)$") + - add: + function: string + params: + key: actionResult + value: success + where: '!exists("actionResult") && (regexMatch("log.subType", "(?i)^(accepted|allowed)$") || regexMatch("log.status", "(?i)^(allow|allowed|accept|accepted)$"))' + - add: + function: string + params: + key: actionResult + value: denied + where: '!exists("actionResult") && oneOf("statusCode", [401,403])' + - add: + function: string + params: + key: actionResult + value: failure + where: '!exists("actionResult") && greaterOrEqual("statusCode",400) && lessThan("statusCode",600)' + - add: + function: string + params: + key: actionResult + value: success + where: '!exists("actionResult") && greaterOrEqual("statusCode",200) && lessThan("statusCode",400)' + + # Keep addresses in IP fields and retain other source values under log. + - rename: + from: + - origin.ip + to: log.unparsedOriginIp + where: exists("origin.ip") && (!(inCIDR("origin.ip","0.0.0.0/0") || inCIDR("origin.ip","::/0")) || oneOf("origin.ip",["0.0.0.0","::"])) + - rename: + from: + - target.ip + to: log.unparsedTargetIp + where: exists("target.ip") && (!(inCIDR("target.ip","0.0.0.0/0") || inCIDR("target.ip","::/0")) || oneOf("target.ip",["0.0.0.0","::"])) + - add: + function: string + params: + key: protocol + value: ICMP + where: equals("protocol",1) + - add: + function: string + params: + key: protocol + value: TCP + where: equals("protocol",6) + - add: + function: string + params: + key: protocol + value: UDP + where: equals("protocol",17) + - add: + function: string + params: + key: protocol + value: GRE + where: equals("protocol",47) + - add: + function: string + params: + key: protocol + value: ESP + where: equals("protocol",50) + - add: + function: string + params: + key: protocol + value: AH + where: equals("protocol",51) + - add: + function: string + params: + key: protocol + value: ICMPV6 + where: equals("protocol",58) + - add: + function: string + params: + key: protocol + value: SCTP + where: equals("protocol",132) + - rename: + from: + - protocol + to: log.ipProtocolNumber + where: exists("protocol") && greaterOrEqual("protocol",0) diff --git a/plugins/alerts/testdata/filter-contracts/sophos.json b/plugins/alerts/testdata/filter-contracts/sophos.json new file mode 100644 index 000000000..03721fbae --- /dev/null +++ b/plugins/alerts/testdata/filter-contracts/sophos.json @@ -0,0 +1,138 @@ +{ + "technology": "Sophos", + "filters": [ + "filters/sophos/sophos_central.yml", + "filters/sophos/sophos_xg_firewall.yml" + ], + "rules": [ + "rules/sophos/sophos_central/behavioral_analysis_alerts.yml", + "rules/sophos/sophos_central/endpoint_threat_detection.yml", + "rules/sophos/sophos_central/exploit_prevention_triggers.yml", + "rules/sophos/sophos_central/managed_threat_response_alerts.yml", + "rules/sophos/sophos_central/ransomware_detection.yml", + "rules/sophos/sophos_central/server_protection_alerts.yml", + "rules/sophos/sophos_central/sophos_central_app_hijacking_prevented.yml", + "rules/sophos/sophos_central/sophos_central_credential_theft_attack_detected.yml", + "rules/sophos/sophos_central/sophos_central_device_compromised.yml", + "rules/sophos/sophos_central/sophos_central_exploit_detected.yml", + "rules/sophos/sophos_central/sophos_central_malware_detected.yml", + "rules/sophos/sophos_central/sophos_central_man_in_the_middle_attack_detected.yml", + "rules/sophos/sophos_central/sophos_central_possible_botnet_detected.yml", + "rules/sophos/sophos_central/sophos_central_possible_brute_force_attack.yml", + "rules/sophos/sophos_central/sophos_central_potential_password_spraying_attack.yml", + "rules/sophos/sophos_central/sophos_central_ransomware_detected.yml", + "rules/sophos/sophos_central/sophos_central_real_time_protection_disabled.yml", + "rules/sophos/sophos_central/sophos_central_unknown_threat_detected.yml", + "rules/sophos/sophos_central/tamper_protection_alerts.yml", + "rules/sophos/sophos_xg_firewall/advanced_threat_protection_alerts.yml", + "rules/sophos/sophos_xg_firewall/sophos_denial_of_service.yml", + "rules/sophos/sophos_xg_firewall/sophos_firewall_probable_malware_detected.yml", + "rules/sophos/sophos_xg_firewall/sophos_high_severity_alerts.yml", + "rules/sophos/sophos_xg_firewall/sophos_initial_access_by_guest_account.yml", + "rules/sophos/sophos_xg_firewall/sophos_ip_spoofing_attack.yml", + "rules/sophos/sophos_xg_firewall/sophos_password_guessing_on_administrator_account.yml", + "rules/sophos/sophos_xg_firewall/sophos_xg_ips_signatures.yml", + "rules/sophos/sophos_xg_firewall/sophos_xg_vpn_auth_failures.yml" + ], + "fixtures": [ + { + "name": "Sophos HTTP 200", + "filter": "sophos/sophos_xg_firewall.yml", + "input": { + "log": { + "statuscode": 200 + } + }, + "expected": { + "actionResult": "success" + }, + "absent": [], + "rules": {} + }, + { + "name": "Sophos HTTP 302", + "filter": "sophos/sophos_xg_firewall.yml", + "input": { + "log": { + "statuscode": 302 + } + }, + "expected": { + "actionResult": "success" + }, + "absent": [], + "rules": {} + }, + { + "name": "Sophos HTTP 403", + "filter": "sophos/sophos_xg_firewall.yml", + "input": { + "log": { + "statuscode": 403 + } + }, + "expected": { + "actionResult": "denied" + }, + "absent": [], + "rules": {} + }, + { + "name": "Sophos HTTP 500", + "filter": "sophos/sophos_xg_firewall.yml", + "input": { + "log": { + "statuscode": 500 + } + }, + "expected": { + "actionResult": "failure" + }, + "absent": [], + "rules": {} + }, + { + "name": "Sophos denied block-page HTTP 200", + "filter": "sophos/sophos_xg_firewall.yml", + "input": { + "log": { + "statuscode": 200, + "subType": "Denied" + } + }, + "expected": { + "actionResult": "denied" + }, + "absent": [], + "rules": {} + }, + { + "name": "sophos_central_denied", + "filter": "sophos/sophos_central.yml", + "input": { + "log": { + "action": "blocked" + } + }, + "expected": { + "actionResult": "denied" + }, + "absent": [], + "rules": {} + }, + { + "name": "sophos_central_failure", + "filter": "sophos/sophos_central.yml", + "input": { + "log": { + "type": "Event::Endpoint::AuthenticationFailure" + } + }, + "expected": { + "actionResult": "failure" + }, + "absent": [], + "rules": {} + } + ] +}