Collect MISP IOC logs

Supported in:

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

  1. Sign in to your MISP web UI as an Administrator.
  2. Go to Administration > List Auth Keys.
  3. Click Add authentication key.
  4. 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

  1. Sign in to your MISP instance using SSH.
  2. Install PyMISP using the following command:

    pip3 install pymisp 
    
  3. 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)
    
    1. 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.
  4. 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.

Get Google SecOps ingestion authentication file

  1. Sign in to the Google SecOps console.
  2. Go to SIEM Settings > Collection Agents.
  3. Download the Ingestion Authentication File. Save the file securely on the system where Bindplane will be installed.

Get Google SecOps customer ID

  1. Sign in to the Google SecOps console.
  2. Go to SIEM Settings > Profile.
  3. Copy and save the Customer ID from the Organization Details section.

Install the Bindplane agent on MISP Server

Linux installation

  1. Open a terminal with root or sudo privileges.
  2. 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

  1. 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).
  2. 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.