From 0c0b40e26ffcc639ff0b7b8069924adbfa6de2be Mon Sep 17 00:00:00 2001 From: iglocska Date: Tue, 3 May 2022 16:10:07 +0200 Subject: [PATCH 1/3] new: [action] module wip --- misp_modules/modules/__init__.py | 1 + misp_modules/modules/action_mod/__init__.py | 1 + misp_modules/modules/action_mod/testaction.py | 74 +++++++++++++++++++ 3 files changed, 76 insertions(+) create mode 100644 misp_modules/modules/action_mod/__init__.py create mode 100644 misp_modules/modules/action_mod/testaction.py diff --git a/misp_modules/modules/__init__.py b/misp_modules/modules/__init__.py index 47ddcbf..97fdc13 100644 --- a/misp_modules/modules/__init__.py +++ b/misp_modules/modules/__init__.py @@ -1,3 +1,4 @@ from .expansion import * # noqa from .import_mod import * # noqa from .export_mod import * # noqa +from .action_mod import * # noqa diff --git a/misp_modules/modules/action_mod/__init__.py b/misp_modules/modules/action_mod/__init__.py new file mode 100644 index 0000000..42fa40e --- /dev/null +++ b/misp_modules/modules/action_mod/__init__.py @@ -0,0 +1 @@ +__all__ = ['testaction'] diff --git a/misp_modules/modules/action_mod/testaction.py b/misp_modules/modules/action_mod/testaction.py new file mode 100644 index 0000000..4e901ac --- /dev/null +++ b/misp_modules/modules/action_mod/testaction.py @@ -0,0 +1,74 @@ +import json +import base64 + +misperrors = {'error': 'Error'} + +# config fields that your code expects from the site admin +moduleconfig = { + 'foo': { + 'type': 'string', + 'description': 'blablabla', + 'value': 'xyz' + }, + 'bar': { + 'type': 'string', + 'value': 'meh' + } +}; + +# blocking modules break the exection of the chain of actions (such as publishing) +blocking = False + +# returns either "boolean" or "data" +# Boolean is used to simply signal that the execution has finished. +# For blocking modules the actual boolean value determines whether we break execution +returns = 'boolean' + + +# the list of hook-points that it can hook +hooks = ['publish'] + + +moduleinfo = {'version': '0.1', 'author': 'Andras Iklody', + 'description': 'This module is merely a test, always returning true. Triggers on event publishing.', + 'module-type': ['action']} + + +def handler(q=False): + if q is False: + return False + r = True + result = json.loads(q) # noqa + output = '' # Insert your magic here! + r = {"data": r} + return r + + +def introspection(): + modulesetup = {} + try: + responseType + modulesetup['responseType'] = responseType + except NameError: + pass + try: + inputSource + modulesetup['resultType'] = resultType + except NameError: + pass + try: + hooks + modulesetup['hooks'] = hooks + except NameError: + pass + try: + hooks + modulesetup['blocking'] = blocking + except NameError: + pass + return modulesetup + + +def version(): + moduleinfo['config'] = moduleconfig + return moduleinfo From cac0c19eed5ee8af23c41d064bf67a36e49bc774 Mon Sep 17 00:00:00 2001 From: iglocska Date: Wed, 4 May 2022 01:26:56 +0200 Subject: [PATCH 2/3] new: [action module] samples added for testing --- misp_modules/modules/action_mod/__init__.py | 2 +- .../modules/action_mod/blockaction.py | 63 +++++++++++++++++ .../modules/action_mod/writeaction.py | 68 +++++++++++++++++++ 3 files changed, 132 insertions(+), 1 deletion(-) create mode 100644 misp_modules/modules/action_mod/blockaction.py create mode 100644 misp_modules/modules/action_mod/writeaction.py diff --git a/misp_modules/modules/action_mod/__init__.py b/misp_modules/modules/action_mod/__init__.py index 42fa40e..8427a03 100644 --- a/misp_modules/modules/action_mod/__init__.py +++ b/misp_modules/modules/action_mod/__init__.py @@ -1 +1 @@ -__all__ = ['testaction'] +__all__ = ['testaction', 'blockaction', 'writeaction'] diff --git a/misp_modules/modules/action_mod/blockaction.py b/misp_modules/modules/action_mod/blockaction.py new file mode 100644 index 0000000..facdeab --- /dev/null +++ b/misp_modules/modules/action_mod/blockaction.py @@ -0,0 +1,63 @@ +import json +import base64 + +misperrors = {'error': 'Error'} + +# config fields that your code expects from the site admin +moduleconfig = { + +}; + +# blocking modules break the exection of the chain of actions (such as publishing) +blocking = True + +# returns either "boolean" or "data" +# Boolean is used to simply signal that the execution has finished. +# For blocking modules the actual boolean value determines whether we break execution +returns = 'boolean' + + +# the list of hook-points that it can hook +hooks = ['publish'] + + +moduleinfo = {'version': '0.1', 'author': 'Andras Iklody', + 'description': 'This module is merely a test, always returning true. Triggers on event publishing.', + 'module-type': ['action']} + + +def handler(q=False): + if q is False: + return False + r = {"data": False, "error": "Barf."} + return r + + +def introspection(): + modulesetup = {} + try: + responseType + modulesetup['responseType'] = responseType + except NameError: + pass + try: + inputSource + modulesetup['resultType'] = resultType + except NameError: + pass + try: + hooks + modulesetup['hooks'] = hooks + except NameError: + pass + try: + hooks + modulesetup['blocking'] = blocking + except NameError: + pass + return modulesetup + + +def version(): + moduleinfo['config'] = moduleconfig + return moduleinfo diff --git a/misp_modules/modules/action_mod/writeaction.py b/misp_modules/modules/action_mod/writeaction.py new file mode 100644 index 0000000..7efab95 --- /dev/null +++ b/misp_modules/modules/action_mod/writeaction.py @@ -0,0 +1,68 @@ +import json +import base64 + +misperrors = {'error': 'Error'} + +# config fields that your code expects from the site admin +moduleconfig = { + +}; + +# blocking modules break the exection of the chain of actions (such as publishing) +blocking = False + +# returns either "boolean" or "data" +# Boolean is used to simply signal that the execution has finished. +# For blocking modules the actual boolean value determines whether we break execution +returns = 'boolean' + + +# the list of hook-points that it can hook +hooks = ['publish'] + + +moduleinfo = {'version': '0.1', 'author': 'Andras Iklody', + 'description': 'This module is merely a test, writing a tmp file with the event info.', + 'module-type': ['action']} + + +def handler(q=False): + if q is False: + return False + request = json.loads(q) + data = request["data"] + f = open("/var/www/MISP7/app/tmp/output.txt","w+") + f.write(data["Event"]["info"]) + f.close() + r = {"data": True} + return r + + +def introspection(): + modulesetup = {} + try: + responseType + modulesetup['responseType'] = responseType + except NameError: + pass + try: + inputSource + modulesetup['resultType'] = resultType + except NameError: + pass + try: + hooks + modulesetup['hooks'] = hooks + except NameError: + pass + try: + hooks + modulesetup['blocking'] = blocking + except NameError: + pass + return modulesetup + + +def version(): + moduleinfo['config'] = moduleconfig + return moduleinfo From 89bc8bf19c3a393f89db3657d61b616e8663fe97 Mon Sep 17 00:00:00 2001 From: Sami Mokaddem Date: Fri, 5 Aug 2022 15:39:12 +0200 Subject: [PATCH 3/3] new: [action_mod] Added MatterMost module and deleted test modules --- misp_modules/modules/action_mod/__init__.py | 2 +- .../modules/action_mod/_utils/utils.py | 70 +++++++++++++ .../modules/action_mod/blockaction.py | 63 ------------ misp_modules/modules/action_mod/mattermost.py | 97 +++++++++++++++++++ misp_modules/modules/action_mod/testaction.py | 63 +++++------- .../modules/action_mod/writeaction.py | 68 ------------- 6 files changed, 192 insertions(+), 171 deletions(-) create mode 100644 misp_modules/modules/action_mod/_utils/utils.py delete mode 100644 misp_modules/modules/action_mod/blockaction.py create mode 100644 misp_modules/modules/action_mod/mattermost.py delete mode 100644 misp_modules/modules/action_mod/writeaction.py diff --git a/misp_modules/modules/action_mod/__init__.py b/misp_modules/modules/action_mod/__init__.py index 8427a03..d706e5c 100644 --- a/misp_modules/modules/action_mod/__init__.py +++ b/misp_modules/modules/action_mod/__init__.py @@ -1 +1 @@ -__all__ = ['testaction', 'blockaction', 'writeaction'] +__all__ = ['testaction', 'mattermost'] diff --git a/misp_modules/modules/action_mod/_utils/utils.py b/misp_modules/modules/action_mod/_utils/utils.py new file mode 100644 index 0000000..3afdc17 --- /dev/null +++ b/misp_modules/modules/action_mod/_utils/utils.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python + +from jinja2.sandbox import SandboxedEnvironment + +default_template = """ +# Tutorial: How to use jinja2 templating + +:warning: For these examples, we consider the module received data under the MISP core format + +1. You can use the dot `.` notation or the subscript syntax `[]` to access attributes of a variable + - `{% raw %}{{ Event.info }}{% endraw %}` -> {{ Event.info }} + - `{% raw %}{{ Event['info'] }}{% endraw %}` -> {{ Event['info'] }} + +2. Jinja2 allows you to easily create list: +```{% raw %} +{% for attribute in Event.Attribute %} +- {{ attribute.value }} +{% endfor %} +{% endraw %}``` + +Gives: +{% for attribute in Event.Attribute %} +- {{ attribute.value }} +{% endfor %} + +3. Jinja2 allows you to add logic +```{% raw %} +{% if "tlp:white" in Event.Tag %} +- This Event has the TLP:WHITE tag +{% else %} +- This Event doesn't have the TLP:WHITE tag +{% endif %} +{% endraw %}``` + +Gives: +{% if "tlp:white" in Event.Tag %} +- This Event has the TLP:WHITE tag +{% else %} +- This Event doesn't have the TLP:WHITE tag +{% endif %} + +## Jinja2 allows you to modify variables by using filters + +3. The `reverse` filter +- `{% raw %}{{ Event.info | reverse }}{% endraw %}` -> {{ Event.info | reverse }} + +4. The `format` filter +- `{% raw %}{{ "%s :: %s" | format(Event.Attribute[0].type, Event.Attribute[0].value) }}{% endraw %}` -> {{ "%s :: %s" | format(Event.Attribute[0].type, Event.Attribute[0].value) }} + +5.The `groupby` filter +```{% raw %} +{% for type, attributes in Event.Attribute|groupby("type") %} +- {{ type }}{% for attribute in attributes %} + - {{ attribute.value }} + {% endfor %} +{% endfor %} +{% endraw %}``` + +Gives: +{% for type, attributes in Event.Attribute|groupby("type") %} +- {{ type }}{% for attribute in attributes %} + - {{ attribute.value }} + {% endfor %} +{% endfor %} +""" + + +def renderTemplate(data, template=default_template): + env = SandboxedEnvironment() + return env.from_string(template).render(data) \ No newline at end of file diff --git a/misp_modules/modules/action_mod/blockaction.py b/misp_modules/modules/action_mod/blockaction.py deleted file mode 100644 index facdeab..0000000 --- a/misp_modules/modules/action_mod/blockaction.py +++ /dev/null @@ -1,63 +0,0 @@ -import json -import base64 - -misperrors = {'error': 'Error'} - -# config fields that your code expects from the site admin -moduleconfig = { - -}; - -# blocking modules break the exection of the chain of actions (such as publishing) -blocking = True - -# returns either "boolean" or "data" -# Boolean is used to simply signal that the execution has finished. -# For blocking modules the actual boolean value determines whether we break execution -returns = 'boolean' - - -# the list of hook-points that it can hook -hooks = ['publish'] - - -moduleinfo = {'version': '0.1', 'author': 'Andras Iklody', - 'description': 'This module is merely a test, always returning true. Triggers on event publishing.', - 'module-type': ['action']} - - -def handler(q=False): - if q is False: - return False - r = {"data": False, "error": "Barf."} - return r - - -def introspection(): - modulesetup = {} - try: - responseType - modulesetup['responseType'] = responseType - except NameError: - pass - try: - inputSource - modulesetup['resultType'] = resultType - except NameError: - pass - try: - hooks - modulesetup['hooks'] = hooks - except NameError: - pass - try: - hooks - modulesetup['blocking'] = blocking - except NameError: - pass - return modulesetup - - -def version(): - moduleinfo['config'] = moduleconfig - return moduleinfo diff --git a/misp_modules/modules/action_mod/mattermost.py b/misp_modules/modules/action_mod/mattermost.py new file mode 100644 index 0000000..dbcd336 --- /dev/null +++ b/misp_modules/modules/action_mod/mattermost.py @@ -0,0 +1,97 @@ +import json +from mattermostdriver import Driver +from ._utils import utils + +misperrors = {'error': 'Error'} + +# config fields that your code expects from the site admin +moduleconfig = { + 'params': { + 'mattermost_hostname': { + 'type': 'string', + 'description': 'The Mattermost domain', + 'value': 'example.mattermost.com', + }, + 'bot_access_token': { + 'type': 'string', + 'description': 'Access token generated when you created the bot account', + }, + 'channel_id': { + 'type': 'string', + 'description': 'The channel you added the bot to', + }, + 'message_template': { + 'type': 'large_string', + 'description': 'The template to be used to generate the message to be posted', + 'value': 'The **template** will be rendered using *Jinja2*!', + }, + }, + # Blocking modules break the exection of the current of action + 'blocking': False, + # Indicates whether parts of the data passed to this module should be filtered. Filtered data can be found under the `filteredItems` key + 'support_filters': True, + # Indicates whether the data passed to this module should be compliant with the MISP core format + 'expect_misp_core_format': False, +} + + +# returns either "boolean" or "data" +# Boolean is used to simply signal that the execution has finished. +# For blocking modules the actual boolean value determines whether we break execution +returns = 'boolean' + +moduleinfo = {'version': '0.1', 'author': 'Sami Mokaddem', + 'description': 'Simplistic module to send message to a Mattermost channel.', + 'module-type': ['action']} + + +def createPost(request): + params = request['params'] + mm = Driver({ + 'url': params['mattermost_hostname'], + 'token': params['bot_access_token'], + 'scheme': 'https', + 'basepath': '/api/v4', + 'port': 443, + }) + mm.login() + + data = {} + if 'matchingData' in request: + data = request['matchingData'] + else: + data = request['data'] + + if params['message_template']: + message = utils.renderTemplate(data, params['message_template']) + else: + message = '```\n{}\n```'.format(json.dumps(data)) + + mm.posts.create_post(options={ + 'channel_id': params['channel_id'], + 'message': message + }) + return True + + +def handler(q=False): + if q is False: + return False + request = json.loads(q) + createPost(request) + r = {"data": True} + return r + + +def introspection(): + modulesetup = {} + try: + modulesetup['config'] = moduleconfig + except NameError: + pass + return modulesetup + + +def version(): + moduleinfo['config'] = moduleconfig + return moduleinfo diff --git a/misp_modules/modules/action_mod/testaction.py b/misp_modules/modules/action_mod/testaction.py index 4e901ac..d773c4e 100644 --- a/misp_modules/modules/action_mod/testaction.py +++ b/misp_modules/modules/action_mod/testaction.py @@ -1,34 +1,36 @@ import json -import base64 +from ._utils import utils misperrors = {'error': 'Error'} # config fields that your code expects from the site admin moduleconfig = { - 'foo': { - 'type': 'string', - 'description': 'blablabla', - 'value': 'xyz' + 'params': { + 'foo': { + 'type': 'string', + 'description': 'blablabla', + 'value': 'xyz' + }, + 'Data extraction path': { + # Extracted data can be found under the `matchingData` key + 'type': 'hash_path', + 'description': 'Only post content extracted from this path', + 'value': 'Attribute.{n}.AttributeTag.{n}.Tag.name', + }, }, - 'bar': { - 'type': 'string', - 'value': 'meh' - } -}; - -# blocking modules break the exection of the chain of actions (such as publishing) -blocking = False + # Blocking modules break the exection of the current of action + 'blocking': False, + # Indicates whether parts of the data passed to this module should be extracted. Extracted data can be found under the `filteredItems` key + 'support_filters': False, + # Indicates whether the data passed to this module should be compliant with the MISP core format + 'expect_misp_core_format': False, +} # returns either "boolean" or "data" # Boolean is used to simply signal that the execution has finished. # For blocking modules the actual boolean value determines whether we break execution returns = 'boolean' - -# the list of hook-points that it can hook -hooks = ['publish'] - - moduleinfo = {'version': '0.1', 'author': 'Andras Iklody', 'description': 'This module is merely a test, always returning true. Triggers on event publishing.', 'module-type': ['action']} @@ -37,33 +39,16 @@ moduleinfo = {'version': '0.1', 'author': 'Andras Iklody', def handler(q=False): if q is False: return False - r = True - result = json.loads(q) # noqa - output = '' # Insert your magic here! - r = {"data": r} + request = json.loads(q) # noqa + success = True + r = {"data": success} return r def introspection(): modulesetup = {} try: - responseType - modulesetup['responseType'] = responseType - except NameError: - pass - try: - inputSource - modulesetup['resultType'] = resultType - except NameError: - pass - try: - hooks - modulesetup['hooks'] = hooks - except NameError: - pass - try: - hooks - modulesetup['blocking'] = blocking + modulesetup['config'] = moduleconfig except NameError: pass return modulesetup diff --git a/misp_modules/modules/action_mod/writeaction.py b/misp_modules/modules/action_mod/writeaction.py deleted file mode 100644 index 7efab95..0000000 --- a/misp_modules/modules/action_mod/writeaction.py +++ /dev/null @@ -1,68 +0,0 @@ -import json -import base64 - -misperrors = {'error': 'Error'} - -# config fields that your code expects from the site admin -moduleconfig = { - -}; - -# blocking modules break the exection of the chain of actions (such as publishing) -blocking = False - -# returns either "boolean" or "data" -# Boolean is used to simply signal that the execution has finished. -# For blocking modules the actual boolean value determines whether we break execution -returns = 'boolean' - - -# the list of hook-points that it can hook -hooks = ['publish'] - - -moduleinfo = {'version': '0.1', 'author': 'Andras Iklody', - 'description': 'This module is merely a test, writing a tmp file with the event info.', - 'module-type': ['action']} - - -def handler(q=False): - if q is False: - return False - request = json.loads(q) - data = request["data"] - f = open("/var/www/MISP7/app/tmp/output.txt","w+") - f.write(data["Event"]["info"]) - f.close() - r = {"data": True} - return r - - -def introspection(): - modulesetup = {} - try: - responseType - modulesetup['responseType'] = responseType - except NameError: - pass - try: - inputSource - modulesetup['resultType'] = resultType - except NameError: - pass - try: - hooks - modulesetup['hooks'] = hooks - except NameError: - pass - try: - hooks - modulesetup['blocking'] = blocking - except NameError: - pass - return modulesetup - - -def version(): - moduleinfo['config'] = moduleconfig - return moduleinfo