Skip to content

Commit 787b84e

Browse files
committed
fix: align Sophos filter and rule contracts
1 parent 6c3af7e commit 787b84e

4 files changed

Lines changed: 289 additions & 43 deletions

File tree

‎filters/audits/sophos.md‎

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Sophos normalization and rule review
2+
3+
Normalize Central/XG outcomes; explicit firewall denial wins over HTTP block-page success; fix command and IP fields.
4+
5+
This draft targets UTMStack `v11`. It contains 2 filter changes
6+
and 0 rule changes for this technology only. Review covered
7+
2 filter configurations and 28 matching shipped rule files.
8+
Unchanged rules are listed in the regression manifest; they are not duplicated in the diff.
9+
10+
## Contract and validation
11+
12+
- Compared exact standard names/types with go-sdk v1.1.31 and the supplied UTMStack dictionaries.
13+
- Checked documented pipeline ordering, rename/move behavior, open vendor log fields,
14+
event-side versus alert-side fields, and surviving fields used by affected rule predicates/history/grouping.
15+
- Strict SDK configuration decoding and actual CEL compilation pass for this scope.
16+
- 7 synthetic normalization cases pass, including SDK Event conversion and any
17+
trigger predicate assertions recorded in the manifest.
18+
- The scoped alerts module tests and `git diff --check` pass with the shared contract runner applied.
19+
20+
The shared alert-contract PR supplies the reusable Go runner for the manifest in
21+
`plugins/alerts/testdata/filter-contracts/sophos.json`. Apply that support before running `go test ./...` in `plugins/alerts`.
22+
23+
The model starts from synthetic extraction results. It does not run complex grok,
24+
JSON/KV/XML/CSV extraction, time conversion, dynamic plugins, historical OpenSearch
25+
queries, or the closed EventProcessor. Raw vendor logs and resulting alerts must
26+
still be checked in staging before rollout. No customer false-positive reduction
27+
has been measured and no production rollout is included.
28+
29+
30+
31+
## References
32+
33+
- [SDK schema](https://github.com/threatwinds/go-sdk/blob/v1.1.31/plugins/plugins.proto)
34+
- [Filter steps](https://github.com/threatwinds/go-sdk/wiki/Filter-Steps-Reference)
35+
- [Standard event schema](https://github.com/threatwinds/go-sdk/wiki/Standard-Event-Schema)
36+
- [Rule implementation](https://github.com/threatwinds/go-sdk/wiki/Implementing-Rules)
37+
38+
`afterEvents`, empty noncapturing grok names, supported numeric strings, and custom
39+
`log.*` fields are accepted. Existing textual protocol casing and vendor action names
40+
are preserved unless a concrete consumer mismatch requires correction.

‎filters/sophos/sophos_central.yml‎

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -55,33 +55,33 @@ pipeline:
5555
function: string
5656
params:
5757
key: actionResult
58-
value: "blocked"
59-
where: 'exists("log.action") && equals("log.action", "blocked")'
58+
value: denied
59+
where: exists("log.action") && equals("log.action", "blocked")
6060

6161
- add:
6262
function: string
6363
params:
6464
key: actionResult
65-
value: "blocked"
66-
where: 'exists("log.type") && contains("log.type", "Prevented") && !exists("actionResult")'
65+
value: denied
66+
where: exists("log.type") && contains("log.type", "Prevented") && !exists("actionResult")
6767

6868
- add:
6969
function: string
7070
params:
7171
key: actionResult
72-
value: "blocked"
73-
where: 'exists("log.type") && oneOf("log.type", ["Event::Endpoint::HmpaApplicationHijacking", "Event::Endpoint::HmpaPrivGuard"]) && !exists("actionResult")'
72+
value: denied
73+
where: exists("log.type") && oneOf("log.type", ["Event::Endpoint::HmpaApplicationHijacking", "Event::Endpoint::HmpaPrivGuard"]) && !exists("actionResult")
7474

7575
- add:
7676
function: string
7777
params:
7878
key: actionResult
79-
value: "blocked"
80-
where: 'exists("log.type") && contains("log.type", "HmpaCred") && !exists("actionResult")'
79+
value: denied
80+
where: exists("log.type") && contains("log.type", "HmpaCred") && !exists("actionResult")
8181

8282
- add:
8383
function: string
8484
params:
8585
key: actionResult
86-
value: "failed"
87-
where: 'exists("log.type") && contains("log.type", "AuthenticationFailure") && !exists("actionResult")'
86+
value: failure
87+
where: exists("log.type") && contains("log.type", "AuthenticationFailure") && !exists("actionResult")

‎filters/sophos/sophos_xg_firewall.yml‎

Lines changed: 101 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -423,8 +423,7 @@ pipeline:
423423
- rename:
424424
from:
425425
- log.ftpcommand
426-
to: command
427-
426+
to: origin.command
428427
- rename:
429428
from:
430429
- log.FTPurl
@@ -546,7 +545,7 @@ pipeline:
546545
- log.outInterface
547546
- log.vLanId
548547
- log.deviceType
549-
- command
548+
- origin.command
550549
- log.clientHostName
551550
- log.ipAddress
552551
- log.clientPhysicalAddress
@@ -604,7 +603,7 @@ pipeline:
604603
- log.outInterface
605604
- log.vLanId
606605
- log.deviceType
607-
- command
606+
- origin.command
608607
- log.clientHostName
609608
- log.ipAddress
610609
- log.clientPhysicalAddress
@@ -684,19 +683,7 @@ pipeline:
684683
to: float
685684

686685
# Adding actionResult based on log.subtype value
687-
- add:
688-
function: 'string'
689-
params:
690-
key: actionResult
691-
value: 'denied'
692-
where: exists("log.subType") && regexMatch("log.subType", "(?i)\bdenied\b")
693686

694-
- add:
695-
function: 'string'
696-
params:
697-
key: actionResult
698-
value: 'accepted'
699-
where: exists("log.subType") && oneOf("log.subType", ["Accepted", "Allowed"])
700687

701688
# Renaming "log.statusCode" to "statusCode" to add it to the event structure
702689
- rename:
@@ -705,22 +692,6 @@ pipeline:
705692
to: statusCode
706693
where: exists("log.statusCode")
707694

708-
# Adding actionResult
709-
# denied by default
710-
- add:
711-
function: 'string'
712-
params:
713-
key: actionResult
714-
value: 'denied'
715-
where: exists("statusCode")
716-
717-
- add:
718-
function: 'string'
719-
params:
720-
key: actionResult
721-
value: 'accepted'
722-
where: exists("statusCode") && ((greaterOrEqual("statusCode", 200) && lessOrEqual("statusCode", 299)) || (greaterOrEqual("statusCode", 300) && lessOrEqual("statusCode", 399) && greaterThan("origin.bytesReceived", 0)))
723-
724695
# Removing unused fields
725696
- delete:
726697
fields:
@@ -756,4 +727,101 @@ pipeline:
756727
- log.logcomponent
757728
- log.logsubtype
758729
- log.name
759-
- log.logtype
730+
- log.logtype
731+
732+
# Normalize explicit outcomes; an unknown outcome remains unset.
733+
- add:
734+
function: string
735+
params:
736+
key: actionResult
737+
value: denied
738+
where: regexMatch("log.subType", "(?i)^(denied|blocked|dropped)$") || regexMatch("log.status", "(?i)^(deny|denied|block|blocked|drop|dropped)$")
739+
- add:
740+
function: string
741+
params:
742+
key: actionResult
743+
value: success
744+
where: '!exists("actionResult") && (regexMatch("log.subType", "(?i)^(accepted|allowed)$") || regexMatch("log.status", "(?i)^(allow|allowed|accept|accepted)$"))'
745+
- add:
746+
function: string
747+
params:
748+
key: actionResult
749+
value: denied
750+
where: '!exists("actionResult") && oneOf("statusCode", [401,403])'
751+
- add:
752+
function: string
753+
params:
754+
key: actionResult
755+
value: failure
756+
where: '!exists("actionResult") && greaterOrEqual("statusCode",400) && lessThan("statusCode",600)'
757+
- add:
758+
function: string
759+
params:
760+
key: actionResult
761+
value: success
762+
where: '!exists("actionResult") && greaterOrEqual("statusCode",200) && lessThan("statusCode",400)'
763+
764+
# Keep addresses in IP fields and retain other source values under log.
765+
- rename:
766+
from:
767+
- origin.ip
768+
to: log.unparsedOriginIp
769+
where: exists("origin.ip") && (!(inCIDR("origin.ip","0.0.0.0/0") || inCIDR("origin.ip","::/0")) || oneOf("origin.ip",["0.0.0.0","::"]))
770+
- rename:
771+
from:
772+
- target.ip
773+
to: log.unparsedTargetIp
774+
where: exists("target.ip") && (!(inCIDR("target.ip","0.0.0.0/0") || inCIDR("target.ip","::/0")) || oneOf("target.ip",["0.0.0.0","::"]))
775+
- add:
776+
function: string
777+
params:
778+
key: protocol
779+
value: ICMP
780+
where: equals("protocol",1)
781+
- add:
782+
function: string
783+
params:
784+
key: protocol
785+
value: TCP
786+
where: equals("protocol",6)
787+
- add:
788+
function: string
789+
params:
790+
key: protocol
791+
value: UDP
792+
where: equals("protocol",17)
793+
- add:
794+
function: string
795+
params:
796+
key: protocol
797+
value: GRE
798+
where: equals("protocol",47)
799+
- add:
800+
function: string
801+
params:
802+
key: protocol
803+
value: ESP
804+
where: equals("protocol",50)
805+
- add:
806+
function: string
807+
params:
808+
key: protocol
809+
value: AH
810+
where: equals("protocol",51)
811+
- add:
812+
function: string
813+
params:
814+
key: protocol
815+
value: ICMPV6
816+
where: equals("protocol",58)
817+
- add:
818+
function: string
819+
params:
820+
key: protocol
821+
value: SCTP
822+
where: equals("protocol",132)
823+
- rename:
824+
from:
825+
- protocol
826+
to: log.ipProtocolNumber
827+
where: exists("protocol") && greaterOrEqual("protocol",0)
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
{
2+
"technology": "Sophos",
3+
"filters": [
4+
"filters/sophos/sophos_central.yml",
5+
"filters/sophos/sophos_xg_firewall.yml"
6+
],
7+
"rules": [
8+
"rules/sophos/sophos_central/behavioral_analysis_alerts.yml",
9+
"rules/sophos/sophos_central/endpoint_threat_detection.yml",
10+
"rules/sophos/sophos_central/exploit_prevention_triggers.yml",
11+
"rules/sophos/sophos_central/managed_threat_response_alerts.yml",
12+
"rules/sophos/sophos_central/ransomware_detection.yml",
13+
"rules/sophos/sophos_central/server_protection_alerts.yml",
14+
"rules/sophos/sophos_central/sophos_central_app_hijacking_prevented.yml",
15+
"rules/sophos/sophos_central/sophos_central_credential_theft_attack_detected.yml",
16+
"rules/sophos/sophos_central/sophos_central_device_compromised.yml",
17+
"rules/sophos/sophos_central/sophos_central_exploit_detected.yml",
18+
"rules/sophos/sophos_central/sophos_central_malware_detected.yml",
19+
"rules/sophos/sophos_central/sophos_central_man_in_the_middle_attack_detected.yml",
20+
"rules/sophos/sophos_central/sophos_central_possible_botnet_detected.yml",
21+
"rules/sophos/sophos_central/sophos_central_possible_brute_force_attack.yml",
22+
"rules/sophos/sophos_central/sophos_central_potential_password_spraying_attack.yml",
23+
"rules/sophos/sophos_central/sophos_central_ransomware_detected.yml",
24+
"rules/sophos/sophos_central/sophos_central_real_time_protection_disabled.yml",
25+
"rules/sophos/sophos_central/sophos_central_unknown_threat_detected.yml",
26+
"rules/sophos/sophos_central/tamper_protection_alerts.yml",
27+
"rules/sophos/sophos_xg_firewall/advanced_threat_protection_alerts.yml",
28+
"rules/sophos/sophos_xg_firewall/sophos_denial_of_service.yml",
29+
"rules/sophos/sophos_xg_firewall/sophos_firewall_probable_malware_detected.yml",
30+
"rules/sophos/sophos_xg_firewall/sophos_high_severity_alerts.yml",
31+
"rules/sophos/sophos_xg_firewall/sophos_initial_access_by_guest_account.yml",
32+
"rules/sophos/sophos_xg_firewall/sophos_ip_spoofing_attack.yml",
33+
"rules/sophos/sophos_xg_firewall/sophos_password_guessing_on_administrator_account.yml",
34+
"rules/sophos/sophos_xg_firewall/sophos_xg_ips_signatures.yml",
35+
"rules/sophos/sophos_xg_firewall/sophos_xg_vpn_auth_failures.yml"
36+
],
37+
"fixtures": [
38+
{
39+
"name": "Sophos HTTP 200",
40+
"filter": "sophos/sophos_xg_firewall.yml",
41+
"input": {
42+
"log": {
43+
"statuscode": 200
44+
}
45+
},
46+
"expected": {
47+
"actionResult": "success"
48+
},
49+
"absent": [],
50+
"rules": {}
51+
},
52+
{
53+
"name": "Sophos HTTP 302",
54+
"filter": "sophos/sophos_xg_firewall.yml",
55+
"input": {
56+
"log": {
57+
"statuscode": 302
58+
}
59+
},
60+
"expected": {
61+
"actionResult": "success"
62+
},
63+
"absent": [],
64+
"rules": {}
65+
},
66+
{
67+
"name": "Sophos HTTP 403",
68+
"filter": "sophos/sophos_xg_firewall.yml",
69+
"input": {
70+
"log": {
71+
"statuscode": 403
72+
}
73+
},
74+
"expected": {
75+
"actionResult": "denied"
76+
},
77+
"absent": [],
78+
"rules": {}
79+
},
80+
{
81+
"name": "Sophos HTTP 500",
82+
"filter": "sophos/sophos_xg_firewall.yml",
83+
"input": {
84+
"log": {
85+
"statuscode": 500
86+
}
87+
},
88+
"expected": {
89+
"actionResult": "failure"
90+
},
91+
"absent": [],
92+
"rules": {}
93+
},
94+
{
95+
"name": "Sophos denied block-page HTTP 200",
96+
"filter": "sophos/sophos_xg_firewall.yml",
97+
"input": {
98+
"log": {
99+
"statuscode": 200,
100+
"subType": "Denied"
101+
}
102+
},
103+
"expected": {
104+
"actionResult": "denied"
105+
},
106+
"absent": [],
107+
"rules": {}
108+
},
109+
{
110+
"name": "sophos_central_denied",
111+
"filter": "sophos/sophos_central.yml",
112+
"input": {
113+
"log": {
114+
"action": "blocked"
115+
}
116+
},
117+
"expected": {
118+
"actionResult": "denied"
119+
},
120+
"absent": [],
121+
"rules": {}
122+
},
123+
{
124+
"name": "sophos_central_failure",
125+
"filter": "sophos/sophos_central.yml",
126+
"input": {
127+
"log": {
128+
"type": "Event::Endpoint::AuthenticationFailure"
129+
}
130+
},
131+
"expected": {
132+
"actionResult": "failure"
133+
},
134+
"absent": [],
135+
"rules": {}
136+
}
137+
]
138+
}

0 commit comments

Comments
 (0)