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:
- Manually download the weekly .xlsx report.
- 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.
- 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:
- Save the code above into a .py file.
- Make sure the requirements are installed: panda, re, sys, os.
- Run the script against your BV .xlsx file:
BV_IOC_Script.py BlueVoyant Weekly IOCs 135.xlsx
- Once the command is run, two new files should be created:
BlueVoyant Weekly IOCs 135_Hashes.csv
andBlueVoyant 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. - 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.
- Search for and launch Microsoft Sentinel.
- Select the proper Log Analytics Workspace if you have more than one.
- Under the
Threat Management
section, selectThreat Intelligence
. - Click the
Import
drop-down menu at the top and selectImport using a file
. - 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 typeBlueVoyant
. - Click
Browse for files
under Upload a file and select your_Hashes.csv
file. ExampleBlueVoyant Weekly IOCs 135_Hashes.csv
. - Then click
Import
. - Repeat the same procedure to upload the
IPs_Domains.csv
file, but make sure to change the Indicator type toAll other indicator types
. - Now you can click on the
Import
drop-down menu again and selectFile 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.