Collect MISP IOC logs
This document explains how to ingest MISP (Malware Information Sharing Platform) IOC logs to Google Security Operations using Bindplane. The parser processes the data in both CSV and JSON formats. It extracts IOC attributes like IP addresses, domains, hashes, and URLs, mapping them to a unified data model (UDM) along with threat details like severity, confidence, and descriptions. The parser handles both single and multiple IOC entries within the input data, normalizing them into a consistent UDM output.
Before you begin
Make sure you have the following prerequisites:
- A Google SecOps instance.
- A Linux host with systemd.
- If running behind a proxy, ensure firewall ports are open per the Bindplane agent requirements.
- Privileged access to your MISP server.
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 IDfrom the Organization detailssection.
Get MISP API credentials
- Sign in to your MISP web interface as an Administrator.
- Go to Administration > List Auth Keys.
- Click Add authentication key.
- Provide the following configuration details: - User: Select the user account associated with the key.
- Optional: Allowed IPs: Specify allowed IP addresses for the key.
- Expiration: Leave empty for no expiration or set as needed.
 
- Click Submit.
- Copy and save the API key in a secure location.
- Click I have noted down my key.
Configure MISP data export
-  Install PyMISP on your MISP server: pip3 install pymisp
-  Create the export directory: sudo mkdir -p /opt/misp/scripts sudo mkdir -p /opt/misp/ioc_export
-  Create the credentials file /opt/misp/scripts/keys.py:misp_url = 'https://<MISP_SERVER_URL>' misp_key = '<MISP_API_KEY>' misp_verifycert = True misp_client_cert = ''- Replace <MISP_SERVER_URL>with your MISP server URL.
- Replace <MISP_API_KEY>with the API key from the prerequisites.
 
- Replace 
-  Create the export script /opt/misp/scripts/misp_export.py:#!/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 = 'Export MISP IOCs to CSV format.' ) parser . add_argument ( "--controller" , default = 'attributes' , help = "Controller to use for search (events, objects, attributes)" ) parser . add_argument ( "--event_id" , help = "Event ID to fetch. Without it, fetches recent data." ) parser . add_argument ( "--attributes" , nargs = '*' , help = "Requested attributes for CSV export" ) parser . add_argument ( "--misp_types" , nargs = '+' , help = "MISP types to fetch (ip-src, hostname, domain, etc.)" ) parser . add_argument ( "--context" , action = 'store_true' , help = "Add event level context (tags, metadata)" ) parser . add_argument ( "--outfile" , required = True , help = "Output file to write the CSV data" ) parser . add_argument ( "--last" , required = True , help = "Time period: days (d), hours (h), minutes (m) - e.g., 1d, 12h, 30m" ) args = parser . parse_args () api = ExpandedPyMISP ( misp_url , misp_key , misp_verifycert , debug = False ) response = api . search ( controller = args . controller , return_format = 'csv' , type_attribute = args . misp_types , publish_timestamp = args . last , include_context = args . context , requested_attributes = args . attributes or None ) with open ( args . outfile , 'w' ) as response_file : response_file . write ( response )- Make the script executable:
 sudo chmod +x /opt/misp/scripts/misp_export.pySchedule MISP data exports- Create scheduled exports using crontab:
 sudo crontab -e
