You are currently viewing Microsoft Sentinel IOC Integration – BlueVoyant Threat Intel Setup
BlueVoyant

Microsoft Sentinel IOC Integration – BlueVoyant Threat Intel Setup

  • Post last modified:September 25, 2025
  • Post category:Threat Intel
  • Reading time:5 mins read

BlueVoyant is a Cybersecurity firm offering different products and solutions including: Managed Detection and Response, Third-party Risk Management, Digital Risk Protection, Cyber Posture Management, and Proactive Defense.

We have a subscription to their Digital Risk Protection platform that includes taking down website and app impersonations and other useful things. Within the platform, BlueVoyant makes available a report/list of IOCs on a weekly basis. I had the idea to integrate this feed into our SIEM solution after learning they have an API available, but that proved technically challenging since it involved their API, Microsoft’s API and the way they encode PDF reports into json files.

My next best option was to come up with a semi-automated way of ingesting these reports into our SIEM’s Threat Intel connector. Here the overall approach I took:

  1. Manually download the weekly .xlsx report.
  2. Run the report against a Python script and it spits out two files: The first file includes just the hashes of files in a format accepted by Microsoft Threat Intel platform. The second file includes the IPs and Domains also in a format that is accepted by Microsoft.
  3. Manually upload the IOCs files to Sentinel.

The entire process takes about 5 minutes once a week and I was happy with the results. Overcomplicating or spending too much time trying to figure out a 100% automated solution was not worth it in my opinion, at least for now.

Technical Details

Components of the weekly BlueVoyant’s reports:

Python script to split and format the new files:

import pandas as pd
import re
import sys
import os

def fang(value):
    """Replace defanged patterns like [.] with ."""
    if isinstance(value, str):
        return value.replace("[.]", ".")
    return value

def process_hashes(df):
    df = df.rename(columns={
        "IOCs Description": "description",
        "MD5": "filehash:md5",
        "SHA1": "filehash:sha-1",
        "SHA256": "filehash:sha-256",
        "Valid From": "validFrom",
        "Valid Until": "validUntil"
    })

    df["fileName"] = ""
    df["directoryPath"] = ""
    df["threatTypes"] = ""
    df["tags"] = "BlueVoyant IOCs_Hash"
    df["name"] = df["description"]
    df["confidence"] = ""
    df["revoked"] = ""
    df["tlpLevel"] = ""
    df["severity"] = ""

    hash_columns = [
        "filehash:md6", "filehash:ripemd-160", "filehash:sha-224", "filehash:sha-384",
        "filehash:sha-512", "filehash:sha3-224", "filehash:sha3-256", "filehash:sha3-384",
        "filehash:sha3-512", "filehash:ssdeep", "filehash:whirlpool"
    ]
    for col in hash_columns:
        df[col] = ""

    final_columns = [
        "fileName", "directoryPath", "threatTypes", "tags", "name", "description",
        "confidence", "revoked", "validFrom", "validUntil", "tlpLevel", "severity",
        "filehash:md5", "filehash:md6", "filehash:ripemd-160", "filehash:sha-1",
        "filehash:sha-224", "filehash:sha-256", "filehash:sha-384", "filehash:sha-512",
        "filehash:sha3-224", "filehash:sha3-256", "filehash:sha3-384", "filehash:sha3-512",
        "filehash:ssdeep", "filehash:whirlpool"
    ]
    return df[final_columns]

