pymisp 2.5.4__py3-none-any.whl → 2.5.7__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 (158) hide show
  1. CHANGELOG.txt +5380 -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.dist-info}/METADATA +23 -28
  72. {pymisp-2.5.4.dist-info → pymisp-2.5.7.dist-info}/RECORD +140 -27
  73. {pymisp-2.5.4.dist-info → pymisp-2.5.7.dist-info}/WHEEL +1 -1
  74. tests/57c4445b-c548-4654-af0b-4be3950d210f.json +1 -0
  75. tests/__init__.py +0 -0
  76. tests/csv_testfiles/invalid_fieldnames.csv +11 -0
  77. tests/csv_testfiles/valid_fieldnames.csv +4 -0
  78. tests/email_testfiles/mail_1.eml.zip +0 -0
  79. tests/email_testfiles/mail_1.msg +0 -0
  80. tests/email_testfiles/mail_1_bom.eml +858 -0
  81. tests/email_testfiles/mail_1_headers_only.eml +28 -0
  82. tests/email_testfiles/mail_2.eml +32 -0
  83. tests/email_testfiles/mail_3.eml +170 -0
  84. tests/email_testfiles/mail_3.msg +0 -0
  85. tests/email_testfiles/mail_4.msg +0 -0
  86. tests/email_testfiles/mail_5.msg +0 -0
  87. tests/email_testfiles/mail_multiple_to.eml +15 -0
  88. tests/email_testfiles/source +1 -0
  89. tests/git-vuln-finder-quagga.json +1493 -0
  90. tests/misp_event.json +76 -0
  91. tests/mispevent_testfiles/attribute.json +21 -0
  92. tests/mispevent_testfiles/attribute_del.json +23 -0
  93. tests/mispevent_testfiles/def_param.json +53 -0
  94. tests/mispevent_testfiles/event.json +8 -0
  95. tests/mispevent_testfiles/event_obj_attr_tag.json +57 -0
  96. tests/mispevent_testfiles/event_obj_def_param.json +62 -0
  97. tests/mispevent_testfiles/event_obj_tag.json +29 -0
  98. tests/mispevent_testfiles/event_tags.json +18 -0
  99. tests/mispevent_testfiles/existing_event.json +4599 -0
  100. tests/mispevent_testfiles/existing_event_edited.json +4601 -0
  101. tests/mispevent_testfiles/galaxy.json +25 -0
  102. tests/mispevent_testfiles/malware.json +19 -0
  103. tests/mispevent_testfiles/malware_exist.json +163 -0
  104. tests/mispevent_testfiles/misp_custom_obj.json +38 -0
  105. tests/mispevent_testfiles/overwrite_file/definition.json +457 -0
  106. tests/mispevent_testfiles/proposals.json +35 -0
  107. tests/mispevent_testfiles/shadow.json +148 -0
  108. tests/mispevent_testfiles/sighting.json +5 -0
  109. tests/mispevent_testfiles/simple.json +2 -0
  110. tests/mispevent_testfiles/test_object_template/definition.json +29 -0
  111. tests/new_misp_event.json +34 -0
  112. tests/reportlab_testfiles/HTML_event.json +1 -0
  113. tests/reportlab_testfiles/galaxy_1.json +1250 -0
  114. tests/reportlab_testfiles/image_event.json +2490 -0
  115. tests/reportlab_testfiles/japanese_test.json +156 -0
  116. tests/reportlab_testfiles/japanese_test_heavy.json +318 -0
  117. tests/reportlab_testfiles/long_event.json +3730 -0
  118. tests/reportlab_testfiles/mainly_objects_1.json +1092 -0
  119. tests/reportlab_testfiles/mainly_objects_2.json +977 -0
  120. tests/reportlab_testfiles/sighting_1.json +305 -0
  121. tests/reportlab_testfiles/sighting_2.json +221 -0
  122. tests/reportlab_testfiles/to_delete1.json +804 -0
  123. tests/reportlab_testfiles/to_delete2.json +1 -0
  124. tests/reportlab_testfiles/to_delete3.json +1 -0
  125. tests/reportlab_testfiles/very_long_event.json +1006 -0
  126. tests/reportlab_testoutputs/to_delete1.json.pdf +391 -0
  127. tests/reportlab_testoutputs/to_delete2.json.pdf +506 -0
  128. tests/reportlab_testoutputs/to_delete3.json.pdf +277 -0
  129. tests/search_index_result.json +69 -0
  130. tests/sharing_groups.json +98 -0
  131. tests/stix1.xml-utf8 +110 -0
  132. tests/stix2.json +1 -0
  133. tests/test_analyst_data.py +123 -0
  134. tests/test_emailobject.py +157 -0
  135. tests/test_fileobject.py +20 -0
  136. tests/test_mispevent.py +473 -0
  137. tests/test_reportlab.py +431 -0
  138. tests/testlive_comprehensive.py +3734 -0
  139. tests/testlive_sync.py +474 -0
  140. pymisp/data/misp-objects/.git +0 -1
  141. pymisp/data/misp-objects/.gitchangelog.rc +0 -289
  142. pymisp/data/misp-objects/.github/workflows/codeql.yml +0 -41
  143. pymisp/data/misp-objects/.github/workflows/nosetests.yml +0 -39
  144. pymisp/data/misp-objects/.travis.yml +0 -16
  145. pymisp/data/misp-objects/LICENSE-software-only.md +0 -661
  146. pymisp/data/misp-objects/LICENSE.md +0 -36
  147. pymisp/data/misp-objects/README.md +0 -567
  148. pymisp/data/misp-objects/docs/time-related-objects.ods +0 -0
  149. pymisp/data/misp-objects/docs/time-related-objects.pdf +0 -0
  150. pymisp/data/misp-objects/jq_all_the_things.sh +0 -29
  151. pymisp/data/misp-objects/tools/adoc_objects.py +0 -145
  152. pymisp/data/misp-objects/tools/alfred_links_to_relarelationships.py +0 -48
  153. pymisp/data/misp-objects/tools/list_of_objects.py +0 -50
  154. pymisp/data/misp-objects/tools/updated.sh +0 -6
  155. pymisp/data/misp-objects/tools/validate_opposites.sh +0 -17
  156. pymisp/data/misp-objects/unique_uuid.py +0 -16
  157. pymisp/data/misp-objects/validate_all.sh +0 -38
  158. {pymisp-2.5.4.dist-info → pymisp-2.5.7.dist-info}/LICENSE +0 -0
