pymisp 2.5.4__py3-none-any.whl → 2.5.7.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of pymisp might be problematic. Click here for more details.

Files changed (92) hide show
  1. CHANGELOG.txt +5393 -0
  2. examples/__init__.py +0 -0
  3. examples/add_attributes_from_csv.py +74 -0
  4. examples/add_email_object.py +29 -0
  5. examples/add_fail2ban_object.py +86 -0
  6. examples/add_feed.py +25 -0
  7. examples/add_file_object.py +47 -0
  8. examples/add_filetype_object_from_csv.py +53 -0
  9. examples/add_generic_object.py +26 -0
  10. examples/add_github_user.py +65 -0
  11. examples/add_gitlab_user.py +56 -0
  12. examples/add_named_attribute.py +25 -0
  13. examples/add_organisations.py +57 -0
  14. examples/add_ssh_authorized_keys.py +29 -0
  15. examples/add_user.py +22 -0
  16. examples/add_vehicle_object.py +22 -0
  17. examples/addtag2.py +45 -0
  18. examples/asciidoc_generator.py +114 -0
  19. examples/cache_all.py +10 -0
  20. examples/copyTagsFromAttributesToEvent.py +68 -0
  21. examples/copy_list.py +93 -0
  22. examples/create_events.py +26 -0
  23. examples/cytomic_orion.py +549 -0
  24. examples/del.py +22 -0
  25. examples/delete_user.py +16 -0
  26. examples/edit_organisation.py +20 -0
  27. examples/edit_user.py +20 -0
  28. examples/falsepositive_disabletoids.py +136 -0
  29. examples/fetch_events_feed.py +15 -0
  30. examples/fetch_warninglist_hits.py +38 -0
  31. examples/freetext.py +22 -0
  32. examples/generate_file_objects.py +78 -0
  33. examples/generate_meta_feed.py +15 -0
  34. examples/get.py +37 -0
  35. examples/get_csv.py +37 -0
  36. examples/get_network_activity.py +187 -0
  37. examples/last.py +48 -0
  38. examples/load_csv.py +94 -0
  39. examples/lookup.py +28 -0
  40. examples/misp2cef.py +71 -0
  41. examples/misp2clamav.py +52 -0
  42. examples/openioc_to_misp.py +27 -0
  43. examples/proofpoint_tap.py +203 -0
  44. examples/proofpoint_vap.py +65 -0
  45. examples/search.py +48 -0
  46. examples/search_attributes_yara.py +40 -0
  47. examples/search_sighting.py +42 -0
  48. examples/server_sync_check_conn.py +32 -0
  49. examples/sharing_groups.py +15 -0
  50. examples/show_sightings.py +168 -0
  51. examples/stats_report.py +405 -0
  52. examples/sync_sighting.py +171 -0
  53. examples/tags.py +25 -0
  54. examples/test_sign.py +19 -0
  55. examples/trustar_misp.py +59 -0
  56. examples/up.py +21 -0
  57. examples/upload.py +60 -0
  58. examples/users_list.py +15 -0
  59. examples/vmray_automation.py +281 -0
  60. examples/vt_to_misp.py +182 -0
  61. examples/warninglists.py +22 -0
  62. examples/yara.py +38 -0
  63. examples/yara_dump.py +98 -0
  64. pymisp/api.py +33 -5
  65. pymisp/data/misp-objects/objects/instagram-account/definition.json +66 -0
  66. pymisp/data/misp-objects/objects/lnk/definition.json +13 -1
  67. pymisp/data/misp-objects/objects/rmm/definition.json +88 -0
  68. pymisp/data/misp-objects/objects/target-system/definition.json +2 -2
  69. pymisp/data/misp-objects/schema_objects.json +1 -1
  70. pymisp/mispevent.py +8 -0
  71. {pymisp-2.5.4.dist-info → pymisp-2.5.7.1.dist-info}/METADATA +23 -28
  72. {pymisp-2.5.4.dist-info → pymisp-2.5.7.1.dist-info}/RECORD +74 -27
  73. {pymisp-2.5.4.dist-info → pymisp-2.5.7.1.dist-info}/WHEEL +1 -1
  74. pymisp/data/misp-objects/.git +0 -1
  75. pymisp/data/misp-objects/.gitchangelog.rc +0 -289
  76. pymisp/data/misp-objects/.github/workflows/codeql.yml +0 -41
  77. pymisp/data/misp-objects/.github/workflows/nosetests.yml +0 -39
  78. pymisp/data/misp-objects/.travis.yml +0 -16
  79. pymisp/data/misp-objects/LICENSE-software-only.md +0 -661
  80. pymisp/data/misp-objects/LICENSE.md +0 -36
  81. pymisp/data/misp-objects/README.md +0 -567
  82. pymisp/data/misp-objects/docs/time-related-objects.ods +0 -0
  83. pymisp/data/misp-objects/docs/time-related-objects.pdf +0 -0
  84. pymisp/data/misp-objects/jq_all_the_things.sh +0 -29
  85. pymisp/data/misp-objects/tools/adoc_objects.py +0 -145
  86. pymisp/data/misp-objects/tools/alfred_links_to_relarelationships.py +0 -48
  87. pymisp/data/misp-objects/tools/list_of_objects.py +0 -50
  88. pymisp/data/misp-objects/tools/updated.sh +0 -6
  89. pymisp/data/misp-objects/tools/validate_opposites.sh +0 -17
  90. pymisp/data/misp-objects/unique_uuid.py +0 -16
  91. pymisp/data/misp-objects/validate_all.sh +0 -38
  92. {pymisp-2.5.4.dist-info → pymisp-2.5.7.1.dist-info}/LICENSE +0 -0
