The quick and dirty way:
sudo pip3 install pymisp
The clean approach as user:
pip3 install --user pymisp
git clone https://github.com/MISP/PyMISP.git
cd PyMISP
virtualenv -p python3 pymisp-env
source pymisp-env/bin/activate
pip install -e .
We assume you're in a virtual environment
If you want to follow along this workshop on your computer, this is the way to go:
pip3 install jupyter
cd docs/tutorial
jupyter-notebook
This page aims to give recommendations about how to efficiently use the pymisp
library.
It is strongly recommended (read "don't do anything else, please") to use the library this way and never, ever modify the python dictionary you get by loading the json blob you receive from the server.
This library is made in a way to hide as much as the complexity as possible and we're happy to improve it is there is something missing.
MISPEvent
is the main class to use when you want to create/update events on a MISP instance.
from pymisp import MISPEvent
event = MISPEvent()
event.info = 'This is my new MISP event' # Required
event.distribution = 0 # Optional, defaults to MISP.default_event_distribution in MISP config
event.threat_level_id = 2 # Optional, defaults to MISP.default_event_threat_level in MISP config
event.analysis = 1 # Optional, defaults to 0 (initial analysis)
print(event.to_json())
First example of helper aiming to make your life easier.
event.add_tag('tlp:white')
print(event.to_json())
The date can be in many different formats. This helper makes sure it normalizes it in a way that will be understood by your MISP instance.
# As text
event.set_date('2018-04-13')
print('Simple', event.date)
# Some weird text format (anything supported by dateparse will work)
event.set_date('Sat Oct 11 00:13:46 2017')
print('Messy', event.date)
# datetime.date
from datetime import date
d = date.today()
print(type(d))
event.set_date(d)
print(event.date)
# datetime.datetime => MISP expects a day, so the hour will be dropped.
from datetime import datetime
d = datetime.now()
print(type(d))
event.set_date(d)
print(event.date)
More useful things: adding attributes to an event.
Attributes have a bunch of parameters you can pass (if you feel like it). If you don't pass them, they'll be automatically set depending on their sane defaults.
The parameters are the following:
attribute = event.add_attribute('ip-dst', '8.8.8.8') # Minimal parameters
print(type(attribute))
print(attribute.to_json())
This is the way to pass other parameters
attribute_second = event.add_attribute('ip-dst', '8.8.8.9', disable_correlation=True)
print(attribute_second.to_json())
Every parameter can be modified in a pythonic way.
attribute.to_ids = False
print(attribute.to_json())
# Using the list of attributes in the event
event.attributes[0].add_tag('tlp:green')
# ... or the variable we got from `add_attribute`
attribute_second.add_tag('tlp:amber')
print(attribute_second.to_json())
print(event.to_json())
Important note: the default approach to delete on MISP is to do a soft delete (meaning the attribute is not displayed on the default view on MISP). The reason we do it this way is that it allows to push delete updates to instances we synchronize with.
The delete method will set the default parameter of the attribute to True
.
attribute.delete()
print(attribute.to_json())
Same idea: you can set the published flag from the api
event.publish()
print(event.published)
attr_type = 'ip-dst'
value = '1.1.1.1'
from pymisp import MISPAttribute
# Attribute data already defined
attribute = MISPAttribute()
attribute.type = attr_type
attribute.value = value
print(attribute)
# An attribute can also be loaded directly from a JSON
json = '''{
"type": "ip-dst",
"value": "127.0.0.1",
"category": "Network activity",
"to_ids": false
}'''
attribute = MISPAttribute()
attribute.from_json(json)
print(attribute)
Objects in MISP are a way to group attributes together in a way that makes sense. The objects are based on templates that are bundled in the library itself.
Note: you can use your own templates, we will see how later
from pymisp import MISPObject
circl_attr = event.add_attribute('ip-dst', '149.13.33.14')
misp_object = MISPObject('domain-ip', standalone=False, default_attributes_parameters=circl_attr)
# Notes:
# * standalone: this object will be attached to a MISPEvent, so the references will be in the dump
# * default_attributes_parameters: keep parameters from a MISPAttribute (useful when expanding a existing one)
misp_object.comment = 'My Fancy new object'
obj_attr = misp_object.add_attribute('domain', value='circl.lu')
obj_attr.add_tag('tlp:green')
misp_object.add_attribute('ip', value='149.13.33.14')
misp_object.add_attribute('first-seen', value='2018-04-11')
misp_object.add_attribute('last-seen', value='2018-06-11')
event.add_object(misp_object)
print(event.to_json())
You can also add the object directly in a misp event this way
from pymisp import MISPObject
misp_object = event.add_object(name='domain-ip', comment='My Fancy new object, in one line')
obj_attr = misp_object.add_attribute('domain', value='circl.lu')
obj_attr.add_tag('tlp:green')
misp_object.add_attribute('ip', value='149.13.33.14')
misp_object.add_attribute('first-seen', value='2018-04-11')
misp_object.add_attribute('last-seen', value='2018-06-11')
misp_object.add_attributes('ip', {'value': '10.8.8.8', 'to_ids': False}, '10.9.8.8')
misp_object.add_reference(obj_attr.uuid, 'related-to', 'Expanded with passive DNS entry')
print(event.to_json())
from pymisp import MISPObject
misp_object = event.add_object(name='domain-ip', comment='My Fancy new object, in one line')
obj_attr = misp_object.add_attribute('domain', value='circl.lu')
obj_attr.add_tag('tlp:green')
misp_object.add_attribute('ip', value='149.13.33.14')
misp_object.first_seen = '2018-04-11'
misp_object.last_seen = '2018-06-11T23:27:40.23356+07:00'
print(misp_object.last_seen)
misp_object.add_attributes('ip', {'value': '10.8.8.8', 'to_ids': False}, '10.9.8.8')
misp_object.add_reference(obj_attr.uuid, 'related-to', 'Expanded with passive DNS entry')
print(event.to_json(indent=2))
For some objects, we have helpers in order to make your life easier. The most relevant example is the file object: when you have a file to push on MISP, there are plenty of indicators you can extract at once, and it is pretty simple to automate, so we made it a oneliner.
Note: This requires a few more dependencies to get the full power of the script:
lief
to extract indicators out of PE/ELF/MachO files, and soon Android binaries.python-magic
to get the mime typepydeep
to compute the ssdeep of the binary whenever possiblepip install lief python-magic git+https://github.com/kbandla/pydeep.git
from pymisp.tools import FileObject
file_obj = FileObject(filepath='../../tests/viper-test-files/test_files/EICAR.com', standalone=False)
print(file_obj.to_json())
event.add_object(file_obj)
print(event.to_json())
(okay, CSV, but that's the same thing, right?)
%%bash
cat ../../tests/csv_testfiles/valid_fieldnames.csv
%%bash
cat ../../tests/csv_testfiles/invalid_fieldnames.csv
from pymisp.tools import CSVLoader
from pymisp import MISPEvent
from pathlib import Path
csv1 = CSVLoader(template_name='file', csv_path=Path('../../tests/csv_testfiles/valid_fieldnames.csv'))
event = MISPEvent()
event.info = 'Test event from CSV loader'
for o in csv1.load():
event.add_object(**o)
print(event.to_json())
event = MISPEvent()
event.info = 'Test event from CSV loader'
csv2 = CSVLoader(template_name='file', csv_path=Path('../../tests/csv_testfiles/invalid_fieldnames.csv'),
fieldnames=['SHA1', 'fileName', 'size-in-bytes'], has_fieldnames=True)
for o in csv2.load():
event.add_object(**o)
print(event.to_json())
This helper is meant to be used when you already have a script that does the mapping between your own code, and the MISPObject template.
from pymisp.tools import GenericObjectGenerator
attributeAsDict = [{'script': ':(){ :|:& };:', 'comment': 'Forkbomb'},
{'language': {'value': 'Bash', 'to_ids': False, 'disable_correlation': True}},
{'filename': {'value': 'forkbomb.sh', 'to_ids': True}},
{'state': 'Malicious'}]
misp_object = GenericObjectGenerator('script', strict=True)
misp_object.generate_attributes(attributeAsDict)
print(misp_object.to_json())
from pymisp.tools import GenericObjectGenerator
attributeAsDict = [{'MyCoolAttribute': {'value': 'critical thing', 'type': 'text'}},
{'MyCoolerAttribute': {'value': 'even worse', 'type': 'text'}}]
misp_object = GenericObjectGenerator('my-cool-template', strict=True) # This is supposed to fail due to the strict parameter
misp_object.generate_attributes(attributeAsDict)
print(misp_object.to_json())
from pymisp.tools import GenericObjectGenerator
from uuid import uuid4
attributeAsDict = [{'MyCoolAttribute': {'value': 'critical thing', 'type': 'text'}},
{'MyCoolerAttribute': {'value': 'even worse', 'type': 'text'}}]
misp_object = GenericObjectGenerator('my-cool-template')
misp_object.generate_attributes(attributeAsDict)
# The parameters below are required if no template is provided.
misp_object.template_uuid = uuid4()
misp_object.templade_id = 1
misp_object.description = "foo"
setattr(misp_object, 'meta-category', 'bar')
print(misp_object.to_json())
Important: The path you pass as parameter for misp_objects_path_custom
needs to contain a directory equals to the value of the parameter name
(same structure as the content of the misp-object
repository)
user_defined_obj = MISPObject(name='test_object_template', strict=True, misp_objects_path_custom='../../tests/mispevent_testfiles')
user_defined_obj.add_attribute('member3', value='foo')
user_defined_obj.add_attribute('member1', value='baz')
print(user_defined_obj.to_json())
The data you receive out of the JSON dump from a MISP instance is a base64 encoded zip with infected
as a password. The zip file contains 2 files, one containing the original file name of the uploaded file, and the other one is the binary.
This is pretty much a pain to use as-is.
So there is an helper for that!
sample = file_obj.get_attributes_by_relation('malware-sample')[0]
print(sample)
print('File name --->', sample.malware_filename)
print(sample.malware_binary)
print('Content of the malware (in bytes) ----->', sample.malware_binary.getvalue())
An other cool helper: one liner to whom you can pass the path to a binary, if it is supported by lief
(PE/ELF/Mach-o), you get the file object, a PE, ELF, or Mach-o object, and the relevant sections.
If it is anything else, it will just generate the the file object.
from pymisp.tools import make_binary_objects
file_obj, bin_obj, sections = make_binary_objects(filepath='../../tests/viper-test-files/test_files/whoami.exe', standalone=False)
event.add_object(file_obj)
if bin_obj:
event.add_object(bin_obj)
for s in sections:
event.add_object(s)
The references are also set by default by this method.
print(bin_obj.uuid)
print(bin_obj.references[0].to_json())
print(event.to_json())
We've been using to_json
a lot. The thing you should know is that every python MISP objects have this method, and it always returns a valid json blob you can send to MISP.
print(event.to_json())
from pymisp import MISPEvent, MISPOrganisation
from pymisp.tools import feed_meta_generator
from pathlib import Path
import json
out_dir = Path('feed_test')
out_dir.mkdir(exist_ok=True)
org = MISPOrganisation()
org.name = "Test Org"
org.uuid = "972360d2-2c96-4004-937c-ba010d03f925"
event = MISPEvent()
event.info = 'This is my new MISP event for a feed'
event.distribution = 1
event.Orgc = org
event.add_attribute('ip-dst', "8.8.8.8")
feed_event = event.to_feed()
with (out_dir / f'{event.uuid}.json').open('w') as f:
json.dump(feed_event, f)
feed_meta_generator(out_dir)
!ls feed_test
!cat feed_test/manifest.json
!echo ''
!cat feed_test/hashes.csv
!rm feed_test/*
We were creating new events, but you will also want to update an existing one.
from pymisp import MISPEvent
existing_event = MISPEvent()
existing_event.load_file('../../tests/mispevent_testfiles/existing_event.json')
print(existing_event.attributes[0])
print(existing_event.attributes[0].tags)
print(existing_event.attributes[0].timestamp)
print(existing_event.attributes[0].to_json())
If you tried to edit an event manually, and never got the updates on the instance, it is probably because the timestamps weren't updated/removed. Or you removed them all, and adding a single tag was making every attributes as new.
PyMISP got you covered.
existing_event.attributes[0].add_tag('tlp:white')
print(existing_event.attributes[0].to_json())
from pymisp import MISPOrganisation
orgc = MISPOrganisation()
orgc.name = 'bazbaz'
orgc.id = 15
orgc.uuid = '5888a98d-a7e8-4183-94bb-4d19950d210f'
# NOTE: Pushing this object will only work if the user has sync right (if not, the orgc key will be ignored)
event.Orgc = orgc
print(event.to_json())
from pathlib import Path
api_file = Path('apikey')
if api_file.exists():
misp_url = 'http://127.0.0.1'
misp_verifycert = False
with open(api_file) as f:
misp_key = f.read().strip()
print(misp_key)
else:
print("Unable to find the api key")
# The URL of the MISP instance to connect to
misp_url = 'https://127.0.0.1:8443/'
# Can be found in the MISP web interface under
# http://+MISP_URL+/users/view/me -> Authkey
misp_key = 'd6OmdDFvU3Seau3UjwvHS1y3tFQbaRNhJhDX0tjh'
# Should PyMISP verify the MISP certificate
misp_verifycert = False
from pymisp import PyMISP
misp = PyMISP(misp_url, misp_key, misp_verifycert)
Returns MISPEvent.
from pymisp import MISPEvent, MISPObject
event = MISPEvent()
event.info = 'This is my new MISP event' # Required
event.distribution = 0 # Optional, defaults to MISP.default_event_distribution in MISP config
event.threat_level_id = 2 # Optional, defaults to MISP.default_event_threat_level in MISP config
event.analysis = 1 # Optional, defaults to 0 (initial analysis)
mispObject = MISPObject('file')
mispObject.add_attribute('filename', type='filename',
value='filename.exe',
Tag=[{'name': 'tlp:amber'}])
event.add_object(mispObject)
print(misp)
existing_event = misp.add_event(event, pythonify=True)
print(existing_event)
mispObject = MISPObject('file')
mispObject.add_attribute('filename', type='filename',
value='filename2.exe',
Tag=[{'name': 'tlp:white'}])
existing_event.add_object(mispObject)
print(existing_event.to_json())
res = misp.update_event(existing_event)
existing_event = MISPEvent()
existing_event.load(res)
print(existing_event.to_json())
misp.toggle_global_pythonify() # Returns PyMISP objects whenever possible, allows to skip pythonify
event = misp.add_event({'distribution': 1, "threat_level_id": 1, "analysis": 1, 'info':"Event from notebook"})
print("Event id: %s" % event.id)
from pymisp import MISPEvent
event_obj = MISPEvent()
event_obj.distribution = 1
event_obj.threat_level_id = 1
event_obj.analysis = 1
event_obj.info = "Event from notebook 2"
event = misp.add_event(event_obj, pythonify=True)
event_id = event.id
print("Event id: %s" % event_id)
event_id = 9
# Fetch by ID
event = misp.get_event(event_id)
print(event)
# Fetch by ID
event = misp_old.get_event(event_id)
print(event)
attr_type = "ip-src"
value = "8.8.8.8"
category = "Network activity"
to_ids = False
value = "9.8.8.8"
from pymisp import MISPAttribute
# Attribute data already defined
attribute = MISPAttribute()
attribute.type = attr_type
attribute.value = value
attribute.category = category
attribute.to_ids = to_ids
attribute_to_change = misp.add_attribute(event_id, attribute, pythonify=True)
print(attribute_to_change.id, attribute_to_change)
from pymisp import MISPAttribute
attr_type = "ip-src"
value = "10.8.8.8"
category = "Network activity"
to_ids = False
# Attribute data already defined
attribute = MISPAttribute()
attribute.type = attr_type
attribute.value = value
attribute.category = category
attribute.to_ids = to_ids
proposal = misp.add_attribute_proposal(event_id, attribute)
print(proposal.id, proposal)
proposal = misp.get_attribute_proposal(1)
print(proposal.to_json())
proposal = misp.accept_attribute_proposal(1)
print(proposal)
proposal = misp.discard_attribute_proposal(2)
print(proposal)
from pymisp import MISPShadowAttribute
proposal = MISPShadowAttribute()
proposal.type = 'ip-dst'
proposal.category = 'External analysis'
proposal.to_ids = False
attribute = misp.update_attribute_proposal(attribute_to_change.id, proposal)
print(attribute.to_json())
attribute = misp.update_attribute_proposal(attribute_to_change.id, {'to_ids': False, 'comment': "This is crap"})
print(attribute.to_json())
from pymisp import MISPAttribute, MISPObject
attr_type = "ip-src"
value = "20.8.8.8"
category = "Network activity"
to_ids = False
# Attribute data already defined
attribute = MISPAttribute()
attribute.type = attr_type
attribute.value = value
attribute.category = category
attribute.to_ids = to_ids
# New Python 3.6 API
event = misp.get(event_id)
## Add the attribute to the event
event.add_attribute(**attribute)
event.add_attribute(type='domain', value='circl.lu', disable_correlation=True)
mispObject = MISPObject('file')
mispObject.add_attribute('filename', type='filename',
value='filename2.exe',
Tag=[{'name': 'tlp:white'}])
event.add_object(mispObject)
## Push the updated event to MISP
event_dict = misp.update_event(event)
print(event_dict)
misp.sighting(value=event.attributes[1].value)
misp.sighting_list(event.attributes[1].id)
misp.direct_call('attributes/add/9', {'type': 'ip-dst', 'value': '8.11.8.8'})
misp.direct_call('events')
misp.sharing_groups()
misp.users()
misp.add_user({'email': 'bar@foo.de'})
misp.organisations()
misp.roles()
misp.feeds(pythonify=True)
misp.cache_feeds_all()