examples/vt_to_misp.py ADDED
@@ -0,0 +1,182 @@
1
+ ''' Convert a VirusTotal report into MISP objects '''
2
+ import argparse
3
+ import json
4
+ import logging
5
+ from datetime import datetime
6
+ from urllib.parse import urlsplit
7
+
8
+ import pymisp
9
+ from pymisp.tools import VTReportObject
10
+
11
+ logging.basicConfig(level=logging.INFO, format="%(asctime)s | %(levelname)s | %(module)s.%(funcName)s.%(lineno)d | %(message)s")
12
+
13
+
14
+ def build_cli():
15
+ '''
16
+ Build the command-line arguments
17
+ '''
18
+ desc = "Take an indicator or list of indicators to search VT for and import the results into MISP"
19
+ post_desc = """
20
+ config.json: Should be a JSON file containing MISP and VirusTotal credentials with the following format:
21
+ {"misp": {"url": "<url_to_misp>", "key": "<misp_api_key>"}, "virustotal": {"key": "<vt_api_key>"}}
22
+ Please note: Only public API features work in the VTReportObject for now. I don't have a quarter million to spare ;)
23
+
24
+ Example:
25
+ python vt_to_misp.py -i 719c97a8cd8db282586c1416894dcaf8 -c ./config.json
26
+ """
27
+ parser = argparse.ArgumentParser(description=desc, epilog=post_desc, formatter_class=argparse.RawTextHelpFormatter)
28
+ parser.add_argument("-e", "--event", help="MISP event id to add to")
29
+ parser.add_argument("-c", "--config", default="config.json", help="Path to JSON configuration file to read")
30
+ indicators = parser.add_mutually_exclusive_group(required=True)
31
+ indicators.add_argument("-i", "--indicator", help="Single indicator to look up")
32
+ indicators.add_argument("-f", "--file", help="File of indicators to look up - one on each line")
33
+ indicators.add_argument("-l", "--link", help="Link to a VirusTotal report")
34
+ return parser.parse_args()
35
+
36
+
37
+ def build_config(path=None):
38
+ '''
39
+ Read a configuration file path. File is expected to be
40
+
41
+ :path: Path to a configuration file
42
+ '''
43
+ try:
44
+ with open(path, "r") as ifile:
45
+ return json.load(ifile)
46
+ except OSError:
47
+ raise OSError("Couldn't find path to configuration file: %s", path)
48
+ except json.JSONDecodeError:
49
+ raise IOError("Couldn't parse configuration file. Please make sure it is a proper JSON document")
50
+
51
+
52
+ def generate_report(indicator, apikey):
53
+ '''
54
+ Build our VirusTotal report object, File object, and AV signature objects
55
+ and link them appropriately
56
+
57
+ :indicator: Indicator hash to search in VT for
58
+ '''
59
+ report_objects = []
60
+ vt_report = VTReportObject(apikey, indicator)
61
+ report_objects.append(vt_report)
62
+ raw_report = vt_report._report
63
+ if vt_report._resource_type == "file":
64
+ file_object = pymisp.MISPObject(name="file")
65
+ file_object.add_attribute("md5", value=raw_report["md5"])
66
+ file_object.add_attribute("sha1", value=raw_report["sha1"])
67
+ file_object.add_attribute("sha256", value=raw_report["sha256"])
68
+ vt_report.add_reference(referenced_uuid=file_object.uuid, relationship_type="report of")
69
+ report_objects.append(file_object)
70
+ elif vt_report._resource_type == "url":
71
+ parsed = urlsplit(indicator)
72
+ url_object = pymisp.MISPObject(name="url")
73
+ url_object.add_attribute("url", value=parsed.geturl())
74
+ url_object.add_attribute("host", value=parsed.hostname)
75
+ url_object.add_attribute("scheme", value=parsed.scheme)
76
+ url_object.add_attribute("port", value=parsed.port)
77
+ vt_report.add_reference(referenced_uuid=url_object.uuid, relationship_type="report of")
78
+ report_objects.append(url_object)
79
+ for antivirus in raw_report["scans"]:
80
+ if raw_report["scans"][antivirus]["detected"]:
81
+ av_object = pymisp.MISPObject(name="av-signature")
82
+ av_object.add_attribute("software", value=antivirus)
83
+ signature_name = raw_report["scans"][antivirus]["result"]
84
+ av_object.add_attribute("signature", value=signature_name, disable_correlation=True)
85
+ vt_report.add_reference(referenced_uuid=av_object.uuid, relationship_type="included-in")
86
+ report_objects.append(av_object)
87
+ return report_objects
88
+
89
+
90
+ def get_misp_event(event_id=None, info=None):
91
+ '''
92
+ Smaller helper function for generating a new MISP event or using a preexisting one
93
+
94
+ :event_id: The event id of the MISP event to upload objects to
95
+
96
+ :info: The event's title/info
97
+ '''
98
+ if event_id:
99
+ event = misp.get_event(event_id)
100
+ elif info:
101
+ event = misp.new_event(info=info)
102
+ else:
103
+ event = misp.new_event(info="VirusTotal Report")
104
+ misp_event = pymisp.MISPEvent()
105
+ misp_event.load(event)
106
+ return misp_event
107
+
108
+
109
+ def main(misp, config, args):
110
+ '''
111
+ Main program logic
112
+
113
+ :misp: PyMISP API object for interfacing with MISP
114
+
115
+ :config: Configuration dictionary
116
+
117
+ :args: Argparse CLI object
118
+ '''
119
+ if args.indicator:
120
+ misp_objects = generate_report(args.indicator, config["virustotal"]["key"])
121
+ if misp_objects:
122
+ misp_event = get_misp_event(args.event, "VirusTotal Report for {}".format(args.indicator))
123
+ submit_to_misp(misp, misp_event, misp_objects)
124
+ elif args.file:
125
+ try:
126
+ reports = []
127
+ with open(args.file, "r") as ifile:
128
+ for indicator in ifile:
129
+ try:
130
+ misp_objects = generate_report(indicator, config["virustotal"]["key"])
131
+ if misp_objects:
132
+ reports.append(misp_objects)
133
+ except pymisp.exceptions.InvalidMISPObject as err:
134
+ logging.error(err)
135
+ if reports:
136
+ current_time = datetime.now().strftime("%x %X")
137
+ misp_event = get_misp_event(args.event, "VirusTotal Reports: {}".format(current_time))
138
+ for report in reports:
139
+ submit_to_misp(misp, misp_event, report)
140
+ except OSError:
141
+ logging.error("Couldn't open indicators file at '%s'. Check path", args.file)
142
+ elif args.link:
143
+ # https://www.virustotal.com/#/file/<ioc>/detection
144
+ indicator = args.link.split("/")[5]
145
+ misp_objects = generate_report(indicator, config["virustotal"]["key"])
146
+ if misp_objects:
147
+ misp_event = get_misp_event(args.event, "VirusTotal Report for {}".format(indicator))
148
+ submit_to_misp(misp, misp_event, misp_objects)
149
+
150
+
151
+ def submit_to_misp(misp, misp_event, misp_objects):
152
+ '''
153
+ Submit a list of MISP objects to a MISP event
154
+
155
+ :misp: PyMISP API object for interfacing with MISP
156
+
157
+ :misp_event: MISPEvent object
158
+
159
+ :misp_objects: List of MISPObject objects. Must be a list
160
+ '''
161
+ # go through round one and only add MISP objects
162
+ for misp_object in misp_objects:
163
+ template_id = misp.get_object_template_id(misp_object.template_uuid)
164
+ misp.add_object(misp_event.id, template_id, misp_object)
165
+ # go through round two and add all the object references for each object
166
+ for misp_object in misp_objects:
167
+ for reference in misp_object.ObjectReference:
168
+ misp.add_object_reference(reference)
169
+
170
+
171
+ if __name__ == "__main__":
172
+ try:
173
+ args = build_cli()
174
+ config = build_config(args.config)
175
+ # change the 'ssl' value if you want to verify your MISP's SSL instance
176
+ misp = pymisp.PyMISP(url=config["misp"]["url"], key=config["misp"]["key"], ssl=False)
177
+ # finally, let's start checking VT and converting the reports
178
+ main(misp, config, args)
179
+ except KeyboardInterrupt:
180
+ print("Bye Felicia")
181
+ except pymisp.exceptions.InvalidMISPObject as err:
182
+ logging.error(err)
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ from pymisp import PyMISP
5
+ from pymisp.tools import load_warninglists
6
+ import argparse
7
+ from keys import misp_url, misp_key, misp_verifycert
8
+
9
+
10
+ if __name__ == '__main__':
11
+
12
+ parser = argparse.ArgumentParser(description='Load the warninglists.')
13
+ parser.add_argument("-p", "--package", action='store_true', help="from the PyMISPWarninglists package.")
14
+ parser.add_argument("-r", "--remote", action='store_true', help="from the MISP instance.")
15
+
16
+ args = parser.parse_args()
17
+
18
+ if args.package:
19
+ print(load_warninglists.from_package())
20
+ elif args.remote:
21
+ pm = PyMISP(misp_url, misp_key, misp_verifycert)
22
+ print(load_warninglists.from_instance(pm))
examples/yara.py ADDED
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ from pymisp import PyMISP
5
+ from keys import misp_url, misp_key,misp_verifycert
6
+ import argparse
7
+ import os
8
+
9
+
10
+ def init(url, key):
11
+ return PyMISP(url, key, misp_verifycert, 'json')
12
+
13
+
14
+ def get_yara(m, event_id, out=None):
15
+ ok, rules = m.get_yara(event_id)
16
+ if not ok:
17
+ print(rules)
18
+ elif out is None:
19
+ print(rules)
20
+ else:
21
+ with open(out, 'w') as f:
22
+ f.write(rules)
23
+
24
+
25
+ if __name__ == '__main__':
26
+ parser = argparse.ArgumentParser(description='Get yara rules from an event.')
27
+ parser.add_argument("-e", "--event", required=True, help="Event ID.")
28
+ parser.add_argument("-o", "--output", help="Output file")
29
+
30
+ args = parser.parse_args()
31
+
32
+ if args.output is not None and os.path.exists(args.output):
33
+ print('Output file already exists, abord.')
34
+ exit(0)
35
+
36
+ misp = init(misp_url, misp_key)
37
+
38
+ get_yara(misp, args.event, args.output)
examples/yara_dump.py ADDED
@@ -0,0 +1,98 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ '''
4
+ YARA dumper for MISP
5
+ by Christophe Vandeplas
6
+ '''
7
+
8
+ import keys
9
+ from pymisp import PyMISP
10
+ import yara
11
+ import re
12
+
13
+
14
+ def dirty_cleanup(value):
15
+ changed = False
16
+ substitutions = (('”', '"'),
17
+ ('“', '"'),
18
+ ('″', '"'),
19
+ ('`', "'"),
20
+ ('\r', ''),
21
+ ('Rule ', 'rule ') # some people write this with the wrong case
22
+ # ('$ ', '$'), # this breaks rules
23
+ # ('\t\t', '\n'), # this breaks rules
24
+ )
25
+ for substitution in substitutions:
26
+ if substitution[0] in value:
27
+ changed = True
28
+ value = value.replace(substitution[0], substitution[1])
29
+ return value, changed
30
+
31
+
32
+ misp = PyMISP(keys.misp_url, keys.misp_key, keys.misp_verify, 'json')
33
+ result = misp.search(controller='attributes', type_attribute='yara')
34
+
35
+ attr_cnt = 0
36
+ attr_cnt_invalid = 0
37
+ attr_cnt_duplicate = 0
38
+ attr_cnt_changed = 0
39
+ yara_rules = []
40
+ yara_rule_names = []
41
+ if result.get('Attribute'):
42
+ for attribute in result.get('Attribute'):
43
+ value = attribute['value']
44
+ event_id = attribute['event_id']
45
+ attribute_id = attribute['id']
46
+
47
+ value = re.sub('^[ \t]*rule ', 'rule misp_e{}_'.format(event_id), value, flags=re.MULTILINE)
48
+ value, changed = dirty_cleanup(value)
49
+ if changed:
50
+ attr_cnt_changed += 1
51
+ if 'global rule' in value: # refuse any global rules as they might disable everything
52
+ continue
53
+ if 'private rule' in value: # private rules need some more rewriting
54
+ priv_rules = re.findall('private rule (\w+)', value, flags=re.MULTILINE)
55
+ for priv_rule in priv_rules:
56
+ value = re.sub(priv_rule, 'misp_e{}_{}'.format(event_id, priv_rule), value, flags=re.MULTILINE)
57
+
58
+ # compile the yara rule to confirm it's validity
59
+ # if valid, ignore duplicate rules
60
+ try:
61
+ attr_cnt += 1
62
+ yara.compile(source=value)
63
+ yara_rules.append(value)
64
+ # print("Rule e{} a{} OK".format(event_id, attribute_id))
65
+ except yara.SyntaxError as e:
66
+ attr_cnt_invalid += 1
67
+ # print("Rule e{} a{} NOK - {}".format(event_id, attribute_id, e))
68
+ except yara.Error as e:
69
+ attr_cnt_invalid += 1
70
+ print(e)
71
+ import traceback
72
+ print(traceback.format_exc())
73
+
74
+ # remove duplicates - process the full yara rule list and process errors to eliminate duplicate rule names
75
+ all_yara_rules = '\n'.join(yara_rules)
76
+ while True:
77
+ try:
78
+ yara.compile(source=all_yara_rules)
79
+ except yara.SyntaxError as e:
80
+ if 'duplicated identifier' in e.args[0]:
81
+ duplicate_rule_names = re.findall('duplicated identifier "(.*)"', e.args[0])
82
+ for item in duplicate_rule_names:
83
+ all_yara_rules = all_yara_rules.replace('rule {}'.format(item), 'rule duplicate_{}'.format(item), 1)
84
+ attr_cnt_duplicate += 1
85
+ continue
86
+ else:
87
+ # This should never happen as all rules were processed before separately. So logically we should only have duplicates.
88
+ exit("ERROR SyntaxError in rules: {}".format(e.args))
89
+ break
90
+
91
+ # save to a file
92
+ fname = 'misp.yara'
93
+ with open(fname, 'w') as f_out:
94
+ f_out.write(all_yara_rules)
95
+
96
+ print("")
97
+ print("MISP attributes with YARA rules: total={} valid={} invalid={} duplicate={} changed={}.".format(attr_cnt, attr_cnt - attr_cnt_invalid, attr_cnt_invalid, attr_cnt_duplicate, attr_cnt_changed))
98
+ print("Valid YARA rule file save to file '{}'. Invalid rules/attributes were ignored.".format(fname))
pymisp/api.py CHANGED
@@ -32,7 +32,7 @@ from .mispevent import MISPEvent, MISPAttribute, MISPSighting, MISPLog, MISPObje
32
32
  MISPRole, MISPServer, MISPFeed, MISPEventDelegation, MISPCommunity, MISPUserSetting, \