def process_observables(df, observable_type):
    df.columns = [col.strip() for col in df.columns]

    if "IOCs Description" in df.columns:
        df["description"] = df["IOCs Description"]
    elif "IOCc Description" in df.columns:
        df["description"] = df["IOCc Description"]
    else:
        raise KeyError("Missing 'IOCs Description' or 'IOCc Description' column.")

    if "Content" not in df.columns:
        print("Available columns:", df.columns.tolist())
        raise KeyError("Missing expected 'Content' column.")
    df["observableValue"] = df["Content"].apply(fang)

    df["validFrom"] = df["Valid From"] if "Valid From" in df.columns else ""
    df["validUntil"] = df["Valid Until"] if "Valid Until" in df.columns else ""

    df["observableType"] = observable_type
    df["threatTypes"] = ""
    df["tags"] = "BlueVoyant IOCs_IP" if observable_type == "ipv4-addr" else "BlueVoyant IOCs_Domain"
    df["name"] = df["description"]
    df["confidence"] = ""
    df["revoked"] = ""
    df["tlpLevel"] = ""
    df["severity"] = ""

    final_columns = [
        "threatTypes", "tags", "name", "description", "confidence", "revoked",
        "validFrom", "validUntil", "tlpLevel", "severity", "observableType", "observableValue"
    ]
    return df[final_columns]

def main():
    if len(sys.argv) < 2:
        print("Usage: python script.py <path_to_excel_file>")
        sys.exit(1)

    file_path = sys.argv[1]
    if not os.path.exists(file_path):
        print(f"❌ File not found: {file_path}")
        sys.exit(1)     
    print(f"πŸ“‚ Processing file: {file_path}")
    base_name = os.path.splitext(os.path.basename(file_path))[0]

    engine = "openpyxl" if file_path.endswith(".xlsx") else "xlrd"
    xls = pd.ExcelFile(file_path, engine=engine)
    df_hashes = pd.read_excel(xls, sheet_name="Hash", engine=engine)
    df_ips = pd.read_excel(xls, sheet_name="IPs", engine=engine)
    df_domains = pd.read_excel(xls, sheet_name="Domains", engine=engine)

    hashes_csv = process_hashes(df_hashes)
    observables_csv = pd.concat([
        process_observables(df_ips, "ipv4-addr"),
        process_observables(df_domains, "domain-name")
    ], ignore_index=True)

    hashes_csv.to_csv(f"{base_name}_Hashes.csv", index=False)
    observables_csv.to_csv(f"{base_name}_IPs_Domains.csv", index=False)

    print(f"βœ… Files saved: {base_name}_Hashes.csv and {base_name}_IPs_Domains.csv")

if __name__ == "__main__":
    main()

Procedure:

  1. Save the code above into a .py file.
  2. Make sure the requirements are installed: panda, re, sys, os.
  3. Run the script against your BV .xlsx file: BV_IOC_Script.py BlueVoyant Weekly IOCs 135.xlsx
  4. Once the command is run, two new files should be created: BlueVoyant Weekly IOCs 135_Hashes.csv and BlueVoyant Weekly IOCs 135_IPs_Domains.csv formatted to fit the Microsoft File Import Templates they provide as examples. (This info as valid as of 9/24/2025.
  5. Once you have the two .csv files, navigate to https://portal.azure.com and make sure you have the necessary roles to access Microsoft Sentinel.
  6. Search for and launch Microsoft Sentinel.
  7. Select the proper Log Analytics Workspace if you have more than one.
  8. Under the Threat Management section, select Threat Intelligence.
  9. Click the Import drop-down menu at the top and select Import using a file.
  10. Keep File format set to CSV. Select File indicators under Indicator type, and type the name of your source under Source. For BlueVoyant, I usually type BlueVoyant.
  11. Click Browse for files under Upload a file and select your _Hashes.csv file. Example BlueVoyant Weekly IOCs 135_Hashes.csv.
  12. Then click Import.
  13. Repeat the same procedure to upload the IPs_Domains.csv file, but make sure to change the Indicator type to All other indicator types.
  14. Now you can click on the Import drop-down menu again and select File import history to check the status of the uploads.

Now all that is left is to make sure the Threat Intelligence Rule Analytics are enabled to take a advantage of this CTI feed and get alerted once any of the IOCs are observed in your environment.

I hope this was useful to some of you looking for a quick a dirty solution. And you are always welcome to provide feedback.