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.
- CHANGELOG.txt +5380 -0
- examples/__init__.py +0 -0
- examples/add_attributes_from_csv.py +74 -0
- examples/add_email_object.py +29 -0
- examples/add_fail2ban_object.py +86 -0
- examples/add_feed.py +25 -0
- examples/add_file_object.py +47 -0
- examples/add_filetype_object_from_csv.py +53 -0
- examples/add_generic_object.py +26 -0
- examples/add_github_user.py +65 -0
- examples/add_gitlab_user.py +56 -0
- examples/add_named_attribute.py +25 -0
- examples/add_organisations.py +57 -0
- examples/add_ssh_authorized_keys.py +29 -0
- examples/add_user.py +22 -0
- examples/add_vehicle_object.py +22 -0
- examples/addtag2.py +45 -0
- examples/asciidoc_generator.py +114 -0
- examples/cache_all.py +10 -0
- examples/copyTagsFromAttributesToEvent.py +68 -0
- examples/copy_list.py +93 -0
- examples/create_events.py +26 -0
- examples/cytomic_orion.py +549 -0
- examples/del.py +22 -0
- examples/delete_user.py +16 -0
- examples/edit_organisation.py +20 -0
- examples/edit_user.py +20 -0
- examples/falsepositive_disabletoids.py +136 -0
- examples/fetch_events_feed.py +15 -0
- examples/fetch_warninglist_hits.py +38 -0
- examples/freetext.py +22 -0
- examples/generate_file_objects.py +78 -0
- examples/generate_meta_feed.py +15 -0
- examples/get.py +37 -0
- examples/get_csv.py +37 -0
- examples/get_network_activity.py +187 -0
- examples/last.py +48 -0
- examples/load_csv.py +94 -0
- examples/lookup.py +28 -0
- examples/misp2cef.py +71 -0
- examples/misp2clamav.py +52 -0
- examples/openioc_to_misp.py +27 -0
- examples/proofpoint_tap.py +203 -0
- examples/proofpoint_vap.py +65 -0
- examples/search.py +48 -0
- examples/search_attributes_yara.py +40 -0
- examples/search_sighting.py +42 -0
- examples/server_sync_check_conn.py +32 -0
- examples/sharing_groups.py +15 -0
- examples/show_sightings.py +168 -0
- examples/stats_report.py +405 -0
- examples/sync_sighting.py +171 -0
- examples/tags.py +25 -0
- examples/test_sign.py +19 -0
- examples/trustar_misp.py +59 -0
- examples/up.py +21 -0
- examples/upload.py +60 -0
- examples/users_list.py +15 -0
- examples/vmray_automation.py +281 -0
- examples/vt_to_misp.py +182 -0
- examples/warninglists.py +22 -0
- examples/yara.py +38 -0
- examples/yara_dump.py +98 -0
- pymisp/api.py +33 -5
- pymisp/data/misp-objects/objects/instagram-account/definition.json +66 -0
- pymisp/data/misp-objects/objects/lnk/definition.json +13 -1
- pymisp/data/misp-objects/objects/rmm/definition.json +88 -0
- pymisp/data/misp-objects/objects/target-system/definition.json +2 -2
- pymisp/data/misp-objects/schema_objects.json +1 -1
- pymisp/mispevent.py +8 -0
- {pymisp-2.5.4.dist-info → pymisp-2.5.7.dist-info}/METADATA +23 -28
- {pymisp-2.5.4.dist-info → pymisp-2.5.7.dist-info}/RECORD +140 -27
- {pymisp-2.5.4.dist-info → pymisp-2.5.7.dist-info}/WHEEL +1 -1
- tests/57c4445b-c548-4654-af0b-4be3950d210f.json +1 -0
- tests/__init__.py +0 -0
- tests/csv_testfiles/invalid_fieldnames.csv +11 -0
- tests/csv_testfiles/valid_fieldnames.csv +4 -0
- tests/email_testfiles/mail_1.eml.zip +0 -0
- tests/email_testfiles/mail_1.msg +0 -0
- tests/email_testfiles/mail_1_bom.eml +858 -0
- tests/email_testfiles/mail_1_headers_only.eml +28 -0
- tests/email_testfiles/mail_2.eml +32 -0
- tests/email_testfiles/mail_3.eml +170 -0
- tests/email_testfiles/mail_3.msg +0 -0
- tests/email_testfiles/mail_4.msg +0 -0
- tests/email_testfiles/mail_5.msg +0 -0
- tests/email_testfiles/mail_multiple_to.eml +15 -0
- tests/email_testfiles/source +1 -0
- tests/git-vuln-finder-quagga.json +1493 -0
- tests/misp_event.json +76 -0
- tests/mispevent_testfiles/attribute.json +21 -0
- tests/mispevent_testfiles/attribute_del.json +23 -0
- tests/mispevent_testfiles/def_param.json +53 -0
- tests/mispevent_testfiles/event.json +8 -0
- tests/mispevent_testfiles/event_obj_attr_tag.json +57 -0
- tests/mispevent_testfiles/event_obj_def_param.json +62 -0
- tests/mispevent_testfiles/event_obj_tag.json +29 -0
- tests/mispevent_testfiles/event_tags.json +18 -0
- tests/mispevent_testfiles/existing_event.json +4599 -0
- tests/mispevent_testfiles/existing_event_edited.json +4601 -0
- tests/mispevent_testfiles/galaxy.json +25 -0
- tests/mispevent_testfiles/malware.json +19 -0
- tests/mispevent_testfiles/malware_exist.json +163 -0
- tests/mispevent_testfiles/misp_custom_obj.json +38 -0
- tests/mispevent_testfiles/overwrite_file/definition.json +457 -0
- tests/mispevent_testfiles/proposals.json +35 -0
- tests/mispevent_testfiles/shadow.json +148 -0
- tests/mispevent_testfiles/sighting.json +5 -0
- tests/mispevent_testfiles/simple.json +2 -0
- tests/mispevent_testfiles/test_object_template/definition.json +29 -0
- tests/new_misp_event.json +34 -0
- tests/reportlab_testfiles/HTML_event.json +1 -0
- tests/reportlab_testfiles/galaxy_1.json +1250 -0
- tests/reportlab_testfiles/image_event.json +2490 -0
- tests/reportlab_testfiles/japanese_test.json +156 -0
- tests/reportlab_testfiles/japanese_test_heavy.json +318 -0
- tests/reportlab_testfiles/long_event.json +3730 -0
- tests/reportlab_testfiles/mainly_objects_1.json +1092 -0
- tests/reportlab_testfiles/mainly_objects_2.json +977 -0
- tests/reportlab_testfiles/sighting_1.json +305 -0
- tests/reportlab_testfiles/sighting_2.json +221 -0
- tests/reportlab_testfiles/to_delete1.json +804 -0
- tests/reportlab_testfiles/to_delete2.json +1 -0
- tests/reportlab_testfiles/to_delete3.json +1 -0
- tests/reportlab_testfiles/very_long_event.json +1006 -0
- tests/reportlab_testoutputs/to_delete1.json.pdf +391 -0
- tests/reportlab_testoutputs/to_delete2.json.pdf +506 -0
- tests/reportlab_testoutputs/to_delete3.json.pdf +277 -0
- tests/search_index_result.json +69 -0
- tests/sharing_groups.json +98 -0
- tests/stix1.xml-utf8 +110 -0
- tests/stix2.json +1 -0
- tests/test_analyst_data.py +123 -0
- tests/test_emailobject.py +157 -0
- tests/test_fileobject.py +20 -0
- tests/test_mispevent.py +473 -0
- tests/test_reportlab.py +431 -0
- tests/testlive_comprehensive.py +3734 -0
- tests/testlive_sync.py +474 -0
- pymisp/data/misp-objects/.git +0 -1
- pymisp/data/misp-objects/.gitchangelog.rc +0 -289
- pymisp/data/misp-objects/.github/workflows/codeql.yml +0 -41
- pymisp/data/misp-objects/.github/workflows/nosetests.yml +0 -39
- pymisp/data/misp-objects/.travis.yml +0 -16
- pymisp/data/misp-objects/LICENSE-software-only.md +0 -661
- pymisp/data/misp-objects/LICENSE.md +0 -36
- pymisp/data/misp-objects/README.md +0 -567
- pymisp/data/misp-objects/docs/time-related-objects.ods +0 -0
- pymisp/data/misp-objects/docs/time-related-objects.pdf +0 -0
- pymisp/data/misp-objects/jq_all_the_things.sh +0 -29
- pymisp/data/misp-objects/tools/adoc_objects.py +0 -145
- pymisp/data/misp-objects/tools/alfred_links_to_relarelationships.py +0 -48
- pymisp/data/misp-objects/tools/list_of_objects.py +0 -50
- pymisp/data/misp-objects/tools/updated.sh +0 -6
- pymisp/data/misp-objects/tools/validate_opposites.sh +0 -17
- pymisp/data/misp-objects/unique_uuid.py +0 -16
- pymisp/data/misp-objects/validate_all.sh +0 -38
- {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)
|
examples/warninglists.py
ADDED
|
@@ -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,
|
|
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:
|
|
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,
|
|
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,
|
|
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,
|
|
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":
|
|
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
|
|
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":
|
|
38
|
+
"version": 2
|
|
39
39
|
}
|
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
|