33
33
  MISPInbox, MISPEventBlocklist, MISPOrganisationBlocklist, MISPEventReport, \
34
34
  MISPGalaxyCluster, MISPGalaxyClusterRelation, MISPCorrelationExclusion, MISPDecayingModel, \
35
- MISPNote, MISPOpinion, MISPRelationship, AnalystDataBehaviorMixin
35
+ MISPNote, MISPOpinion, MISPRelationship, MISPAnalystData
36
36
  from .abstract import pymisp_json_default, MISPTag, AbstractMISP, describe_types
37
37
 
38
38
 
@@ -499,6 +499,20 @@ class PyMISP:
499
499
  response = self._prepare_request('POST', f'events/contact/{event_id}', data=to_post)
500
500
  return self._check_json_response(response)
501
501
 
502
+ def enrich_event(self, event: MISPEvent | int | str | UUID, enrich_with: str | list[str]) -> dict[str, Any]:
503
+ """Enrich an event with data from one or more module.
504
+
505
+ :param event: event to enrich
506
+ :param enrich_with: module name or list of module names to use for enrichment
507
+ """
508
+ event_id = get_uuid_or_id_from_abstract_misp(event)
509
+ if isinstance(enrich_with, str):
510
+ enrich_with = [enrich_with]
511
+
512
+ to_post = {module_name: True for module_name in enrich_with}
513
+ response = self._prepare_request('POST', f'/events/enrichEvent/{event_id}', data=to_post)
514
+ return self._check_json_response(response)
515
+
502
516
  # ## END Event ###
