Collect MISP IOC logs
This guide outlines the steps to ingest MISP (Malware Information Sharing Platform) IOC (Indicators of Compromise) logs to Google Security Operations using Bindplane. The parser extracts IOCs from MISP data formatted as JSON or CSV. It parses the input, maps fields to the Unified Data Model (UDM), handles various IOC types (such as IP, domain, and file hashes), and enriches the data with threat intelligence context like confidence and severity. The parser also performs specific logic for different data formats and handles cases with missing or unsupported fields.
Before you begin
Make sure you have the following prerequisites:
- Google SecOps instance
- Linux host with
systemd
- If running behind a proxy, firewall ports are open
- Privileged access to your MISP server
Get MISP API Key
- Sign in to your MISP web UI as an Administrator.
- Go to Administration > List Auth Keys.
- Click Add authentication key.
- Provide the following Key configuration:
- User: Select the user account associated with the key.
- Allowed IPs: You can optionally specify allowed IP addresses for the key.
- Copy and save the key in a secure location.
- Click I have noted down my key.
Configure MISP logs export
- Sign in to your MISP instance using SSH.
Install PyMISP using the following command:
pip3 install pymisp
Modify the
get_csv.py
export script using the following:#!/usr/bin/env python3 # -*- coding: utf-8 -*- import argparse from pymisp import ExpandedPyMISP from keys import misp_url, misp_key, misp_verifycert if __name__ == '__main__': parser = argparse.ArgumentParser(description='Get MISP data in a CSV format.') parser.add_argument("--controller", default='attributes', help="Attribute to use for the search (events, objects, attributes)") parser.add_argument("-e", "--event_id", help="Event ID to fetch. Without it, it will fetch the whole database.") parser.add_argument("-a", "--attribute", nargs='+', help="Attribute column names") parser.add_argument("-o", "--object_attribute", nargs='+', help="Object attribute column names") parser.add_argument("-t", "--misp_types", nargs='+', help="MISP types to fetch (ip-src, hostname, ...)") parser.add_argument("-c", "--context", action='store_true', help="Add event level context (tags...)") parser.add_argument("-f", "--outfile", help="Output file to write the CSV.") parser.add_argument("-l", "--last", required=True, help="can be defined in days, hours, minutes (for example 5d or 12h or 30m).") args = parser.parse_args() pymisp = ExpandedPyMISP(misp_url, misp_key, misp_verifycert, debug=True) attr = [] if args.attribute: attr += args.attribute if args.object_attribute: attr += args.object_attribute if not attr: attr = None print(args.context) response = pymisp.search(return_format='csv', controller=args.controller, eventid=args.event_id, requested_attributes=attr, publish_timestamp=args.last, type_attribute=args.misp_types, include_context=args.context) if args.outfile: with open(args.outfile, 'w') as f: f.write(response) else: print(response)
- Edit
keys.py
file to include your MISP API credentials and URL:
misp_url = 'https://<MISP_URL>' misp_key = '<MISP_API_KEY>' misp_verifycert = False misp_client_cert = ''
- Replace
<MISP_URL>
with your MISP IP or hostname. - Replace
<MISP_API_KEY
with the actual API key generated previously.
- Edit
Open crontab using the
crontab -e
command and enter the following:0 20 * * * python3 /opt/misp/<YOUR_EXPORT_SCRIPT_PATH> -f /opt/misp/ioc_export/url.log -t "url" -l 1d -c 0 20 * * * python3 /opt/misp/<YOUR_EXPORT_SCRIPT_PATH> -f /opt/misp/ioc_export/ip-dst.log -t "ip-dst" -l 1d -c 0 20 * * * python3 /opt/misp/<YOUR_EXPORT_SCRIPT_PATH> -f /opt/misp/ioc_export/ip-src.log -t "ip-src" -l 1d -c 0 20 * * * python3 /opt/misp/<YOUR_EXPORT_SCRIPT_PATH> -f /opt/misp/ioc_export/domain.log -t "domain" -l 1d -c 0 20 * * * python3 /opt/misp/<YOUR_EXPORT_SCRIPT_PATH> -f /opt/misp/ioc_export/sha256.log -t "sha256" -l 1d -c 0 20 * * * python3 /opt/misp/<YOUR_EXPORT_SCRIPT_PATH> -f /opt/misp/ioc_export/file.log -t "filename" -l 1d -c 0 20 * * * python3 /opt/misp/<YOUR_EXPORT_SCRIPT_PATH> -f /opt/misp/ioc_export/registry.log -t "registry" -l 1d -c 0 20 * * * python3 /opt/misp/<YOUR_EXPORT_SCRIPT_PATH> -f /opt/misp/ioc_export/mutex.log -t "mutex" -l 1d -c 0 20 * * * python3 /opt/misp/<YOUR_EXPORT_SCRIPT_PATH> -f /opt/misp/ioc_export/threat-actor.log -t "threat-actor" -l 1d -c
- Update
<YOUR_EXPORT_SCRIPT_PATH>
according to the actual export script location.
- Update
Get Google SecOps ingestion authentication file
- Sign in to the Google SecOps console.
- Go to SIEM Settings > Collection Agents.
- Download the Ingestion Authentication File. Save the file securely on the system where Bindplane will be installed.
Get Google SecOps customer ID
- Sign in to the Google SecOps console.
- Go to SIEM Settings > Profile.
- Copy and save the Customer ID from the Organization Details section.
Install the Bindplane agent on MISP Server
Linux installation
- Open a terminal with root or sudo privileges.
Run the following command:
sudo sh -c "$(curl -fsSlL https://github.com/observiq/bindplane-agent/releases/latest/download/install_unix.sh)" install_unix.sh
Additional installation resources
For additional installation options, consult the installation guide.
Configure the Bindplane agent to ingest MISP log files and send to Google SecOps
- Access the configuration file:
- Locate the
config.yaml
file. Typically, it's in the/etc/bindplane-agent/
directory on Linux. - Open the file using a text editor (for example,
nano
,vi
, or Notepad).
- Locate the
Edit the
config.yaml
file as follows:receivers: filelog: file_path: /opt/misp/ioc_export/*.log log_type: 'file' exporters: chronicle/chronicle_w_labels: compression: gzip # Adjust the path to the credentials file you downloaded in Step 1 creds_file_path: '/path/to/ingestion-authentication-file.json' # Replace with your actual customer ID from Step 2 customer_id: <customer_id> endpoint: malachiteingestion-pa.googleapis.com # Add optional ingestion labels for better organization ingestion_labels: log_type: 'MISP_IOC' raw_log_field: body service: pipelines: logs/source0__chronicle_w_labels-0: receivers: - filelog exporters: - chronicle/chronicle_w_labels
- Replace the port and IP address as required in your infrastructure.
- Replace
<customer_id>
with the actual Customer ID. - Update
/path/to/ingestion-authentication-file.json
to the path where the authentication file was saved in the Get Google SecOps ingestion authentication file section.
Restart the Bindplane agent to apply the changes
To restart the Bindplane agent in Linux, run the following command:
sudo systemctl restart bindplane-agent
UDM mapping table
Log Field | UDM Mapping | Logic |
---|---|---|
Attribute.category |
event.idm.entity.metadata.threat.category_details |
Directly mapped from Attribute.category in the nested JSON object within the "data" field. Used in the JSON parsing path. |
Attribute.comment |
event.idm.entity.metadata.threat.summary |
Directly mapped from Attribute.comment in the nested JSON object within the "data" field. Used in the JSON parsing path. |
Attribute.deleted |
event.idm.entity.metadata.threat.detection_fields.value |
Directly mapped from Attribute.deleted and added as a detection field with key "Attribute deleted". Used in the JSON parsing path. |
Attribute.event_id |
event.idm.entity.metadata.threat.detection_fields.value |
Directly mapped from Attribute.event_id and added as a detection field with key "Attribute event_id". Used in the JSON parsing path. |
Attribute.first_seen |
event.idm.entity.metadata.threat.detection_fields.value |
Directly mapped from Attribute.first_seen and added as a detection field with key "Attribute first_seen". Used in the JSON parsing path. |
Attribute.id |
event.idm.entity.metadata.threat.detection_fields.value |
Directly mapped from Attribute.id and added as a detection field with key "Attribute id" or "Attribute id $$" depending on the parsing path. Used in both CSV and JSON parsing paths. |
Attribute.timestamp |
event.idm.entity.metadata.threat.detection_fields.value |
Directly mapped from Attribute.timestamp and added as a detection field with key "Attribute timestamp". Used in the JSON parsing path. |
Attribute.to_ids |
event.idm.entity.metadata.threat.detection_fields.value |
Directly mapped from Attribute.to_ids and added as a detection field with key "Attribute to_ids". Used in the JSON parsing path. |
Attribute.type |
log_type |
Directly mapped from Attribute.type in the nested JSON object within the "data" field. Used as an interim field, and later used to populate other UDM fields. Used in the JSON parsing path. |
Attribute.uuid |
event.idm.entity.metadata.product_entity_id |
Directly mapped from Attribute.uuid in the nested JSON object within the "data" field. Used in the JSON parsing path. |
Attribute.value |
Multiple | The value of this field is used to populate several UDM fields depending on the Attribute.type (or log_type if derived from Attribute.type ):- event.idm.entity.entity.hostname if type is "domain".- event.idm.entity.entity.file.md5 if type is "md5".- event.idm.entity.entity.file.sha1 if type is "sha1".- event.idm.entity.entity.file.sha256 if type is "sha256".- event.idm.entity.entity.resource.name if type is "mutex".- event.idm.entity.entity.registry.registry_key if type is "regkey".- event.idm.entity.entity.user.email_addresses if type is "threat-actor".- event.idm.entity.entity.url if type is uri or url .- event.idm.entity.entity.file.full_path if type is "filename".- Parsed for IP and port if type is "ip-dst|port", "ip-dst", or "ip-src". Used in the JSON parsing path. |
column1 |
event.idm.entity.metadata.product_entity_id |
Directly mapped from column1 in the CSV parsing path. |
column14 |
Part of event.idm.entity.metadata.threat.description |
Concatenated with description to form the final description in the threat metadata. Used in the CSV parsing path. |
column16 |
event.idm.entity.metadata.threat.threat_feed_name , event.ioc.feed_name |
Directly mapped from column16 . Used in the CSV parsing path. |
column18 |
event.idm.entity.metadata.threat.severity_details , event.ioc.raw_severity |
Directly mapped from column18 . Used in the CSV parsing path. |
column21 |
Part of event.idm.entity.metadata.threat.description , event.ioc.description |
Used as the base for the description, later concatenated with event_info . Used in the CSV parsing path. |
column3 |
Part of event.ioc.categorization |
Directly mapped from column3 and concatenated with " IOCs" to form the final categorization. Used in the CSV parsing path. |
column4 |
event.idm.entity.metadata.description |
Directly mapped from column4 . Used in the CSV parsing path. |
column5 |
Multiple | The value of this field is used to populate several UDM fields depending on the column4 field (which maps to type ):- event.idm.entity.entity.hostname if type is "domain".- Parsed for IP and port if type is "ip-dst|port", "ip-dst", or "ip-src".- event.idm.entity.entity.file.md5 if type is "md5".- event.idm.entity.entity.file.sha1 if type is "sha1".- event.idm.entity.entity.file.sha256 if type is "sha256".- event.idm.entity.entity.resource.name if type is "mutex".- event.idm.entity.entity.registry.registry_key if type is "regkey".- event.idm.entity.entity.user.email_addresses if type is "threat-actor".- event.idm.entity.entity.url if type is uri or url .- event.idm.entity.entity.file.full_path if type is "filename". Used in the CSV parsing path. |
column6 |
event.idm.entity.metadata.threat.summary |
Directly mapped from column6 . Used in the CSV parsing path. |
column8 |
event.ioc.active_timerange.start , event.idm.entity.metadata.interval.start_time |
Parsed as a UNIX timestamp. Used in the CSV parsing path. |
date description |
event.idm.entity.metadata.threat.description |
Directly mapped from description in the nested JSON object within the "data" field. Used in the JSON parsing path. |
event_creator_email |
event.idm.entity.entity.labels.value |
Directly mapped from event_creator_email and added as a label with key "event_creator_email". Used in the JSON parsing path. |
event_id Feed.publish |
event.idm.entity.metadata.threat.detection_fields.value |
Directly mapped from Feed.publish and added as a detection field with key "Feed publish". Used in the JSON parsing path. |
first_seen |
event.ioc.active_timerange.start , event.idm.entity.metadata.interval.start_time |
Parsed as a timestamp in the format "yyyy-MM-ddTHH:mm:ssZZ". Used in the JSON parsing path. |
id info |
event.idm.entity.metadata.description |
Directly mapped from info in the nested JSON object within the "data" field. Used in the JSON parsing path. |
last_seen |
event.ioc.active_timerange.end |
Parsed as a timestamp in the format "yyyy-MM-ddTHH:mm:ssZZ". Used in the JSON parsing path. |
Org.name |
event.idm.entity.metadata.threat.detection_fields.value |
Directly mapped from Org.name and added as a detection field with key "Org name". Used in the JSON parsing path. |
published |
event.idm.entity.metadata.threat.detection_fields.value |
Directly mapped from published and added as a detection field with key "published". Used in the JSON parsing path. |
Tag.colour |
event.idm.entity.metadata.threat.detection_fields.value |
Directly mapped from Tag.colour and added as a detection field with key "tag colour". Used in the JSON parsing path. |
Tag.exportable |
event.idm.entity.metadata.threat.detection_fields.value |
Directly mapped from Tag.exportable and added as a detection field with key "tag exportable". Used in the JSON parsing path. |
Tag.hide_tag |
event.idm.entity.metadata.threat.detection_fields.value |
Directly mapped from Tag.hide_tag and added as a detection field with key "tag hide_tag". Used in the JSON parsing path. |
Tag.id |
event.idm.entity.metadata.threat.detection_fields.value |
Directly mapped from Tag.id and added as a detection field with key "tag id". Used in the JSON parsing path. |
Tag.is_custom_galaxy |
event.idm.entity.metadata.threat.detection_fields.value |
Directly mapped from Tag.is_custom_galaxy and added as a detection field with key "tag is_custom_galaxy". Used in the JSON parsing path. |
Tag.is_galaxy |
event.idm.entity.metadata.threat.detection_fields.value |
Directly mapped from Tag.is_galaxy and added as a detection field with key "tag is_galaxy". Used in the JSON parsing path. |
Tag.isinherited |
event.idm.entity.metadata.threat.detection_fields.value |
Directly mapped from Tag.isinherited and added as a detection field with key "tag isinherited". Used in the JSON parsing path. |
Tag.name |
event.idm.entity.metadata.threat.detection_fields.value |
Directly mapped from Tag.name and added as a detection field with key "tag name". Used in the JSON parsing path. |
Tag.numerical_value |
event.idm.entity.metadata.threat.detection_fields.value |
Directly mapped from Tag.numerical_value and added as a detection field with key "tag numerical_value". Used in the JSON parsing path. |
Tag.user_id |
event.idm.entity.metadata.threat.detection_fields.value |
Directly mapped from Tag.user_id and added as a detection field with key "tag user_id". Used in the JSON parsing path. |
threat_level_id |
event.idm.entity.entity.labels.value |
Directly mapped from threat_level_id and added as a label with key "threat_level_id". Used in the JSON parsing path. |
timestamp |
event.idm.entity.metadata.collected_timestamp , event.idm.entity.metadata.interval.start_time |
Parsed as a UNIX timestamp. Used in the CSV parsing path. |
uuid |
event.idm.entity.metadata.vendor_name |
Set to "MISP" by the parser. Set to "MISP" by the parser. Set to a default value far in the future (253402300799 seconds since epoch). Determined by the parser based on the type or log_type field. Can be "FILE", "DOMAIN_NAME", "IP_ADDRESS", "MUTEX", "RESOURCE", or "USER". Can be derived from Attribute.comment or Attribute.value depending on the Attribute.type . Parsed from Attribute.value or column5 if the type is IP related. Parsed from Attribute.value or column5 if the type is "ip-dst|port". Derived from column3 in CSV parsing or Attribute.category in JSON parsing. Derived from column21 and column14 in CSV parsing. Derived from column8 or first_seen . Derived from last_seen . Derived from description using a grok pattern. Derived from column16 or set to "MISP". Derived from column18 . Parsed from Attribute.value or column5 if the type is IP related. Parsed from Attribute.value or column5 if the type is "ip-dst|port". Derived from Attribute.value or column5 if the type is "domain". Derived from the confidence field, which is extracted from the description field. Values can be "HIGH_CONFIDENCE", "MEDIUM_CONFIDENCE", "LOW_CONFIDENCE", or "UNKNOWN_CONFIDENCE". Directly mapped from the confidence field, which is extracted from the description field. Directly mapped from the threat_level field, which is derived from column18 in the CSV parsing path. Directly mapped from the feed_name field, which is derived from column16 in the CSV parsing path. Derived from column21 and column14 in CSV parsing. Derived from column6 in CSV parsing or Attribute.comment in JSON parsing. Several fields are added as detection fields with their corresponding keys. Several fields are added as labels with their corresponding keys. Copied from the top-level timestamp field in the raw log. |
Need more help? Get answers from Community members and Google SecOps professionals.