-  Add the following cron entries: # Export different IOC types daily with context 0 0 * * * python3 /opt/misp/scripts/misp_export.py --outfile /opt/misp/ioc_export/domains.csv --misp_types domain --last 1d --context --attributes uuid event_id category type value comment to_ids date attribute_tag event_info 0 1 * * * python3 /opt/misp/scripts/misp_export.py --outfile /opt/misp/ioc_export/ip-src.csv --misp_types ip-src --last 1d --context --attributes uuid event_id category type value comment to_ids date attribute_tag event_info 0 2 * * * python3 /opt/misp/scripts/misp_export.py --outfile /opt/misp/ioc_export/ip-dst.csv --misp_types ip-dst --last 1d --context --attributes uuid event_id category type value comment to_ids date attribute_tag event_info 0 3 * * * python3 /opt/misp/scripts/misp_export.py --outfile /opt/misp/ioc_export/urls.csv --misp_types url --last 1d --context --attributes uuid event_id category type value comment to_ids date attribute_tag event_info 0 4 * * * python3 /opt/misp/scripts/misp_export.py --outfile /opt/misp/ioc_export/sha256.csv --misp_types sha256 --last 1d --context --attributes uuid event_id category type value comment to_ids date attribute_tag event_info 0 5 * * * python3 /opt/misp/scripts/misp_export.py --outfile /opt/misp/ioc_export/filenames.csv --misp_types filename --last 1d --context --attributes uuid event_id category type value comment to_ids date attribute_tag event_info 0 6 * * * python3 /opt/misp/scripts/misp_export.py --outfile /opt/misp/ioc_export/registries.csv --misp_types regkey --last 1d --context --attributes uuid event_id category type value comment to_ids date attribute_tag event_info 0 7 * * * python3 /opt/misp/scripts/misp_export.py --outfile /opt/misp/ioc_export/mutexes.csv --misp_types mutex --last 1d --context --attributes uuid event_id category type value comment to_ids date attribute_tag event_info
-  Optionally, schedule a pull of feeds from MISP: 23 0 * * * curl --insecure --header "Authorization: <MISP_API_KEY>" --header "Accept: application/json" --header "Content-Type: application/json" https://<MISP_SERVER_URL>/feeds/fetchFromAllFeeds
Install the Bindplane agent
Install the Bindplane agent on your Linux operating system according to the following instructions.
Linux installation
- Open a terminal with root or sudo privileges.
-  Run the following command: sudo sh -c " $( curl -fsSlL https://github.com/observiq/bindplane-otel-collector/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 logs and send to Google SecOps
-  Access the configuration file: - Locate the config.yamlfile. Typically, it's in the/etc/bindplane-agent/directory on Linux.
- Open the file using a text editor (for example, nano,vi).
 