503
517
 
504
518
  # ## BEGIN Event Report ###
@@ -621,14 +635,14 @@ class PyMISP:
621
635
  # ## END Galaxy Cluster ###
622
636
 
623
637
  # ## BEGIN Analyst Data ###a
624
- def get_analyst_data(self, analyst_data: AnalystDataBehaviorMixin | int | str | UUID,
638
+ def get_analyst_data(self, analyst_data: MISPAnalystData | int | str | UUID,
625
639
  pythonify: bool = False) -> dict[str, Any] | MISPNote | MISPOpinion | MISPRelationship:
626
640
  """Get an analyst data from a MISP instance
627
641
 
628
642
  :param analyst_data: analyst data to get
629
643
  :param pythonify: Returns a list of PyMISP Objects instead of the plain json output. Warning: it might use a lot of RAM
630
644
  """
631
- if isinstance(analyst_data, AnalystDataBehaviorMixin):
645
+ if isinstance(analyst_data, MISPAnalystData):
632
646
  analyst_data_type = analyst_data.analyst_data_object_type
633
647
  else:
634
648
  analyst_data_type = 'all'
@@ -666,7 +680,7 @@ class PyMISP:
666
680
  :param analyst_data_id: analyst data ID to update
667
681
  :param pythonify: Returns a PyMISP Object instead of the plain json output
668
682
  """
669
- if isinstance(analyst_data, AnalystDataBehaviorMixin):
683
+ if isinstance(analyst_data, MISPAnalystData):
670
684
  analyst_data_type = analyst_data.analyst_data_object_type
671
685
  else:
672
686
  analyst_data_type = 'all'
@@ -685,7 +699,7 @@ class PyMISP:
685
699
 
686
700
  :param analyst_data: analyst data to delete
687
701
  """
688
- if isinstance(analyst_data, AnalystDataBehaviorMixin):
702
+ if isinstance(analyst_data, MISPAnalystData):
689
703
  analyst_data_type = analyst_data.analyst_data_object_type
690
704
  else:
691
705
  analyst_data_type = 'all'
@@ -1102,6 +1116,20 @@ class PyMISP:
1102
1116
  a.from_dict(**response)
1103
1117
  return a
1104
1118
 
1119
+ def enrich_attribute(self, attribute: MISPAttribute | int | str | UUID, enrich_with: str | list[str]) -> dict[str, Any]:
1120
+ """Enrich an attribute with data from one or more module.
1121
+
1122
+ :param attribute: attribute to enrich
1123
+ :param enrich_with: module name or list of module names to use for enrichment
1124
+ """
1125
+ attribute_id = get_uuid_or_id_from_abstract_misp(attribute)
1126
+ if isinstance(enrich_with, str):
1127
+ enrich_with = [enrich_with]
1128
+
1129
+ to_post = {module_name: True for module_name in enrich_with}
1130
+ response = self._prepare_request('POST', f'/attributes/enrich/{attribute_id}', data=to_post)
1131
+ return self._check_json_response(response)
1132
+
1105
1133
  # ## END Attribute ###
1106
1134
 
1107
1135
  # ## BEGIN Attribute Proposal ###
@@ -0,0 +1,66 @@
1
+ {
2
+ "attributes": {
3
+ "account-id": {
4
+ "description": "Account id.",
5
+ "misp-attribute": "text",
6
+ "ui-priority": 1
7
+ },
8
+ "account-name": {
9
+ "description": "Account name.",
10
+ "misp-attribute": "text",
11
+ "ui-priority": 1
12
+ },
13
+ "archive": {
14
+ "description": "Archive of the account (Internet Archive, Archive.is, etc).",
15
+ "disable_correlation": true,
16
+ "misp-attribute": "link",
17
+ "multiple": true,
18
+ "ui-priority": 1
19
+ },
20
+ "attachment": {
21
+ "description": "A screen capture or exported list of contacts etc.",
22
+ "misp-attribute": "attachment",
23
+ "multiple": true,
24
+ "ui-priority": 1
25
+ },
26
+ "description": {
27
+ "description": "A description of the user.",
28
+ "misp-attribute": "text",
29
+ "ui-priority": 1
30
+ },
31
+ "is-verified": {
32
+ "description": "If the user is verified.",
33
+ "misp-attribute": "boolean",
34
+ "multiple": false,
35
+ "ui-priority": 1
36
+ },
37
+ "link": {
38
+ "description": "Original link to the page (supposed harmless).",
39
+ "misp-attribute": "link",
40
+ "ui-priority": 1
41
+ },
42
+ "url": {
43
+ "description": "Original URL location of the page (potentially malicious).",
44
+ "misp-attribute": "url",
45
+ "ui-priority": 1
46
+ },
47
+ "user-avatar": {
48
+ "description": "A user profile picture or avatar.",
49
+ "misp-attribute": "attachment",
50
+ "multiple": true,
51
+ "ui-priority": 1
52
+ }
53
+ },
54
+ "description": "Instagram account.",
55
+ "meta-category": "misc",
56
+ "name": "instagram-account",
57
+ "requiredOneOf": [
58
+ "account-name",
59
+ "account-id",
60
+ "description",
61
+ "archive",
62
+ "link"
63
+ ],
64
+ "uuid": "656ced16-23f3-4322-925d-c9c961684999",
65
+ "version": 1
66
+ }
@@ -116,6 +116,12 @@
116
116
  "misp-attribute": "text",
117
117
  "ui-priority": 0
118
118
  },
