diff --git a/changes/65d5b1092a529b30765cbf10acf0ef51.yaml b/changes/65d5b1092a529b30765cbf10acf0ef51.yaml new file mode 100644 index 0000000000..0b576de23d --- /dev/null +++ b/changes/65d5b1092a529b30765cbf10acf0ef51.yaml @@ -0,0 +1,6 @@ +--- +desc: Added ``it:app:suricata:rule`` and ``it:app:suricata:matched`` forms. +desc:literal: false +prs: [] +type: model +... diff --git a/synapse/models/infotech.py b/synapse/models/infotech.py index 444b23bf3b..33b84f8ae0 100644 --- a/synapse/models/infotech.py +++ b/synapse/models/infotech.py @@ -1049,6 +1049,12 @@ def getModelDefs(self): ('it:app:snort:hit', ('guid', {}), { 'doc': 'An instance of a snort rule hit.', }), + ('it:app:suricata:rule', ('guid', {}), { + 'doc': 'A suricata rule.', + }), + ('it:app:suricata:matched', ('guid', {}), { + 'doc': 'An instance of a suricata rule hit.', + }), ('it:reveng:function', ('guid', {}), { 'doc': 'A function inside an executable.', }), @@ -1119,6 +1125,8 @@ def getModelDefs(self): 'doc': 'The target node was returned as a result of running the query.'}), (('it:app:snort:rule', 'detects', None), { 'doc': 'The snort rule is intended for use in detecting the target node.'}), + (('it:app:suricata:rule', 'detects', None), { + 'doc': 'The suricata rule is intended for use in detecting the target node.'}), (('it:app:yara:rule', 'detects', None), { 'doc': 'The YARA rule is intended for use in detecting the target node.'}), (('it:dev:repo', 'has', 'inet:url'), { @@ -3207,6 +3215,59 @@ def getModelDefs(self): 'doc': 'Set to true if the network traffic was dropped due to the match.'}), )), + ('it:app:suricata:rule', {}, ( + + ('id', ('str', {}), { + 'doc': 'The suricata rule id.'}), + + ('text', ('str', {}), { + 'disp': {'hint': 'text'}, + 'doc': 'The suricata rule text.'}), + + ('name', ('str', {}), { + 'doc': 'The name of the suricata rule.'}), + + ('desc', ('str', {}), { + 'disp': {'hint': 'text'}, + 'doc': 'A brief description of the suricata rule.'}), + + ('version', ('it:semver', {}), { + 'doc': 'The current version of the rule.'}), + + ('author', ('ps:contact', {}), { + 'doc': 'Contact info for the author of the rule.'}), + + ('created', ('time', {}), { + 'doc': 'The time the rule was created.'}), + + ('updated', ('time', {}), { + 'doc': 'The time the rule was most recently modified.'}), + + ('enabled', ('bool', {}), { + 'doc': 'The rule enabled status to be used for suricata evaluation engines.'}), + )), + + ('it:app:suricata:matched', {}, ( + + ('rule', ('it:app:suricata:rule', {}), { + 'doc': 'The suricata rule that matched the file.'}), + + ('target', ('inet:flow', {}), { + 'doc': 'The inet:flow that matched the suricata rule.'}), + + ('time', ('time', {}), { + 'doc': 'The time that the rule matched the network flow.'}), + + ('sensor', ('it:host', {}), { + 'doc': 'The sensor host node that produced the hit.'}), + + ('version', ('it:semver', {}), { + 'doc': 'The version of the rule at the time of match.'}), + + ('dropped', ('bool', {}), { + 'doc': 'Set to true if the network traffic was dropped due to the match.'}), + )), + ('it:sec:stix:bundle', {}, ( ('id', ('str', {}), { 'doc': 'The id field from the STIX bundle.'}), diff --git a/synapse/tests/test_model_infotech.py b/synapse/tests/test_model_infotech.py index cb724a0600..620b8488f2 100644 --- a/synapse/tests/test_model_infotech.py +++ b/synapse/tests/test_model_infotech.py @@ -1779,6 +1779,55 @@ async def test_it_app_snort(self): self.eq(0x10000200003, nodes[0].get('version')) + async def test_it_app_suricata(self): + + async with self.getTestCore() as core: + + hit = s_common.guid() + rule = s_common.guid() + flow = s_common.guid() + host = s_common.guid() + opts = {'vars': {'rule': rule, 'flow': flow, 'host': host, 'hit': hit}} + + nodes = await core.nodes(''' + [ it:app:suricata:rule=$rule + :id=999 + :text=gronk + :name=foo + :desc=bar + :author = {[ ps:contact=* :name=visi ]} + :created = 20120101 + :updated = 20220101 + :enabled=1 + :version=1.2.3 + +(detects)> {[ it:prod:softname=woot ]} + ] + ''', opts=opts) + + self.len(1, nodes) + self.eq('999', nodes[0].get('id')) + self.eq('foo', nodes[0].get('name')) + self.eq('gronk', nodes[0].get('text')) + self.eq('bar', nodes[0].get('desc')) + self.eq(True, nodes[0].get('enabled')) + self.eq(0x10000200003, nodes[0].get('version')) + self.eq(1325376000000, nodes[0].get('created')) + self.eq(1640995200000, nodes[0].get('updated')) + self.nn(nodes[0].get('author')) + + self.len(1, await core.nodes('it:app:suricata:rule -(detects)> it:prod:softname')) + + nodes = await core.nodes('''[ it:app:suricata:matched=$hit + :rule=$rule :target=$flow :time=2015 :sensor=$host + :version=1.2.3 :dropped=true ]''', opts=opts) + self.len(1, nodes) + self.true(nodes[0].get('dropped')) + self.eq(rule, nodes[0].get('rule')) + self.eq(flow, nodes[0].get('target')) + self.eq(host, nodes[0].get('sensor')) + self.eq(1420070400000, nodes[0].get('time')) + self.eq(0x10000200003, nodes[0].get('version')) + async def test_it_reveng(self): async with self.getTestCore() as core: