Skip to content

Commit d27f175

Browse files
nasbenchphantinuss
andauthored
add missing mitre tactics + missing author validators (#50)
* add missing mitre tactics validator * add author validator * chore: group by missing technique, not by tactic, for the error message --------- Co-authored-by: phantinuss <79651203+phantinuss@users.noreply.github.com>
1 parent b9ba169 commit d27f175

5 files changed

Lines changed: 161 additions & 1 deletion

File tree

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "pySigma-validators-sigmahq"
3-
version = "0.10.2"
3+
version = "0.11.0"
44
description = "pySigma SigmaHQ validators"
55
authors = ["François Hubaut <frack113@users.noreply.github.com>"]
66
license = "LGPL-2.1-only"

sigma/validators/sigmahq/metadata.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,22 @@ def validate(self, rule: SigmaRuleBase) -> List[SigmaValidationIssue]:
2727
return []
2828

2929

30+
@dataclass
31+
class SigmahqAuthorExistenceIssue(SigmaValidationIssue):
32+
description: ClassVar[str] = "Rule is missing the author field"
33+
severity: ClassVar[SigmaValidationIssueSeverity] = SigmaValidationIssueSeverity.MEDIUM
34+
35+
36+
class SigmahqAuthorExistenceValidator(SigmaRuleValidator):
37+
"""Checks if a rule is missing the author field."""
38+
39+
def validate(self, rule: SigmaRuleBase) -> List[SigmaValidationIssue]:
40+
if rule.author is None:
41+
return [SigmahqAuthorExistenceIssue([rule])]
42+
else:
43+
return []
44+
45+
3046
@dataclass
3147
class SigmahqStatusIssue(SigmaValidationIssue):
3248
description: ClassVar[str] = (

sigma/validators/sigmahq/tags.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
SigmaValidationIssue,
1010
SigmaValidationIssueSeverity,
1111
)
12+
13+
from sigma.data.mitre_attack import mitre_attack_techniques_tactics_mapping
14+
1215
from .config import ConfigHQ
1316

1417
config = ConfigHQ()
@@ -91,3 +94,56 @@ def validate(self, rule: SigmaRuleBase) -> List[SigmaValidationIssue]:
9194
if tag.namespace == "tlp" and tag.name not in self.allowed_tlp:
9295
return [SigmahqTagsTlpIssue([rule], tlp=tag.name)]
9396
return []
97+
98+
99+
@dataclass
100+
class SigmahqTagsTechniquesWithoutTacticsIssue(SigmaValidationIssue):
101+
description: ClassVar[str] = (
102+
"A MITRE ATT&CK technique tag was found without its corresponding tactic name. (e.g. when using 'attack.t1059' you have to add 'attack.execution' as well)"
103+
)
104+
severity: ClassVar[SigmaValidationIssueSeverity] = SigmaValidationIssueSeverity.HIGH
105+
techniques: List[str]
106+
missing_tactic: str
107+
108+
109+
class SigmahqTagsTechniquesWithoutTacticsValidator(SigmaRuleValidator):
110+
"""Ensures that MITRE ATT&CK technique tags have their corresponding tactic tags."""
111+
112+
def validate(self, rule: SigmaRuleBase) -> List[SigmaValidationIssue]:
113+
issues = []
114+
115+
attack_tags = [tag for tag in rule.tags or [] if tag.namespace == "attack"]
116+
117+
technique_tags = [
118+
tag.name
119+
for tag in attack_tags
120+
if tag.name.startswith("t") and any(c.isdigit() for c in tag.name)
121+
]
122+
tactic_tags = [tag.name for tag in attack_tags if not tag.name.startswith("t")]
123+
124+
missing_tactics = []
125+
for technique in technique_tags:
126+
technique_upper = technique.upper()
127+
128+
if technique_upper in mitre_attack_techniques_tactics_mapping:
129+
required_tactics = mitre_attack_techniques_tactics_mapping[technique_upper]
130+
missing_tactics.extend(
131+
[tactic for tactic in required_tactics if tactic not in tactic_tags]
132+
)
133+
134+
if missing_tactics:
135+
for missing_tactic in set(missing_tactics):
136+
techniques = [
137+
technique
138+
for technique in technique_tags
139+
if missing_tactic in mitre_attack_techniques_tactics_mapping[technique.upper()]
140+
]
141+
issues.append(
142+
SigmahqTagsTechniquesWithoutTacticsIssue(
143+
[rule],
144+
techniques=["attack." + t for t in techniques],
145+
missing_tactic="attack." + missing_tactic,
146+
)
147+
)
148+
149+
return issues

tests/test_metadata.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
from sigma.validators.sigmahq.metadata import (
88
SigmahqStatusExistenceIssue,
99
SigmahqStatusExistenceValidator,
10+
SigmahqAuthorExistenceIssue,
11+
SigmahqAuthorExistenceValidator,
1012
SigmahqStatusIssue,
1113
SigmahqStatusValidator,
1214
SigmahqDateExistenceIssue,
@@ -157,6 +159,40 @@ def test_validator_SigmahqStatusExistence_valid():
157159
assert validator.validate(rule) == []
158160

159161

162+
def test_validator_SigmahqAuthorExistence():
163+
validator = SigmahqAuthorExistenceValidator()
164+
rule = SigmaRule.from_yaml(
165+
"""
166+
title: Test
167+
logsource:
168+
category: test
169+
detection:
170+
sel:
171+
field: value
172+
condition: sel
173+
"""
174+
)
175+
assert validator.validate(rule) == [SigmahqAuthorExistenceIssue([rule])]
176+
177+
178+
def test_validator_SigmahqAuthorExistence_valid():
179+
validator = SigmahqAuthorExistenceValidator()
180+
rule = SigmaRule.from_yaml(
181+
"""
182+
title: Test
183+
author: test
184+
status: test
185+
logsource:
186+
category: test
187+
detection:
188+
sel:
189+
field: value
190+
condition: sel
191+
"""
192+
)
193+
assert validator.validate(rule) == []
194+
195+
160196
def test_validator_SigmahqDescriptionExistence():
161197
validator = SigmahqDescriptionExistenceValidator()
162198
rule = SigmaRule.from_yaml(

tests/test_tags.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
SigmahqTagsUniqueDetectionValidator,
1313
SigmahqTagsUniqueTlpIssue,
1414
SigmahqTagsUniqueTlpValidator,
15+
SigmahqTagsTechniquesWithoutTacticsIssue,
16+
SigmahqTagsTechniquesWithoutTacticsValidator,
1517
)
1618

1719

@@ -223,3 +225,53 @@ def test_validator_SigmahqTagsNoDetection():
223225
"""
224226
)
225227
assert validator.validate(rule) == []
228+
229+
230+
def test_validator_SigmahqTagsTechniquesWithoutTactics():
231+
validator = SigmahqTagsTechniquesWithoutTacticsValidator()
232+
rule = SigmaRule.from_yaml(
233+
"""
234+
title: test
235+
status: unsupported
236+
tags:
237+
- attack.t1027.004
238+
- attack.t1027.005
239+
logsource:
240+
category: test
241+
detection:
242+
sel:
243+
field: path\\*something
244+
condition: sel
245+
"""
246+
)
247+
248+
assert validator.validate(rule) == [
249+
SigmahqTagsTechniquesWithoutTacticsIssue(
250+
[rule],
251+
techniques=["attack.t1027.004", "attack.t1027.005"],
252+
missing_tactic="attack.defense-evasion",
253+
)
254+
]
255+
256+
257+
def test_validator_SigmahqTagsTechniquesWithoutTactics_valid():
258+
validator = SigmahqTagsTechniquesWithoutTacticsValidator()
259+
rule = SigmaRule.from_yaml(
260+
"""
261+
title: test
262+
status: unsupported
263+
tags:
264+
- attack.t1027.004
265+
- attack.t1027.005
266+
- attack.defense-evasion
267+
- attack.t1003
268+
- attack.credential-access
269+
logsource:
270+
category: test
271+
detection:
272+
sel:
273+
field: path\\*something
274+
condition: sel
275+
"""
276+
)
277+
assert validator.validate(rule) == []

0 commit comments

Comments
 (0)