119
+ "lnk-mft-object": {
120
+ "description": "LinkTargetIDList MFT entry. Name:MFTID|SeqID|BTimestamp",
121
+ "misp-attribute": "text",
122
+ "multiple": true,
123
+ "ui-priority": 0
124
+ },
119
125
  "lnk-modification-time": {
120
126
  "categories": [
121
127
  "Other"
@@ -125,6 +131,11 @@
125
131
  "misp-attribute": "datetime",
126
132
  "ui-priority": 0
127
133
  },
134
+ "lnk-propertystore-sid": {
135
+ "description": "SID reference in ExtraData.PropertyStore",
136
+ "misp-attribute": "text",
137
+ "ui-priority": 0
138
+ },
128
139
  "lnk-relative-path": {
129
140
  "description": "Relative path",
130
141
  "disable_correlation": true,
@@ -250,6 +261,7 @@
250
261
  "description": "Free text value to attach to the file",
251
262
  "disable_correlation": true,
252
263
  "misp-attribute": "text",
264
+ "multiple": true,
253
265
  "recommended": false,
254
266
  "ui-priority": 1
255
267
  },
@@ -275,5 +287,5 @@
275
287
  "sha512/256"
276
288
  ],
277
289
  "uuid": "ad13533e-1853-4da0-a111-33a7ce7e6c09",