@@ -0,0 +1,171 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ '''
4
+ Koen Van Impe
5
+
6
+ Sync sightings between MISP instances
7
+
8
+ Put this script in crontab to run every /15 or /60
9
+ */5 * * * * mispuser /usr/bin/python3 /home/mispuser/PyMISP/examples/sync_sighting.py
10
+
11
+ Uses a drift file to keep track of latest timestamp synced (config)
12
+ Install on "clients", these push the sightings back to authoritative MISP instance
13
+
14
+ '''
15
+
16
+ from pymisp import PyMISP
17
+ from keys import misp_url, misp_key, misp_verifycert
18
+ from keys import misp_authoritive_url, misp_authoritive_key, misp_authoritive_verifycert
19
+
20
+ import sys
21
+ import time
22
+
23
+
24
+ def init(url, key, verifycert):
25
+ '''
26
+ Template to get MISP module started
27
+ '''
28
+ return PyMISP(url, key, verifycert, 'json')
29
+
30
+
31
+ def search_sightings(misp, timestamp, timestamp_now):
32
+ '''
33
+ Search all the local sightings
34
+ Extend the sighting with the attribute UUID
35
+ '''
36
+ completed_sightings = []
37
+
38
+ try:
39
+ found_sightings = misp.search_sightings(date_from=timestamp, date_to=timestamp_now)
40
+ except Exception as e:
41
+ sys.exit("Unable to search for sightings")
42
+
43
+ if found_sightings is not None and 'response' in found_sightings:
44
+ for s in found_sightings['response']:
45
+ if 'Sighting' in s:
46
+ sighting = s['Sighting']
47
+ if 'attribute_id' in sighting:
48
+ attribute_id = sighting['attribute_id']
49
+
50
+ # Query the attribute to get the uuid
51
+ # We need this to update the sighting on the other instance
52
+ try:
53
+ attribute = misp.get_attribute(attribute_id)
54
+ except Exception as e:
55
+ if module_DEBUG:
56
+ print("Unable to fetch attribute UUID for ID %s " % attribute_id)
57
+ continue
58
+
59
+ if 'Attribute' in attribute and 'uuid' in attribute['Attribute']:
60
+ attribute_uuid = attribute['Attribute']['uuid']
61
+ completed_sightings.append({'attribute_uuid': attribute_uuid, 'date_sighting': sighting['date_sighting'], 'source': sighting['source'], 'type': sighting['type'], 'uuid': sighting['uuid']})
62
+ else:
63
+ if module_DEBUG:
64
+ print("No information returned for attribute ID %s " % attribute_id)
65
+ continue
66
+
67
+ return completed_sightings
68
+
69
+
70
+ def sync_sightings(misp, misp_authoritive, found_sightings, verify_before_push, custom_sighting_text):
71
+ '''
72
+ Walk through all the sightings
73
+ '''
74
+ if found_sightings is not None:
75
+ for sighting in found_sightings:
76
+ attribute_uuid = sighting['attribute_uuid']
77
+ date_sighting = sighting['date_sighting']
78
+ source = sighting['source']
79
+ if not source:
80
+ source = custom_sighting_text
81
+ type = sighting['type']
82
+
83
+ # Fail safe
84
+ if verify_before_push:
85
+ if sighting_exists(misp_authoritive, sighting):
86
+ continue
87
+ else:
88
+ continue
89
+ else:
90
+ push_sighting(misp_authoritive, attribute_uuid, date_sighting, source, type)
91
+ continue
92
+ return True
93
+ return False
94
+
95
+
96
+ def push_sighting(misp_authoritive, attribute_uuid, date_sighting, source, type):
97
+ '''
98
+ Push sighting to the authoritative server
99
+ '''
100
+ if attribute_uuid:
101
+ try:
102
+ misp_authoritive.sighting(uuid=attribute_uuid, source=source, type=type, timestamp=date_sighting)
103
+ if module_DEBUG:
104
+ print("Pushed sighting for %s on %s" % (attribute_uuid, date_sighting))
105
+ return True
106
+ except Exception as e:
107
+ if module_DEBUG:
108
+ print("Unable to update attribute %s " % (attribute_uuid))
109
+ return False
110
+
111
+
112
+ def sighting_exists(misp_authoritive, sighting):
113
+ '''
114
+ Check if the sighting exists on the authoritative server
115
+ sightings/restSearch/attribute for uuid is not supported in MISP
116
+
117
+ optionally to implement
118
+ '''
119
+ return False
120
+
121
+
122
+ def set_drift_timestamp(drift_timestamp, drift_timestamp_path):
123
+ '''
124
+ Save the timestamp in a (local) file
125
+ '''
126
+ try:
127
+ with open(drift_timestamp_path, 'w+') as f:
128
+ f.write(str(drift_timestamp))
129
+ return True
130
+ except IOError:
131
+ sys.exit("Unable to write drift_timestamp %s to %s" % (drift_timestamp, drift_timestamp_path))
132
+ return False
133
+
134
+
135
+ def get_drift_timestamp(drift_timestamp_path):
136
+ '''
137
+ From when do we start with the sightings?
138
+ '''
139
+ try:
140
+ with open(drift_timestamp_path) as f:
141
+ drift = f.read()
142
+ if drift:
143
+ drift = int(float(drift))
144
+ else:
145
+ drift = 0
146
+ except IOError:
147
+ drift = 0
148
+
149
+ return drift
150
+
151
+
152
+ if __name__ == '__main__':
153
+ misp = init(misp_url, misp_key, misp_verifycert)
154
+ misp_authoritive = init(misp_authoritive_url, misp_authoritive_key, misp_authoritive_verifycert)
155
+ drift_timestamp_path = '/home/mispuser/PyMISP/examples/sync_sighting.drift'
156
+
157
+ drift_timestamp = get_drift_timestamp(drift_timestamp_path=drift_timestamp_path)
158
+ timestamp_now = time.time()
159
+ module_DEBUG = True
160
+
161
+ # Get all attribute sightings
162
+ found_sightings = search_sightings(misp, drift_timestamp, timestamp_now)
163
+ if found_sightings is not None and len(found_sightings) > 0:
164
+ if sync_sightings(misp, misp_authoritive, found_sightings, verify_before_push=False, custom_sighting_text="Custom Sighting"):
165
+ set_drift_timestamp(timestamp_now, drift_timestamp_path)
166
+ if module_DEBUG:
167
+ print("Sighting drift file updated to %s " % (timestamp_now))
168
+ else:
169
+ sys.exit("Unable to sync sync_sightings")
170
+ else:
171
+ sys.exit("No sightings found")
examples/tags.py ADDED
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ from pymisp import ExpandedPyMISP
5
+ from keys import misp_url, misp_key, misp_verifycert
6
+ import argparse
7
+ import json
8
+
9
+
10
+ def get_tags(m):
11
+ result = m.get_all_tags(True)
12
+ r = result
13
+ print(json.dumps(r) + '\n')
14
+
15
+
16
+ if __name__ == '__main__':
17
+ parser = argparse.ArgumentParser(description='Get tags from MISP instance.')
18
+
19
+ args = parser.parse_args()
20
+
21
+ misp = ExpandedPyMISP(misp_url, misp_key, misp_verifycert)
22
+
23
+ tags = misp.tags(pythonify=True)
24
+ for tag in tags:
25
+ print(tag.to_json())
examples/test_sign.py ADDED
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ import argparse
5
+
6
+ from pymisp import mispevent
7
+
8
+
9
+ if __name__ == '__main__':
10
+ parser = argparse.ArgumentParser(description='Sign & verify a MISP event.')
11
+ parser.add_argument("-i", "--input", required=True, help="Json file")
12
+ parser.add_argument("-u", "--uid", required=True, help="GPG UID")
13
+ args = parser.parse_args()
14
+
15
+ me = mispevent.MISPEvent()
16
+ me.load(args.input)
17
+
18
+ me.sign(args.uid)
19
+ me.verify(args.uid)
@@ -0,0 +1,59 @@
1
+ from trustar import TruStar, datetime_to_millis
2
+ from datetime import datetime, timedelta
3
+ from keys import misp_url, misp_key, misp_verifycert
4
+ from pymisp import PyMISP, MISPEvent, MISPOrganisation, MISPObject
5
+
6
+ # enclave_ids = '7a33144f-aef3-442b-87d4-dbf70d8afdb0' # RHISAC
7
+ enclave_ids = None
8
+
9
+ time_interval = {'days': 30, 'hours': 0}
10
+
11
+ distribution = None # Optional, defaults to MISP.default_event_distribution in MISP config
12
+ threat_level_id = None # Optional, defaults to MISP.default_event_threat_level in MISP config
13
+ analysis = None # Optional, defaults to 0 (initial analysis)
14
+
15
+
16
+
17
+ tru = TruStar()
18
+
19
+ misp = PyMISP(misp_url, misp_key, misp_verifycert)
20
+
21
+ now = datetime.now()
22
+
23
+ # date range for pulling reports is last 4 hours when script is run
24
+ to_time = datetime.now()
25
+ from_time = to_time - timedelta(**time_interval)
26
+
27
+ # convert to millis since epoch
28
+ to_time = datetime_to_millis(to_time)
29
+ from_time = datetime_to_millis(from_time)
30
+
31
+ if not enclave_ids:
32
+ reports = tru.get_reports(from_time=from_time,
33
+ to_time=to_time)
34
+ else:
35
+ reports = tru.get_reports(from_time=from_time,
36
+ to_time=to_time,
37
+ is_enclave=True,
38
+ enclave_ids=enclave_ids)
39
+
40
+ # loop through each trustar report and create MISP events for each
41
+ for report in reports:
42
+ # initialize and set MISPEvent()
43
+ event = MISPEvent()
44
+ event.info = report.title
45
+ event.distribution = distribution
46
+ event.threat_level_id = threat_level_id
47
+ event.analysis = analysis
48
+
49
+ # get tags for report
50
+ for tag in tru.get_enclave_tags(report.id):
51
+ event.add_tag(tag.name)
52
+
53
+ obj = MISPObject('trustar_report', standalone=False, strict=True)
54
+ # get indicators for report
55
+ for indicator in tru.get_indicators_for_report(report.id):
56
+ obj.add_attribute(indicator.type, indicator.value)
57
+ event.add_object(obj)
58
+ # post each event to MISP via API
59
+ misp.add_event(event)
examples/up.py ADDED
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ from pymisp import ExpandedPyMISP, MISPEvent
5
+ from keys import misp_url, misp_key, misp_verifycert
6
+ import argparse
7
+
8
+
9
+ if __name__ == '__main__':
10
+ parser = argparse.ArgumentParser(description="Update a MISP event.")
11
+ parser.add_argument("-e", "--event", required=True, help="Event ID to update.")
12
+ parser.add_argument("-i", "--input", required=True, help="Input file")
13
+
14
+ args = parser.parse_args()
15
+
16
+ misp = ExpandedPyMISP(misp_url, misp_key, misp_verifycert)
17
+
18
+ me = MISPEvent()
19
+ me.load_file(args.input)
20
+
21
+ result = misp.update_event(me, args.event)
examples/upload.py ADDED
@@ -0,0 +1,60 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+
4
+ from pymisp import ExpandedPyMISP, MISPEvent, MISPAttribute
5
+ from keys import misp_url, misp_key, misp_verifycert
6
+ import argparse
7
+ from pathlib import Path
8
+
9
+ if __name__ == '__main__':
10
+ parser = argparse.ArgumentParser(description='Send malware sample to MISP.')
11
+ parser.add_argument("-u", "--upload", type=str, required=True, help="File or directory of files to upload.")
12
+ parser.add_argument("-d", "--distrib", type=int, help="The distribution setting used for the attributes and for the newly created event, if relevant. [0-3].")
13
+ parser.add_argument("-c", "--comment", type=str, help="Comment for the uploaded file(s).")
14
+ parser.add_argument('-m', '--is-malware', action='store_true', help='The file(s) to upload are malwares')
15
+ parser.add_argument('--expand', action='store_true', help='(Only if the file is a malware) Run lief expansion (creates objects)')
16
+ parser.add_argument("-e", "--event", type=int, default=None, help="Not supplying an event ID will cause MISP to create a single new event for all of the POSTed malware samples.")
17
+ parser.add_argument("-i", "--info", help="Used to populate the event info field if no event ID supplied.")
18
+ args = parser.parse_args()
19
+
20
+ misp = ExpandedPyMISP(misp_url, misp_key, misp_verifycert)
21
+
22
+ files = []
23
+ p = Path(args.upload)
24
+ if p.is_file():
25
+ files = [p]
26
+ elif p.is_dir():
27
+ files = [f for f in p.glob('**/*') if f.is_file()]
28
+ else:
29
+ print('invalid upload path (must be file or dir)')
30
+ exit(0)
31
+
32
+ if args.is_malware:
33
+ arg_type = 'malware-sample'
34
+ else:
35
+ arg_type = 'attachment'
36
+
37
+ # Create attributes
38
+ attributes = []
39
+ for f in files:
40
+ a = MISPAttribute()
41
+ a.type = arg_type
42
+ a.value = f.name
43
+ a.data = f
44
+ a.comment = args.comment
45
+ a.distribution = args.distrib
46
+ if args.expand and arg_type == 'malware-sample':
47
+ a.expand = 'binary'
48
+ attributes.append(a)
49
+
50
+ if args.event:
51
+ for a in attributes:
52
+ misp.add_attribute(args.event, a)
53
+ else:
54
+ m = MISPEvent()
55
+ m.info = args.info
56
+ m.distribution = args.distrib
57
+ m.attributes = attributes
58
+ if args.expand and arg_type == 'malware-sample':
59
+ m.run_expansions()
60
+ misp.add_event(m)
examples/users_list.py ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ from pymisp import ExpandedPyMISP
5
+ from keys import misp_url, misp_key, misp_verifycert
6
+ import argparse
7
+
8
+
9
+ if __name__ == '__main__':
10
+ parser = argparse.ArgumentParser(description='Get a list of the sharing groups from the MISP instance.')
11
+
12
+ misp = ExpandedPyMISP(misp_url, misp_key, misp_verifycert)
13
+
14
+ users_list = misp.users(pythonify=True)
15
+ print(users_list)
@@ -0,0 +1,281 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Jens Thom (VMRay), Koen Van Impe
5
+
6
+ VMRay automatic import
7
+ Put this script in crontab to run every /15 or /60
8
+ */5 * * * * mispuser /usr/bin/python3 /home/mispuser/PyMISP/examples/vmray_automation.py
9
+
10
+ Calls "vmray_import" for all events that have an 'incomplete' VMray analysis
11
+
12
+ Do inline config in "main".
13
+ If your MISP user is not an admin, you cannot use `get_config`,
14
+ use `overwrite_config` instead.
15
+ Example config:
16
+ config = {
17
+ "vmray_import_enabled": True,
18
+ "vmray_import_apikey": vmray_api_key,
19
+ "vmray_import_url": vmray_server,
20
+ "vmray_import_disable_tags": False,
21
+ "vmray_import_disable_misp_objects": False,
22
+ "vmray_import_ignore_analysis_finished": False,
23
+ "services_port": 6666,
24
+ "services_url": "http://localhost",
25
+ "Artifacts": "1",
26
+ "VTI": "1",
27
+ "IOCs": "1",
28
+ "Analysis Details": "1",
29
+ }
30
+ """
31
+
32
+ import logging
33
+ import urllib
34
+
35
+ from typing import Any, Dict, List, Optional
36
+
37
+ import requests
38
+
39
+ from keys import misp_key, misp_url, misp_verifycert
40
+ from pymisp import ExpandedPyMISP
41
+
42
+ # Suppress those "Unverified HTTPS request is being made"
43
+ import urllib3
44
+ urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
45
+
46
+
47
+ def is_url(url: str) -> bool:
48
+ try:
49
+ result = urllib.parse.urlparse(url)
50
+ return result.scheme and result.netloc
51
+ except ValueError:
52
+ return False
53
+
54
+
55
+ class VMRayAutomationException(Exception):
56
+ pass
57
+
58
+
59
+ class VMRayAutomation:
60
+ def __init__(
61
+ self,
62
+ misp_url: str,
63
+ misp_key: str,
64
+ verify_cert: bool = False,
65
+ debug: bool = False,
66
+ ) -> None:
67
+ # setup logging
68
+ log_level = logging.DEBUG if debug else logging.INFO
69
+ log_format = "%(asctime)s - %(name)s - %(levelname)8s - %(message)s"
70
+
71
+ logging.basicConfig(level=log_level, format=log_format)
72
+ logging.getLogger("pymisp").setLevel(log_level)
73
+ self.logger = logging.getLogger(self.__class__.__name__)
74
+
75
+ self.misp_url = misp_url.rstrip("/")
76
+ self.misp_key = misp_key
77
+ self.verifycert = verify_cert
78
+ self.misp = ExpandedPyMISP(misp_url, misp_key, ssl=verify_cert, debug=debug)
79
+ self.config = {}
80
+ self.tag_incomplete = 'workflow:state="incomplete"'
81
+
82
+ @staticmethod
83
+ def _setting_enabled(value: bool) -> bool:
84
+ if not value:
85
+ raise VMRayAutomationException(
86
+ "VMRay import is disabled. "
87
+ "Please enable `vmray_import` in the MISP settings."
88
+ )
89
+
90
+ return True
91
+
92
+ @staticmethod
93
+ def _setting_apikey(value: str) -> str:
94
+ if not value:
95
+ raise VMRayAutomationException(
96
+ "VMRay API key not set. Please set the API key in the MISP settings."
97
+ )
98
+
99
+ return value
100
+
101
+ @staticmethod
102
+ def _setting_url(value: str) -> str:
103
+ if not value:
104
+ raise VMRayAutomationException(
105
+ "VMRay URL not set. Please set the URL in the MISP settings."
106
+ )
107
+
108
+ if not is_url(value):
109
+ raise VMRayAutomationException("Not a valid URL")
110
+
111
+ return value
112
+
113
+ @staticmethod
114
+ def _setting_disabled(value: str) -> bool:
115
+ return value.lower() in ["no", "false"]
116
+
117
+ @staticmethod
118
+ def _services_port(value: int) -> bool:
119
+ if value == 0:
120
+ return 6666
121
+ return value
122
+
123
+ @staticmethod
124
+ def services_url(value: str) -> bool:
125
+ if not is_url(value):
126
+ raise VMRayAutomationException("Services URL is not valid.")
127
+
128
+ return value
129
+
130
+ @property
131
+ def vmray_settings(self) -> Dict[str, Any]:
132
+ return {
133
+ "vmray_import_enabled": self._setting_enabled,
134
+ "vmray_import_apikey": self._setting_apikey,
135
+ "vmray_import_url": self._setting_url,
136
+ "vmray_import_disable_tags": self._setting_disabled,
137
+ "vmray_import_disable_misp_objects": self._setting_disabled,
138
+ "vmray_import_ignore_analysis_finished": self._setting_disabled,
139
+ "services_port": self._services_port,
140
+ "services_url": self.services_url,
141
+ }
142
+
143
+ def _get_misp_settings(self) -> List[Dict[str, Any]]:
144
+ misp_headers = {
145
+ "Content-Type": "application/json",
146
+ "Accept": "application/json",
147
+ "Authorization": self.misp_key,
148
+ }
149
+
150
+ response = requests.get(
151
+ f"{self.misp_url}/servers/serverSettings.json",
152
+ verify=self.verifycert,
153
+ headers=misp_headers,
154
+ )
155
+
156
+ if response.status_code == 200:
157
+ settings = response.json()
158
+ if "finalSettings" in settings:
159
+ return settings["finalSettings"]
160
+
161
+ raise VMRayAutomationException("Could not get settings from MISP server.")
162
+
163
+ def get_config(self) -> None:
164
+ self.logger.debug("Loading confing...")
165
+ # get settings from MISP server
166
+ settings = self._get_misp_settings()
167
+ for setting in settings:
168
+ config_name = setting["setting"].replace("Plugin.Import_", "")
169
+ if config_name in self.vmray_settings:
170
+ func = self.vmray_settings[config_name]
171
+ value = func(setting["value"])
172
+ self.config[config_name] = value
173
+
174
+ # set default `vmray_import` settings
175
+ self.config.setdefault("VTI", "1")
176
+ self.config.setdefault("IOCs", "1")
177
+ self.config.setdefault("Artifacts", "0")
178
+ self.config.setdefault("Analysis Details", "1")
179
+
180
+ self.logger.info("Loading config: Done.")
181
+
182
+ def overwrite_config(self, config: Dict[str, Any]) -> None:
183
+ self.config.update(config)
184
+
185
+ def _get_sample_id(self, value: str) -> Optional[int]:
186
+ vmray_sample_id_text = "VMRay Sample ID: "
187
+ if not value.startswith(vmray_sample_id_text):
188
+ self.logger.warning("Invalid Sample ID: %s.", value)
189
+ return None
190
+
191
+ return int(value.replace(vmray_sample_id_text, ""))
192
+
193
+ def _call_vmray_import(self, sample_id: int, event_id: str) -> Dict[str, Any]:
194
+ url = f"{self.config['services_url']}:{self.config['services_port']}/query"
195
+
196
+ config = {"Sample ID": sample_id}
197
+ for key, value in self.config.items():
198
+ vmray_config_key = key.replace("vmray_import_", "")
199
+ config[vmray_config_key] = str(value)
200
+
201
+ data = {
202
+ "module": "vmray_import",
203
+ "event_id": event_id,
204
+ "config": config,
205
+ "data": "",
206
+ }
207
+
208
+ self.logger.debug("calling `vmray_import`: url=%s, config=%s", url, config)
209
+ response = requests.post(url, json=data)
210
+ if response.status_code != 200:
211
+ raise VMRayAutomationException(
212
+ f"MISP modules returned status code `{response.status_code}`"
213
+ )
214
+
215
+ json_response = response.json()
216
+ if "error" in json_response:
217
+ error = json_response["error"]
218
+ raise VMRayAutomationException(f"MISP modules returned error: {error}")
219
+
220
+ return json_response
221
+
222
+ def _add_event_attributes(self, event_id: int, attributes: Dict[str, Any]) -> None:
223
+ event = self.misp.get_event(event_id, pythonify=True)
224
+ for attr in attributes["Attribute"]:
225
+ event.add_attribute(**attr)
226
+
227
+ self.misp.update_event(event)
228
+
229
+ def _add_event_objects(self, event_id: int, objects: Dict[str, Any]) -> None:
230
+ event = self.misp.get_event(event_id, pythonify=True)
231
+ for obj in objects["Object"]:
232
+ event.add_object(**obj)
233
+
234
+ if "Tag" in objects:
235
+ for tag in objects["Tag"]:
236
+ event.add_tag(tag["name"])
237
+
238
+ self.misp.update_event(event)
239
+
240
+ def _add_misp_event(self, event_id: int, response: Dict[str, Any]) -> None:
241
+ if self.config["vmray_import_disable_misp_objects"]:
242
+ self._add_event_attributes(event_id, response["results"])
243
+ else:
244
+ self._add_event_objects(event_id, response["results"])
245
+
246
+ def import_incomplete_analyses(self) -> None:
247
+ self.logger.info("Searching for attributes with tag='%s'", self.tag_incomplete)
248
+ result = self.misp.search("attributes", tags=self.tag_incomplete)
249
+ attributes = result["Attribute"]
250
+
251
+ for attr in attributes:
252
+ event_id = int(attr["event_id"])
253
+ self.logger.info("Processing event ID `%d`.", event_id)
254
+
255
+ sample_id = self._get_sample_id(attr["value"])
256
+ if not sample_id:
257
+ continue
258
+
259
+ response = self._call_vmray_import(sample_id, event_id)
260
+ self._add_misp_event(event_id, response)
261
+ self.misp.untag(attr["uuid"], self.tag_incomplete)
262
+
263
+
264
+ def main():
265
+ debug = False
266
+ config = {
267
+ "Artifacts": "0",
268
+ "VTI": "1",
269
+ "IOCs": "1",
270
+ "Analysis Details": "0",
271
+ "vmray_import_disable_misp_objects": False,
272
+ }
273
+
274
+ automation = VMRayAutomation(misp_url, misp_key, misp_verifycert, debug)
275
+ automation.get_config() # only possible with admin user
276
+ automation.overwrite_config(config)
277
+ automation.import_incomplete_analyses()
278
+
279
+
280
+ if __name__ == "__main__":
281
+ main()