- Locate the 
-  Edit the config.yamlfile as follows:receivers : filelog : file_path : /opt/misp/ioc_export/*.log 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 <CUSTOMER_ID>with your actual Customer ID from the prerequisites.
- Update /path/to/ingestion-authentication-file.jsonto the path where the authentication file was saved.
 
- Replace 
Restart Bindplane agent to apply the changes
-  To restart the Bindplane agent in Linux, run the following command: sudo systemctl restart observiq-otel-collector
UDM mapping table
| Log field | UDM mapping | Logic | 
|---|---|---|
|   
Attribute.category | entity.metadata.threat.category_details | Direct mapping from the categoryfield in theAttributeobject. | 
|   
Attribute.comment | entity.metadata.threat.summary | Direct mapping from the commentfield in theAttributeobject. | 
|   
Attribute.deleted | entity.metadata.threat.detection_fields.value | Direct mapping from the deletedfield in theAttributeobject. The key is set toAttribute deleted. | 
|   
Attribute.event_id | entity.metadata.threat.detection_fields.value | Direct mapping from the event_idfield in theAttributeobject. The key is set toAttribute event_id. | 
|   
Attribute.first_seen | entity.metadata.threat.detection_fields.value | Direct mapping from the first_seenfield in theAttributeobject. The key is set toAttribute first_seen. | 
|   
Attribute.id | entity.metadata.threat.detection_fields.value | Direct mapping from the idfield in theAttributeobject. The key is set toAttribute idorAttribute id $$depending on the format of the raw log. | 
|   
Attribute.timestamp | entity.metadata.threat.detection_fields.value | Direct mapping from the timestampfield in theAttributeobject. The key is set toAttribute timestamp. | 
|   
Attribute.to_ids | entity.metadata.threat.detection_fields.value | Direct mapping from the to_idsfield in theAttributeobject. The key is set toAttribute to_ids. | 
|   
Attribute.type | entity.metadata.threat.category_details | Direct mapping from the typefield in theAttributeobject. | 
|   
Attribute.type | log_type | Used to determine the type of IOC and map it to the appropriate UDM fields. | 
|   
Attribute.uuid | entity.metadata.product_entity_id | Direct mapping from the uuidfield in theAttributeobject. | 
|   
Attribute.value | entity.entity.file.full_path | Mapped if the Attribute.typeisfilename. | 
|   
Attribute.value | entity.entity.file.md5 | Mapped if the Attribute.typeismd5. | 
|   
Attribute.value | entity.entity.file.sha1 | Mapped if the Attribute.typeissha1. | 
|   
Attribute.value | entity.entity.file.sha256 | Mapped if the Attribute.typeissha256. | 
|   
Attribute.value | entity.entity.hostname | Mapped if the Attribute.typeisdomain. | 
|   
Attribute.value | entity.entity.ip | Mapped if the Attribute.typeisip-dst,ip-dst|port, orip-src. The value is extracted using a grok pattern. | 
|   
Attribute.value | entity.entity.resource.name | Mapped if the Attribute.typeismutex. | 
|   
Attribute.value | entity.entity.registry.registry_key | Mapped if the Attribute.typeisregkey. | 
|   
Attribute.value | entity.entity.url | Mapped if the Attribute.typeisuriorURL. | 
|   
column1 | entity.metadata.product_entity_id | Direct mapping from the first column in the CSV data. | 
|   
column14 | event_info | Used to append additional information to the threat_sr.descriptionfield. | 
|   
column16 | event_source_org | Direct mapping from the 16th column in the CSV data. | 
|   
column18 | threat_level | Direct mapping from the 18th column in the CSV data. | 
|   
column21 | description | Direct mapping from the 21st column in the CSV data. | 
|   
column3 | misp_category | Direct mapping from the third column in the CSV data. | 
|   
column4 | type | Direct mapping from the fourth column in the CSV data. | 
|   
column5 | value | Direct mapping from the fifth column in the CSV data. | 
|   
column6 | comment | Direct mapping from the sixth column in the CSV data. | 
|   
column8 | ts1 | Direct mapping from the eighth column in the CSV data. | 
|   
description | ioc.description | The value is generated by combining the descriptionfield with theevent_infofield, separated by-  additional info:. | 
|   
description | entity.metadata.threat.description | Direct mapping from the descriptionfield. | 
|   
event_creator_email | entity.entity.labels.value | Direct mapping from the event_creator_emailfield. The key is set toevent_creator_email. | 
|   
event_source_org | ioc.feed_name | Direct mapping from the event_source_orgfield. | 
|   
event_source_org | entity.metadata.threat.threat_feed_name | Direct mapping from the event_source_orgfield. | 
|   
Feed.publish | entity.metadata.threat.detection_fields.value | Direct mapping from the publishfield in theFeedobject. The key is set toFeed publish. | 
|   
first_seen | ioc.active_timerange.start | Direct mapping from the first_seenfield. The value is parsed as a date. | 
|   
first_seen | entity.metadata.interval.start_time | Direct mapping from the first_seenfield. The value is parsed as a date. | 
|   
info | entity.metadata.description | Direct mapping from the infofield. | 
|   
last_seen | ioc.active_timerange.end | Direct mapping from the last_seenfield. The value is parsed as a date. | 
|   
log.category | ioc.categorization | Direct mapping from the categoryfield in thelogobject. | 
|   
log.category | entity.metadata.threat.category_details | Direct mapping from the categoryfield in thelogobject. | 
|   
log.comment | entity.entity.file.full_path | Mapped if the log.typeisfilenameand thecommentfield is notArtifacts dropped. | 
|   
log.comment | entity.metadata.threat.detection_fields.value | Direct mapping from the commentfield in thelogobject. The key is set toAttribute comment. | 
|   
log.comment | entity.metadata.threat.summary | Direct mapping from the commentfield in thelogobject. | 
|   
log.deleted | entity.metadata.threat.detection_fields.value | Direct mapping from the deletedfield in thelogobject. The key is set toAttribute deleted. | 
|   
log.event_id | entity.metadata.threat.detection_fields.value | Direct mapping from the event_idfield in thelogobject. The key is set toAttribute event_id. | 
|   
log.first_seen | entity.metadata.threat.detection_fields.value | Direct mapping from the first_seenfield in thelogobject. The key is set toAttribute first_seen. | 
|   
log.id | entity.metadata.threat.detection_fields.value | Direct mapping from the idfield in thelogobject. The key is set toAttribute id. | 
|   
log.timestamp | entity.metadata.threat.detection_fields.value | Direct mapping from the timestampfield in thelogobject. The key is set toAttribute timestamp. | 
|   
log.to_ids | entity.metadata.threat.detection_fields.value | Direct mapping from the to_idsfield in thelogobject. The key is set toAttribute to_ids. | 
|   
log.type | ioc.categorization | Direct mapping from the typefield in thelogobject. | 
|   
log.type | log_type | Used to determine the type of IOC and map it to the appropriate UDM fields. | 
|   
log.uuid | entity.metadata.product_entity_id | Direct mapping from the uuidfield in thelogobject. | 
|   
log.value | entity.entity.file.full_path | Mapped if the log.typeisfilename. | 
|   
log.value | entity.entity.file.md5 | Mapped if the log.typeismd5. | 
|   
log.value | entity.entity.file.sha1 | Mapped if the log.typeissha1. | 
|   
log.value | entity.entity.file.sha256 | Mapped if the log.typeissha256. | 
|   
log.value | entity.entity.hostname | Mapped if the log.typeisdomain. | 
|   
log.value | entity.entity.ip | Mapped if the log.typeisip-dst,ip-dst|port, orip-src. The value is extracted using a grok pattern. | 
|   
log.value | entity.entity.resource.name | Mapped if the log.typeismutex. | 
|   
log.value | entity.entity.registry.registry_key | Mapped if the log.typeisregkey. | 
|   
log.value | entity.entity.url | Mapped if the log.typeisuriorurl. | 
|   
log.value | ioc.domain_and_ports.domain | Mapped if the log.typeisdomain. | 
|   
log.value | entity.entity.user.email_addresses | Mapped if the log.typeisthreat-actor. | 
|   
misp_category | entity.metadata.threat.category_details | Direct mapping from the misp_categoryfield. | 
|   
Org.name | entity.metadata.threat.detection_fields.value | Direct mapping from the namefield in theOrgobject. The key is set toOrg name. | 
|   
published | entity.metadata.threat.detection_fields.value | Direct mapping from the publishedfield. The key is set topublished. | 
|   
Tag.colour | entity.metadata.threat.detection_fields.value | Direct mapping from the colourfield in theTagobject. The key is set totag colour. | 
|   
Tag.exportable | entity.metadata.threat.detection_fields.value | Direct mapping from the exportablefield in theTagobject. The key is set totag exportable. | 
|   
Tag.hide_tag | entity.metadata.threat.detection_fields.value | Direct mapping from the hide_tagfield in theTagobject. The key is set totag hide_tag. | 
|   
Tag.id | entity.metadata.threat.detection_fields.value | Direct mapping from the idfield in theTagobject. The key is set totag id. | 
|   
Tag.is_custom_galaxy | entity.metadata.threat.detection_fields.value | Direct mapping from the is_custom_galaxyfield in theTagobject. The key is set totag is_custom_galaxy. | 
|   
Tag.is_galaxy | entity.metadata.threat.detection_fields.value | Direct mapping from the is_galaxyfield in theTagobject. The key is set totag is_galaxy. | 
|   
Tag.isinherited | entity.metadata.threat.detection_fields.value | Direct mapping from the isinheritedfield in theTagobject. The key is set totag isinherited. | 
|   
Tag.name | entity.metadata.threat.detection_fields.value | Direct mapping from the namefield in theTagobject. The key is set totag name. | 
|   
Tag.numerical_value | entity.metadata.threat.detection_fields.value | Direct mapping from the numerical_valuefield in theTagobject. The key is set totag numerical_value. | 
|   
Tag.user_id | entity.metadata.threat.detection_fields.value | Direct mapping from the user_idfield in theTagobject. The key is set totag user_id. | 
|   
threat_level | ioc.raw_severity | Direct mapping from the threat_levelfield. | 
|   
threat_level | entity.metadata.threat.severity_details | Direct mapping from the threat_levelfield. | 
|   
threat_level_id | entity.entity.labels.value | Direct mapping from the threat_level_idfield. The key is set tothreat_level_id. | 
|   
ts1 | ioc.active_timerange.start | Direct mapping from the ts1field. The value is parsed as a date. | 
|   
ts1 | entity.metadata.interval.start_time | Direct mapping from the ts1field. The value is parsed as a date. | 
|  | entity.entity.file.full_path | Mapped if the typeisfilename. | 
|  | entity.entity.file.md5 | Mapped if the typeismd5. | 
|  | entity.entity.file.sha1 | Mapped if the typeissha1. | 
|  | entity.entity.file.sha256 | Mapped if the typeissha256. | 
|  | entity.entity.hostname | Mapped if the typeisdomain. | 
|  | entity.entity.ip | Mapped if the typeisip-dst,ip-dst|port, orip-src. The value is extracted using a grok pattern. | 
|  | entity.entity.port | Mapped if the portfield is not empty. The value is converted to an integer. | 
|  | entity.entity.resource.name | Mapped if the typeismutex. | 
|  | entity.entity.resource.resource_subtype | Mapped if the typeisregkey. The value is set toregkey. | 
|  | entity.entity.resource.resource_type | Mapped if the typeismutexorregkey. The value is set toMUTEXorSTORAGE_OBJECTrespectively. | 
|  | entity.entity.registry.registry_key | Mapped if the typeisregkey. | 
|  | entity.entity.url | Mapped if the typeisuriorurl. | 
|  | entity.metadata.collected_timestamp | The value is set to the timestamp of the raw log entry. | 
|  | entity.metadata.description | The value is set to the typefield if the raw log is in CSV format. Otherwise, it's set to theinfofield. | 
|  | entity.metadata.entity_type | The value is determined based on the typeorlog_typefield. It can beDOMAIN_NAME,FILE,IP_ADDRESS,MUTEX,RESOURCE, orURL. | 
|  | entity.metadata.interval.end_time | The value is set to a default value of 253402300799 seconds. | 
|  | entity.metadata.interval.start_time | The value is set to the first_seenfield if it's not empty. Otherwise, it's set to a default value of 1 second or the timestamp of the raw log entry. | 
|  | entity.metadata.product_name | The value is set to MISP. | 
|  | entity.metadata.threat.confidence | The value is set to UNKNOWN_CONFIDENCEif theconfidencefield is empty orf. Otherwise, it's set toHIGH_CONFIDENCE,MEDIUM_CONFIDENCE, orLOW_CONFIDENCEbased on the value of theconfidencefield. | 
|  | entity.metadata.threat.confidence_details | Direct mapping from the confidencefield. | 
|  | entity.metadata.threat.detection_fields | The value is a list of key-value pairs extracted from various fields in the raw log. | 
|  | entity.metadata.vendor_name | The value is set to MISP. | 
|  | ioc.active_timerange.end | The value is set to the last_seenfield if it's not empty. | 
|  | ioc.active_timerange.start | The value is set to the ts1orfirst_seenfield if they are not empty. Otherwise, it's set to a default value of 1 second. | 
|  | ioc.categorization | The value is set to misp_category IOCsif the raw log is in CSV format. Otherwise, it's set to thecategoryfield in theAttributeorlogobject. | 
|  | ioc.confidence_score | Direct mapping from the confidencefield. | 
|  | ioc.description | The value is generated by combining the descriptionfield with theevent_infofield, separated by-  additional info:. | 
|  | ioc.domain_and_ports.domain | Mapped if the typeorlog_typeisdomain. | 
|  | ioc.feed_name | The value is set to MISPif theevent_source_orgfield is empty. Otherwise, it's set to theevent_source_orgfield. | 
|  | ioc.ip_and_ports.ip_address | Mapped if the ipfield is not empty. The value is converted to an IP address. | 
|  | ioc.ip_and_ports.ports | Mapped if the portfield is not empty. The value is converted to an unsigned integer. | 
|  | ioc.raw_severity | Direct mapping from the threat_levelfield. | 
|  | timestamp | The value is set to the timestamp of the raw log entry. | 
Need more help? Get answers from Community members and Google SecOps professionals.