278
- "version": 1
290
+ "version": 2
279
291
  }
@@ -0,0 +1,88 @@
1
+ {
2
+ "attributes": {
3
+ "api-key": {
4
+ "description": "Authentication or API key used by the RMM agent",
5
+ "misp-attribute": "text",
6
+ "multiple": true,
7
+ "ui-priority": 0
8
+ },
9
+ "comment": {
10
+ "description": "A description of the RMM.",
11
+ "misp-attribute": "comment",
12
+ "ui-priority": 0
13
+ },
14
+ "guid": {
15
+ "description": "GUID or reference of the RMM agent or deployment ID.",
16
+ "misp-attribute": "text",
17
+ "multiple": true,
18
+ "ui-priority": 0
19
+ },
20
+ "hostname": {
21
+ "description": "hostname of the control server for the RMM agent.",
22
+ "misp-attribute": "hostname",
23
+ "ui-priority": 0
24
+ },
25
+ "name": {
26
+ "description": "Name of the RMM agent.",
27
+ "misp-attribute": "text",
28
+ "ui-priority": 0
29
+ },
30
+ "rmm-type": {
31
+ "description": "Type of the RMM agent described.",
32
+ "disable_correlation": true,
33
+ "misp-attribute": "text",
34
+ "sane_default": [
35
+ "AnyDesk",
36
+ "Atera",
37
+ "ASG",
38
+ "BeAnywhere",
39
+ "Domotz",
40
+ "DWservice",
41
+ "Fixme.it",
42
+ "Fleetdeck.io",
43
+ "GetScreen",
44
+ "Itarian",
45
+ "Level.io",
46
+ "Logmein",
47
+ "ManageEngine",
48
+ "MeshCentral",
49
+ "N-Able",
50
+ "Pulseway",
51
+ "RattyRat",
52
+ "Rport",
53
+ "Rsocx",
54
+ "RustDesk",
55
+ "RustScan",
56
+ "ScreenConnect",
57
+ "Splashtop",
58
+ "SSH",
59
+ "Tactical RMM",
60
+ "Teamviewer",
61
+ "TightVNC",
62
+ "TrendMicro",
63
+ "Sorillus",
64
+ "Xeox",
65
+ "ZeroTier"
66
+ ],
67
+ "ui-priority": 0
68
+ },
69
+ "url": {
70
+ "description": "url of the control server for the RMM agent.",
71
+ "misp-attribute": "url",
72
+ "ui-priority": 0
73
+ }
74
+ },
75
+ "description": "An object describing a RMM agent.",
76
+ "meta-category": "misc",
77
+ "name": "rmm",
78
+ "requiredOneOf": [
79
+ "comment",
80
+ "rmm-type",
81
+ "guid",
82
+ "url",
83
+ "hostname",
84
+ "api-key"
85
+ ],
86
+ "uuid": "2cdcfb2a-dd74-4d88-ae02-6f381b312666",
87
+ "version": 4
88
+ }
@@ -28,12 +28,12 @@
28
28
  "ui-priority": 1
29
29
  }
30
30
  },
31
- "description": "Description about an targeted system, this could potentially be a compromissed internal system",
31
+ "description": "Description about an targeted system, this could potentially be a compromised internal system",
32
32
  "meta-category": "internal",
33
33
  "name": "target-system",
34
34
  "requiredOneOf": [
35
35
  "targeted_machine"
36
36
  ],
37
37
  "uuid": "3110944f-eca0-4c94-9d61-a84d022228a4",
38
- "version": 1
38
+ "version": 2
39
39
  }
@@ -68,9 +68,9 @@
68
68
  "dkim",
69
69
  "dkim-signature",
70
70
  "dns-soa-email",
71
+ "dom-hash",
71
72
  "domain",
72
73
  "domain|ip",
73
- "dom-hash",
74
74
  "email",
75
75
  "email-attachment",
76
76
  "email-body",
pymisp/mispevent.py CHANGED
@@ -2532,6 +2532,14 @@ class MISPAnalystData(AbstractMISP):
2532
2532
  def analyst_data_object_type(self) -> str:
2533
2533
  return self._analyst_data_object_type
2534
2534
 
2535
+ @property
2536
+ def notes(self) -> list[MISPNote]:
2537
+ return self.Note
2538
+
2539
+ @property
2540
+ def opinions(self) -> list[MISPOpinion]:
2541
+ return self.Opinion
2542
+
2535
2543
  @property
2536
2544
  def org(self) -> MISPOrganisation:
2537
2545
  return self.Org