diff --git a/Logos/vmray.svg b/Logos/vmray.svg
index 266f7d3c357..6e8a034dd9a 100755
--- a/Logos/vmray.svg
+++ b/Logos/vmray.svg
@@ -1,5 +1,5 @@
-
+
+
+
+
+ image/svg+xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Solutions/VMRay/Data Connectors/VMRay/__init__.py b/Solutions/VMRay/Data Connectors/VMRay/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/Solutions/VMRay/Data Connectors/VMRay/app.py b/Solutions/VMRay/Data Connectors/VMRay/app.py
new file mode 100644
index 00000000000..bbbff574a62
--- /dev/null
+++ b/Solutions/VMRay/Data Connectors/VMRay/app.py
@@ -0,0 +1,69 @@
+"""
+Main Function
+"""
+
+# pylint: disable=logging-fstring-interpolation
+
+import logging
+from datetime import datetime, timedelta
+from traceback import format_exc
+
+import azure.functions as func
+
+from .const import IOC_LIST, VMRay_CONFIG
+from .utils import (
+ IOC_MAPPING_FUNCTION,
+ get_last_saved_timestamp,
+ get_sample_ioc,
+ get_submission,
+ save_checkpoint,
+ submit_indicator,
+)
+
+
+def main(mytimer: func.TimerRequest) -> None:
+ """
+ Main handler function to interact with the VMRay API, triggered on a timer.
+
+ This function manages VMRay API access tokens, performs API calls including
+ pagination to retrieve all relevant data, and handles errors gracefully.
+ It is designed to run at scheduled intervals via a timer trigger.
+
+ Parameters
+ ----------
+ mytimer : func.TimerRequest
+ Timer object that triggers the function execution on a defined schedule.
+ """
+ try:
+ if mytimer.past_due:
+ logging.info("The timer is past due!")
+ return
+
+ initial_date_time = get_last_saved_timestamp() or (
+ datetime.now() - timedelta(days=int(VMRay_CONFIG.INITIAL_FETCH))
+ ).strftime("%Y-%m-%dT00:00:00")
+ logging.info(f"last_run {get_last_saved_timestamp()}")
+ sample_verdict = VMRay_CONFIG.VMRAY_SAMPLE_VERDICTS
+ sample_verdict = sample_verdict.split(" & ")
+ logging.info(f"verdict {sample_verdict}")
+ current_date = datetime.now().strftime("%Y-%m-%dT%H:%M:%S")
+ submissions_list = get_submission(
+ sample_verdict, initial_date_time, current_date
+ )
+ for sub in submissions_list:
+ if sub.get("submission_finished"):
+ iocs = get_sample_ioc(sub.get("submission_sample_id"))
+ for key, value in iocs.items():
+ if key in IOC_LIST:
+ IOC_MAPPING_FUNCTION[key](
+ value,
+ sub.get("submission_sample_id"),
+ sub.get("submission_id"),
+ sample_verdict,
+ )
+ submit_indicator()
+ save_checkpoint(current_date)
+ except Exception as ex:
+ error_detatl = format_exc()
+ logging.error(f"something went wrong. Error: {ex}. Traceback {error_detatl}")
+
diff --git a/Solutions/VMRay/Data Connectors/VMRay/const.py b/Solutions/VMRay/Data Connectors/VMRay/const.py
new file mode 100644
index 00000000000..b90fc469c76
--- /dev/null
+++ b/Solutions/VMRay/Data Connectors/VMRay/const.py
@@ -0,0 +1,67 @@
+"""
+Constant File
+"""
+from dataclasses import dataclass
+from os import environ
+
+# pylint: disable=invalid-name
+
+@dataclass
+class VMRayConfig:
+ """
+ VMRay Configuration
+ """
+ API_KEY: str
+ BASE_URL: str
+ VMRAY_SAMPLE_VERDICTS: str
+ INITIAL_FETCH: str
+ VALID_UNTIL: str
+ CONNECTOR_NAME: str = "VMRayThreatIntelligenceSentinel:1.0.0"
+ RETRIES: int = 5
+ BACKOFF: int = 1
+
+
+VMRay_CONFIG = VMRayConfig(
+ API_KEY=environ.get("VmrayAPIKey", ""),
+ BASE_URL=environ.get("VmrayBaseURL", ""),
+ VMRAY_SAMPLE_VERDICTS=environ.get("VmraySampleVerdict", "Malicious & Suspicious"),
+ INITIAL_FETCH=environ.get("VmrayInitialFetchDate", "90"),
+ VALID_UNTIL=environ.get("IndicatorExpirationInDays", "30")
+)
+
+
+@dataclass
+class APIConfig:
+ """
+ Microsoft API Configurations
+ """
+ APPLICATION_ID: str
+ APPLICATION_SECRET: str
+ AUTH_URL: str
+ URL: str
+ RESOURCE_APPLICATION_ID_URI: str = "https://management.azure.com"
+ USER_AGENT: str = "MSSentinelVMRayThreatIntelligenceSentinel:1.0.0"
+ SLEEP: int = 60
+ TIMEOUT: int = 300
+ MAX_TI_INDICATORS_PER_REQUEST:int = 100
+
+
+SENTINEL_API = APIConfig(
+ APPLICATION_ID=environ.get("AzureClientID", ""),
+ APPLICATION_SECRET=environ.get("AzureClientSecret", ""),
+ AUTH_URL=f"https://login.microsoftonline.com/{environ.get('AzureTenantID', '')}/oauth2/token",
+ URL=f"https://api.ti.sentinel.azure.com/workspaces/{environ.get('AzureWorkspaceID', '')}/"
+ f"threat-intelligence-stix-objects:upload?api-version=2024-02-01-preview"
+ )
+
+RETRY_STATUS_CODE = [500, 501, 502, 503, 504, 429]
+IPV4REGEX = r"^(?P(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))[:]?(?P\d+)?$"
+IPV6REGEX = r"^(?:(?:[0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|(?:[0-9a-fA-F]{1,4}:){1,7}:|(?:[0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|(?:[0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|(?:[0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|(?:[0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|(?:[0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:(?:(:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$" # noqa: E501
+CONFIDENCE = {"malicious": "100", "suspicious": "75"}
+HASH_TYPE_LIST = [
+ ("MD5", "md5_hash"),
+ ("SHA-1", "sha1_hash"),
+ ("SHA-256", "sha256_hash"),
+]
+IOC_LIST = ["domains", "ips", "urls", "files"]
+
diff --git a/Solutions/VMRay/Data Connectors/VMRay/function.json b/Solutions/VMRay/Data Connectors/VMRay/function.json
new file mode 100644
index 00000000000..5bfbe5fb4f6
--- /dev/null
+++ b/Solutions/VMRay/Data Connectors/VMRay/function.json
@@ -0,0 +1,11 @@
+{
+ "scriptFile": "app.py",
+ "bindings": [
+ {
+ "name": "mytimer",
+ "type": "timerTrigger",
+ "direction": "in",
+ "schedule": "%Polling%"
+ }
+ ]
+}
diff --git a/Solutions/VMRay/Data Connectors/VMRay/state_manager.py b/Solutions/VMRay/Data Connectors/VMRay/state_manager.py
new file mode 100644
index 00000000000..bcb18f190af
--- /dev/null
+++ b/Solutions/VMRay/Data Connectors/VMRay/state_manager.py
@@ -0,0 +1,65 @@
+"""
+StateManager of Azure Function App
+"""
+from azure.core.exceptions import ResourceNotFoundError
+from azure.storage.fileshare import ShareClient, ShareFileClient
+
+
+class StateManager:
+ """
+ Manages state persistence using Azure File Share storage.
+
+ Parameters
+ ----------
+ connection_string : str
+ Azure Storage account connection string.
+ share_name : str, optional
+ Name of the Azure File Share to use (default is "funcstatemarkershare").
+ file_path : str, optional
+ Path of the file within the share to store the marker (default is "funcstatemarkerfile").
+ """
+
+ def __init__(
+ self,
+ connection_string,
+ share_name="funcstatemarkershare",
+ file_path="funcstatemarkerfile",
+ ):
+ self.share_cli = ShareClient.from_connection_string(
+ conn_str=connection_string, share_name=share_name, is_emulated=True
+ )
+ self.file_cli = ShareFileClient.from_connection_string(
+ conn_str=connection_string,
+ share_name=share_name,
+ file_path=file_path,
+ is_emulated=True,
+ )
+
+ def post(self, marker_text: str):
+ """
+ Saves the given marker text to the Azure File Share.
+
+ Parameters
+ ----------
+ marker_text : str
+ The content (typically a timestamp) to store in the state file.
+ """
+ try:
+ self.file_cli.upload_file(marker_text)
+ except ResourceNotFoundError:
+ self.share_cli.create_share()
+ self.file_cli.upload_file(marker_text)
+
+ def get(self):
+ """
+ Retrieves the stored marker text from the Azure File Share.
+
+ Returns
+ -------
+ str or None
+ The stored marker text.
+ """
+ try:
+ return self.file_cli.download_file().readall().decode()
+ except ResourceNotFoundError:
+ return None
diff --git a/Solutions/VMRay/Data Connectors/VMRay/utils.py b/Solutions/VMRay/Data Connectors/VMRay/utils.py
new file mode 100644
index 00000000000..94e06930a26
--- /dev/null
+++ b/Solutions/VMRay/Data Connectors/VMRay/utils.py
@@ -0,0 +1,592 @@
+"""
+Core Logic of Function App
+"""
+
+# pylint: disable=logging-fstring-interpolation
+import logging
+import re
+import uuid
+from datetime import datetime, timezone, timedelta
+from os import environ
+from time import sleep
+from typing import Dict, Optional, Union
+
+import requests
+
+from .const import (
+ CONFIDENCE,
+ HASH_TYPE_LIST,
+ IPV4REGEX,
+ IPV6REGEX,
+ RETRY_STATUS_CODE,
+ SENTINEL_API,
+ VMRay_CONFIG,
+)
+from .state_manager import StateManager
+from .vmray_api import VMRay
+
+FETCHED_SAMPLE_IOCS = {}
+
+INDICATOR_LIST = []
+
+vmray = VMRay(logging)
+state = StateManager(environ.get("AzureWebJobsStorage"))
+
+def get_last_saved_timestamp() -> Optional[int]:
+ """
+ Retrieves the last saved Unix timestamp from persistent state.
+
+ Checks the system state for the last recorded timestamp of a previous
+ successful operation. If no timestamp exists (e.g., on the first run),
+ returns None.
+
+ Returns
+ -------
+ Optional[int]
+ The Unix timestamp of the last saved run, or None if not available.
+ """
+ last_run_date_time = state.get()
+ logging.debug("Last saved timestamp is %s", last_run_date_time)
+
+ return last_run_date_time if last_run_date_time else None
+
+
+def save_checkpoint(timestamp: str) -> None:
+ """
+ Saves the provided timestamp as the current system checkpoint.
+
+ Parameters
+ ----------
+ timestamp : str
+ The timestamp representing when the checkpoint is created.
+
+ Returns
+ -------
+ None
+ """
+ state.post(timestamp)
+
+
+def get_submission(verdict: list, from_time: str, to_time: str) -> list:
+ """
+ Retrieves submissions filtered by verdict and a specified time range.
+
+ Parameters
+ ----------
+ verdict : list of str
+ A list of verdict strings used to filter submissions
+ from_time : str
+ Start point (ISO 8601 format).
+ to_time : str
+ End point (ISO 8601 format).
+
+ Returns
+ -------
+ list of dict
+ A list of submission dictionaries.
+ """
+ submission_response = []
+ try:
+ logging.info(f"Fetching submission from {from_time}~{to_time}")
+ for ver in verdict:
+ params = {
+ "submission_finish_time": f"{from_time}~{to_time}",
+ "submission_verdict": ver.strip().lower(),
+ }
+ response = vmray.retry_request("GET", "/rest/submission", param=params)
+ submission_response.extend(response)
+ return submission_response
+ except Exception as err:
+ logging.error(f"Error {err}")
+ return submission_response
+
+
+def get_sample_ioc(sample_id: str) -> dict:
+ """
+ Retrieves IOCs for a given sample ID.
+
+ Parameters
+ ----------
+ sample_id : str
+ Unique identifier for the sample whose IOCs should be retrieved.
+
+ Returns
+ -------
+ dict
+ A dictionary containing IOCs for the specified sample ID.
+ Returns an empty dictionary if retrieval fails or no data is found.
+ """
+ try:
+ if not sample_id in FETCHED_SAMPLE_IOCS:
+ ioc_response = vmray.retry_request(
+ "GET", f"/rest/sample/{sample_id}/iocs"
+ ).get("iocs", {})
+ FETCHED_SAMPLE_IOCS[sample_id] = ioc_response
+ return ioc_response
+ return FETCHED_SAMPLE_IOCS[sample_id]
+ except Exception as err:
+ logging.error(f"Error {err}")
+ return {}
+
+
+def add_domain_indicator(
+ domains: list, sample_id: str, submission_id: str, verdicts: list
+) -> None:
+ """
+ Adds domain indicators to the global indicator list if the verdict of the
+ domains matches any of the provided verdicts.
+
+ Parameters
+ ----------
+ domains : list
+ List of domain ioc.
+ sample_id : str
+ ID of the related sample.
+ submission_id : str
+ ID of the related submission.
+ verdicts : list
+ List of accepted verdicts.
+ """
+ try:
+ accepted_verdicts = {v.lower() for v in verdicts}
+
+ for domain in domains:
+ verdict = domain.get("verdict", "").lower()
+ domain_value = domain.get("domain")
+
+ if verdict not in accepted_verdicts:
+ continue
+ pattern = f"[domain-name:value = '{domain_value}']"
+ unique_id = gen_unique_id("domain", domain_value)
+ confidence = CONFIDENCE.get(verdict, 0)
+
+ indicator_data = get_static_data(
+ unique_id,
+ domain,
+ pattern,
+ sample_id,
+ submission_id,
+ domain_value,
+ confidence,
+ )
+
+ INDICATOR_LIST.append(indicator_data)
+ except Exception as err:
+ logging.error(f"Error processing domain indicators: {err}")
+
+
+def add_file_indicators(
+ files: list, sample_id: str, submission_id: str, verdicts: list
+) -> None:
+ """
+ Processes files and adds hash-based indicators to a global list based on verdicts.
+
+ Parameters
+ ----------
+ files : list
+ List of file ioc.
+ sample_id : str
+ Unique sample id.
+ submission_id : str
+ Submission id.
+ verdicts : list
+ List of accepted verdicts.
+ """
+ try:
+ accepted_verdicts = {v.lower() for v in verdicts}
+
+ for file in files:
+ verdict = file.get("verdict", "").lower()
+ if verdict not in accepted_verdicts:
+ continue
+
+ file_hashes = file.get("hashes", [])
+ if not file_hashes:
+ logging.warning(f"No hashes found in file entry: {file}")
+ continue
+
+ hash_data = file_hashes[0]
+ filename = file.get("filename", "")
+ confidence = CONFIDENCE.get(verdict, 0)
+
+ for hash_type, key in HASH_TYPE_LIST:
+ hash_value = hash_data.get(key)
+ if not hash_value:
+ logging.warning(f"{key} not found in file: {file}")
+ continue
+
+ pattern = f"[file:hashes.'{hash_type}' = '{hash_value}']"
+ unique_id = gen_unique_id("file", hash_value)
+ label = filename or hash_value
+
+ indicator_data = get_static_data(
+ unique_id,
+ file,
+ pattern,
+ sample_id,
+ submission_id,
+ label,
+ confidence,
+ )
+
+ INDICATOR_LIST.append(indicator_data)
+
+ except Exception as err:
+ logging.error(f"Error processing file indicators: {err}")
+
+
+def check_ip(ip: str) -> str | None:
+ """
+ Determines the type of IP address based on its format.
+
+ Parameters
+ ----------
+ ip : str
+ The IP address to check.
+
+ Returns
+ -------
+ str or None
+ """
+ if re.match(IPV4REGEX, ip):
+ return "ipv4-addr"
+ if re.match(IPV6REGEX, ip):
+ return "ipv6-addr"
+
+ return None
+
+
+def add_ip_indicator(
+ ips: list, sample_id: str, submission_id: str, verdicts: list
+) -> None:
+ """
+ Adds IP indicators to the global indicator list based on verdict filtering.
+
+ Parameters
+ ----------
+ ips : list
+ List of IP.
+ sample_id : str
+ Sample ID.
+ submission_id : str
+ Submission ID.
+ verdicts : list
+ List of accepted verdicts.
+ """
+ try:
+ accepted_verdicts = {v.lower() for v in verdicts}
+
+ for ip_entry in ips:
+ verdict = ip_entry.get("verdict", "").lower()
+ ip_address = ip_entry.get("ip_address", "")
+
+ if verdict not in accepted_verdicts:
+ continue
+
+ ip_type = check_ip(ip_address)
+ if not ip_type:
+ logging.warning(f"Unrecognized IP type for address: {ip_address}")
+ continue
+
+ pattern = f"[{ip_type}:value = '{ip_address}']"
+ unique_id = gen_unique_id("ip", ip_address)
+ confidence = CONFIDENCE.get(verdict, 0)
+
+ indicator_data = get_static_data(
+ unique_id,
+ ip_entry,
+ pattern,
+ sample_id,
+ submission_id,
+ ip_address,
+ confidence,
+ )
+
+ INDICATOR_LIST.append(indicator_data)
+
+ except Exception as err:
+ logging.error(f"Error processing IP indicators: {err}")
+
+
+def add_url_indicator(urls: list, sample_id: str, submission_id: str, verdicts: list):
+ """
+ Adds URL indicators to the global indicator list (INDICATOR_LIST) based on verdict filtering.
+
+ Parameters
+ ----------
+ urls : list
+ List of URL.
+ sample_id : str
+ Sample ID.
+ submission_id : str
+ Submission ID.
+ verdicts : list
+ List of accepted verdicts.
+ """
+ try:
+ accepted_verdicts = {v.lower() for v in verdicts}
+
+ for url_entry in urls:
+ verdict = url_entry.get("verdict", "").lower()
+ url_value = url_entry.get("url", "")
+
+ if verdict not in accepted_verdicts:
+ continue
+
+ pattern = f"[url:value = '{url_value}']"
+ unique_id = gen_unique_id("url", url_value)
+ confidence = CONFIDENCE.get(verdict, 0)
+
+ indicator_data = get_static_data(
+ unique_id,
+ url_entry,
+ pattern,
+ sample_id,
+ submission_id,
+ url_value,
+ confidence,
+ )
+
+ INDICATOR_LIST.append(indicator_data)
+
+ except Exception as err:
+ logging.error(f"Error processing URL indicators: {err}")
+
+
+def gen_unique_id(
+ indicator_type: str, indicator_value: str, threat_source: str = "VMRay"
+):
+ """
+ Generates a unique identifier string for a threat indicator.
+
+ Parameters
+ ----------
+ indicator_type : str
+ The type of indicator.
+ indicator_value : str
+ The indicator value such.
+ threat_source : str, optional
+ Indicator source.
+
+ Returns
+ -------
+ str
+ Unique indicator id.
+ """
+ custom_namespace = uuid.uuid5(uuid.NAMESPACE_DNS, threat_source)
+ name_string = f"{indicator_type}:{indicator_value}"
+ indicator_uuid = uuid.uuid5(custom_namespace, name_string)
+ return f"indicator--{indicator_uuid}"
+
+
+def get_utc_time() -> str:
+ """
+ Returns the current UTC time formatted as an ISO 8601 timestamp.
+
+ Returns
+ -------
+ str
+ The current UTC time as an ISO 8601 timestamp with milliseconds,
+ e.g., '2025-06-26T14:03:12.123Z'.
+ """
+ current_time = datetime.now(timezone.utc)
+ formatted_time = (
+ current_time.strftime("%Y-%m-%dT%H:%M:%S.")
+ + f"{current_time.microsecond // 1000:03d}Z"
+ )
+ return formatted_time
+
+
+def get_static_data(
+ unique_uuid, indicator, pattern, sample_id, submission_id, name, confidence
+) -> Dict[str, Union[str, int, list]]:
+ """
+ Constructs a structured dictionary representing a static threat indicator.
+
+ Parameters
+ ----------
+ unique_uuid : str
+ A globally unique identifier for the indicator.
+ indicator : dict
+ Indicators metadata.
+ pattern : str
+ A STIX pattern string.
+ sample_id : str
+ Sample ID.
+ submission_id : str
+ Submission ID.
+ name : str
+ Name of the indicator.
+ confidence : int
+ An integer representing the confidence score (0–100) assigned to the indicator.
+
+ Returns
+ -------
+ dict
+ A dictionary representing the structured threat indicator
+ """
+ analysis = ", ".join(map(str, indicator.get("analysis_ids", [])))
+ categories = ", ".join(indicator.get("categories", []))
+ classifications = ", ".join(indicator.get("classifications", []))
+ threat_names = indicator.get("threat_names", [])
+ expiration_date = (
+ datetime.now(timezone.utc) + timedelta(days=int(VMRay_CONFIG.VALID_UNTIL))
+ ).strftime("%Y-%m-%dT%H:%M:%SZ")
+ t_type = []
+ for threat in threat_names:
+ if re.match(r"^[a-zA-Z0-9\s]+$", threat):
+ t_type.append(threat)
+ tags = [
+ f"sample_id: {sample_id}",
+ f"submission_id: {submission_id}",
+ f"threat_names: {', '.join(t_type)}",
+ f"Classifications: {classifications}",
+ ]
+
+ data = {
+ "type": "indicator",
+ "spec_version": "2.1",
+ "id": unique_uuid,
+ "created": get_utc_time(),
+ "modified": get_utc_time(),
+ "revoked": False,
+ "labels": tags,
+ "confidence": confidence,
+ "external_references": [
+ {
+ "source_name": "VMRay Threat Intelligence",
+ "description": f"Sample ID {sample_id}\nSubmission ID {submission_id}",
+ "url": f"{VMRay_CONFIG.BASE_URL}/samples/{sample_id}#summary",
+ }
+ ],
+ "name": name,
+ "description": f"Sample URL: {VMRay_CONFIG.BASE_URL}/samples/{sample_id}#summary,"
+ f"\nAnalysis IDs: {analysis},\nCategories: {categories}",
+ "indicator_types": [indicator.get("ioc_type", "")],
+ "pattern": pattern,
+ "pattern_type": "stix",
+ "pattern_version": "2.1",
+ "valid_from": get_utc_time(),
+ "valid_until": expiration_date
+ }
+ return data
+
+
+IOC_MAPPING_FUNCTION = {
+ "domains": add_domain_indicator,
+ "ips": add_ip_indicator,
+ "urls": add_url_indicator,
+ "files": add_file_indicators,
+}
+
+
+def create_indicator(indicator_data: list) -> requests.Response:
+ """
+ Creates a threat intelligence indicator in the Sentinel system.
+
+ Parameters
+ ----------
+ indicator_data : list
+ The STIX-formatted threat intelligence data to be submitted.
+
+ Returns
+ -------
+ requests.Response
+ The HTTP response from the indicator creation API call.
+
+ Raises
+ ------
+ Exception
+ Raised if the maximum retry attempts are exceeded due to specific
+ errors such as rate limiting or connection failures.
+ Exception
+ Raised for any other unexpected errors during indicator creation.
+ """
+ retry_count_429 = 0
+ retry_connection = 0
+ indicator = {
+ "sourcesystem": "VMRayThreatIntelligence",
+ "stixobjects": indicator_data,
+ }
+
+ azure_login_payload = {
+ "grant_type": "client_credentials",
+ "client_id": SENTINEL_API.APPLICATION_ID,
+ "client_secret": SENTINEL_API.APPLICATION_SECRET,
+ "resource": SENTINEL_API.RESOURCE_APPLICATION_ID_URI,
+ }
+
+ while retry_count_429 <= 3:
+ try:
+ response = requests.post(
+ url=SENTINEL_API.AUTH_URL,
+ data=azure_login_payload,
+ timeout=SENTINEL_API.TIMEOUT,
+ )
+ response.raise_for_status()
+ access_token = response.json().get("access_token")
+ headers = {
+ "Authorization": f"Bearer {access_token}",
+ "User-Agent": SENTINEL_API.USER_AGENT,
+ "Content-Type": "application/json",
+ }
+ response = requests.post(
+ SENTINEL_API.URL,
+ headers=headers,
+ json=indicator,
+ timeout=SENTINEL_API.TIMEOUT,
+ )
+ response.raise_for_status()
+ return response
+ except requests.HTTPError as herr:
+ if response.status_code in RETRY_STATUS_CODE:
+ retry_count_429 += 1
+ logging.warning(
+ f"Attempt {retry_count_429}: HTTP {response.status_code}."
+ f" Retrying after {SENTINEL_API.SLEEP}s..."
+ )
+ sleep(SENTINEL_API.SLEEP)
+ continue
+ logging.error(f"HTTPError from Sentinel API: {herr}")
+ raise Exception(herr) from herr
+ except (
+ requests.ConnectionError,
+ requests.exceptions.RequestException,
+ ) as conn_err:
+ if retry_connection < 3:
+ retry_connection += 1
+ logging.warning(
+ f"Attempt {retry_connection}: Connection error."
+ f" Retrying after {SENTINEL_API.SLEEP}s..."
+ )
+ sleep(SENTINEL_API.SLEEP)
+ continue
+ logging.error(f"Connection failed after retries: {conn_err}")
+ raise Exception(conn_err) from conn_err
+
+ except Exception as err:
+ logging.error(f"Unexpected error: {err}")
+ raise Exception(err) from err
+
+ raise Exception("Failed to create indicator after multiple retries.")
+
+
+def submit_indicator() -> bool:
+ """
+ Submit Indicator to sentinel
+
+ Returns
+ -------
+ bool
+ """
+ try:
+ logging.info(f"length of indicator {len(INDICATOR_LIST)}")
+ for i in range(0, len(INDICATOR_LIST), SENTINEL_API.MAX_TI_INDICATORS_PER_REQUEST):
+ create_indicator(
+ INDICATOR_LIST[i : i + SENTINEL_API.MAX_TI_INDICATORS_PER_REQUEST]
+ )
+ return True
+ except Exception as err:
+ logging.info(f"Error occurred during IOC creation: {err}")
+ raise
+
diff --git a/Solutions/VMRay/Data Connectors/VMRay/vmray_api.py b/Solutions/VMRay/Data Connectors/VMRay/vmray_api.py
new file mode 100644
index 00000000000..4d1d1148595
--- /dev/null
+++ b/Solutions/VMRay/Data Connectors/VMRay/vmray_api.py
@@ -0,0 +1,119 @@
+"""
+VMRay API Configurations
+"""
+
+from time import sleep
+
+from vmray.rest_api import VMRayRESTAPI, VMRayRESTAPIError
+
+from .const import RETRY_STATUS_CODE, VMRay_CONFIG
+
+
+class VMRay:
+ """
+ Wrapper class for VMRayRESTAPI modules and functions.
+ Import this class to submit samples and retrieve reports.
+ """
+
+ def __init__(self, log):
+ """
+ Initialize, authenticate and healthcheck the VMRay instance,
+ use VMRayConfig as configuration
+ :param log: logger instance
+ :return void
+ """
+ self.api = None
+ self.log = log
+
+ self.healthcheck()
+
+ def healthcheck(self):
+ """
+ Healthcheck for VMRay REST API, uses system_info endpoint
+ :raise: When healthcheck error occurred during the connection wih REST API
+ :return: boolean status of VMRay REST API
+ """
+ method = "GET"
+ url = "/rest/system_info"
+
+ try:
+ self.api = VMRayRESTAPI(
+ VMRay_CONFIG.BASE_URL,
+ VMRay_CONFIG.API_KEY,
+ connector_name=VMRay_CONFIG.CONNECTOR_NAME,
+ )
+ self.api.call(method, url)
+ self.log.info("VMRay Healthcheck is successfully.")
+ return True
+ except VMRayRESTAPIError as verr:
+ self.log.error(f"Healthcheck failed due to error in VMRay: {verr.args}")
+ raise
+ except Exception as err:
+ self.log.error(f"Healthcheck failed. Error: {err}")
+ raise
+
+ def retry_request(
+ self,
+ method,
+ url,
+ vmray_retries=VMRay_CONFIG.RETRIES,
+ backoff=VMRay_CONFIG.BACKOFF,
+ param=None,
+ ):
+ """
+ Call VMRay API with retry logic for server errors and rate-limiting.
+
+ Parameters
+ ----------
+ method : str
+ HTTP method (GET, POST, etc.)
+ url : str
+ Full API URL to call.
+ vmray_retries : int
+ Maximum number of retry attempts.
+ backoff : int
+ Time in seconds between retries.
+ param : dict
+ Request parameters or data payload.
+
+ Returns
+ -------
+ dict
+ Parsed JSON response from VMRay API.
+
+ Raises
+ ------
+ Exception
+ If all retries fail or a non-retryable error occurs.
+ """
+ attempt = 0
+
+ while attempt < vmray_retries:
+ try:
+ response = self.api.call(method, url, params=param)
+ return response
+ except VMRayRESTAPIError as err:
+ status_code = getattr(err, "status_code", None)
+ if status_code in RETRY_STATUS_CODE:
+ self.log.warning(
+ f"HTTP {status_code}: {getattr(err, 'message', 'No message')}"
+ )
+ self.log.info(
+ f"Retrying ({attempt + 1}/{vmray_retries}) after {backoff}s..."
+ )
+ sleep(backoff)
+ attempt += 1
+ continue
+ self.log.error(f"VMRay error: {err}")
+ raise
+
+ except ValueError as verr:
+ self.log.error(f"ValueError: {verr}")
+ raise
+
+ except Exception as ex:
+ self.log.error(f"Unexpected error during VMRay API call: {ex}")
+ raise
+
+ self.log.error("Failed to complete VMRay request after multiple retries.")
+ raise Exception("VMRay request failed after all retry attempts.")
diff --git a/Solutions/VMRay/Data Connectors/VMRayConn.zip b/Solutions/VMRay/Data Connectors/VMRayConn.zip
new file mode 100644
index 00000000000..902a5b79921
Binary files /dev/null and b/Solutions/VMRay/Data Connectors/VMRayConn.zip differ
diff --git a/Solutions/VMRay/Data Connectors/VMRayThreatIntelligence_FunctionApp.json b/Solutions/VMRay/Data Connectors/VMRayThreatIntelligence_FunctionApp.json
new file mode 100644
index 00000000000..849c62c83c9
--- /dev/null
+++ b/Solutions/VMRay/Data Connectors/VMRayThreatIntelligence_FunctionApp.json
@@ -0,0 +1,148 @@
+{
+ "id": "VMRay",
+ "title": "VMRayThreatIntelligence",
+ "publisher": "VMRay",
+ "descriptionMarkdown": "VMRayThreatIntelligence connector automatically generates and feeds threat intelligence for all submissions to VMRay, improving threat detection and incident response in Sentinel. This seamless integration empowers teams to proactively address emerging threats.",
+ "graphQueries": [
+ {
+ "metricName": "VMRay Threat Indicators data received",
+ "legend": "ThreatIntelligenceIndicator | where SourceSystem contains 'VMRay'",
+ "baseQuery": "ThreatIntelligenceIndicator | where SourceSystem contains 'VMRay'"
+ },
+ {
+ "metricName": "Non-VMRay Threat Indicators data received",
+ "legend": "ThreatIntelligenceIndicator | where SourceSystem !contains 'VMRay'",
+ "baseQuery": "ThreatIntelligenceIndicator | where SourceSystem !contains 'VMRay'"
+ }
+ ],
+ "sampleQueries": [
+ {
+ "description": "VMRay Based Indicators Events - All VMRay threat indicators in Microsoft Sentinel Threat Intelligence.",
+ "query": "ThreatIntelligenceIndicator\n | where SourceSystem contains 'VMRay'\n | sort by TimeGenerated desc"
+ },
+ {
+ "description": "Non-VMRay Based Indicators Events - All Non-VMRay threat indicators in Microsoft Sentinel Threat Intelligence.",
+ "query": "ThreatIntelligenceIndicator\n | where SourceSystem !contains 'VMRay'\n | sort by TimeGenerated desc"
+ }
+ ],
+ "dataTypes": [
+ {
+ "name": "ThreatIntelligenceIndicator",
+ "lastDataReceivedQuery": "ThreatIntelligenceIndicator\n | summarize Time = max(TimeGenerated)\n | where isnotempty(Time)"
+ }
+ ],
+ "connectivityCriterias": [
+ {
+ "type": "IsConnectedQuery",
+ "value": [
+ "ThreatIntelligenceIndicator\n | where SourceSystem contains 'VMRay'\n | summarize LastLogReceived = max(TimeGenerated)\n | project IsConnected = LastLogReceived > ago(30d)"
+ ]
+ },
+ {
+ "type": "IsConnectedQuery",
+ "value": [
+ "Report_links_data_CL\n | summarize LastLogReceived = max(TimeGenerated)\n | project IsConnected = LastLogReceived > ago(30d)"
+ ]
+ },
+ {
+ "type": "IsConnectedQuery",
+ "value": [
+ "ThreatIntelligenceIndicator\n | where SourceSystem !contains 'VMRay'\n | summarize LastLogReceived = max(TimeGenerated)\n | project IsConnected = LastLogReceived > ago(30d)"
+ ]
+ }
+ ],
+ "availability": {
+ "status": 1,
+ "isPreview": true
+ },
+ "permissions": {
+ "resourceProvider": [
+ {
+ "provider": "Microsoft.OperationalInsights/workspaces",
+ "permissionsDisplayText": "read and write permissions on the workspace are required.",
+ "providerDisplayName": "Workspace",
+ "scope": "Workspace",
+ "requiredPermissions": {
+ "write": true,
+ "read": true,
+ "delete": true
+ }
+ },
+ {
+ "provider": "Microsoft.OperationalInsights/workspaces/sharedKeys",
+ "permissionsDisplayText": "read permissions to shared keys for the workspace are required. [See the documentation to learn more about workspace keys](https://docs.microsoft.com/azure/azure-monitor/platform/agent-windows#obtain-workspace-id-and-key).",
+ "providerDisplayName": "Keys",
+ "scope": "Workspace",
+ "requiredPermissions": {
+ "action": true
+ }
+ }
+ ],
+ "customs": [
+ {
+ "name": "Azure Subscription",
+ "description": "Azure Subscription with owner role is required to register an application in azure active directory() and assign role of contributor to app in resource group."
+ },
+ {
+ "name": "Microsoft.Web/sites permissions",
+ "description": "Read and write permissions to Azure Functions to create a Function App is required. [See the documentation to learn more about Azure Functions](https://docs.microsoft.com/azure/azure-functions/)."
+ },
+ {
+ "name": "REST API Credentials/permissions",
+ "description": "**VMRay API Key** is required."
+ }
+ ]
+ },
+ "instructionSteps": [
+ {
+ "title": "",
+ "description": ">**NOTE:** This connector uses Azure Functions to connect to the VMRay API to pull VMRay Threat IOCs into Microsoft Sentinel. This might result in additional costs for data ingestion and for storing data in Azure Blob Storage costs. Check the [Azure Functions pricing page](https://azure.microsoft.com/pricing/details/functions/) and [Azure Blob Storage pricing page](https://azure.microsoft.com/pricing/details/storage/blobs/) for details."
+ },
+ {
+ "title": "",
+ "description": ">**(Optional Step)** Securely store workspace and API authorization key(s) or token(s) in Azure Key Vault. Azure Key Vault provides a secure mechanism to store and retrieve key values. [Follow these instructions](https://docs.microsoft.com/azure/app-service/app-service-key-vault-references) to use Azure Key Vault with an Azure Function App."
+ },
+ {
+ "instructions": [
+ {
+ "parameters": {
+ "fillWith": [
+ "WorkspaceId"
+ ],
+ "label": "Workspace ID"
+ },
+ "type": "CopyableLabel"
+ },
+ {
+ "parameters": {
+ "fillWith": [
+ "PrimaryKey"
+ ],
+ "label": "Primary Key"
+ },
+ "type": "CopyableLabel"
+ }
+ ]
+ },
+ {
+ "title": "Option 1 - Azure Resource Manager (ARM) Template for Flex Consumption Plan",
+ "description": "Use this method for automated deployment of the data connector using an ARM Template.\n\n1. Click the **Deploy to Azure** button below. \n\n\t[](https://aka.ms/sentinel-VMRay-azuredeploy)\n2. Select the preferred **Subscription**, **Resource Group** and **Location**. \n3. Enter the **Application ID**, **Tenant ID**,**Client Secret**, **VMRay API Key**, **VMRay Initial Fetch Date**, **TimeInterval** and deploy.\n4. Mark the checkbox labeled **I agree to the terms and conditions stated above**.\n5. Click **Purchase** to deploy."
+ },
+ {
+ "title": "Option 1 - Azure Resource Manager (ARM) Template for Premium Plan",
+ "description": "Use this method for automated deployment of the data connector using an ARM Template.\n\n1. Click the **Deploy to Azure** button below. \n\n\t[](https://aka.ms/sentinel-VMRay-azuredeploy)\n2. Select the preferred **Subscription**, **Resource Group** and **Location**. \n3. Enter the **Application ID**, **Tenant ID**,**Client Secret**, **VMRay API Key**, **VMRay Initial Fetch Date**, **TimeInterval** and deploy.\n4. Mark the checkbox labeled **I agree to the terms and conditions stated above**.\n5. Click **Purchase** to deploy."
+ },
+ {
+ "title": "Option 2 - Manual Deployment of Azure Functions",
+ "description": "Use the following step-by-step instructions to deploy the VMRay data connector manually with Azure Functions (Deployment via Visual Studio Code)."
+ },
+ {
+ "title": "",
+ "description": "**1. Deploy a Function App**\n\n> NOTE:You will need to [prepare VS code](https://docs.microsoft.com/azure/azure-functions/functions-create-first-function-python#prerequisites) for Azure function development.\n\n1. Download the [Azure Function App](https://aka.ms/sentinel-VMRay-functionapp) file. Extract archive to your local development computer.\n2. Start VS Code. Choose File in the main menu and select Open Folder.\n3. Select the top level folder from extracted files.\n4. Choose the Azure icon in the Activity bar, then in the **Azure: Functions** area, choose the **Deploy to function app** button.\nIf you aren't already signed in, choose the Azure icon in the Activity bar, then in the **Azure: Functions** area, choose **Sign in to Azure**\nIf you're already signed in, go to the next step.\n5. Provide the following information at the prompts:\n\n\ta. **Select folder:** Choose a folder from your workspace or browse to one that contains your function app.\n\n\tb. **Select Subscription:** Choose the subscription to use.\n\n\tc. Select **Create new Function App in Azure** (Don't choose the Advanced option)\n\n\td. **Enter a globally unique name for the function app:** Type a name that is valid in a URL path. The name you type is validated to make sure that it's unique in Azure Functions. (e.g. VMRayXXX).\n\n\te. **Select a runtime:** Choose Python 3.11.\n\n\tf. Select a location for new resources. For better performance and lower costs choose the same [region](https://azure.microsoft.com/regions/) where Microsoft sentinel is located.\n\n6. Deployment will begin. A notification is displayed after your function app is created and the deployment package is applied.\n7. Go to Azure Portal for the Function App configuration."
+ },
+ {
+ "title": "",
+ "description": "**2. Configure the Function App**\\n\\n1. In the Function App, select the Function App Name and select **Configuration**.\\n2. In the **Application settings** tab, select **+ New application setting**.\\n3. Add each of the following application settings individually, with their respective string values (case-sensitive): \\n\\tApplication ID\\n\\tTenant ID\\n\\tClient Secret\\n\\tVMRay API Key\\n\\tVMRay Initial Fetch Date\\n\\tTimeInterval - Use logAnalyticsUri to override the log analytics API endpoint for dedicated cloud. For example, for public cloud, leave the value empty; for Azure GovUS cloud environment, specify the value in the following format: `https://.ods.opinsights.azure.us`\\n3. Once all application settings have been entered, click **Save**."
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Solutions/VMRay/Data Connectors/azuredeploy_VMRayThreatIntelligenceFuncApp_AzureFunction_flex.json b/Solutions/VMRay/Data Connectors/azuredeploy_VMRayThreatIntelligenceFuncApp_AzureFunction_flex.json
new file mode 100644
index 00000000000..3beb00337de
--- /dev/null
+++ b/Solutions/VMRay/Data Connectors/azuredeploy_VMRayThreatIntelligenceFuncApp_AzureFunction_flex.json
@@ -0,0 +1,340 @@
+{
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "contentVersion": "1.0.0.0",
+ "parameters": {
+ "FunctionName": {
+ "defaultValue": "vmrayfeed",
+ "minLength": 1,
+ "maxLength": 20,
+ "type": "string"
+ },
+ "VmrayBaseURL": {
+ "type": "string",
+ "defaultValue": "https://us.cloud.vmray.com",
+ "minLength": 1
+ },
+ "VmrayAPIKey": {
+ "type": "securestring",
+ "defaultValue": "",
+ "minLength": 1
+ },
+ "VmraySampleVerdict": {
+ "type": "string",
+ "allowedValues": [
+ "Malicious",
+ "Suspicious",
+ "Malicious & Suspicious"
+ ],
+ "defaultValue": "Malicious"
+ },
+ "VmrayInitialFetchDate": {
+ "type": "string",
+ "defaultValue": "90",
+ "metadata": {
+ "description": "Please provide initial fetch interval in days"
+ }
+ },
+ "IndicatorExpirationInDays": {
+ "type": "string",
+ "defaultValue": "30",
+ "metadata": {
+ "description": "Please specify the number of days the indicator should remain valid."
+ }
+ },
+ "TimeInterval": {
+ "type": "string",
+ "allowedValues": [
+ "Every 5 min",
+ "Every 10 min",
+ "Every 60 min",
+ "Every 6 hours",
+ "Every 12 hours",
+ "Every 24 hours"
+ ],
+ "defaultValue": "Every 6 hours",
+ "metadata": {
+ "description": "Select the Interval."
+ }
+ },
+ "AzureClientID": {
+ "type": "string",
+ "minLength": 1,
+ "metadata": {
+ "description": "Enter Azure Client Id that you have created during app registration."
+ }
+ },
+ "AzureClientSecret": {
+ "type": "securestring",
+ "minLength": 1,
+ "metadata": {
+ "description": "Enter Azure Client Secret that you have created during creating the client secret."
+ }
+ },
+ "AzureTenantID": {
+ "type": "string",
+ "minLength": 1,
+ "metadata": {
+ "description": "Enter Azure Tenant Id of your Azure Active Directory."
+ }
+ },
+ "AzureWorkspaceID": {
+ "type": "string",
+ "minLength": 1,
+ "metadata": {
+ "description": "Enter workspace id Id of your Azure Active Directory."
+ }
+ },
+ "AppInsightsWorkspaceResourceID": {
+ "type": "string",
+ "metadata": {
+ "description": "Migrate Classic Application Insights to Log Analytic Workspace which is retiring by 29 Febraury 2024. Use 'Log Analytic Workspace-->Properties' blade having 'Resource ID' property value. This is a fully qualified resourceId which is in format '/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.OperationalInsights/workspaces/{workspaceName}'"
+ }
+ }
+ },
+ "variables": {
+ "FunctionName": "[concat(toLower(parameters('FunctionName')), take(uniqueString(resourceGroup().id), 3))]",
+ "StorageAccount": "[concat('vmrayti', take(uniqueString(resourceGroup().id), 3))]",
+ "storageRoleDefinitionId": "b7e6dc6d-f1e8-4753-8033-0f276bb0955b",
+ "deploymentStorageContainerName": "vmraystoragecontainer",
+ "StorageSuffix": "[environment().suffixes.storage]",
+ "PollingMap": {
+ "Every 5 min": "*/5 * * * *",
+ "Every 10 min": "*/10 * * * *",
+ "Every 60 min": "0 * * * *",
+ "Every 6 hours": "0 */6 * * *",
+ "Every 12 hours": "0 */12 * * *",
+ "Every 24 hours" : "0 0 * * *"
+ },
+ "Polling": "[variables('PollingMap')[parameters('TimeInterval')]]"
+ },
+ "resources": [
+ {
+ "type": "Microsoft.Insights/components",
+ "apiVersion": "2020-02-02",
+ "name": "[variables('FunctionName')]",
+ "location": "[resourceGroup().location]",
+ "kind": "web",
+ "properties": {
+ "Application_Type": "web",
+ "ApplicationId": "[variables('FunctionName')]",
+ "WorkspaceResourceId": "[parameters('AppInsightsWorkspaceResourceID')]"
+ }
+ },
+ {
+ "type": "Microsoft.Storage/storageAccounts",
+ "apiVersion": "2023-05-01",
+ "name": "[variables('StorageAccount')]",
+ "location": "[resourceGroup().location]",
+ "kind": "StorageV2",
+ "sku": {
+ "name": "Standard_LRS"
+ },
+ "properties": {
+ "supportsHttpsTrafficOnly": true,
+ "defaultToOAuthAuthentication": true,
+ "allowBlobPublicAccess": false
+ }
+ },
+ {
+ "type": "Microsoft.Storage/storageAccounts/blobServices",
+ "apiVersion": "2023-01-01",
+ "name": "[format('{0}/{1}', variables('StorageAccount'), 'default')]",
+ "dependsOn": [
+ "[resourceId('Microsoft.Storage/storageAccounts', variables('StorageAccount'))]"
+ ]
+ },
+ {
+ "type": "Microsoft.Storage/storageAccounts/blobServices/containers",
+ "apiVersion": "2023-01-01",
+ "name": "[format('{0}/{1}/{2}', variables('StorageAccount'), 'default', variables('deploymentStorageContainerName'))]",
+ "properties": {
+ "publicAccess": "None"
+ },
+ "dependsOn": [
+ "[resourceId('Microsoft.Storage/storageAccounts/blobServices', variables('StorageAccount'), 'default')]"
+ ]
+ },
+ {
+ "type": "Microsoft.Web/serverfarms",
+ "apiVersion": "2023-12-01",
+ "name": "[variables('FunctionName')]",
+ "location": "[resourceGroup().location]",
+ "kind": "functionapp",
+ "sku": {
+ "tier": "FlexConsumption",
+ "name": "FC1"
+ },
+ "properties": {
+ "reserved": true
+ }
+ },
+ {
+ "type": "Microsoft.Web/sites",
+ "apiVersion": "2023-12-01",
+ "name": "[variables('FunctionName')]",
+ "location": "[resourceGroup().location]",
+ "kind": "functionapp,linux",
+ "identity": {
+ "type": "SystemAssigned"
+ },
+ "properties": {
+ "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('FunctionName'))]",
+ "functionAppConfig": {
+ "deployment": {
+ "storage": {
+ "type": "blobContainer",
+ "value": "[concat(reference(resourceId('Microsoft.Storage/storageAccounts', variables('StorageAccount'))).primaryEndpoints.blob, variables('deploymentStorageContainerName'))]",
+ "authentication": {
+ "type": "SystemAssignedIdentity"
+ }
+ }
+ },
+ "scaleAndConcurrency": {
+ "maximumInstanceCount": 100,
+ "instanceMemoryMB": 4096
+ },
+ "runtime": {
+ "name": "python",
+ "version": "3.11"
+ }
+ },
+ "siteConfig": {
+ "appSettings": [
+ {
+ "name": "FUNCTIONS_EXTENSION_VERSION",
+ "value": "~4"
+ },
+ {
+ "name": "APPINSIGHTS_INSTRUMENTATIONKEY",
+ "value": "[reference(resourceId('Microsoft.insights/components', variables('FunctionName')), '2020-02-02').InstrumentationKey]"
+ },
+ {
+ "name": "APPLICATIONINSIGHTS_CONNECTION_STRING",
+ "value": "[reference(resourceId('Microsoft.Insights/components', variables('FunctionName')), '2020-02-02').ConnectionString]"
+ },
+ {
+ "name": "AzureWebJobsStorage",
+ "value": "[concat('DefaultEndpointsProtocol=https;AccountName=', toLower(variables('StorageAccount')),';AccountKey=',listKeys(resourceId('Microsoft.Storage/storageAccounts', toLower(variables('StorageAccount'))), '2019-06-01').keys[0].value, ';EndpointSuffix=',toLower(variables('StorageSuffix')))]"
+ },
+ {
+ "name": "VmrayBaseURL",
+ "value": "[parameters('VmrayBaseURL')]"
+ },
+ {
+ "name": "VmrayAPIKey",
+ "value": "[parameters('VmrayAPIKey')]"
+ },
+ {
+ "name": "VmraySampleVerdict",
+ "value": "[parameters('VmraySampleVerdict')]"
+ },
+ {
+ "name": "VmrayInitialFetchDate",
+ "value": "[parameters('VmrayInitialFetchDate')]"
+ },
+ {
+ "name": "IndicatorExpirationInDays",
+ "value": "[parameters('IndicatorExpirationInDays')]"
+ },
+ {
+ "name": "AzureClientID",
+ "value": "[parameters('AzureClientID')]"
+ },
+ {
+ "name": "AzureClientSecret",
+ "value": "[parameters('AzureClientSecret')]"
+ },
+ {
+ "name": "AzureTenantID",
+ "value": "[parameters('AzureTenantID')]"
+ },
+ {
+ "name": "AzureWorkspaceID",
+ "value": "[parameters('AzureWorkspaceID')]"
+ },
+ {
+ "name": "Polling",
+ "value": "[variables('Polling')]"
+ }
+ ]
+ }
+ },
+ "dependsOn": [
+ "[resourceId('Microsoft.Web/serverfarms', variables('FunctionName'))]",
+ "[resourceId('Microsoft.Storage/storageAccounts', variables('StorageAccount'))]",
+ "[resourceId('Microsoft.Insights/components', variables('FunctionName'))]"
+ ]
+ },
+ {
+ "type": "Microsoft.Authorization/roleAssignments",
+ "apiVersion": "2020-04-01-preview",
+ "name": "[guid(resourceId('Microsoft.Storage/storageAccounts', variables('StorageAccount')), variables('storageRoleDefinitionId'))]",
+ "scope": "[concat('Microsoft.Storage/storageAccounts', '/', variables('StorageAccount'))]",
+ "properties": {
+ "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', variables('storageRoleDefinitionId'))]",
+ "principalId": "[reference(resourceId('Microsoft.Web/sites', variables('FunctionName')), '2023-12-01', 'Full').identity.principalId]"
+ },
+ "dependsOn": [
+ "[resourceId('Microsoft.Storage/storageAccounts', variables('StorageAccount'))]",
+ "[resourceId('Microsoft.Web/sites', variables('FunctionName'))]"
+ ]
+ },
+ {
+ "type": "Microsoft.Resources/deploymentScripts",
+ "apiVersion": "2020-10-01",
+ "name": "WaitSection",
+ "location": "[resourceGroup().location]",
+ "dependsOn": [
+ "[resourceId('Microsoft.Web/sites', variables('FunctionName'))]"
+ ],
+ "kind": "AzurePowerShell",
+ "properties": {
+ "azPowerShellVersion": "7.0",
+ "scriptContent": "start-sleep -Seconds 30",
+ "cleanupPreference": "Always",
+ "retentionInterval": "PT1H"
+ }
+ },
+ {
+ "type": "Microsoft.Web/sites/extensions",
+ "apiVersion": "2022-09-01",
+ "name": "[format('{0}/{1}', variables('FunctionName'), 'onedeploy')]",
+ "dependsOn": [
+ "WaitSection"
+ ],
+ "properties": {
+ "packageUri": "https://github.com/vmray/ms-sentinel/raw/refs/heads/main/VMRayThreatIntelligence/FlexConsumptionPlan/VMRayConn.zip",
+ "remoteBuild": true
+ }
+ },
+ {
+ "type": "Microsoft.Storage/storageAccounts/fileServices",
+ "apiVersion": "2019-06-01",
+ "name": "[concat(variables('StorageAccount'), '/default')]",
+ "dependsOn": [
+ "[resourceId('Microsoft.Storage/storageAccounts', tolower(variables('StorageAccount')))]"
+ ],
+ "sku": {
+ "name": "Standard_LRS",
+ "tier": "Standard"
+ },
+ "properties": {
+ "cors": {
+ "corsRules": []
+ }
+ }
+ },
+ {
+ "type": "Microsoft.Storage/storageAccounts/fileServices/shares",
+ "apiVersion": "2019-06-01",
+ "name": "[concat(variables('StorageAccount'), '/default/', tolower(variables('StorageAccount')))]",
+ "dependsOn": [
+ "[resourceId('Microsoft.Storage/storageAccounts/fileServices', variables('StorageAccount'), 'default')]",
+ "[resourceId('Microsoft.Storage/storageAccounts', variables('StorageAccount'))]"
+ ],
+ "properties": {
+ "shareQuota": 5120
+ }
+ }
+ ]
+}
diff --git a/Solutions/VMRay/Data Connectors/azuredeploy_VMRayThreatIntelligenceFuncApp_AzureFunction_premium.json b/Solutions/VMRay/Data Connectors/azuredeploy_VMRayThreatIntelligenceFuncApp_AzureFunction_premium.json
new file mode 100644
index 00000000000..6dd234f62ec
--- /dev/null
+++ b/Solutions/VMRay/Data Connectors/azuredeploy_VMRayThreatIntelligenceFuncApp_AzureFunction_premium.json
@@ -0,0 +1,334 @@
+{
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "contentVersion": "1.0.0.0",
+ "parameters": {
+ "functionAppName": {
+ "defaultValue": "vmrayfeed",
+ "minLength": 1,
+ "maxLength": 20,
+ "type": "string"
+ },
+ "storageAccountType": {
+ "type": "string",
+ "defaultValue": "Standard_LRS",
+ "allowedValues": [
+ "Standard_LRS",
+ "Standard_GRS",
+ "Standard_RAGRS"
+ ],
+ "metadata": {
+ "description": "Storage Account type"
+ }
+ },
+ "functionAppPlanSku": {
+ "type": "string",
+ "defaultValue": "EP1",
+ "allowedValues": [
+ "EP1",
+ "EP2",
+ "EP3"
+ ],
+ "metadata": {
+ "description": "Specifies the Azure Function hosting plan SKU."
+ }
+ },
+ "VmrayBaseURL": {
+ "type": "string",
+ "defaultValue": "https://us.cloud.vmray.com",
+ "minLength": 1
+ },
+ "VmrayAPIKey": {
+ "type": "securestring",
+ "defaultValue": "",
+ "minLength": 1
+ },
+ "VmraySampleVerdict": {
+ "type": "string",
+ "allowedValues": [
+ "Malicious",
+ "Suspicious",
+ "Malicious & Suspicious"
+ ],
+ "defaultValue": "Malicious"
+ },
+ "VmrayInitialFetchDate": {
+ "type": "string",
+ "defaultValue": "90",
+ "metadata": {
+ "description": "Please provide initial fetch interval in days"
+ }
+ },
+ "IndicatorExpirationInDays": {
+ "type": "string",
+ "defaultValue": "30",
+ "metadata": {
+ "description": "Please specify the number of days the indicator should remain valid."
+ }
+ },
+ "TimeInterval": {
+ "type": "string",
+ "allowedValues": [
+ "Every 5 min",
+ "Every 10 min",
+ "Every 60 min",
+ "Every 6 hours",
+ "Every 12 hours",
+ "Every 24 hours"
+ ],
+ "defaultValue": "Every 6 hours",
+ "metadata": {
+ "description": "Select the Interval."
+ }
+ },
+ "AzureClientID": {
+ "type": "string",
+ "minLength": 1,
+ "metadata": {
+ "description": "Enter Azure Client Id that you have created during app registration."
+ }
+ },
+ "AzureClientSecret": {
+ "type": "securestring",
+ "minLength": 1,
+ "metadata": {
+ "description": "Enter Azure Client Secret that you have created during creating the client secret."
+ }
+ },
+ "AzureTenantID": {
+ "type": "string",
+ "minLength": 1,
+ "metadata": {
+ "description": "Enter Azure Tenant Id of your Azure Active Directory."
+ }
+ },
+ "AzureWorkspaceID": {
+ "type": "string",
+ "minLength": 1,
+ "metadata": {
+ "description": "Enter workspace id Id of your Azure Active Directory."
+ }
+ },
+ "AppInsightsWorkspaceResourceID": {
+ "type": "string",
+ "metadata": {
+ "description": "Migrate Classic Application Insights to Log Analytic Workspace which is retiring by 29 Febraury 2024. Use 'Log Analytic Workspace-->Properties' blade having 'Resource ID' property value. This is a fully qualified resourceId which is in format '/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.OperationalInsights/workspaces/{workspaceName}'"
+ }
+ }
+ },
+ "variables": {
+ "FunctionName": "[concat(toLower(parameters('functionAppName')), take(uniqueString(resourceGroup().id), 3))]",
+ "hostingPlanName": "[parameters('functionAppName')]",
+ "applicationInsightsName": "[concat(toLower(parameters('functionAppName')), take(uniqueString(resourceGroup().id), 3))]",
+ "deploymentStorageContainerName": "vmraystoragecontainer",
+ "storageAccountName": "[concat('vmrayti', take(uniqueString(resourceGroup().id), 3))]",
+ "PollingMap": {
+ "Every 5 min": "*/5 * * * *",
+ "Every 10 min": "*/10 * * * *",
+ "Every 60 min": "0 * * * *",
+ "Every 6 hours": "0 */6 * * *",
+ "Every 12 hours": "0 */12 * * *",
+ "Every 24 hours" : "0 0 * * *"
+ },
+ "Polling": "[variables('PollingMap')[parameters('TimeInterval')]]"
+ },
+ "resources": [
+ {
+ "type": "Microsoft.Storage/storageAccounts",
+ "apiVersion": "2022-05-01",
+ "name": "[variables('storageAccountName')]",
+ "location": "[resourceGroup().location]",
+ "sku": {
+ "name": "[parameters('storageAccountType')]"
+ },
+ "kind": "StorageV2",
+ "properties": {
+ "supportsHttpsTrafficOnly": true,
+ "defaultToOAuthAuthentication": true,
+ "allowBlobPublicAccess": false
+ }
+ },
+ {
+ "type": "Microsoft.Web/serverfarms",
+ "apiVersion": "2022-03-01",
+ "name": "[variables('hostingPlanName')]",
+ "location": "[resourceGroup().location]",
+ "sku": {
+ "tier": "ElasticPremium",
+ "name": "[parameters('functionAppPlanSku')]",
+ "family": "EP"
+ },
+ "properties": {
+ "maximumElasticWorkerCount": 20,
+ "reserved": true
+ },
+ "kind": "elastic"
+ },
+ {
+ "type": "Microsoft.Insights/components",
+ "apiVersion": "2020-02-02",
+ "name": "[variables('FunctionName')]",
+ "location": "[resourceGroup().location]",
+ "kind": "web",
+ "properties": {
+ "Application_Type": "web",
+ "ApplicationId": "[variables('FunctionName')]",
+ "WorkspaceResourceId": "[parameters('AppInsightsWorkspaceResourceID')]"
+ }
+ },
+ {
+ "type": "Microsoft.Storage/storageAccounts/blobServices",
+ "apiVersion": "2023-01-01",
+ "name": "[format('{0}/{1}', variables('storageAccountName'), 'default')]",
+ "dependsOn": [
+ "[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]"
+ ]
+ },
+ {
+ "type": "Microsoft.Storage/storageAccounts/blobServices/containers",
+ "apiVersion": "2023-01-01",
+ "name": "[format('{0}/{1}/{2}', variables('storageAccountName'), 'default', variables('deploymentStorageContainerName'))]",
+ "properties": {
+ "publicAccess": "None"
+ },
+ "dependsOn": [
+ "[resourceId('Microsoft.Storage/storageAccounts/blobServices', variables('storageAccountName'), 'default')]"
+ ]
+ },
+ {
+ "type": "Microsoft.Web/sites",
+ "apiVersion": "2023-12-01",
+ "name": "[variables('FunctionName')]",
+ "location": "[resourceGroup().location]",
+ "kind": "functionapp,linux",
+ "properties": {
+ "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName'))]",
+ "siteConfig": {
+ "linuxFxVersion": "python|3.11",
+ "appSettings": [
+ {
+ "name": "APPINSIGHTS_INSTRUMENTATIONKEY",
+ "value": "[reference(resourceId('Microsoft.Insights/components', variables('applicationInsightsName')), '2015-05-01').InstrumentationKey]"
+ },
+ {
+ "name": "AzureWebJobsStorage",
+ "value": "[format('DefaultEndpointsProtocol=https;AccountName={0};EndpointSuffix={1};AccountKey={2}', variables('storageAccountName'), environment().suffixes.storage, listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')), '2022-05-01').keys[0].value)]"
+ },
+ {
+ "name": "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING",
+ "value": "[format('DefaultEndpointsProtocol=https;AccountName={0};EndpointSuffix={1};AccountKey={2}', variables('storageAccountName'), environment().suffixes.storage, listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')), '2022-05-01').keys[0].value)]"
+ },
+ {
+ "name": "WEBSITE_CONTENTSHARE",
+ "value": "[toLower(variables('FunctionName'))]"
+ },
+ {
+ "name": "storageAccountName",
+ "value": "[variables('storageAccountName')]"
+ },
+ {
+ "name": "FUNCTIONS_EXTENSION_VERSION",
+ "value": "~4"
+ },
+ {
+ "name": "FUNCTIONS_WORKER_RUNTIME",
+ "value": "python"
+ },
+ {
+ "name": "WEBSITE_NODE_DEFAULT_VERSION",
+ "value": "~14"
+ },
+ {
+ "name": "WEBSITE_RUN_FROM_PACKAGE",
+ "value": "1"
+ },
+ {
+ "name": "VmrayBaseURL",
+ "value": "[parameters('VmrayBaseURL')]"
+ },
+ {
+ "name": "VmrayAPIKey",
+ "value": "[parameters('VmrayAPIKey')]"
+ },
+ {
+ "name": "VmraySampleVerdict",
+ "value": "[parameters('VmraySampleVerdict')]"
+ },
+ {
+ "name": "VmrayInitialFetchDate",
+ "value": "[parameters('VmrayInitialFetchDate')]"
+ },
+ {
+ "name": "IndicatorExpirationInDays",
+ "value": "[parameters('IndicatorExpirationInDays')]"
+ },
+ {
+ "name": "AzureClientID",
+ "value": "[parameters('AzureClientID')]"
+ },
+ {
+ "name": "AzureClientSecret",
+ "value": "[parameters('AzureClientSecret')]"
+ },
+ {
+ "name": "AzureTenantID",
+ "value": "[parameters('AzureTenantID')]"
+ },
+ {
+ "name": "AzureWorkspaceID",
+ "value": "[parameters('AzureWorkspaceID')]"
+ },
+ {
+ "name": "Polling",
+ "value": "[variables('Polling')]"
+ }
+ ]
+ }
+ },
+ "dependsOn": [
+ "[resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName'))]",
+ "[resourceId('Microsoft.Insights/components', variables('applicationInsightsName'))]",
+ "[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]"
+ ]
+ },
+ {
+ "type": "Microsoft.Storage/storageAccounts/fileServices",
+ "apiVersion": "2019-06-01",
+ "name": "[concat(variables('storageAccountName'), '/default')]",
+ "dependsOn": [
+ "[resourceId('Microsoft.Storage/storageAccounts', tolower(variables('storageAccountName')))]"
+ ],
+ "sku": {
+ "name": "[parameters('storageAccountType')]",
+ "tier": "ElasticPremium"
+ },
+ "properties": {
+ "cors": {
+ "corsRules": []
+ }
+ }
+ },
+ {
+ "type": "Microsoft.Web/sites/extensions",
+ "apiVersion": "2022-03-01",
+ "name": "[format('{0}/{1}', variables('FunctionName'), 'zipdeploy')]",
+ "properties": {
+ "packageUri": "https://github.com/vmray/ms-sentinel/raw/refs/heads/main/VMRayThreatIntelligence/PremiumPlan/VMRayConn.zip"
+ },
+ "dependsOn": [
+ "[resourceId('Microsoft.Web/sites', variables('FunctionName'))]"
+ ]
+ },
+ {
+ "type": "Microsoft.Storage/storageAccounts/fileServices/shares",
+ "apiVersion": "2019-06-01",
+ "name": "[concat(variables('storageAccountName'), '/default/', tolower(variables('storageAccountName')))]",
+ "dependsOn": [
+ "[resourceId('Microsoft.Storage/storageAccounts/fileServices', variables('storageAccountName'), 'default')]",
+ "[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]"
+ ],
+ "properties": {
+ "shareQuota": 5120
+ }
+ }
+ ]
+}
diff --git a/Solutions/VMRay/Data Connectors/host.json b/Solutions/VMRay/Data Connectors/host.json
new file mode 100644
index 00000000000..7bc7cf1fe09
--- /dev/null
+++ b/Solutions/VMRay/Data Connectors/host.json
@@ -0,0 +1,16 @@
+{
+ "version": "2.0",
+ "logging": {
+ "applicationInsights": {
+ "samplingSettings": {
+ "isEnabled": true,
+ "excludedTypes": "Request"
+ }
+ }
+ },
+ "extensionBundle": {
+ "id": "Microsoft.Azure.Functions.ExtensionBundle",
+ "version": "[4.*, 5.0.0)"
+ },
+ "functionTimeout": "01:00:00"
+}
diff --git a/Solutions/VMRay/Data Connectors/proxies.json b/Solutions/VMRay/Data Connectors/proxies.json
new file mode 100644
index 00000000000..13ca746ccf8
--- /dev/null
+++ b/Solutions/VMRay/Data Connectors/proxies.json
@@ -0,0 +1,4 @@
+{
+ "$schema": "http://json.schemastore.org/proxies",
+ "proxies": {}
+}
\ No newline at end of file
diff --git a/Solutions/VMRay/Data Connectors/requirements.txt b/Solutions/VMRay/Data Connectors/requirements.txt
new file mode 100644
index 00000000000..8388bfa9b34
--- /dev/null
+++ b/Solutions/VMRay/Data Connectors/requirements.txt
@@ -0,0 +1,4 @@
+azure-functions==1.23.0
+requests==2.32.3
+vmray-rest-api==6.0.0
+azure-storage-file-share==12.22.0
diff --git a/Solutions/VMRay/Data/Solution_VMRay.json b/Solutions/VMRay/Data/Solution_VMRay.json
new file mode 100644
index 00000000000..06659e0e7c2
--- /dev/null
+++ b/Solutions/VMRay/Data/Solution_VMRay.json
@@ -0,0 +1,21 @@
+{
+ "Name": "VMRay",
+ "Author": "VMRay",
+ "Logo": " ",
+ "Description": " The VMRay Connector for Microsoft Sentinel enhances security operations by providing enriched threat intelligence, enabling faster and more informed responses to security incidents. The integration has two main parts: first, URL detonation and enrichment, which provides detailed insights into suspicious URLs. Second, it automatically generates and feeds threat intelligence for all submissions to VMRay, improving threat detection and incident response in Sentinel. This seamless integration empowers teams to proactively address emerging threats.",
+ "Workbooks": [],
+ "Playbooks": [
+ "Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/azuredeploy.json",
+ "Playbooks/Submit-URL-VMRay-Analyzer/azuredeploy.json",
+ "Playbooks/VMRay-Sandbox_Outlook_Attachment/azuredeploy.json"
+ ],
+ "Data Connectors": [
+ "Data Connectors/VMRayThreatIntelligence_FunctionApp.json"
+ ],
+ "Hunting Queries": [],
+ "BasePath": "D:/GitHub/Azure-Sentinel/Solutions/VMRay",
+ "Version": "3.0.0",
+ "Metadata": "SolutionMetadata.json",
+ "TemplateSpec": true,
+ "Is1PConnector": false
+}
diff --git a/Solutions/VMRay/Images/01.png b/Solutions/VMRay/Images/01.png
new file mode 100644
index 00000000000..d70fe49d57b
Binary files /dev/null and b/Solutions/VMRay/Images/01.png differ
diff --git a/Solutions/VMRay/Images/02.png b/Solutions/VMRay/Images/02.png
new file mode 100644
index 00000000000..4484d45cb4a
Binary files /dev/null and b/Solutions/VMRay/Images/02.png differ
diff --git a/Solutions/VMRay/Images/02a.png b/Solutions/VMRay/Images/02a.png
new file mode 100644
index 00000000000..0cf43aca8c1
Binary files /dev/null and b/Solutions/VMRay/Images/02a.png differ
diff --git a/Solutions/VMRay/Images/03.png b/Solutions/VMRay/Images/03.png
new file mode 100644
index 00000000000..4f6672ed252
Binary files /dev/null and b/Solutions/VMRay/Images/03.png differ
diff --git a/Solutions/VMRay/Images/04.png b/Solutions/VMRay/Images/04.png
new file mode 100644
index 00000000000..16d580dbbe6
Binary files /dev/null and b/Solutions/VMRay/Images/04.png differ
diff --git a/Solutions/VMRay/Images/05.png b/Solutions/VMRay/Images/05.png
new file mode 100644
index 00000000000..55155d1f231
Binary files /dev/null and b/Solutions/VMRay/Images/05.png differ
diff --git a/Solutions/VMRay/Images/06.png b/Solutions/VMRay/Images/06.png
new file mode 100644
index 00000000000..8bddcdb0d98
Binary files /dev/null and b/Solutions/VMRay/Images/06.png differ
diff --git a/Solutions/VMRay/Images/07.png b/Solutions/VMRay/Images/07.png
new file mode 100644
index 00000000000..23aaa6160bd
Binary files /dev/null and b/Solutions/VMRay/Images/07.png differ
diff --git a/Solutions/VMRay/Images/08.png b/Solutions/VMRay/Images/08.png
new file mode 100644
index 00000000000..33fa3fc94b1
Binary files /dev/null and b/Solutions/VMRay/Images/08.png differ
diff --git a/Solutions/VMRay/Images/09.png b/Solutions/VMRay/Images/09.png
new file mode 100644
index 00000000000..2eef83173f8
Binary files /dev/null and b/Solutions/VMRay/Images/09.png differ
diff --git a/Solutions/VMRay/Images/10.png b/Solutions/VMRay/Images/10.png
new file mode 100644
index 00000000000..9a4186002a3
Binary files /dev/null and b/Solutions/VMRay/Images/10.png differ
diff --git a/Solutions/VMRay/Images/11.png b/Solutions/VMRay/Images/11.png
new file mode 100644
index 00000000000..22f9dddbedc
Binary files /dev/null and b/Solutions/VMRay/Images/11.png differ
diff --git a/Solutions/VMRay/Images/12.png b/Solutions/VMRay/Images/12.png
new file mode 100644
index 00000000000..dc07637c7e3
Binary files /dev/null and b/Solutions/VMRay/Images/12.png differ
diff --git a/Solutions/VMRay/Images/13.png b/Solutions/VMRay/Images/13.png
new file mode 100644
index 00000000000..0fc288e2f19
Binary files /dev/null and b/Solutions/VMRay/Images/13.png differ
diff --git a/Solutions/VMRay/Images/13a.png b/Solutions/VMRay/Images/13a.png
new file mode 100644
index 00000000000..4ec6906e639
Binary files /dev/null and b/Solutions/VMRay/Images/13a.png differ
diff --git a/Solutions/VMRay/Images/14.png b/Solutions/VMRay/Images/14.png
new file mode 100644
index 00000000000..9b7ef302fb2
Binary files /dev/null and b/Solutions/VMRay/Images/14.png differ
diff --git a/Solutions/VMRay/Images/15.png b/Solutions/VMRay/Images/15.png
new file mode 100644
index 00000000000..7fb2658f859
Binary files /dev/null and b/Solutions/VMRay/Images/15.png differ
diff --git a/Solutions/VMRay/Images/15a.png b/Solutions/VMRay/Images/15a.png
new file mode 100644
index 00000000000..6da2f543f51
Binary files /dev/null and b/Solutions/VMRay/Images/15a.png differ
diff --git a/Solutions/VMRay/Images/16.png b/Solutions/VMRay/Images/16.png
new file mode 100644
index 00000000000..8f66a6ce716
Binary files /dev/null and b/Solutions/VMRay/Images/16.png differ
diff --git a/Solutions/VMRay/Images/17.png b/Solutions/VMRay/Images/17.png
new file mode 100644
index 00000000000..0fc288e2f19
Binary files /dev/null and b/Solutions/VMRay/Images/17.png differ
diff --git a/Solutions/VMRay/Images/18.png b/Solutions/VMRay/Images/18.png
new file mode 100644
index 00000000000..98cc9a5eb2c
Binary files /dev/null and b/Solutions/VMRay/Images/18.png differ
diff --git a/Solutions/VMRay/Images/19.png b/Solutions/VMRay/Images/19.png
new file mode 100644
index 00000000000..ea2962b3169
Binary files /dev/null and b/Solutions/VMRay/Images/19.png differ
diff --git a/Solutions/VMRay/Images/20.png b/Solutions/VMRay/Images/20.png
new file mode 100644
index 00000000000..c049c183792
Binary files /dev/null and b/Solutions/VMRay/Images/20.png differ
diff --git a/Solutions/VMRay/Images/21.png b/Solutions/VMRay/Images/21.png
new file mode 100644
index 00000000000..d14f5b20705
Binary files /dev/null and b/Solutions/VMRay/Images/21.png differ
diff --git a/Solutions/VMRay/Images/22.png b/Solutions/VMRay/Images/22.png
new file mode 100644
index 00000000000..b473bd01163
Binary files /dev/null and b/Solutions/VMRay/Images/22.png differ
diff --git a/Solutions/VMRay/Images/23.png b/Solutions/VMRay/Images/23.png
new file mode 100644
index 00000000000..136edaf0f17
Binary files /dev/null and b/Solutions/VMRay/Images/23.png differ
diff --git a/Solutions/VMRay/Images/24.png b/Solutions/VMRay/Images/24.png
new file mode 100644
index 00000000000..b234e91c90c
Binary files /dev/null and b/Solutions/VMRay/Images/24.png differ
diff --git a/Solutions/VMRay/Images/24a.png b/Solutions/VMRay/Images/24a.png
new file mode 100644
index 00000000000..c751597c2db
Binary files /dev/null and b/Solutions/VMRay/Images/24a.png differ
diff --git a/Solutions/VMRay/Images/25.png b/Solutions/VMRay/Images/25.png
new file mode 100644
index 00000000000..2f7bbc967c5
Binary files /dev/null and b/Solutions/VMRay/Images/25.png differ
diff --git a/Solutions/VMRay/Images/26.png b/Solutions/VMRay/Images/26.png
new file mode 100644
index 00000000000..b67a1a4a433
Binary files /dev/null and b/Solutions/VMRay/Images/26.png differ
diff --git a/Solutions/VMRay/Images/27.png b/Solutions/VMRay/Images/27.png
new file mode 100644
index 00000000000..298e0849317
Binary files /dev/null and b/Solutions/VMRay/Images/27.png differ
diff --git a/Solutions/VMRay/Images/28.png b/Solutions/VMRay/Images/28.png
new file mode 100644
index 00000000000..284bf961582
Binary files /dev/null and b/Solutions/VMRay/Images/28.png differ
diff --git a/Solutions/VMRay/Images/28a.png b/Solutions/VMRay/Images/28a.png
new file mode 100644
index 00000000000..8e7709cb899
Binary files /dev/null and b/Solutions/VMRay/Images/28a.png differ
diff --git a/Solutions/VMRay/Images/28b.png b/Solutions/VMRay/Images/28b.png
new file mode 100644
index 00000000000..0f36632ce22
Binary files /dev/null and b/Solutions/VMRay/Images/28b.png differ
diff --git a/Solutions/VMRay/Images/29.png b/Solutions/VMRay/Images/29.png
new file mode 100644
index 00000000000..26e03ef2921
Binary files /dev/null and b/Solutions/VMRay/Images/29.png differ
diff --git a/Solutions/VMRay/Images/30.png b/Solutions/VMRay/Images/30.png
new file mode 100644
index 00000000000..ac2499117ae
Binary files /dev/null and b/Solutions/VMRay/Images/30.png differ
diff --git a/Solutions/VMRay/Images/31.png b/Solutions/VMRay/Images/31.png
new file mode 100644
index 00000000000..ffd18331453
Binary files /dev/null and b/Solutions/VMRay/Images/31.png differ
diff --git a/Solutions/VMRay/Images/32.png b/Solutions/VMRay/Images/32.png
new file mode 100644
index 00000000000..b06449abc6d
Binary files /dev/null and b/Solutions/VMRay/Images/32.png differ
diff --git a/Solutions/VMRay/Images/38.png b/Solutions/VMRay/Images/38.png
new file mode 100644
index 00000000000..eb72b78c873
Binary files /dev/null and b/Solutions/VMRay/Images/38.png differ
diff --git a/Solutions/VMRay/Images/40.png b/Solutions/VMRay/Images/40.png
new file mode 100644
index 00000000000..8155152c8cf
Binary files /dev/null and b/Solutions/VMRay/Images/40.png differ
diff --git a/Solutions/VMRay/Images/app_per.png b/Solutions/VMRay/Images/app_per.png
new file mode 100644
index 00000000000..1e56b225fa0
Binary files /dev/null and b/Solutions/VMRay/Images/app_per.png differ
diff --git a/Solutions/VMRay/Images/d1.png b/Solutions/VMRay/Images/d1.png
new file mode 100644
index 00000000000..93cf6ec77e1
Binary files /dev/null and b/Solutions/VMRay/Images/d1.png differ
diff --git a/Solutions/VMRay/Images/d2.png b/Solutions/VMRay/Images/d2.png
new file mode 100644
index 00000000000..f9f5e952e95
Binary files /dev/null and b/Solutions/VMRay/Images/d2.png differ
diff --git a/Solutions/VMRay/Images/d3.png b/Solutions/VMRay/Images/d3.png
new file mode 100644
index 00000000000..f7a14c16e52
Binary files /dev/null and b/Solutions/VMRay/Images/d3.png differ
diff --git a/Solutions/VMRay/Images/email_playbook.png b/Solutions/VMRay/Images/email_playbook.png
new file mode 100644
index 00000000000..d61759472cb
Binary files /dev/null and b/Solutions/VMRay/Images/email_playbook.png differ
diff --git a/Solutions/VMRay/Images/image011 b/Solutions/VMRay/Images/image011
new file mode 100644
index 00000000000..aa7bc032590
Binary files /dev/null and b/Solutions/VMRay/Images/image011 differ
diff --git a/Solutions/VMRay/Images/solution_overview.png b/Solutions/VMRay/Images/solution_overview.png
new file mode 100644
index 00000000000..661c1278225
Binary files /dev/null and b/Solutions/VMRay/Images/solution_overview.png differ
diff --git a/Solutions/VMRay/Images/ti_feed.png b/Solutions/VMRay/Images/ti_feed.png
new file mode 100644
index 00000000000..34856854c32
Binary files /dev/null and b/Solutions/VMRay/Images/ti_feed.png differ
diff --git a/Solutions/VMRay/Images/url_playbook.png b/Solutions/VMRay/Images/url_playbook.png
new file mode 100644
index 00000000000..842a2b23c8c
Binary files /dev/null and b/Solutions/VMRay/Images/url_playbook.png differ
diff --git a/Solutions/VMRay/Package/3.0.0.zip b/Solutions/VMRay/Package/3.0.0.zip
new file mode 100644
index 00000000000..60ed3336ac6
Binary files /dev/null and b/Solutions/VMRay/Package/3.0.0.zip differ
diff --git a/Solutions/VMRay/Package/createUiDefinition.json b/Solutions/VMRay/Package/createUiDefinition.json
new file mode 100644
index 00000000000..da304dd8a8e
--- /dev/null
+++ b/Solutions/VMRay/Package/createUiDefinition.json
@@ -0,0 +1,114 @@
+{
+ "$schema": "https://schema.management.azure.com/schemas/0.1.2-preview/CreateUIDefinition.MultiVm.json#",
+ "handler": "Microsoft.Azure.CreateUIDef",
+ "version": "0.1.2-preview",
+ "parameters": {
+ "config": {
+ "isWizard": false,
+ "basics": {
+ "description": " \n\n**Note:** Please refer to the following before installing the solution: \n\n• Review the solution [Release Notes](https://github.com/Azure/Azure-Sentinel/tree/master/Solutions/VMRay/ReleaseNotes.md)\n\n • There may be [known issues](https://aka.ms/sentinelsolutionsknownissues) pertaining to this Solution, please refer to them before installing.\n\nVMRay Threat Intelligence allows integration of intelligence-based IOC data identified by VMRay Analyzer.\n\n**Data Connectors:** 1, **Function Apps:** 1, **Playbooks:** 2\n\n[Learn more about Microsoft Sentinel](https://aka.ms/azuresentinel) | [Learn more about Solutions](https://aka.ms/azuresentinelsolutionsdoc)",
+ "subscription": {
+ "resourceProviders": [
+ "Microsoft.OperationsManagement/solutions",
+ "Microsoft.OperationalInsights/workspaces/providers/alertRules",
+ "Microsoft.Insights/workbooks",
+ "Microsoft.Logic/workflows"
+ ]
+ },
+ "location": {
+ "metadata": {
+ "hidden": "Hiding location, we get it from the log analytics workspace"
+ },
+ "visible": false
+ },
+ "resourceGroup": {
+ "allowExisting": true
+ }
+ }
+ },
+ "basics": [
+ {
+ "name": "getLAWorkspace",
+ "type": "Microsoft.Solutions.ArmApiControl",
+ "toolTip": "This filters by workspaces that exist in the Resource Group selected",
+ "condition": "[greater(length(resourceGroup().name),0)]",
+ "request": {
+ "method": "GET",
+ "path": "[concat(subscription().id,'/providers/Microsoft.OperationalInsights/workspaces?api-version=2020-08-01')]"
+ }
+ },
+ {
+ "name": "workspace",
+ "type": "Microsoft.Common.DropDown",
+ "label": "Workspace",
+ "placeholder": "Select a workspace",
+ "toolTip": "This dropdown will list only workspace that exists in the Resource Group selected",
+ "constraints": {
+ "allowedValues": "[map(filter(basics('getLAWorkspace').value, (filter) => contains(toLower(filter.id), toLower(resourceGroup().name))), (item) => parse(concat('{\"label\":\"', item.name, '\",\"value\":\"', item.name, '\"}')))]",
+ "required": true
+ },
+ "visible": true
+ }
+ ],
+ "steps": [
+ {
+ "name": "dataconnectors",
+ "label": "Data Connectors",
+ "bladeTitle": "Data Connectors",
+ "elements": [
+ {
+ "name": "dataconnectors1-text",
+ "type": "Microsoft.Common.TextBlock",
+ "options": {
+ "text": "This Solution installs the data connector for VMRay. You can get VMRay custom log data in your Microsoft Sentinel workspace. After installing the solution, configure and enable this data connector by following guidance in Manage solution view."
+ }
+ },
+ {
+ "name": "dataconnectors-link1",
+ "type": "Microsoft.Common.TextBlock",
+ "options": {
+ "link": {
+ "label": "Learn more about connecting data sources",
+ "uri": "https://docs.microsoft.com/azure/sentinel/connect-data-sources"
+ }
+ }
+ }
+ ]
+ },
+ {
+ "name": "playbooks",
+ "label": "Playbooks",
+ "subLabel": {
+ "preValidation": "Configure the playbooks",
+ "postValidation": "Done"
+ },
+ "bladeTitle": "Playbooks",
+ "elements": [
+ {
+ "name": "playbooks-text",
+ "type": "Microsoft.Common.TextBlock",
+ "options": {
+ "text": "This solution installs the Playbook templates to help implement your Security Orchestration, Automation and Response (SOAR) operations. After installing the solution, these will be deployed under Playbook Templates in the Automation blade in Microsoft Sentinel. They can be configured and managed from the Manage solution view in Content Hub."
+ }
+ },
+ {
+ "name": "playbooks-link",
+ "type": "Microsoft.Common.TextBlock",
+ "options": {
+ "link": {
+ "label": "Learn more",
+ "uri": "https://docs.microsoft.com/azure/sentinel/tutorial-respond-threats-playbook?WT.mc_id=Portal-Microsoft_Azure_CreateUIDef"
+ }
+ }
+ }
+ ]
+ }
+ ],
+ "outputs": {
+ "workspace-location": "[first(map(filter(basics('getLAWorkspace').value, (filter) => and(contains(toLower(filter.id), toLower(resourceGroup().name)),equals(filter.name,basics('workspace')))), (item) => item.location))]",
+ "location": "[location()]",
+ "workspace": "[basics('workspace')]"
+ }
+ }
+}
+
diff --git a/Solutions/VMRay/Package/mainTemplate.json b/Solutions/VMRay/Package/mainTemplate.json
new file mode 100644
index 00000000000..f283ee97d4e
--- /dev/null
+++ b/Solutions/VMRay/Package/mainTemplate.json
@@ -0,0 +1,2209 @@
+{
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "author": "VMRay",
+ "comments": "Solution template for VMRay"
+ },
+ "parameters": {
+ "location": {
+ "type": "string",
+ "minLength": 1,
+ "defaultValue": "[resourceGroup().location]",
+ "metadata": {
+ "description": "Not used, but needed to pass arm-ttk test `Location-Should-Not-Be-Hardcoded`. We instead use the `workspace-location` which is derived from the LA workspace"
+ }
+ },
+ "workspace-location": {
+ "type": "string",
+ "defaultValue": "",
+ "metadata": {
+ "description": "[concat('Region to deploy solution resources -- separate from location selection',parameters('location'))]"
+ }
+ },
+ "workspace": {
+ "defaultValue": "",
+ "type": "string",
+ "metadata": {
+ "description": "Workspace name for Log Analytics where Microsoft Sentinel is setup"
+ }
+ }
+ },
+ "variables": {
+ "_solutionName": "VMRay",
+ "_solutionVersion": "3.0.0",
+ "solutionId": "vmray.microsoft-sentinel-solution-vmray",
+ "_solutionId": "[variables('solutionId')]",
+ "VMRayEnrichment_FunctionAppConnector": "VMRayEnrichment_FunctionAppConnector",
+ "_VMRayEnrichment_FunctionAppConnector": "[variables('VMRayEnrichment_FunctionAppConnector')]",
+ "TemplateEmptyArray": "[json('[]')]",
+ "playbookVersion1": "1.0",
+ "playbookContentId1": "VMRayEnrichment_FunctionAppConnector",
+ "_playbookContentId1": "[variables('playbookContentId1')]",
+ "playbookTemplateSpecName1": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-fa-',uniquestring(variables('_playbookContentId1'))))]",
+ "workspaceResourceId": "[resourceId('microsoft.OperationalInsights/Workspaces', parameters('workspace'))]",
+ "_playbookcontentProductId1": "[concat(take(variables('_solutionId'),50),'-','fa','-', uniqueString(concat(variables('_solutionId'),'-','AzureFunction','-',variables('_playbookContentId1'),'-', variables('playbookVersion1'))))]",
+ "Submit-URL-VMRay-Analyzer": "Submit-URL-VMRay-Analyzer",
+ "_Submit-URL-VMRay-Analyzer": "[variables('Submit-URL-VMRay-Analyzer')]",
+ "playbookVersion2": "1.0",
+ "playbookContentId2": "Submit-URL-VMRay-Analyzer",
+ "_playbookContentId2": "[variables('playbookContentId2')]",
+ "playbookId2": "[resourceId('Microsoft.Logic/workflows', variables('playbookContentId2'))]",
+ "playbookTemplateSpecName2": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-pl-',uniquestring(variables('_playbookContentId2'))))]",
+ "_playbookcontentProductId2": "[concat(take(variables('_solutionId'),50),'-','pl','-', uniqueString(concat(variables('_solutionId'),'-','Playbook','-',variables('_playbookContentId2'),'-', variables('playbookVersion2'))))]",
+ "blanks": "[replace('b', 'b', '')]",
+ "VMRay-Sandbox_Outlook_Attachment": "VMRay-Sandbox_Outlook_Attachment",
+ "_VMRay-Sandbox_Outlook_Attachment": "[variables('VMRay-Sandbox_Outlook_Attachment')]",
+ "playbookVersion3": "1.0",
+ "playbookContentId3": "VMRay-Sandbox_Outlook_Attachment",
+ "_playbookContentId3": "[variables('playbookContentId3')]",
+ "playbookId3": "[resourceId('Microsoft.Logic/workflows', variables('playbookContentId3'))]",
+ "playbookTemplateSpecName3": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-pl-',uniquestring(variables('_playbookContentId3'))))]",
+ "_playbookcontentProductId3": "[concat(take(variables('_solutionId'),50),'-','pl','-', uniqueString(concat(variables('_solutionId'),'-','Playbook','-',variables('_playbookContentId3'),'-', variables('playbookVersion3'))))]",
+ "uiConfigId1": "VMRay",
+ "_uiConfigId1": "[variables('uiConfigId1')]",
+ "dataConnectorContentId1": "VMRay",
+ "_dataConnectorContentId1": "[variables('dataConnectorContentId1')]",
+ "dataConnectorId1": "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/dataConnectors', variables('_dataConnectorContentId1'))]",
+ "_dataConnectorId1": "[variables('dataConnectorId1')]",
+ "dataConnectorTemplateSpecName1": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-dc-',uniquestring(variables('_dataConnectorContentId1'))))]",
+ "dataConnectorVersion1": "1.0.0",
+ "_dataConnectorcontentProductId1": "[concat(take(variables('_solutionId'),50),'-','dc','-', uniqueString(concat(variables('_solutionId'),'-','DataConnector','-',variables('_dataConnectorContentId1'),'-', variables('dataConnectorVersion1'))))]",
+ "_solutioncontentProductId": "[concat(take(variables('_solutionId'),50),'-','sl','-', uniqueString(concat(variables('_solutionId'),'-','Solution','-',variables('_solutionId'),'-', variables('_solutionVersion'))))]"
+ },
+ "resources": [
+ {
+ "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates",
+ "apiVersion": "2023-04-01-preview",
+ "name": "[variables('playbookTemplateSpecName1')]",
+ "location": "[parameters('workspace-location')]",
+ "dependsOn": [
+ "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]"
+ ],
+ "properties": {
+ "description": "VMRayEnrichment_FunctionAppConnector Playbook with template version 3.0.0",
+ "mainTemplate": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "contentVersion": "[variables('playbookVersion1')]",
+ "parameters": {
+ "vmrayBaseURL": {
+ "type": "string",
+ "defaultValue": "https://us.cloud.vmray.com",
+ "minLength": 1
+ },
+ "vmrayAPIKey": {
+ "type": "securestring",
+ "defaultValue": "",
+ "minLength": 1
+ },
+ "Resubmit": {
+ "type": "bool",
+ "defaultValue": true,
+ "metadata": {
+ "description": "If true, the sample will be resubmitted to VMRay analyser, even if the sample hash was found in VMRay."
+ }
+ },
+ "FunctionAppName": {
+ "defaultValue": "vmrayenrich",
+ "type": "string"
+ },
+ "AppInsightsWorkspaceResourceID": {
+ "type": "string",
+ "metadata": {
+ "description": "Migrate Classic Application Insights to Log Analytic Workspace which is retiring by 29 Febraury 2024. Use 'Log Analytic Workspace-->Properties' blade having 'Resource ID' property value. This is a fully qualified resourceId which is in format '/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.OperationalInsights/workspaces/{workspaceName}'"
+ }
+ }
+ },
+ "variables": {
+ "FunctionName": "[[concat(toLower(parameters('FunctionAppName')), take(uniqueString(resourceGroup().id), 3))]",
+ "StorageSuffix": "[[environment().suffixes.storage]",
+ "workspace-location-inline": "[concat('[resourceGroup().locatio', 'n]')]",
+ "playbookContentId1": "VMRayEnrichment_FunctionAppConnector",
+ "playbookId1": "[[resourceId('Microsoft.Logic/workflows', variables('playbookContentId1'))]",
+ "workspace-name": "[parameters('workspace')]",
+ "workspaceResourceId": "[[resourceId('microsoft.OperationalInsights/Workspaces', variables('workspace-name'))]"
+ },
+ "resources": [
+ {
+ "type": "Microsoft.Insights/components",
+ "apiVersion": "2020-02-02",
+ "name": "[[variables('FunctionName')]",
+ "location": "[[variables('workspace-location-inline')]",
+ "kind": "web",
+ "properties": {
+ "Application_Type": "web",
+ "ApplicationId": "[[variables('FunctionName')]",
+ "WorkspaceResourceId": "[[parameters('AppInsightsWorkspaceResourceID')]"
+ }
+ },
+ {
+ "type": "Microsoft.Storage/storageAccounts",
+ "apiVersion": "2019-06-01",
+ "name": "[[tolower(variables('FunctionName'))]",
+ "location": "[[variables('workspace-location-inline')]",
+ "sku": {
+ "name": "Standard_LRS",
+ "tier": "Standard"
+ },
+ "kind": "StorageV2",
+ "properties": {
+ "networkAcls": {
+ "bypass": "AzureServices",
+ "virtualNetworkRules": "[variables('TemplateEmptyArray')]",
+ "ipRules": "[variables('TemplateEmptyArray')]",
+ "defaultAction": "Allow"
+ },
+ "supportsHttpsTrafficOnly": true,
+ "encryption": {
+ "services": {
+ "file": {
+ "keyType": "Account",
+ "enabled": true
+ },
+ "blob": {
+ "keyType": "Account",
+ "enabled": true
+ }
+ },
+ "keySource": "Microsoft.Storage"
+ }
+ }
+ },
+ {
+ "type": "Microsoft.Storage/storageAccounts/blobServices",
+ "apiVersion": "2019-06-01",
+ "name": "[[concat(variables('FunctionName'), '/default')]",
+ "dependsOn": [
+ "[[resourceId('Microsoft.Storage/storageAccounts', tolower(variables('FunctionName')))]"
+ ],
+ "sku": {
+ "name": "Standard_LRS",
+ "tier": "Standard"
+ },
+ "properties": {
+ "cors": {
+ "corsRules": "[variables('TemplateEmptyArray')]"
+ },
+ "deleteRetentionPolicy": {
+ "enabled": false
+ }
+ }
+ },
+ {
+ "type": "Microsoft.Storage/storageAccounts/fileServices",
+ "apiVersion": "2019-06-01",
+ "name": "[[concat(variables('FunctionName'), '/default')]",
+ "dependsOn": [
+ "[[resourceId('Microsoft.Storage/storageAccounts', tolower(variables('FunctionName')))]"
+ ],
+ "sku": {
+ "name": "Standard_LRS",
+ "tier": "Standard"
+ },
+ "properties": {
+ "cors": {
+ "corsRules": "[variables('TemplateEmptyArray')]"
+ }
+ }
+ },
+ {
+ "type": "Microsoft.Web/sites",
+ "apiVersion": "2018-11-01",
+ "name": "[[variables('FunctionName')]",
+ "location": "[[variables('workspace-location-inline')]",
+ "dependsOn": [
+ "[[resourceId('Microsoft.Storage/storageAccounts', tolower(variables('FunctionName')))]",
+ "[[resourceId('Microsoft.Insights/components', variables('FunctionName'))]"
+ ],
+ "kind": "functionapp,linux",
+ "identity": {
+ "type": "SystemAssigned"
+ },
+ "properties": {
+ "name": "[[variables('FunctionName')]",
+ "httpsOnly": true,
+ "clientAffinityEnabled": true,
+ "alwaysOn": true,
+ "reserved": true,
+ "siteConfig": {
+ "linuxFxVersion": "python|3.11"
+ }
+ },
+ "resources": [
+ {
+ "apiVersion": "2018-11-01",
+ "type": "config",
+ "name": "appsettings",
+ "dependsOn": [
+ "[[concat('Microsoft.Web/sites/', variables('FunctionName'))]"
+ ],
+ "properties": {
+ "FUNCTIONS_EXTENSION_VERSION": "~4",
+ "FUNCTIONS_WORKER_RUNTIME": "python",
+ "APPINSIGHTS_INSTRUMENTATIONKEY": "[[reference(resourceId('Microsoft.insights/components', variables('FunctionName')), '2020-02-02').InstrumentationKey]",
+ "APPLICATIONINSIGHTS_CONNECTION_STRING": "[[reference(resourceId('microsoft.insights/components', variables('FunctionName')), '2020-02-02').ConnectionString]",
+ "AzureWebJobsStorage": "[[concat('DefaultEndpointsProtocol=https;AccountName=', toLower(variables('FunctionName')),';AccountKey=',listKeys(resourceId('Microsoft.Storage/storageAccounts', toLower(variables('FunctionName'))), '2023-04-01').keys[0].value, ';EndpointSuffix=',toLower(variables('StorageSuffix')))]",
+ "vmrayBaseURL": "[[parameters('vmrayBaseURL')]",
+ "vmrayAPIKey": "[[parameters('vmrayAPIKey')]",
+ "Resubmit": "[[parameters('Resubmit')]",
+ "WEBSITE_RUN_FROM_PACKAGE": "https://github.com/vmray/ms-sentinel/raw/refs/heads/main/VMRayEnrichment/VMRayEnrichemntFuncApp.zip?raw=true"
+ }
+ }
+ ]
+ },
+ {
+ "type": "Microsoft.Storage/storageAccounts/blobServices/containers",
+ "apiVersion": "2019-06-01",
+ "name": "[[concat(variables('FunctionName'), '/default/azure-webjobs-hosts')]",
+ "dependsOn": [
+ "[[resourceId('Microsoft.Storage/storageAccounts/blobServices', variables('FunctionName'), 'default')]",
+ "[[resourceId('Microsoft.Storage/storageAccounts', variables('FunctionName'))]"
+ ],
+ "properties": {
+ "publicAccess": "None"
+ }
+ },
+ {
+ "type": "Microsoft.Storage/storageAccounts/blobServices/containers",
+ "apiVersion": "2019-06-01",
+ "name": "[[concat(variables('FunctionName'), '/default/azure-webjobs-secrets')]",
+ "dependsOn": [
+ "[[resourceId('Microsoft.Storage/storageAccounts/blobServices', variables('FunctionName'), 'default')]",
+ "[[resourceId('Microsoft.Storage/storageAccounts', variables('FunctionName'))]"
+ ],
+ "properties": {
+ "publicAccess": "None"
+ }
+ },
+ {
+ "type": "Microsoft.Storage/storageAccounts/fileServices/shares",
+ "apiVersion": "2019-06-01",
+ "name": "[[concat(variables('FunctionName'), '/default/', tolower(variables('FunctionName')))]",
+ "dependsOn": [
+ "[[resourceId('Microsoft.Storage/storageAccounts/fileServices', variables('FunctionName'), 'default')]",
+ "[[resourceId('Microsoft.Storage/storageAccounts', variables('FunctionName'))]"
+ ],
+ "properties": {
+ "shareQuota": 5120
+ }
+ },
+ {
+ "type": "Microsoft.OperationalInsights/workspaces/providers/metadata",
+ "apiVersion": "2022-01-01-preview",
+ "name": "[[concat(variables('workspace-name'),'/Microsoft.SecurityInsights/',concat('AzureFunction-', last(split(variables('playbookId1'),'/'))))]",
+ "properties": {
+ "parentId": "[[variables('playbookId1')]",
+ "contentId": "[variables('_playbookContentId1')]",
+ "kind": "AzureFunction",
+ "version": "[variables('playbookVersion1')]",
+ "source": {
+ "kind": "Solution",
+ "name": "VMRay",
+ "sourceId": "[variables('_solutionId')]"
+ },
+ "author": {
+ "name": "VMRay"
+ },
+ "support": {
+ "name": "VMRay",
+ "email": "support@vmray.com",
+ "tier": "Partner",
+ "link": "https://www.vmray.com/contact/customer-support/"
+ }
+ }
+ }
+ ]
+ },
+ "packageKind": "Solution",
+ "packageVersion": "[variables('_solutionVersion')]",
+ "packageName": "[variables('_solutionName')]",
+ "packageId": "[variables('_solutionId')]",
+ "contentSchemaVersion": "3.0.0",
+ "contentId": "[variables('_playbookContentId1')]",
+ "contentKind": "AzureFunction",
+ "displayName": "VMRayEnrichment_FunctionAppConnector",
+ "contentProductId": "[variables('_playbookcontentProductId1')]",
+ "id": "[variables('_playbookcontentProductId1')]",
+ "version": "[variables('playbookVersion1')]"
+ }
+ },
+ {
+ "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates",
+ "apiVersion": "2023-04-01-preview",
+ "name": "[variables('playbookTemplateSpecName2')]",
+ "location": "[parameters('workspace-location')]",
+ "dependsOn": [
+ "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]"
+ ],
+ "properties": {
+ "description": "Submit-URL-VMRay-Analyzer Playbook with template version 3.0.0",
+ "mainTemplate": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "contentVersion": "[variables('playbookVersion2')]",
+ "parameters": {
+ "PlaybookName": {
+ "defaultValue": "Submit-URL-VMRay-Analyzer",
+ "type": "string",
+ "metadata": {
+ "description": "Name of the Logic App/Playbook"
+ }
+ },
+ "WorkspaceID": {
+ "defaultValue": "",
+ "type": "String",
+ "metadata": {
+ "description": "Log Analytics Workspace ID"
+ }
+ },
+ "FunctionAppName": {
+ "defaultValue": "vmrayenrich",
+ "type": "string",
+ "metadata": {
+ "description": "Name of the FunctionApp"
+ }
+ }
+ },
+ "variables": {
+ "functionappName": "[[concat(toLower(parameters('FunctionAppName')), take(uniqueString(resourceGroup().id), 3))]",
+ "AzureSentinelConnectionName": "[[concat('azuresentinel-', parameters('PlaybookName'))]",
+ "connection-1": "[[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', variables('workspace-location-inline'), '/managedApis/azuresentinel')]",
+ "_connection-1": "[[variables('connection-1')]",
+ "workspace-location-inline": "[concat('[resourceGroup().locatio', 'n]')]",
+ "workspace-name": "[parameters('workspace')]",
+ "workspaceResourceId": "[[resourceId('microsoft.OperationalInsights/Workspaces', variables('workspace-name'))]"
+ },
+ "resources": [
+ {
+ "type": "Microsoft.Web/connections",
+ "apiVersion": "2016-06-01",
+ "name": "[[variables('AzureSentinelConnectionName')]",
+ "location": "[[variables('workspace-location-inline')]",
+ "kind": "V1",
+ "properties": {
+ "displayName": "[[variables('AzureSentinelConnectionName')]",
+ "parameterValueType": "Alternative",
+ "api": {
+ "id": "[[variables('_connection-1')]"
+ }
+ }
+ },
+ {
+ "type": "Microsoft.Logic/workflows",
+ "apiVersion": "2017-07-01",
+ "name": "[[parameters('PlaybookName')]",
+ "location": "[[variables('workspace-location-inline')]",
+ "identity": {
+ "type": "SystemAssigned"
+ },
+ "dependsOn": [
+ "[[resourceId('Microsoft.Web/connections', variables('AzureSentinelConnectionName'))]"
+ ],
+ "properties": {
+ "state": "Enabled",
+ "definition": {
+ "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#",
+ "contentVersion": "1.0.0.0",
+ "parameters": {
+ "Indicator_Expiration_In_Days": {
+ "defaultValue": "30",
+ "type": "String"
+ },
+ "$connections": {
+ "type": "Object"
+ }
+ },
+ "triggers": {
+ "Microsoft_Sentinel_incident": {
+ "type": "ApiConnectionWebhook",
+ "inputs": {
+ "host": {
+ "connection": {
+ "name": "@parameters('$connections')['azuresentinel']['connectionId']"
+ }
+ },
+ "body": {
+ "callback_url": "@listCallbackUrl()"
+ },
+ "path": "/incident-creation"
+ }
+ }
+ },
+ "actions": {
+ "Entities_-_Get_URLs": {
+ "runAfter": {
+ "Sorted_VMRay_VTIS": [
+ "Succeeded"
+ ]
+ },
+ "type": "ApiConnection",
+ "inputs": {
+ "host": {
+ "connection": {
+ "name": "@parameters('$connections')['azuresentinel']['connectionId']"
+ }
+ },
+ "method": "post",
+ "body": "@triggerBody()?['object']?['properties']?['relatedEntities']",
+ "path": "/entities/url"
+ }
+ },
+ "For_each_URL": {
+ "foreach": "@body('Entities_-_Get_URLs')?['URLs']",
+ "actions": {
+ "UplaodURL": {
+ "type": "Function",
+ "inputs": {
+ "body": {
+ "url": "@items('For_each_URL')['url']",
+ "tags": [
+ "url_submission_logic_app"
+ ]
+ },
+ "function": {
+ "id": "[[concat('/subscriptions/', subscription().subscriptionId, '/resourceGroups/', resourceGroup().name, '/providers/Microsoft.Web/sites/', variables('Functionappname'), '/functions/UplaodURL')]"
+ }
+ }
+ },
+ "Check_if_the_status_is_200": {
+ "actions": {
+ "For_each_submission": {
+ "foreach": "@body('UplaodURL')?['vmray_submission']",
+ "actions": {
+ "Set_Submission_Status": {
+ "type": "SetVariable",
+ "inputs": {
+ "name": "submission_status",
+ "value": false
+ }
+ },
+ "Wait_untill_submission_is_completed": {
+ "actions": {
+ "Delay_By_2_minutes": {
+ "type": "Wait",
+ "inputs": {
+ "interval": {
+ "count": 2,
+ "unit": "Minute"
+ }
+ }
+ },
+ "GetVMRaySubmission": {
+ "runAfter": {
+ "Delay_By_2_minutes": [
+ "Succeeded"
+ ]
+ },
+ "type": "Function",
+ "inputs": {
+ "body": {
+ "submission_id": "@items('For_each_submission')['SubmissionID']"
+ },
+ "function": {
+ "id": "[[concat('/subscriptions/', subscription().subscriptionId, '/resourceGroups/', resourceGroup().name, '/providers/Microsoft.Web/sites/', variables('Functionappname'), '/functions/GetVMRaySubmission')]"
+ }
+ }
+ },
+ "Check_if_submission_is_completed": {
+ "actions": {
+ "Set_Submission_Status_to_True": {
+ "type": "SetVariable",
+ "inputs": {
+ "name": "submission_status",
+ "value": true
+ }
+ },
+ "GetVMRaySample": {
+ "runAfter": {
+ "Set_Submission_Status_to_True": [
+ "Succeeded"
+ ]
+ },
+ "type": "Function",
+ "inputs": {
+ "body": {
+ "sample_id": "@body('GetVMRaySubmission')?['submission_sample_id']"
+ },
+ "function": {
+ "id": "[[concat('/subscriptions/', subscription().subscriptionId, '/resourceGroups/', resourceGroup().name, '/providers/Microsoft.Web/sites/', variables('Functionappname'), '/functions/GetVMRaySample')]"
+ }
+ }
+ },
+ "Check_If_Sample_Verdict_is_Suspicious_or_Malicious": {
+ "actions": {
+ "Set_Submission_Report_Variable": {
+ "type": "SetVariable",
+ "inputs": {
+ "name": "submission_report",
+ "value": [
+ {
+ "Submission Status": "@body('GetVMRaySubmission')['submission_status']",
+ "Sample Verdict": "@body('GetVMRaySample')['sample_verdict']",
+ "Classification": "@body('GetVMRaySample')['sample_classifications']",
+ "Threat Names": "@body('GetVMRaySample')['sample_threat_names']",
+ "Link to the Full Report": "@body('GetVMRaySample')['sample_webif_url']",
+ "Submission ID": "@body('GetVMRaySubmission')['submission_id']",
+ "Sample ID": "@body('GetVMRaySample')['sample_id']",
+ "Notes": "@body('GetVMRaySample')['sample_verdict_reason_description']"
+ }
+ ]
+ }
+ },
+ "Create_HTML_table": {
+ "runAfter": {
+ "Set_Submission_Report_Variable": [
+ "Succeeded"
+ ]
+ },
+ "type": "Table",
+ "inputs": {
+ "from": "@variables('submission_report')",
+ "format": "HTML"
+ }
+ },
+ "Add_comment_to_incident_(V3)": {
+ "runAfter": {
+ "Create_HTML_table": [
+ "Succeeded"
+ ]
+ },
+ "type": "ApiConnection",
+ "inputs": {
+ "host": {
+ "connection": {
+ "name": "@parameters('$connections')['azuresentinel']['connectionId']"
+ }
+ },
+ "method": "post",
+ "body": {
+ "incidentArmId": "@triggerBody()?['object']?['id']",
+ "message": "VMRay Submission Details for URL: @{replace(replace(items('For_each_URL')?['url'],'https://',''),'http://','')}
@{body('Create_HTML_table')}@{body('Create_HTML_table_2')}
"
+ },
+ "path": "/Incidents/Comment"
+ }
+ },
+ "GetVMRayIOCs": {
+ "runAfter": {
+ "Add_comment_to_incident_(V3)": [
+ "Succeeded"
+ ]
+ },
+ "type": "Function",
+ "inputs": {
+ "body": {
+ "sample_id": "@body('GetVMRaySample')['sample_id']",
+ "submission_id": "@body('GetVMRaySubmission')['submission_id']",
+ "sample_verdict": [
+ "malicious",
+ "suspicious"
+ ],
+ "incident_id": "@triggerBody()?['object']?['properties']?['incidentNumber']",
+ "valid_until": "@parameters('Indicator_Expiration_In_Days')"
+ },
+ "function": {
+ "id": "[[concat('/subscriptions/', subscription().subscriptionId, '/resourceGroups/', resourceGroup().name, '/providers/Microsoft.Web/sites/', variables('Functionappname'), '/functions/GetVMRayIOCs')]"
+ }
+ }
+ },
+ "Check_if_VMRay_has_any_indicators_for_the_sample": {
+ "type": "If",
+ "expression": {
+ "and": [
+ {
+ "greater": [
+ "@length(body('GetVMRayIOCs')['custom_resp'])",
+ 0
+ ]
+ }
+ ]
+ },
+ "actions": {
+ "Threat_Intelligence_-_Upload_Indicators_of_Compromise_(V2)_(Preview)": {
+ "type": "ApiConnection",
+ "inputs": {
+ "host": {
+ "connection": {
+ "name": "@parameters('$connections')['azuresentinel']['connectionId']"
+ }
+ },
+ "method": "post",
+ "body": {
+ "sourcesystem": "VMRayThreatIntelligence",
+ "indicators": "@body('GetVMRayIOCs')['custom_resp']"
+ },
+ "path": "/V2/ThreatIntelligence/@{encodeURIComponent(triggerBody()?['workspaceId'])}/UploadIndicators/"
+ }
+ }
+ },
+ "runAfter": {
+ "GetVMRayIOCs": [
+ "Succeeded"
+ ]
+ }
+ }
+ },
+ "runAfter": {
+ "Create_HTML_table_2": [
+ "Succeeded"
+ ]
+ },
+ "else": {
+ "actions": {
+ "Set_Submission_Report_Variable_01": {
+ "type": "SetVariable",
+ "inputs": {
+ "name": "submission_report",
+ "value": [
+ {
+ "Submission Status": "@body('GetVMRaySubmission')['submission_status']",
+ "Sample Verdict": "@body('GetVMRaySample')['sample_verdict']",
+ "Classification": "@body('GetVMRaySample')['sample_classifications']",
+ "Threat Names": "@body('GetVMRaySample')['sample_threat_names']",
+ "Link to the Full Report": "@body('GetVMRaySample')['sample_webif_url']",
+ "Submission ID": "@body('GetVMRaySubmission')['submission_id']",
+ "Sample ID": "@body('GetVMRaySample')['sample_id']",
+ "Notes": "@body('GetVMRaySample')['sample_verdict_reason_description']"
+ }
+ ]
+ }
+ },
+ "Create_HTML_table_1": {
+ "runAfter": {
+ "Set_Submission_Report_Variable_01": [
+ "Succeeded"
+ ]
+ },
+ "type": "Table",
+ "inputs": {
+ "from": "@variables('submission_report')",
+ "format": "HTML"
+ }
+ },
+ "Add_comment_to_incident_(V3)_1": {
+ "runAfter": {
+ "Create_HTML_table_1": [
+ "Succeeded"
+ ]
+ },
+ "type": "ApiConnection",
+ "inputs": {
+ "host": {
+ "connection": {
+ "name": "@parameters('$connections')['azuresentinel']['connectionId']"
+ }
+ },
+ "method": "post",
+ "body": {
+ "incidentArmId": "@triggerBody()?['object']?['id']",
+ "message": "VMRay Submission Details for URL: @{replace(replace(items('For_each_URL')?['url'],'https://',''),'http://','')} @{body('Create_HTML_table_1')}@{body('Create_HTML_table_2')}
"
+ },
+ "path": "/Incidents/Comment"
+ }
+ }
+ }
+ },
+ "expression": {
+ "and": [
+ {
+ "not": {
+ "equals": [
+ "@body('GetVMRaySample')['sample_verdict']",
+ "clean"
+ ]
+ }
+ }
+ ]
+ },
+ "type": "If"
+ },
+ "vmrayenrichcox-GetVMRayVTIs": {
+ "runAfter": {
+ "GetVMRaySample": [
+ "Succeeded"
+ ]
+ },
+ "type": "Function",
+ "inputs": {
+ "body": {
+ "sample_id": "@body('GetVMRaySubmission')?['submission_sample_id']"
+ },
+ "function": {
+ "id": "[[concat('/subscriptions/', subscription().subscriptionId, '/resourceGroups/', resourceGroup().name, '/providers/Microsoft.Web/sites/', variables('Functionappname'), '/functions/GetVMRayVTIs')]"
+ }
+ }
+ },
+ "Set_VTIS": {
+ "runAfter": {
+ "vmrayenrichcox-GetVMRayVTIs": [
+ "Succeeded"
+ ]
+ },
+ "type": "SetVariable",
+ "inputs": {
+ "name": "sample_vtis",
+ "value": "@body('vmrayenrichcox-GetVMRayVTIs')['threat_indicators']"
+ }
+ },
+ "Create_HTML_table_2": {
+ "runAfter": {
+ "Set_variable": [
+ "Succeeded"
+ ]
+ },
+ "type": "Table",
+ "inputs": {
+ "from": "@reverse(variables('sorted_vmray_vtis'))",
+ "format": "HTML"
+ }
+ },
+ "For_each_VTI": {
+ "foreach": "@variables('sample_vtis')",
+ "actions": {
+ "Compose": {
+ "type": "Compose",
+ "inputs": {
+ "Severity": "@items('For_each_VTI')?['score']",
+ "Category": "@items('For_each_VTI')?['category']",
+ "Operation": "@items('For_each_VTI')?['operation']",
+ "Classifications": "@items('For_each_VTI')?['classifications']"
+ }
+ },
+ "Append_to_array_variable": {
+ "runAfter": {
+ "Compose": [
+ "Succeeded"
+ ]
+ },
+ "type": "AppendToArrayVariable",
+ "inputs": {
+ "name": "vmray_vtis",
+ "value": "@outputs('Compose')"
+ }
+ }
+ },
+ "runAfter": {
+ "Set_variable_2": [
+ "Succeeded"
+ ]
+ },
+ "type": "Foreach"
+ },
+ "Set_variable": {
+ "runAfter": {
+ "For_each_VTI": [
+ "Succeeded"
+ ]
+ },
+ "type": "SetVariable",
+ "inputs": {
+ "name": "sorted_vmray_vtis",
+ "value": "@sort(variables('vmray_vtis'), 'Severity')"
+ }
+ },
+ "Set_variable_1": {
+ "runAfter": {
+ "Set_VTIS": [
+ "Succeeded"
+ ]
+ },
+ "type": "SetVariable",
+ "inputs": {
+ "name": "vmray_vtis",
+ "value": "@null"
+ }
+ },
+ "Set_variable_2": {
+ "runAfter": {
+ "Set_variable_1": [
+ "Succeeded"
+ ]
+ },
+ "type": "SetVariable",
+ "inputs": {
+ "name": "sorted_vmray_vtis",
+ "value": "@null"
+ }
+ }
+ },
+ "runAfter": {
+ "GetVMRaySubmission": [
+ "Succeeded"
+ ]
+ },
+ "expression": {
+ "and": [
+ {
+ "equals": [
+ "@body('GetVMRaySubmission')?['submission_finished']",
+ true
+ ]
+ }
+ ]
+ },
+ "type": "If"
+ }
+ },
+ "runAfter": {
+ "Set_Submission_Status": [
+ "Succeeded"
+ ]
+ },
+ "expression": "@equals(variables('submission_status'),true)",
+ "limit": {
+ "count": 60,
+ "timeout": "PT1H"
+ },
+ "type": "Until"
+ }
+ },
+ "type": "Foreach"
+ }
+ },
+ "runAfter": {
+ "UplaodURL": [
+ "Succeeded"
+ ]
+ },
+ "else": {
+ "actions": {
+ "Add_Comment_to_incident": {
+ "type": "ApiConnection",
+ "inputs": {
+ "host": {
+ "connection": {
+ "name": "@parameters('$connections')['azuresentinel-1']['connectionId']"
+ }
+ },
+ "method": "post",
+ "body": {
+ "incidentArmId": "@triggerBody()?['object']?['id']",
+ "message": "Invalid API Response
"
+ },
+ "path": "/Incidents/Comment"
+ }
+ }
+ }
+ },
+ "expression": {
+ "and": [
+ {
+ "equals": [
+ "@outputs('UplaodURL')['statusCode']",
+ 200
+ ]
+ }
+ ]
+ },
+ "type": "If"
+ }
+ },
+ "runAfter": {
+ "Entities_-_Get_URLs": [
+ "Succeeded"
+ ]
+ },
+ "type": "Foreach"
+ },
+ "SubmissionReportforTable": {
+ "runAfter": {
+ "check_submission_status": [
+ "Succeeded"
+ ]
+ },
+ "type": "InitializeVariable",
+ "inputs": {
+ "variables": [
+ {
+ "name": "submission_report",
+ "type": "array"
+ }
+ ]
+ }
+ },
+ "check_submission_status": {
+ "type": "InitializeVariable",
+ "inputs": {
+ "variables": [
+ {
+ "name": "submission_status",
+ "type": "boolean",
+ "value": false
+ }
+ ]
+ }
+ },
+ "Sample_VTIS": {
+ "runAfter": {
+ "SubmissionReportforTable": [
+ "Succeeded"
+ ]
+ },
+ "type": "InitializeVariable",
+ "inputs": {
+ "variables": [
+ {
+ "name": "sample_vtis",
+ "type": "array"
+ }
+ ]
+ }
+ },
+ "VMRay_VTIS": {
+ "runAfter": {
+ "Sample_VTIS": [
+ "Succeeded"
+ ]
+ },
+ "type": "InitializeVariable",
+ "inputs": {
+ "variables": [
+ {
+ "name": "vmray_vtis",
+ "type": "array"
+ }
+ ]
+ }
+ },
+ "Sorted_VMRay_VTIS": {
+ "runAfter": {
+ "VMRay_VTIS": [
+ "Succeeded"
+ ]
+ },
+ "type": "InitializeVariable",
+ "inputs": {
+ "variables": [
+ {
+ "name": "sorted_vmray_vtis",
+ "type": "array"
+ }
+ ]
+ }
+ }
+ }
+ },
+ "parameters": {
+ "$connections": {
+ "value": {
+ "azuresentinel": {
+ "connectionId": "[[resourceId('Microsoft.Web/connections', variables('AzureSentinelConnectionName'))]",
+ "connectionName": "[[variables('AzureSentinelConnectionName')]",
+ "id": "[[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', variables('workspace-location-inline'), '/managedApis/azuresentinel')]",
+ "connectionProperties": {
+ "authentication": {
+ "type": "ManagedServiceIdentity"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "tags": {
+ "hidden-SentinelWorkspaceId": "[[variables('workspaceResourceId')]"
+ }
+ },
+ {
+ "type": "Microsoft.OperationalInsights/workspaces/providers/metadata",
+ "apiVersion": "2022-01-01-preview",
+ "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('Playbook-', last(split(variables('playbookId2'),'/'))))]",
+ "properties": {
+ "parentId": "[variables('playbookId2')]",
+ "contentId": "[variables('_playbookContentId2')]",
+ "kind": "Playbook",
+ "version": "[variables('playbookVersion2')]",
+ "source": {
+ "kind": "Solution",
+ "name": "VMRay",
+ "sourceId": "[variables('_solutionId')]"
+ },
+ "author": {
+ "name": "VMRay"
+ },
+ "support": {
+ "name": "VMRay",
+ "email": "support@vmray.com",
+ "tier": "Partner",
+ "link": "https://www.vmray.com/contact/customer-support/"
+ },
+ "dependencies": {
+ "criteria": [
+ {
+ "kind": "AzureFunction",
+ "contentId": "[variables('_VMRayEnrichment_FunctionAppConnector')]",
+ "version": "[variables('playbookVersion1')]"
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "metadata": {
+ "title": "VMRay URL Analyis",
+ "description": "Submits a url or set of urls associated with an incident to VMRay for Analyis.",
+ "prerequisites": "VMRay API Key.",
+ "postDeploymentSteps": [
+ "None"
+ ],
+ "lastUpdateTime": "2025-02-20T00:00:00Z",
+ "entities": [
+ "url"
+ ],
+ "tags": [
+ "Enrichment"
+ ],
+ "releaseNotes": {
+ "version": "1.0",
+ "title": "[variables('blanks')]",
+ "notes": [
+ "Initial version"
+ ]
+ }
+ }
+ },
+ "packageKind": "Solution",
+ "packageVersion": "[variables('_solutionVersion')]",
+ "packageName": "[variables('_solutionName')]",
+ "packageId": "[variables('_solutionId')]",
+ "contentSchemaVersion": "3.0.0",
+ "contentId": "[variables('_playbookContentId2')]",
+ "contentKind": "Playbook",
+ "displayName": "Submit-URL-VMRay-Analyzer",
+ "contentProductId": "[variables('_playbookcontentProductId2')]",
+ "id": "[variables('_playbookcontentProductId2')]",
+ "version": "[variables('playbookVersion2')]"
+ }
+ },
+ {
+ "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates",
+ "apiVersion": "2023-04-01-preview",
+ "name": "[variables('playbookTemplateSpecName3')]",
+ "location": "[parameters('workspace-location')]",
+ "dependsOn": [
+ "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]"
+ ],
+ "properties": {
+ "description": "VMRay-Sandbox_Outlook_Attachment Playbook with template version 3.0.0",
+ "mainTemplate": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "contentVersion": "[variables('playbookVersion3')]",
+ "parameters": {
+ "PlaybookName": {
+ "defaultValue": "VMRay-Sandbox_Outlook_Attachment",
+ "type": "string",
+ "metadata": {
+ "description": "Name of the Logic App/Playbook"
+ }
+ },
+ "WorkspaceName": {
+ "defaultValue": "",
+ "type": "String",
+ "metadata": {
+ "description": "Log Analytics Workspace name"
+ }
+ },
+ "WorkspaceID": {
+ "defaultValue": "",
+ "type": "String",
+ "metadata": {
+ "description": "Log Analytics Workspace ID"
+ }
+ },
+ "FunctionAppName": {
+ "defaultValue": "vmrayenrich",
+ "type": "string",
+ "metadata": {
+ "description": "Name of the FunctionApp"
+ }
+ }
+ },
+ "variables": {
+ "functionappName": "[[concat(toLower(parameters('FunctionAppName')), take(uniqueString(resourceGroup().id), 3))]",
+ "AzureSentinelConnectionName": "[[concat('azuresentinel-', parameters('PlaybookName'))]",
+ "Office365V1": "[[concat('office365-', parameters('PlaybookName'))]",
+ "subscription": "[[last(split(subscription().id, '/'))]",
+ "resourceGroupName": "[[resourceGroup().name]",
+ "connection-1": "[[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', variables('workspace-location-inline'), '/managedApis/office365')]",
+ "_connection-1": "[[variables('connection-1')]",
+ "connection-2": "[[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', variables('workspace-location-inline'), '/managedApis/azuresentinel')]",
+ "_connection-2": "[[variables('connection-2')]",
+ "workspace-location-inline": "[concat('[resourceGroup().locatio', 'n]')]",
+ "workspace-name": "[parameters('workspace')]",
+ "workspaceResourceId": "[[resourceId('microsoft.OperationalInsights/Workspaces', variables('workspace-name'))]"
+ },
+ "resources": [
+ {
+ "type": "Microsoft.Web/connections",
+ "apiVersion": "2016-06-01",
+ "name": "[[variables('Office365V1')]",
+ "location": "[[variables('workspace-location-inline')]",
+ "properties": {
+ "displayName": "[[variables('Office365V1')]",
+ "api": {
+ "id": "[[variables('_connection-1')]"
+ }
+ }
+ },
+ {
+ "type": "Microsoft.Web/connections",
+ "apiVersion": "2016-06-01",
+ "name": "[[variables('AzureSentinelConnectionName')]",
+ "location": "[[variables('workspace-location-inline')]",
+ "kind": "V1",
+ "properties": {
+ "displayName": "[[variables('AzureSentinelConnectionName')]",
+ "parameterValueType": "Alternative",
+ "api": {
+ "id": "[[variables('_connection-2')]"
+ }
+ }
+ },
+ {
+ "type": "Microsoft.Logic/workflows",
+ "apiVersion": "2017-07-01",
+ "name": "[[parameters('PlaybookName')]",
+ "location": "[[variables('workspace-location-inline')]",
+ "identity": {
+ "type": "SystemAssigned"
+ },
+ "dependsOn": [
+ "[[resourceId('Microsoft.Web/connections', variables('Office365V1'))]",
+ "[[resourceId('Microsoft.Web/connections', variables('AzureSentinelConnectionName'))]"
+ ],
+ "properties": {
+ "state": "Disabled",
+ "definition": {
+ "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#",
+ "contentVersion": "1.0.0.0",
+ "parameters": {
+ "Indicator_Expiration_In_Days": {
+ "defaultValue": "30",
+ "type": "String"
+ },
+ "$connections": {
+ "type": "Object"
+ }
+ },
+ "triggers": {
+ "When_a_new_email_arrives_(V3)": {
+ "splitOn": "@triggerBody()?['value']",
+ "type": "ApiConnectionNotification",
+ "inputs": {
+ "host": {
+ "connection": {
+ "name": "@parameters('$connections')['office365']['connectionId']"
+ }
+ },
+ "fetch": {
+ "pathTemplate": {
+ "template": "/v3/Mail/OnNewEmail"
+ },
+ "method": "get",
+ "queries": {
+ "importance": "Any",
+ "fetchOnlyWithAttachment": true,
+ "includeAttachments": true,
+ "folderPath": "Inbox"
+ }
+ },
+ "subscribe": {
+ "body": {
+ "NotificationUrl": "@listCallbackUrl()"
+ },
+ "pathTemplate": {
+ "template": "/GraphMailSubscriptionPoke/$subscriptions"
+ },
+ "method": "post",
+ "queries": {
+ "importance": "Any",
+ "fetchOnlyWithAttachment": true,
+ "folderPath": "Inbox"
+ }
+ }
+ }
+ }
+ },
+ "actions": {
+ "Submission_Status": {
+ "runAfter": {
+ "Submission_Object": [
+ "Succeeded"
+ ]
+ },
+ "type": "InitializeVariable",
+ "inputs": {
+ "variables": [
+ {
+ "name": "submission_status",
+ "type": "boolean",
+ "value": false
+ }
+ ]
+ }
+ },
+ "Submission_Report": {
+ "runAfter": {
+ "Submission_Status": [
+ "Succeeded"
+ ]
+ },
+ "type": "InitializeVariable",
+ "inputs": {
+ "variables": [
+ {
+ "name": "submission_report",
+ "type": "array"
+ }
+ ]
+ }
+ },
+ "Email_Body": {
+ "runAfter": {
+ "Submission_Report": [
+ "Succeeded"
+ ]
+ },
+ "type": "InitializeVariable",
+ "inputs": {
+ "variables": [
+ {
+ "name": "email_body",
+ "type": "string"
+ }
+ ]
+ }
+ },
+ "File_Name": {
+ "runAfter": {
+ "Email_Body": [
+ "Succeeded"
+ ]
+ },
+ "type": "InitializeVariable",
+ "inputs": {
+ "variables": [
+ {
+ "name": "file_name",
+ "type": "string"
+ }
+ ]
+ }
+ },
+ "Submission_Object": {
+ "type": "InitializeVariable",
+ "inputs": {
+ "variables": [
+ {
+ "name": "submissin_obj",
+ "type": "array"
+ }
+ ]
+ }
+ },
+ "For_Every_Attachment": {
+ "foreach": "@triggerBody()?['attachments']",
+ "actions": {
+ "VMRayUploadSample": {
+ "type": "Function",
+ "inputs": {
+ "body": {
+ "file": "@item()?['contentBytes']",
+ "name": "@item()?['name']",
+ "tags": [
+ "outlook_attachment_logic_app_submission"
+ ]
+ },
+ "function": {
+ "id": "[[concat('/subscriptions/', subscription().subscriptionId, '/resourceGroups/', resourceGroup().name, '/providers/Microsoft.Web/sites/', variables('Functionappname'), '/functions/VMRayUploadSample')]"
+ }
+ }
+ },
+ "Append_to_Submission_Object_to_Variable": {
+ "runAfter": {
+ "VMRayUploadSample": [
+ "Succeeded"
+ ]
+ },
+ "type": "AppendToArrayVariable",
+ "inputs": {
+ "name": "submissin_obj",
+ "value": {
+ "submission_id": "@body('VMRayUploadSample')['vmray_submission']",
+ "filename": "@variables('file_name')"
+ }
+ }
+ }
+ },
+ "runAfter": {
+ "File_Name": [
+ "Succeeded"
+ ]
+ },
+ "type": "Foreach"
+ },
+ "For_Every_Submission_Object": {
+ "foreach": "@variables('submissin_obj')",
+ "actions": {
+ "Set_Submission_Status_to_False": {
+ "type": "SetVariable",
+ "inputs": {
+ "name": "submission_status",
+ "value": false
+ }
+ },
+ "Wait_untill_submission_is_completed": {
+ "actions": {
+ "GetVMRaySubmission": {
+ "type": "Function",
+ "inputs": {
+ "body": {
+ "submission_id": "@items('For_Every_Submission_Object')['submission_id'][0]['SubmissionID']"
+ },
+ "function": {
+ "id": "[[concat('/subscriptions/', subscription().subscriptionId, '/resourceGroups/', resourceGroup().name, '/providers/Microsoft.Web/sites/', variables('Functionappname'), '/functions/GetVMRaySubmission')]"
+ }
+ }
+ },
+ "Check_If_Submission_is_finished": {
+ "actions": {
+ "GetVMRaySample": {
+ "type": "Function",
+ "inputs": {
+ "body": {
+ "sample_id": "@body('GetVMRaySubmission')?['submission_sample_id']"
+ },
+ "function": {
+ "id": "[[concat('/subscriptions/', subscription().subscriptionId, '/resourceGroups/', resourceGroup().name, '/providers/Microsoft.Web/sites/', variables('Functionappname'), '/functions/GetVMRaySample')]"
+ }
+ }
+ },
+ "Check_If_Sample_Verdict_is_Malicious_or_Suspicious": {
+ "actions": {
+ "Set_submission_report": {
+ "type": "SetVariable",
+ "inputs": {
+ "name": "submission_report",
+ "value": [
+ {
+ "Submission ID": "@body('GetVMRaySubmission')['submission_id']",
+ "Submission Status": "@body('GetVMRaySubmission')['submission_status']",
+ "Sample ID": "@body('GetVMRaySample')['sample_id']",
+ "Sample Classifications": "@body('GetVMRaySample')['sample_classifications']",
+ "Sample Threat Names": "@body('GetVMRaySample')['sample_threat_names']",
+ "Sample Webif URL": "@body('GetVMRaySample')['sample_webif_url']",
+ "Sample Verdict": "@body('GetVMRaySample')['sample_verdict']",
+ "Sample VTI Score": "@body('GetVMRaySample')['sample_vti_score']",
+ "Sample Verdict Reason Code": "@body('GetVMRaySample')['sample_verdict_reason_code']",
+ "Sample Verdict Reason Description": "@body('GetVMRaySample')['sample_verdict_reason_description']"
+ }
+ ]
+ }
+ },
+ "Create_HTML_table": {
+ "runAfter": {
+ "Set_submission_report": [
+ "Succeeded"
+ ]
+ },
+ "type": "Table",
+ "inputs": {
+ "from": "@variables('submission_report')",
+ "format": "HTML"
+ }
+ },
+ "Create_incident": {
+ "runAfter": {
+ "Create_HTML_table": [
+ "Succeeded"
+ ]
+ },
+ "type": "ApiConnection",
+ "inputs": {
+ "host": {
+ "connection": {
+ "name": "@parameters('$connections')['azuresentinel']['connectionId']"
+ }
+ },
+ "method": "put",
+ "body": {
+ "title": "VMRay Email Attachment Scan Submission Details for file - @{body('GetVMRaySample')['sample_filename']}",
+ "severity": "Medium",
+ "status": "New",
+ "description": "VMRay Email Attachment Scan . From:@{triggerBody()?['from']} . To: @{triggerBody()?['toRecipients']}"
+ },
+ "path": "[[concat('/Incidents/subscriptions/',variables('subscription'),'/resourceGroups/',variables('resourceGroupName'),'/workspaces/',parameters('WorkspaceName'))]"
+ }
+ },
+ "Add_comment_to_incident_(V3)": {
+ "runAfter": {
+ "Create_incident": [
+ "Succeeded"
+ ]
+ },
+ "type": "ApiConnection",
+ "inputs": {
+ "host": {
+ "connection": {
+ "name": "@parameters('$connections')['azuresentinel']['connectionId']"
+ }
+ },
+ "method": "post",
+ "body": {
+ "incidentArmId": "@body('Create_incident')?['id']",
+ "message": "VMRay Submission for File - @{items('For_Every_Submission_Object')['filename']} @{body('Create_HTML_table')}
"
+ },
+ "path": "/Incidents/Comment"
+ }
+ },
+ "GetVMRayIOCs": {
+ "runAfter": {
+ "Add_comment_to_incident_(V3)": [
+ "Succeeded"
+ ]
+ },
+ "type": "Function",
+ "inputs": {
+ "body": {
+ "sample_id": "@body('GetVMRaySample')['sample_id']",
+ "submission_id": "@body('GetVMRaySubmission')['submission_id']",
+ "sample_verdict": [
+ "malicious",
+ "suspicious"
+ ],
+ "incident_id": "@body('Create_incident')?['properties']?['incidentNumber']",
+ "valid_until": "@parameters('Indicator_Expiration_In_Days')"
+ },
+ "function": {
+ "id": "[[concat('/subscriptions/', subscription().subscriptionId, '/resourceGroups/', resourceGroup().name, '/providers/Microsoft.Web/sites/', variables('Functionappname'), '/functions/GetVMRayIOCs')]"
+ }
+ }
+ },
+ "Check_if_VMRay_has_any_indicators_for_the_sample": {
+ "actions": {
+ "Threat_Intelligence_-_Upload_Indicators_of_Compromise_(V2)_(Preview)": {
+ "type": "ApiConnection",
+ "inputs": {
+ "host": {
+ "connection": {
+ "name": "@parameters('$connections')['azuresentinel']['connectionId']"
+ }
+ },
+ "method": "post",
+ "body": {
+ "sourcesystem": "VMRayThreatIntelligence",
+ "indicators": "@body('GetVMRayIOCs')['custom_resp']"
+ },
+ "path": "[[concat('/V2/ThreatIntelligence/',parameters('WorkspaceID'),'/UploadIndicators/')]"
+ }
+ }
+ },
+ "runAfter": {
+ "GetVMRayIOCs": [
+ "Succeeded"
+ ]
+ },
+ "expression": {
+ "and": [
+ {
+ "greater": [
+ "@length(body('GetVMRayIOCs')['custom_resp'])",
+ 0
+ ]
+ }
+ ]
+ },
+ "type": "If"
+ },
+ "Set_email_body": {
+ "runAfter": {
+ "Check_if_VMRay_has_any_indicators_for_the_sample": [
+ "Succeeded"
+ ]
+ },
+ "type": "SetVariable",
+ "inputs": {
+ "name": "email_body",
+ "value": "\n
\n\n\nSubmission ID \nSubmission Status \nSample ID \nSample Classifications \nSample Threat Names \nSample Webif URL \nSample Verdict \nSample VTI Score \nSample Verdict Reason Code \nSample Verdict Reason Description \n \n \n\n\n@{body('GetVMRaySubmission')['submission_id']} \n@{body('GetVMRaySubmission')['submission_status']} \n@{body('GetVMRaySample')['sample_id']} \n@{body('GetVMRaySample')['sample_classifications']} \n@{body('GetVMRaySample')['sample_threat_names']} \n@{body('GetVMRaySample')['sample_webif_url']} \n@{body('GetVMRaySample')['sample_verdict']} \n@{body('GetVMRaySample')['sample_vti_score']} \n@{body('GetVMRaySample')['sample_verdict_reason_code']} \n@{body('GetVMRaySample')['sample_verdict_reason_description']} \n \n \n
\n
\n"
+ }
+ },
+ "Send_an_email_(V2)": {
+ "runAfter": {
+ "Set_email_body": [
+ "Succeeded"
+ ]
+ },
+ "type": "ApiConnection",
+ "inputs": {
+ "host": {
+ "connection": {
+ "name": "@parameters('$connections')['office365']['connectionId']"
+ }
+ },
+ "method": "post",
+ "body": {
+ "To": "abc@abc.com",
+ "Subject": "VMRay Email Attachment Scan for file - @{body('GetVMRaySample')['sample_filename']}",
+ "Body": "@{variables('email_body')}
",
+ "Importance": "Normal"
+ },
+ "path": "/v2/Mail"
+ }
+ },
+ "Set_Submission_Status_to_True": {
+ "runAfter": {
+ "Send_an_email_(V2)": [
+ "Succeeded"
+ ]
+ },
+ "type": "SetVariable",
+ "inputs": {
+ "name": "submission_status",
+ "value": true
+ }
+ }
+ },
+ "runAfter": {
+ "GetVMRaySample": [
+ "Succeeded"
+ ]
+ },
+ "expression": {
+ "and": [
+ {
+ "not": {
+ "equals": [
+ "@body('GetVMRaySample')['sample_verdict']",
+ "clean"
+ ]
+ }
+ }
+ ]
+ },
+ "type": "If"
+ }
+ },
+ "runAfter": {
+ "GetVMRaySubmission": [
+ "Succeeded"
+ ]
+ },
+ "else": {
+ "actions": {
+ "Delay_by_2_minutes": {
+ "type": "Wait",
+ "inputs": {
+ "interval": {
+ "count": 2,
+ "unit": "Minute"
+ }
+ }
+ },
+ "Set_submission_status_to_false_again": {
+ "runAfter": {
+ "Delay_by_2_minutes": [
+ "Succeeded"
+ ]
+ },
+ "type": "SetVariable",
+ "inputs": {
+ "name": "submission_status",
+ "value": false
+ }
+ }
+ }
+ },
+ "expression": {
+ "and": [
+ {
+ "equals": [
+ "@body('GetVMRaySubmission')['submission_finished']",
+ true
+ ]
+ }
+ ]
+ },
+ "type": "If"
+ }
+ },
+ "runAfter": {
+ "Set_Submission_Status_to_False": [
+ "Succeeded"
+ ]
+ },
+ "expression": "@equals(variables('submission_status'),true)",
+ "limit": {
+ "count": 60,
+ "timeout": "PT1H"
+ },
+ "type": "Until"
+ }
+ },
+ "runAfter": {
+ "For_Every_Attachment": [
+ "Succeeded"
+ ]
+ },
+ "type": "Foreach"
+ }
+ }
+ },
+ "parameters": {
+ "$connections": {
+ "value": {
+ "office365": {
+ "connectionId": "[[resourceId('Microsoft.Web/connections', variables('Office365V1'))]",
+ "connectionName": "[[variables('Office365V1')]",
+ "id": "[[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', variables('workspace-location-inline'), '/managedApis/office365')]"
+ },
+ "azuresentinel": {
+ "connectionId": "[[resourceId('Microsoft.Web/connections', variables('AzureSentinelConnectionName'))]",
+ "connectionName": "[[variables('AzureSentinelConnectionName')]",
+ "id": "[[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', variables('workspace-location-inline'), '/managedApis/azuresentinel')]",
+ "connectionProperties": {
+ "authentication": {
+ "type": "ManagedServiceIdentity"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "tags": {
+ "hidden-SentinelWorkspaceId": "[[variables('workspaceResourceId')]"
+ }
+ },
+ {
+ "type": "Microsoft.OperationalInsights/workspaces/providers/metadata",
+ "apiVersion": "2022-01-01-preview",
+ "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('Playbook-', last(split(variables('playbookId3'),'/'))))]",
+ "properties": {
+ "parentId": "[variables('playbookId3')]",
+ "contentId": "[variables('_playbookContentId3')]",
+ "kind": "Playbook",
+ "version": "[variables('playbookVersion3')]",
+ "source": {
+ "kind": "Solution",
+ "name": "VMRay",
+ "sourceId": "[variables('_solutionId')]"
+ },
+ "author": {
+ "name": "VMRay"
+ },
+ "support": {
+ "name": "VMRay",
+ "email": "support@vmray.com",
+ "tier": "Partner",
+ "link": "https://www.vmray.com/contact/customer-support/"
+ },
+ "dependencies": {
+ "criteria": [
+ {
+ "kind": "AzureFunction",
+ "contentId": "[variables('_VMRayEnrichment_FunctionAppConnector')]",
+ "version": "[variables('playbookVersion1')]"
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "metadata": {
+ "title": "VMRay Email Attachment Analyis",
+ "description": "Submits a attachment or set of attachment associated with an office 365 email to VMRay for Analyis.",
+ "prerequisites": "VMRay API Key.",
+ "postDeploymentSteps": [
+ "None"
+ ],
+ "lastUpdateTime": "2025-02-20T00:00:00Z",
+ "entities": [
+ ""
+ ],
+ "tags": [
+ "Enrichment"
+ ],
+ "releaseNotes": {
+ "version": "1.0",
+ "title": "[variables('blanks')]",
+ "notes": [
+ "Initial version"
+ ]
+ }
+ }
+ },
+ "packageKind": "Solution",
+ "packageVersion": "[variables('_solutionVersion')]",
+ "packageName": "[variables('_solutionName')]",
+ "packageId": "[variables('_solutionId')]",
+ "contentSchemaVersion": "3.0.0",
+ "contentId": "[variables('_playbookContentId3')]",
+ "contentKind": "Playbook",
+ "displayName": "VMRay-Sandbox_Outlook_Attachment",
+ "contentProductId": "[variables('_playbookcontentProductId3')]",
+ "id": "[variables('_playbookcontentProductId3')]",
+ "version": "[variables('playbookVersion3')]"
+ }
+ },
+ {
+ "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates",
+ "apiVersion": "2023-04-01-preview",
+ "name": "[variables('dataConnectorTemplateSpecName1')]",
+ "location": "[parameters('workspace-location')]",
+ "dependsOn": [
+ "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]"
+ ],
+ "properties": {
+ "description": "VMRay data connector with template version 3.0.0",
+ "mainTemplate": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "contentVersion": "[variables('dataConnectorVersion1')]",
+ "parameters": {},
+ "variables": {},
+ "resources": [
+ {
+ "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',variables('_dataConnectorContentId1'))]",
+ "apiVersion": "2021-03-01-preview",
+ "type": "Microsoft.OperationalInsights/workspaces/providers/dataConnectors",
+ "location": "[parameters('workspace-location')]",
+ "kind": "GenericUI",
+ "properties": {
+ "connectorUiConfig": {
+ "id": "[variables('_uiConfigId1')]",
+ "title": "VMRayThreatIntelligence (using Azure Functions)",
+ "publisher": "VMRay",
+ "descriptionMarkdown": "VMRayThreatIntelligence connector automatically generates and feeds threat intelligence for all submissions to VMRay, improving threat detection and incident response in Sentinel. This seamless integration empowers teams to proactively address emerging threats.",
+ "graphQueries": [
+ {
+ "metricName": "VMRay Threat Indicators data received",
+ "legend": "ThreatIntelligenceIndicator | where SourceSystem contains 'VMRay'",
+ "baseQuery": "ThreatIntelligenceIndicator | where SourceSystem contains 'VMRay'"
+ },
+ {
+ "metricName": "Non-VMRay Threat Indicators data received",
+ "legend": "ThreatIntelligenceIndicator | where SourceSystem !contains 'VMRay'",
+ "baseQuery": "ThreatIntelligenceIndicator | where SourceSystem !contains 'VMRay'"
+ }
+ ],
+ "sampleQueries": [
+ {
+ "description": "VMRay Based Indicators Events - All VMRay threat indicators in Microsoft Sentinel Threat Intelligence.",
+ "query": "ThreatIntelligenceIndicator\n | where SourceSystem contains 'VMRay'\n | sort by TimeGenerated desc"
+ },
+ {
+ "description": "Non-VMRay Based Indicators Events - All Non-VMRay threat indicators in Microsoft Sentinel Threat Intelligence.",
+ "query": "ThreatIntelligenceIndicator\n | where SourceSystem !contains 'VMRay'\n | sort by TimeGenerated desc"
+ }
+ ],
+ "dataTypes": [
+ {
+ "name": "ThreatIntelligenceIndicator",
+ "lastDataReceivedQuery": "ThreatIntelligenceIndicator\n | summarize Time = max(TimeGenerated)\n | where isnotempty(Time)"
+ }
+ ],
+ "connectivityCriterias": [
+ {
+ "type": "IsConnectedQuery",
+ "value": [
+ "ThreatIntelligenceIndicator\n | where SourceSystem contains 'VMRay'\n | summarize LastLogReceived = max(TimeGenerated)\n | project IsConnected = LastLogReceived > ago(30d)"
+ ]
+ },
+ {
+ "type": "IsConnectedQuery",
+ "value": [
+ "Report_links_data_CL\n | summarize LastLogReceived = max(TimeGenerated)\n | project IsConnected = LastLogReceived > ago(30d)"
+ ]
+ },
+ {
+ "type": "IsConnectedQuery",
+ "value": [
+ "ThreatIntelligenceIndicator\n | where SourceSystem !contains 'VMRay'\n | summarize LastLogReceived = max(TimeGenerated)\n | project IsConnected = LastLogReceived > ago(30d)"
+ ]
+ }
+ ],
+ "availability": {
+ "status": 1,
+ "isPreview": false
+ },
+ "permissions": {
+ "resourceProvider": [
+ {
+ "provider": "Microsoft.OperationalInsights/workspaces",
+ "permissionsDisplayText": "read and write permissions on the workspace are required.",
+ "providerDisplayName": "Workspace",
+ "scope": "Workspace",
+ "requiredPermissions": {
+ "write": true,
+ "read": true,
+ "delete": true
+ }
+ },
+ {
+ "provider": "Microsoft.OperationalInsights/workspaces/sharedKeys",
+ "permissionsDisplayText": "read permissions to shared keys for the workspace are required. [See the documentation to learn more about workspace keys](https://docs.microsoft.com/azure/azure-monitor/platform/agent-windows#obtain-workspace-id-and-key).",
+ "providerDisplayName": "Keys",
+ "scope": "Workspace",
+ "requiredPermissions": {
+ "action": true
+ }
+ }
+ ],
+ "customs": [
+ {
+ "name": "Azure Subscription",
+ "description": "Azure Subscription with owner role is required to register an application in azure active directory() and assign role of contributor to app in resource group."
+ },
+ {
+ "name": "Microsoft.Web/sites permissions",
+ "description": "Read and write permissions to Azure Functions to create a Function App is required. [See the documentation to learn more about Azure Functions](https://docs.microsoft.com/azure/azure-functions/)."
+ },
+ {
+ "name": "REST API Credentials/permissions",
+ "description": "**VMRay API Key** is required."
+ }
+ ]
+ },
+ "instructionSteps": [
+ {
+ "description": ">**NOTE:** This connector uses Azure Functions to connect to the VMRay API to pull VMRay Threat IOCs into Microsoft Sentinel. This might result in additional costs for data ingestion and for storing data in Azure Blob Storage costs. Check the [Azure Functions pricing page](https://azure.microsoft.com/pricing/details/functions/) and [Azure Blob Storage pricing page](https://azure.microsoft.com/pricing/details/storage/blobs/) for details."
+ },
+ {
+ "description": ">**(Optional Step)** Securely store workspace and API authorization key(s) or token(s) in Azure Key Vault. Azure Key Vault provides a secure mechanism to store and retrieve key values. [Follow these instructions](https://docs.microsoft.com/azure/app-service/app-service-key-vault-references) to use Azure Key Vault with an Azure Function App."
+ },
+ {
+ "instructions": [
+ {
+ "parameters": {
+ "fillWith": [
+ "WorkspaceId"
+ ],
+ "label": "Workspace ID"
+ },
+ "type": "CopyableLabel"
+ },
+ {
+ "parameters": {
+ "fillWith": [
+ "PrimaryKey"
+ ],
+ "label": "Primary Key"
+ },
+ "type": "CopyableLabel"
+ }
+ ]
+ },
+ {
+ "description": "Use this method for automated deployment of the data connector using an ARM Template.\n\n1. Click the **Deploy to Azure** button below. \n\n\t[](https://aka.ms/sentinel-VMRay-azuredeploy)\n2. Select the preferred **Subscription**, **Resource Group** and **Location**. \n3. Enter the **Application ID**, **Tenant ID**,**Client Secret**, **VMRay API Key**, **VMRay Initial Fetch Date**, **TimeInterval** and deploy.\n4. Mark the checkbox labeled **I agree to the terms and conditions stated above**.\n5. Click **Purchase** to deploy.",
+ "title": "Option 1 - Azure Resource Manager (ARM) Template for Flex Consumption Plan"
+ },
+ {
+ "description": "Use this method for automated deployment of the data connector using an ARM Template.\n\n1. Click the **Deploy to Azure** button below. \n\n\t[](https://aka.ms/sentinel-VMRay-azuredeploy)\n2. Select the preferred **Subscription**, **Resource Group** and **Location**. \n3. Enter the **Application ID**, **Tenant ID**,**Client Secret**, **VMRay API Key**, **VMRay Initial Fetch Date**, **TimeInterval** and deploy.\n4. Mark the checkbox labeled **I agree to the terms and conditions stated above**.\n5. Click **Purchase** to deploy.",
+ "title": "Option 1 - Azure Resource Manager (ARM) Template for Premium Plan"
+ },
+ {
+ "description": "Use the following step-by-step instructions to deploy the VMRay data connector manually with Azure Functions (Deployment via Visual Studio Code).",
+ "title": "Option 2 - Manual Deployment of Azure Functions"
+ },
+ {
+ "description": "**1. Deploy a Function App**\n\n> NOTE:You will need to [prepare VS code](https://docs.microsoft.com/azure/azure-functions/functions-create-first-function-python#prerequisites) for Azure function development.\n\n1. Download the [Azure Function App](https://aka.ms/sentinel-VMRay-functionapp) file. Extract archive to your local development computer.\n2. Start VS Code. Choose File in the main menu and select Open Folder.\n3. Select the top level folder from extracted files.\n4. Choose the Azure icon in the Activity bar, then in the **Azure: Functions** area, choose the **Deploy to function app** button.\nIf you aren't already signed in, choose the Azure icon in the Activity bar, then in the **Azure: Functions** area, choose **Sign in to Azure**\nIf you're already signed in, go to the next step.\n5. Provide the following information at the prompts:\n\n\ta. **Select folder:** Choose a folder from your workspace or browse to one that contains your function app.\n\n\tb. **Select Subscription:** Choose the subscription to use.\n\n\tc. Select **Create new Function App in Azure** (Don't choose the Advanced option)\n\n\td. **Enter a globally unique name for the function app:** Type a name that is valid in a URL path. The name you type is validated to make sure that it's unique in Azure Functions. (e.g. VMRayXXX).\n\n\te. **Select a runtime:** Choose Python 3.11.\n\n\tf. Select a location for new resources. For better performance and lower costs choose the same [region](https://azure.microsoft.com/regions/) where Microsoft sentinel is located.\n\n6. Deployment will begin. A notification is displayed after your function app is created and the deployment package is applied.\n7. Go to Azure Portal for the Function App configuration."
+ },
+ {
+ "description": "**2. Configure the Function App**\\n\\n1. In the Function App, select the Function App Name and select **Configuration**.\\n2. In the **Application settings** tab, select **+ New application setting**.\\n3. Add each of the following application settings individually, with their respective string values (case-sensitive): \\n\\tApplication ID\\n\\tTenant ID\\n\\tClient Secret\\n\\tVMRay API Key\\n\\tVMRay Initial Fetch Date\\n\\tTimeInterval - Use logAnalyticsUri to override the log analytics API endpoint for dedicated cloud. For example, for public cloud, leave the value empty; for Azure GovUS cloud environment, specify the value in the following format: `https://.ods.opinsights.azure.us`\\n3. Once all application settings have been entered, click **Save**."
+ }
+ ]
+ }
+ }
+ },
+ {
+ "type": "Microsoft.OperationalInsights/workspaces/providers/metadata",
+ "apiVersion": "2023-04-01-preview",
+ "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('DataConnector-', last(split(variables('_dataConnectorId1'),'/'))))]",
+ "properties": {
+ "parentId": "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/dataConnectors', variables('_dataConnectorContentId1'))]",
+ "contentId": "[variables('_dataConnectorContentId1')]",
+ "kind": "DataConnector",
+ "version": "[variables('dataConnectorVersion1')]",
+ "source": {
+ "kind": "Solution",
+ "name": "VMRay",
+ "sourceId": "[variables('_solutionId')]"
+ },
+ "author": {
+ "name": "VMRay"
+ },
+ "support": {
+ "name": "VMRay",
+ "email": "support@vmray.com",
+ "tier": "Partner",
+ "link": "https://www.vmray.com/contact/customer-support/"
+ }
+ }
+ }
+ ]
+ },
+ "packageKind": "Solution",
+ "packageVersion": "[variables('_solutionVersion')]",
+ "packageName": "[variables('_solutionName')]",
+ "packageId": "[variables('_solutionId')]",
+ "contentSchemaVersion": "3.0.0",
+ "contentId": "[variables('_dataConnectorContentId1')]",
+ "contentKind": "DataConnector",
+ "displayName": "VMRayThreatIntelligence (using Azure Functions)",
+ "contentProductId": "[variables('_dataConnectorcontentProductId1')]",
+ "id": "[variables('_dataConnectorcontentProductId1')]",
+ "version": "[variables('dataConnectorVersion1')]"
+ }
+ },
+ {
+ "type": "Microsoft.OperationalInsights/workspaces/providers/metadata",
+ "apiVersion": "2023-04-01-preview",
+ "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('DataConnector-', last(split(variables('_dataConnectorId1'),'/'))))]",
+ "dependsOn": [
+ "[variables('_dataConnectorId1')]"
+ ],
+ "location": "[parameters('workspace-location')]",
+ "properties": {
+ "parentId": "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/dataConnectors', variables('_dataConnectorContentId1'))]",
+ "contentId": "[variables('_dataConnectorContentId1')]",
+ "kind": "DataConnector",
+ "version": "[variables('dataConnectorVersion1')]",
+ "source": {
+ "kind": "Solution",
+ "name": "VMRay",
+ "sourceId": "[variables('_solutionId')]"
+ },
+ "author": {
+ "name": "VMRay"
+ },
+ "support": {
+ "name": "VMRay",
+ "email": "support@vmray.com",
+ "tier": "Partner",
+ "link": "https://www.vmray.com/contact/customer-support/"
+ }
+ }
+ },
+ {
+ "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',variables('_dataConnectorContentId1'))]",
+ "apiVersion": "2021-03-01-preview",
+ "type": "Microsoft.OperationalInsights/workspaces/providers/dataConnectors",
+ "location": "[parameters('workspace-location')]",
+ "kind": "GenericUI",
+ "properties": {
+ "connectorUiConfig": {
+ "title": "VMRayThreatIntelligence (using Azure Functions)",
+ "publisher": "VMRay",
+ "descriptionMarkdown": "VMRayThreatIntelligence connector automatically generates and feeds threat intelligence for all submissions to VMRay, improving threat detection and incident response in Sentinel. This seamless integration empowers teams to proactively address emerging threats.",
+ "graphQueries": [
+ {
+ "metricName": "VMRay Threat Indicators data received",
+ "legend": "ThreatIntelligenceIndicator | where SourceSystem contains 'VMRay'",
+ "baseQuery": "ThreatIntelligenceIndicator | where SourceSystem contains 'VMRay'"
+ },
+ {
+ "metricName": "Non-VMRay Threat Indicators data received",
+ "legend": "ThreatIntelligenceIndicator | where SourceSystem !contains 'VMRay'",
+ "baseQuery": "ThreatIntelligenceIndicator | where SourceSystem !contains 'VMRay'"
+ }
+ ],
+ "dataTypes": [
+ {
+ "name": "ThreatIntelligenceIndicator",
+ "lastDataReceivedQuery": "ThreatIntelligenceIndicator\n | summarize Time = max(TimeGenerated)\n | where isnotempty(Time)"
+ }
+ ],
+ "connectivityCriterias": [
+ {
+ "type": "IsConnectedQuery",
+ "value": [
+ "ThreatIntelligenceIndicator\n | where SourceSystem contains 'VMRay'\n | summarize LastLogReceived = max(TimeGenerated)\n | project IsConnected = LastLogReceived > ago(30d)"
+ ]
+ },
+ {
+ "type": "IsConnectedQuery",
+ "value": [
+ "Report_links_data_CL\n | summarize LastLogReceived = max(TimeGenerated)\n | project IsConnected = LastLogReceived > ago(30d)"
+ ]
+ },
+ {
+ "type": "IsConnectedQuery",
+ "value": [
+ "ThreatIntelligenceIndicator\n | where SourceSystem !contains 'VMRay'\n | summarize LastLogReceived = max(TimeGenerated)\n | project IsConnected = LastLogReceived > ago(30d)"
+ ]
+ }
+ ],
+ "sampleQueries": [
+ {
+ "description": "VMRay Based Indicators Events - All VMRay threat indicators in Microsoft Sentinel Threat Intelligence.",
+ "query": "ThreatIntelligenceIndicator\n | where SourceSystem contains 'VMRay'\n | sort by TimeGenerated desc"
+ },
+ {
+ "description": "Non-VMRay Based Indicators Events - All Non-VMRay threat indicators in Microsoft Sentinel Threat Intelligence.",
+ "query": "ThreatIntelligenceIndicator\n | where SourceSystem !contains 'VMRay'\n | sort by TimeGenerated desc"
+ }
+ ],
+ "availability": {
+ "status": 1,
+ "isPreview": false
+ },
+ "permissions": {
+ "resourceProvider": [
+ {
+ "provider": "Microsoft.OperationalInsights/workspaces",
+ "permissionsDisplayText": "read and write permissions on the workspace are required.",
+ "providerDisplayName": "Workspace",
+ "scope": "Workspace",
+ "requiredPermissions": {
+ "write": true,
+ "read": true,
+ "delete": true
+ }
+ },
+ {
+ "provider": "Microsoft.OperationalInsights/workspaces/sharedKeys",
+ "permissionsDisplayText": "read permissions to shared keys for the workspace are required. [See the documentation to learn more about workspace keys](https://docs.microsoft.com/azure/azure-monitor/platform/agent-windows#obtain-workspace-id-and-key).",
+ "providerDisplayName": "Keys",
+ "scope": "Workspace",
+ "requiredPermissions": {
+ "action": true
+ }
+ }
+ ],
+ "customs": [
+ {
+ "name": "Azure Subscription",
+ "description": "Azure Subscription with owner role is required to register an application in azure active directory() and assign role of contributor to app in resource group."
+ },
+ {
+ "name": "Microsoft.Web/sites permissions",
+ "description": "Read and write permissions to Azure Functions to create a Function App is required. [See the documentation to learn more about Azure Functions](https://docs.microsoft.com/azure/azure-functions/)."
+ },
+ {
+ "name": "REST API Credentials/permissions",
+ "description": "**VMRay API Key** is required."
+ }
+ ]
+ },
+ "instructionSteps": [
+ {
+ "description": ">**NOTE:** This connector uses Azure Functions to connect to the VMRay API to pull VMRay Threat IOCs into Microsoft Sentinel. This might result in additional costs for data ingestion and for storing data in Azure Blob Storage costs. Check the [Azure Functions pricing page](https://azure.microsoft.com/pricing/details/functions/) and [Azure Blob Storage pricing page](https://azure.microsoft.com/pricing/details/storage/blobs/) for details."
+ },
+ {
+ "description": ">**(Optional Step)** Securely store workspace and API authorization key(s) or token(s) in Azure Key Vault. Azure Key Vault provides a secure mechanism to store and retrieve key values. [Follow these instructions](https://docs.microsoft.com/azure/app-service/app-service-key-vault-references) to use Azure Key Vault with an Azure Function App."
+ },
+ {
+ "instructions": [
+ {
+ "parameters": {
+ "fillWith": [
+ "WorkspaceId"
+ ],
+ "label": "Workspace ID"
+ },
+ "type": "CopyableLabel"
+ },
+ {
+ "parameters": {
+ "fillWith": [
+ "PrimaryKey"
+ ],
+ "label": "Primary Key"
+ },
+ "type": "CopyableLabel"
+ }
+ ]
+ },
+ {
+ "description": "Use this method for automated deployment of the data connector using an ARM Template.\n\n1. Click the **Deploy to Azure** button below. \n\n\t[](https://aka.ms/sentinel-VMRay-azuredeploy)\n2. Select the preferred **Subscription**, **Resource Group** and **Location**. \n3. Enter the **Application ID**, **Tenant ID**,**Client Secret**, **VMRay API Key**, **VMRay Initial Fetch Date**, **TimeInterval** and deploy.\n4. Mark the checkbox labeled **I agree to the terms and conditions stated above**.\n5. Click **Purchase** to deploy.",
+ "title": "Option 1 - Azure Resource Manager (ARM) Template for Flex Consumption Plan"
+ },
+ {
+ "description": "Use this method for automated deployment of the data connector using an ARM Template.\n\n1. Click the **Deploy to Azure** button below. \n\n\t[](https://aka.ms/sentinel-VMRay-azuredeploy)\n2. Select the preferred **Subscription**, **Resource Group** and **Location**. \n3. Enter the **Application ID**, **Tenant ID**,**Client Secret**, **VMRay API Key**, **VMRay Initial Fetch Date**, **TimeInterval** and deploy.\n4. Mark the checkbox labeled **I agree to the terms and conditions stated above**.\n5. Click **Purchase** to deploy.",
+ "title": "Option 1 - Azure Resource Manager (ARM) Template for Premium Plan"
+ },
+ {
+ "description": "Use the following step-by-step instructions to deploy the VMRay data connector manually with Azure Functions (Deployment via Visual Studio Code).",
+ "title": "Option 2 - Manual Deployment of Azure Functions"
+ },
+ {
+ "description": "**1. Deploy a Function App**\n\n> NOTE:You will need to [prepare VS code](https://docs.microsoft.com/azure/azure-functions/functions-create-first-function-python#prerequisites) for Azure function development.\n\n1. Download the [Azure Function App](https://aka.ms/sentinel-VMRay-functionapp) file. Extract archive to your local development computer.\n2. Start VS Code. Choose File in the main menu and select Open Folder.\n3. Select the top level folder from extracted files.\n4. Choose the Azure icon in the Activity bar, then in the **Azure: Functions** area, choose the **Deploy to function app** button.\nIf you aren't already signed in, choose the Azure icon in the Activity bar, then in the **Azure: Functions** area, choose **Sign in to Azure**\nIf you're already signed in, go to the next step.\n5. Provide the following information at the prompts:\n\n\ta. **Select folder:** Choose a folder from your workspace or browse to one that contains your function app.\n\n\tb. **Select Subscription:** Choose the subscription to use.\n\n\tc. Select **Create new Function App in Azure** (Don't choose the Advanced option)\n\n\td. **Enter a globally unique name for the function app:** Type a name that is valid in a URL path. The name you type is validated to make sure that it's unique in Azure Functions. (e.g. VMRayXXX).\n\n\te. **Select a runtime:** Choose Python 3.11.\n\n\tf. Select a location for new resources. For better performance and lower costs choose the same [region](https://azure.microsoft.com/regions/) where Microsoft sentinel is located.\n\n6. Deployment will begin. A notification is displayed after your function app is created and the deployment package is applied.\n7. Go to Azure Portal for the Function App configuration."
+ },
+ {
+ "description": "**2. Configure the Function App**\\n\\n1. In the Function App, select the Function App Name and select **Configuration**.\\n2. In the **Application settings** tab, select **+ New application setting**.\\n3. Add each of the following application settings individually, with their respective string values (case-sensitive): \\n\\tApplication ID\\n\\tTenant ID\\n\\tClient Secret\\n\\tVMRay API Key\\n\\tVMRay Initial Fetch Date\\n\\tTimeInterval - Use logAnalyticsUri to override the log analytics API endpoint for dedicated cloud. For example, for public cloud, leave the value empty; for Azure GovUS cloud environment, specify the value in the following format: `https://.ods.opinsights.azure.us`\\n3. Once all application settings have been entered, click **Save**."
+ }
+ ],
+ "id": "[variables('_uiConfigId1')]"
+ }
+ }
+ },
+ {
+ "type": "Microsoft.OperationalInsights/workspaces/providers/contentPackages",
+ "apiVersion": "2023-04-01-preview",
+ "location": "[parameters('workspace-location')]",
+ "properties": {
+ "version": "3.0.0",
+ "kind": "Solution",
+ "contentSchemaVersion": "3.0.0",
+ "displayName": "VMRay",
+ "publisherDisplayName": "VMRay",
+ "descriptionHtml": "Note: Please refer to the following before installing the solution:
\n• Review the solution Release Notes
\n• There may be known issues pertaining to this Solution, please refer to them before installing.
\n The VMRay Connector for Microsoft Sentinel enhances security operations by providing enriched threat intelligence, enabling faster and more informed responses to security incidents. The integration has two main parts: first, URL detonation and enrichment, which provides detailed insights into suspicious URLs. Second, it automatically generates and feeds threat intelligence for all submissions to VMRay, improving threat detection and incident response in Sentinel. This seamless integration empowers teams to proactively address emerging threats.
\nData Connectors: 1, Function Apps: 1, Playbooks: 2
\nLearn more about Microsoft Sentinel | Learn more about Solutions
\n",
+ "contentKind": "Solution",
+ "contentProductId": "[variables('_solutioncontentProductId')]",
+ "id": "[variables('_solutioncontentProductId')]",
+ "icon": " ",
+ "contentId": "[variables('_solutionId')]",
+ "parentId": "[variables('_solutionId')]",
+ "source": {
+ "kind": "Solution",
+ "name": "VMRay",
+ "sourceId": "[variables('_solutionId')]"
+ },
+ "author": {
+ "name": "VMRay"
+ },
+ "support": {
+ "name": "VMRay",
+ "email": "support@vmray.com",
+ "tier": "Partner",
+ "link": "https://www.vmray.com/contact/customer-support/"
+ },
+ "dependencies": {
+ "operator": "AND",
+ "criteria": [
+ {
+ "kind": "AzureFunction",
+ "contentId": "[variables('_VMRayEnrichment_FunctionAppConnector')]",
+ "version": "[variables('playbookVersion1')]"
+ },
+ {
+ "kind": "Playbook",
+ "contentId": "[variables('_Submit-URL-VMRay-Analyzer')]",
+ "version": "[variables('playbookVersion2')]"
+ },
+ {
+ "kind": "Playbook",
+ "contentId": "[variables('_VMRay-Sandbox_Outlook_Attachment')]",
+ "version": "[variables('playbookVersion3')]"
+ },
+ {
+ "kind": "DataConnector",
+ "contentId": "[variables('_dataConnectorContentId1')]",
+ "version": "[variables('dataConnectorVersion1')]"
+ }
+ ]
+ },
+ "firstPublishDate": "2025-07-23",
+ "providers": [
+ "VMRay Inc"
+ ],
+ "categories": {
+ "domains": [
+ "Security - Automation (SOAR)",
+ "Security - Threat Intelligence"
+ ]
+ }
+ },
+ "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/', variables('_solutionId'))]"
+ }
+ ],
+ "outputs": {}
+}
+
diff --git a/Solutions/VMRay/Package/testParameters.json b/Solutions/VMRay/Package/testParameters.json
new file mode 100644
index 00000000000..8df65879926
--- /dev/null
+++ b/Solutions/VMRay/Package/testParameters.json
@@ -0,0 +1,24 @@
+{
+ "location": {
+ "type": "string",
+ "minLength": 1,
+ "defaultValue": "[resourceGroup().location]",
+ "metadata": {
+ "description": "Not used, but needed to pass arm-ttk test `Location-Should-Not-Be-Hardcoded`. We instead use the `workspace-location` which is derived from the LA workspace"
+ }
+ },
+ "workspace-location": {
+ "type": "string",
+ "defaultValue": "",
+ "metadata": {
+ "description": "[concat('Region to deploy solution resources -- separate from location selection',parameters('location'))]"
+ }
+ },
+ "workspace": {
+ "defaultValue": "",
+ "type": "string",
+ "metadata": {
+ "description": "Workspace name for Log Analytics where Microsoft Sentinel is setup"
+ }
+ }
+}
diff --git a/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/GetAnalysisBySampleID/__init__.py b/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/GetAnalysisBySampleID/__init__.py
new file mode 100644
index 00000000000..e738d6dacb8
--- /dev/null
+++ b/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/GetAnalysisBySampleID/__init__.py
@@ -0,0 +1 @@
+# pylint: disable=invalid-name
diff --git a/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/GetAnalysisBySampleID/app.py b/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/GetAnalysisBySampleID/app.py
new file mode 100644
index 00000000000..4caf01e8237
--- /dev/null
+++ b/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/GetAnalysisBySampleID/app.py
@@ -0,0 +1,48 @@
+"""
+Core Logic of Function App
+"""
+# pylint: disable=logging-fstring-interpolation
+
+import traceback
+from json import dumps
+import logging
+import azure.functions as func
+from vmray_api import VMRay
+
+
+def main(req: func.HttpRequest) -> func.HttpResponse:
+ """
+ This function handles VMRay API and returns HTTP response
+ :param req: func.HttpRequest
+ """
+ logging.info(f"Resource Requested: {func.HttpRequest}")
+
+ try:
+ sample_id = req.params.get("sample_id", "") or req.get_json().get(
+ "sample_id", ""
+ )
+ limit = req.params.get("limit") or req.get_json().get("limit")
+ if not sample_id:
+ return func.HttpResponse(
+ "Invalid Request. Missing 'sample_id' parameter.", status_code=400
+ )
+ logging.info(f"sample_id {sample_id}")
+ vmray = VMRay(logging)
+ vmray.check_id(sample_id)
+ endpoint = f"/rest/analysis/sample/{sample_id}"
+ params = {"_limit": limit}
+ response = vmray.request_vmray_api("GET", endpoint, params)
+ return func.HttpResponse(
+ dumps(response),
+ headers={"Content-Type": "application/json"},
+ status_code=200,
+ )
+ except KeyError as ke:
+ logging.error(f"Invalid Settings. {ke.args} configuration is missing.")
+ return func.HttpResponse(
+ "Invalid Settings. Configuration is missing.", status_code=500
+ )
+ except Exception as ex:
+ error_detail = traceback.format_exc()
+ logging.error(f"Exception Occurred: {str(ex)}, Traceback {error_detail}")
+ return func.HttpResponse("Internal Server Exception", status_code=500)
diff --git a/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/GetAnalysisBySampleID/function.json b/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/GetAnalysisBySampleID/function.json
new file mode 100644
index 00000000000..17fce6ffdb1
--- /dev/null
+++ b/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/GetAnalysisBySampleID/function.json
@@ -0,0 +1,21 @@
+{
+ "scriptFile": "app.py",
+ "bindings": [
+ {
+ "authLevel": "function",
+ "type": "httpTrigger",
+ "direction": "in",
+ "name": "req",
+ "methods": [
+ "get",
+ "post"
+ ]
+ },
+ {
+ "type": "http",
+ "direction": "out",
+ "name": "$return"
+ }
+ ]
+ }
+
\ No newline at end of file
diff --git a/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/GetVMRayIOCs/__init__.py b/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/GetVMRayIOCs/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/GetVMRayIOCs/app.py b/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/GetVMRayIOCs/app.py
new file mode 100644
index 00000000000..28b81a2f747
--- /dev/null
+++ b/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/GetVMRayIOCs/app.py
@@ -0,0 +1,75 @@
+# pylint: disable=logging-fstring-interpolation
+import logging
+import traceback
+from json import dumps
+
+import azure.functions as func
+
+from vmray_api import VMRay
+
+from .utils import IOC_LIST, IOC_MAPPING_FUNCTION, INDICATOR_LIST
+
+
+def main(req: func.HttpRequest) -> func.HttpResponse:
+ """
+ This function handles VMRay API and returns HTTP response
+ :param req: func.HttpRequest
+ """
+ logging.info(f"Resource Requested: {func.HttpRequest}")
+
+ try:
+ sample_id = req.params.get("sample_id") or req.get_json().get("sample_id")
+ submission_id = req.params.get("submission_id") or req.get_json().get(
+ "submission_id"
+ )
+ sample_verdict = req.params.get("sample_verdict") or req.get_json().get(
+ "sample_verdict"
+ )
+ incident_id = req.params.get("incident_id") or req.get_json().get("incident_id")
+ valid_until = req.params.get("valid_until") or req.get_json().get("valid_until")
+
+ if not sample_id:
+ return func.HttpResponse(
+ "Invalid Request. Missing 'sample_id' parameter.", status_code=400
+ )
+ logging.info(f"sample_id: {sample_id}")
+ vmray = VMRay(logging)
+ vmray.check_id(sample_id)
+ endpoint = f"/rest/sample/{sample_id}/iocs"
+ response = vmray.request_vmray_api("GET", endpoint)
+
+ if submission_id and sample_verdict:
+ iocs_resp = response.get("iocs", {})
+ for key, value in iocs_resp.items():
+ if key in IOC_LIST:
+ IOC_MAPPING_FUNCTION[key](
+ value,
+ int(sample_id),
+ submission_id,
+ sample_verdict,
+ incident_id,
+ valid_until
+ )
+ logging.info(f"length of indicator {len(INDICATOR_LIST)}")
+
+ return func.HttpResponse(
+ dumps({"api_resp": response, "custom_resp": INDICATOR_LIST}),
+ headers={"Content-Type": "application/json"},
+ status_code=200,
+ )
+ return func.HttpResponse(
+ dumps(response),
+ headers={"Content-Type": "application/json"},
+ status_code=200,
+ )
+
+ except KeyError as ke:
+ logging.error(f"Invalid Settings. {ke.args} configuration is missing.")
+ return func.HttpResponse(
+ "Invalid Settings. Configuration is missing.", status_code=500
+ )
+ except Exception as ex:
+ error_detail = traceback.format_exc()
+ logging.error(f"Exception Occurred: {str(ex)}, Traceback {error_detail}")
+ return func.HttpResponse("Internal Server Exception", status_code=500)
+
diff --git a/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/GetVMRayIOCs/function.json b/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/GetVMRayIOCs/function.json
new file mode 100644
index 00000000000..17fce6ffdb1
--- /dev/null
+++ b/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/GetVMRayIOCs/function.json
@@ -0,0 +1,21 @@
+{
+ "scriptFile": "app.py",
+ "bindings": [
+ {
+ "authLevel": "function",
+ "type": "httpTrigger",
+ "direction": "in",
+ "name": "req",
+ "methods": [
+ "get",
+ "post"
+ ]
+ },
+ {
+ "type": "http",
+ "direction": "out",
+ "name": "$return"
+ }
+ ]
+ }
+
\ No newline at end of file
diff --git a/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/GetVMRayIOCs/utils.py b/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/GetVMRayIOCs/utils.py
new file mode 100644
index 00000000000..d1c1901e628
--- /dev/null
+++ b/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/GetVMRayIOCs/utils.py
@@ -0,0 +1,429 @@
+"""
+utils functions
+"""
+
+# pylint: disable=logging-fstring-interpolation
+import logging
+import re
+import uuid
+from datetime import datetime, timezone, timedelta
+from os import environ
+
+vmrayBaseURL = environ["vmrayBaseURL"]
+IOC_LIST = ["domains", "ips", "urls", "files"]
+CONFIDENCE = {"malicious": "100", "suspicious": "75"}
+INDICATOR_LIST = []
+HASH_TYPE_LIST = [
+ ("MD5", "md5_hash"),
+ ("SHA-1", "sha1_hash"),
+ ("SHA-256", "sha256_hash"),
+]
+ipv4Regex = r"^(?P(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))[:]?(?P\d+)?$"
+ipv6Regex = r"^(?:(?:[0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|(?:[0-9a-fA-F]{1,4}:){1,7}:|(?:[0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|(?:[0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|(?:[0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|(?:[0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|(?:[0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:(?:(:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$" # noqa: E501
+
+
+def add_domain_indicator(
+ domains: list,
+ sample_id: str,
+ submission_id: str,
+ verdicts: list,
+ incident_id: str,
+ valid_until: str,
+) -> None:
+ """
+ Adds domain indicators to the global indicator list if the verdict of the
+ domains matches any of the provided verdicts.
+
+ Parameters
+ ----------
+ domains : list
+ List of domain ioc.
+ sample_id : str
+ ID of the related sample.
+ submission_id : str
+ ID of the related submission.
+ verdicts : list
+ List of accepted verdicts.
+ incident_id: str
+ Sentinel Incident ID
+ valid_until: str
+ Indicator Expiration time in days
+ """
+ try:
+ accepted_verdicts = {v.lower() for v in verdicts}
+
+ for domain in domains:
+ verdict = domain.get("verdict", "").lower()
+ domain_value = domain.get("domain")
+
+ if verdict not in accepted_verdicts:
+ continue
+ pattern = f"[domain-name:value = '{domain_value}']"
+ unique_id = gen_unique_id("domain", domain_value)
+ confidence = CONFIDENCE.get(verdict, 0)
+
+ indicator_data = get_static_data(
+ unique_id,
+ domain,
+ pattern,
+ sample_id,
+ submission_id,
+ domain_value,
+ confidence,
+ incident_id,
+ valid_until,
+ )
+
+ INDICATOR_LIST.append(indicator_data)
+ except Exception as err:
+ logging.error(f"Error processing domain indicators: {err}")
+
+
+def add_file_indicators(
+ files: list,
+ sample_id: str,
+ submission_id: str,
+ verdicts: list,
+ incident_id: str,
+ valid_until: str,
+) -> None:
+ """
+ Processes files and adds hash-based indicators to a global list based on verdicts.
+
+ Parameters
+ ----------
+ files : list
+ List of file ioc.
+ sample_id : str
+ Unique sample id.
+ submission_id : str
+ Submission id.
+ verdicts : list
+ List of accepted verdicts.
+ incident_id: str
+ Sentinel Incident ID
+ valid_until: str
+ Indicator Expiration time in days
+ """
+ try:
+ accepted_verdicts = {v.lower() for v in verdicts}
+
+ for file in files:
+ verdict = file.get("verdict", "").lower()
+ if verdict not in accepted_verdicts:
+ continue
+
+ file_hashes = file.get("hashes", [])
+ if not file_hashes:
+ logging.warning(f"No hashes found in file entry: {file}")
+ continue
+
+ hash_data = file_hashes[0]
+ filename = file.get("filename", "")
+ confidence = CONFIDENCE.get(verdict, 0)
+
+ for hash_type, key in HASH_TYPE_LIST:
+ hash_value = hash_data.get(key)
+ if not hash_value:
+ logging.warning(f"{key} not found in file: {file}")
+ continue
+
+ pattern = f"[file:hashes.'{hash_type}' = '{hash_value}']"
+ unique_id = gen_unique_id("file", hash_value)
+ label = filename or hash_value
+
+ indicator_data = get_static_data(
+ unique_id,
+ file,
+ pattern,
+ sample_id,
+ submission_id,
+ label,
+ confidence,
+ incident_id,
+ valid_until,
+ )
+
+ INDICATOR_LIST.append(indicator_data)
+
+ except Exception as err:
+ logging.error(f"Error processing file indicators: {err}")
+
+
+def check_ip(ip: str) -> str | None:
+ """
+ Determines the type of IP address based on its format.
+
+ Parameters
+ ----------
+ ip : str
+ The IP address to check.
+
+ Returns
+ -------
+ str or None
+ """
+ if re.match(ipv4Regex, ip):
+ return "ipv4-addr"
+ if re.match(ipv6Regex, ip):
+ return "ipv6-addr"
+
+ return None
+
+
+def add_ip_indicator(
+ ips: list,
+ sample_id: str,
+ submission_id: str,
+ verdicts: list,
+ incident_id: str,
+ valid_until: str,
+) -> None:
+ """
+ Adds IP indicators to the global indicator list based on verdict filtering.
+
+ Parameters
+ ----------
+ ips : list
+ List of IP.
+ sample_id : str
+ Sample ID.
+ submission_id : str
+ Submission ID.
+ verdicts : list
+ List of accepted verdicts.
+ incident_id: str
+ Sentinel Incident ID
+ valid_until: str
+ Indicator Expiration time in days
+ """
+ try:
+ accepted_verdicts = {v.lower() for v in verdicts}
+
+ for ip_entry in ips:
+ verdict = ip_entry.get("verdict", "").lower()
+ ip_address = ip_entry.get("ip_address", "")
+
+ if verdict not in accepted_verdicts:
+ continue
+
+ ip_type = check_ip(ip_address)
+ if not ip_type:
+ logging.warning(f"Unrecognized IP type for address: {ip_address}")
+ continue
+
+ pattern = f"[{ip_type}:value = '{ip_address}']"
+ unique_id = gen_unique_id("ip", ip_address)
+ confidence = CONFIDENCE.get(verdict, 0)
+
+ indicator_data = get_static_data(
+ unique_id,
+ ip_entry,
+ pattern,
+ sample_id,
+ submission_id,
+ ip_address,
+ confidence,
+ incident_id,
+ valid_until,
+ )
+
+ INDICATOR_LIST.append(indicator_data)
+
+ except Exception as err:
+ logging.error(f"Error processing IP indicators: {err}")
+
+
+def add_url_indicator(
+ urls: list,
+ sample_id: str,
+ submission_id: str,
+ verdicts: list,
+ incident_id: str,
+ valid_until: str,
+):
+ """
+ Adds URL indicators to the global indicator list (INDICATOR_LIST) based on verdict filtering.
+
+ Parameters
+ ----------
+ urls : list
+ List of URL.
+ sample_id : str
+ Sample ID.
+ submission_id : str
+ Submission ID.
+ verdicts : list
+ List of accepted verdicts.
+ incident_id: str
+ Sentinel Incident ID
+ valid_until: str
+ Indicator Expiration time in days
+ """
+ try:
+ accepted_verdicts = {v.lower() for v in verdicts}
+
+ for url_entry in urls:
+ verdict = url_entry.get("verdict", "").lower()
+ url_value = url_entry.get("url", "")
+
+ if verdict not in accepted_verdicts:
+ continue
+
+ pattern = f"[url:value = '{url_value}']"
+ unique_id = gen_unique_id("url", url_value)
+ confidence = CONFIDENCE.get(verdict, 0)
+
+ indicator_data = get_static_data(
+ unique_id,
+ url_entry,
+ pattern,
+ sample_id,
+ submission_id,
+ url_value,
+ confidence,
+ incident_id,
+ valid_until,
+ )
+
+ INDICATOR_LIST.append(indicator_data)
+
+ except Exception as err:
+ logging.error(f"Error processing URL indicators: {err}")
+
+
+def gen_unique_id(
+ indicator_type: str, indicator_value: str, threat_source: str = "VMRay"
+):
+ """
+ Generates a unique identifier string for a threat indicator.
+
+ Parameters
+ ----------
+ indicator_type : str
+ The type of indicator.
+ indicator_value : str
+ The indicator value such.
+ threat_source : str, optional
+ Indicator source.
+
+ Returns
+ -------
+ str
+ Unique indicator id.
+ """
+ custom_namespace = uuid.uuid5(uuid.NAMESPACE_DNS, threat_source)
+ name_string = f"{indicator_type}:{indicator_value}"
+ indicator_uuid = uuid.uuid5(custom_namespace, name_string)
+ return f"indicator--{indicator_uuid}"
+
+
+def get_utc_time() -> str:
+ """
+ Returns the current UTC time formatted as an ISO 8601 timestamp.
+
+ Returns
+ -------
+ str
+ The current UTC time as an ISO 8601 timestamp with milliseconds,
+ e.g., '2025-06-26T14:03:12.123Z'.
+ """
+ current_time = datetime.now(timezone.utc)
+ formatted_time = (
+ current_time.strftime("%Y-%m-%dT%H:%M:%S.")
+ + f"{current_time.microsecond // 1000:03d}Z"
+ )
+ return formatted_time
+
+
+def get_static_data(
+ unique_uuid,
+ indicator,
+ pattern,
+ sample_id,
+ submission_id,
+ name,
+ confidence,
+ incident_id,
+ valid_until,
+) -> dict:
+ """
+ Constructs a structured dictionary representing a static threat indicator.
+
+ Parameters
+ ----------
+ unique_uuid : str
+ A globally unique identifier for the indicator.
+ indicator : dict
+ Indicators metadata.
+ pattern : str
+ A STIX pattern string.
+ sample_id : str
+ Sample ID.
+ submission_id : str
+ Submission ID.
+ name : str
+ Name of the indicator.
+ confidence : int
+ An integer representing the confidence score (0–100) assigned to the indicator.
+ incident_id: str
+ Sentinel Incident ID
+ valid_until: str
+ Indicator Expiration time in days
+ Returns
+ -------
+ dict
+ A dictionary representing the structured threat indicator
+ """
+ analysis = ", ".join(map(str, indicator.get("analysis_ids", [])))
+ categories = ", ".join(indicator.get("categories", []))
+ threat_names = indicator.get("threat_names", [])
+ t_type = []
+ for threat in threat_names:
+ if re.match(r"^[a-zA-Z0-9\s]+$", threat):
+ t_type.append(threat)
+ tags = [
+ f"sample_id: {sample_id}",
+ f"submission_id: {submission_id}",
+ f"incident_id: {incident_id}",
+ f"threat_names: {','.join(t_type)}",
+ ] + indicator.get("classifications", [])
+ expiration_date = (
+ datetime.now(timezone.utc) + timedelta(days=int(valid_until))
+ ).strftime("%Y-%m-%dT%H:%M:%SZ")
+ data = {
+ "type": "indicator",
+ "spec_version": "2.1",
+ "id": unique_uuid,
+ "created": get_utc_time(),
+ "modified": get_utc_time(),
+ "revoked": False,
+ "labels": tags,
+ "confidence": confidence,
+ "external_references": [
+ {
+ "source_name": "VMRay Threat Intelligence",
+ "description": f"Sample ID {sample_id}\nSubmission ID {submission_id}",
+ "url": f"{vmrayBaseURL}/samples/{sample_id}#summary",
+ }
+ ],
+ "name": name,
+ "description": f"Sample URL: {vmrayBaseURL}/samples/{sample_id}#summary,"
+ f"\nAnalysis IDs: {analysis},\nCategories: {categories}",
+ "indicator_types": [indicator.get("ioc_type", "")],
+ "pattern": pattern,
+ "pattern_type": "stix",
+ "pattern_version": "2.1",
+ "valid_from": get_utc_time(),
+ "valid_until": expiration_date,
+ }
+ return data
+
+
+IOC_MAPPING_FUNCTION = {
+ "domains": add_domain_indicator,
+ "ips": add_ip_indicator,
+ "urls": add_url_indicator,
+ "files": add_file_indicators,
+}
+
diff --git a/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/GetVMRaySample/__init__.py b/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/GetVMRaySample/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/GetVMRaySample/app.py b/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/GetVMRaySample/app.py
new file mode 100644
index 00000000000..a925f99fa30
--- /dev/null
+++ b/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/GetVMRaySample/app.py
@@ -0,0 +1,46 @@
+"""
+Main function
+"""
+# pylint: disable=logging-fstring-interpolation
+import logging
+import traceback
+from json import dumps
+
+import azure.functions as func
+
+from vmray_api import VMRay
+
+
+def main(req: func.HttpRequest) -> func.HttpResponse:
+ """
+ This function handles VMRay API and returns HTTP response
+ :param req: func.HttpRequest
+ """
+ logging.info(f"Resource Requested: {func.HttpRequest}")
+
+ try:
+ sample_id = req.params.get("sample_id") or req.get_json().get("sample_id")
+ if not sample_id:
+ return func.HttpResponse(
+ "Invalid Request. Missing 'sample_id' parameter.", status_code=400
+ )
+ logging.info(f"sample_id {sample_id}")
+ vmray = VMRay(logging)
+ vmray.check_id(sample_id)
+ endpoint = f"/rest/sample/{sample_id}"
+
+ response = vmray.request_vmray_api("GET", endpoint)
+ return func.HttpResponse(
+ dumps(response),
+ headers={"Content-Type": "application/json"},
+ status_code=200,
+ )
+ except KeyError as ke:
+ logging.error(f"Invalid Settings. {ke.args} configuration is missing.")
+ return func.HttpResponse(
+ "Invalid Settings. Configuration is missing.", status_code=500
+ )
+ except Exception as ex:
+ error_detail = traceback.format_exc()
+ logging.error(f"Exception Occurred: {str(ex)}, Traceback {error_detail}")
+ return func.HttpResponse("Internal Server Exception", status_code=500)
diff --git a/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/GetVMRaySample/function.json b/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/GetVMRaySample/function.json
new file mode 100644
index 00000000000..17fce6ffdb1
--- /dev/null
+++ b/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/GetVMRaySample/function.json
@@ -0,0 +1,21 @@
+{
+ "scriptFile": "app.py",
+ "bindings": [
+ {
+ "authLevel": "function",
+ "type": "httpTrigger",
+ "direction": "in",
+ "name": "req",
+ "methods": [
+ "get",
+ "post"
+ ]
+ },
+ {
+ "type": "http",
+ "direction": "out",
+ "name": "$return"
+ }
+ ]
+ }
+
\ No newline at end of file
diff --git a/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/GetVMRaySampleByHash/__init__.py b/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/GetVMRaySampleByHash/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/GetVMRaySampleByHash/app.py b/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/GetVMRaySampleByHash/app.py
new file mode 100644
index 00000000000..e4969436c6f
--- /dev/null
+++ b/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/GetVMRaySampleByHash/app.py
@@ -0,0 +1,54 @@
+"""
+Main Function
+"""
+# pylint: disable=logging-fstring-interpolation
+import logging
+import traceback
+from json import dumps
+
+import azure.functions as func
+
+from vmray_api import VMRay
+
+
+def main(req: func.HttpRequest) -> func.HttpResponse:
+ """
+ This function handles VMRay API and returns HTTP response
+ :param req: func.HttpRequest
+ """
+ logging.info(f"Resource Requested: {func.HttpRequest}")
+
+ try:
+ hash_value = req.params.get("hash") or req.get_json().get("hash")
+ if not hash_value:
+ return func.HttpResponse(
+ "Invalid Request. Missing 'hash' parameter.", status_code=400
+ )
+ logging.info(f"hash {hash}")
+ hash_type_lookup = {32: "md5", 40: "sha1", 64: "sha256"}
+ hash_type = hash_type_lookup.get(len(hash_value))
+ if hash_type is None:
+ error_string = " or ".join(
+ f"{len_} ({type_})" for len_, type_ in hash_type_lookup.items()
+ )
+ raise ValueError(
+ f"Invalid hash provided, must be of length {error_string}. "
+ f"Provided hash had a length of {len(hash_value)}."
+ )
+ endpoint = f"/rest/sample/{hash_type}/{hash_value}"
+ vmray = VMRay(logging)
+ response = vmray.request_vmray_api("GET", endpoint)
+ return func.HttpResponse(
+ dumps(response),
+ headers={"Content-Type": "application/json"},
+ status_code=200,
+ )
+ except KeyError as ke:
+ logging.error(f"Invalid Settings. {ke.args} configuration is missing.")
+ return func.HttpResponse(
+ "Invalid Settings. Configuration is missing.", status_code=500
+ )
+ except Exception as ex:
+ error_detail = traceback.format_exc()
+ logging.error(f"Exception Occurred: {str(ex)}, Traceback {error_detail}")
+ return func.HttpResponse("Internal Server Exception", status_code=500)
diff --git a/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/GetVMRaySampleByHash/function.json b/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/GetVMRaySampleByHash/function.json
new file mode 100644
index 00000000000..17fce6ffdb1
--- /dev/null
+++ b/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/GetVMRaySampleByHash/function.json
@@ -0,0 +1,21 @@
+{
+ "scriptFile": "app.py",
+ "bindings": [
+ {
+ "authLevel": "function",
+ "type": "httpTrigger",
+ "direction": "in",
+ "name": "req",
+ "methods": [
+ "get",
+ "post"
+ ]
+ },
+ {
+ "type": "http",
+ "direction": "out",
+ "name": "$return"
+ }
+ ]
+ }
+
\ No newline at end of file
diff --git a/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/GetVMRaySubmission/__init__.py b/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/GetVMRaySubmission/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/GetVMRaySubmission/app.py b/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/GetVMRaySubmission/app.py
new file mode 100644
index 00000000000..d751e8a23dd
--- /dev/null
+++ b/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/GetVMRaySubmission/app.py
@@ -0,0 +1,50 @@
+"""
+Main Function
+"""
+# pylint: disable=logging-fstring-interpolation
+
+import logging
+
+import traceback
+from json import dumps
+
+import azure.functions as func
+
+from vmray_api import VMRay
+
+
+def main(req: func.HttpRequest) -> func.HttpResponse:
+ """
+ This function handles VMRay API and returns HTTP response
+ :param req: func.HttpRequest
+ """
+ logging.info(f"Resource Requested: {func.HttpRequest}")
+
+ try:
+ submission_id = req.params.get("submission_id") or req.get_json().get(
+ "submission_id"
+ )
+ if not submission_id:
+ return func.HttpResponse(
+ "Invalid Request. Missing 'submission_id' parameter.", status_code=400
+ )
+ logging.info(f"submission_id {submission_id}")
+ vmray = VMRay(logging)
+ vmray.check_id(submission_id)
+
+ endpoint = f"/rest/submission/{submission_id}"
+ response = vmray.request_vmray_api("GET", endpoint)
+ return func.HttpResponse(
+ dumps(response),
+ headers={"Content-Type": "application/json"},
+ status_code=200,
+ )
+ except KeyError as ke:
+ logging.error(f"Invalid Settings. {ke.args} configuration is missing.")
+ return func.HttpResponse(
+ "Invalid Settings. Configuration is missing.", status_code=500
+ )
+ except Exception as ex:
+ error_detail = traceback.format_exc()
+ logging.error(f"Exception Occurred: {str(ex)}, Traceback {error_detail}")
+ return func.HttpResponse("Internal Server Exception", status_code=500)
diff --git a/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/GetVMRaySubmission/function.json b/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/GetVMRaySubmission/function.json
new file mode 100644
index 00000000000..17fce6ffdb1
--- /dev/null
+++ b/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/GetVMRaySubmission/function.json
@@ -0,0 +1,21 @@
+{
+ "scriptFile": "app.py",
+ "bindings": [
+ {
+ "authLevel": "function",
+ "type": "httpTrigger",
+ "direction": "in",
+ "name": "req",
+ "methods": [
+ "get",
+ "post"
+ ]
+ },
+ {
+ "type": "http",
+ "direction": "out",
+ "name": "$return"
+ }
+ ]
+ }
+
\ No newline at end of file
diff --git a/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/GetVMRayThreatIndicator/__init__.py b/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/GetVMRayThreatIndicator/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/GetVMRayThreatIndicator/app.py b/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/GetVMRayThreatIndicator/app.py
new file mode 100644
index 00000000000..7e4970c103d
--- /dev/null
+++ b/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/GetVMRayThreatIndicator/app.py
@@ -0,0 +1,47 @@
+"""
+Main Function
+"""
+
+# pylint: disable=logging-fstring-interpolation
+
+import logging
+import traceback
+from json import dumps
+
+import azure.functions as func
+
+from vmray_api import VMRay
+
+
+def main(req: func.HttpRequest) -> func.HttpResponse:
+ """
+ This function handles VMRay API and returns HTTP response
+ :param req: func.HttpRequest
+ """
+ logging.info(f"Resource Requested: {func.HttpRequest}")
+
+ try:
+ sample_id = req.params.get("sample_id") or req.get_json().get("sample_id")
+ if not sample_id:
+ return func.HttpResponse(
+ "Invalid Request. Missing 'sample_id' parameter.", status_code=400
+ )
+ logging.info(f"sample_id {sample_id}")
+ vmray = VMRay(logging)
+ vmray.check_id(sample_id)
+ endpoint = f"/rest/sample/{sample_id}/threat_indicators"
+ response = vmray.request_vmray_api("GET", endpoint)
+ return func.HttpResponse(
+ dumps(response),
+ headers={"Content-Type": "application/json"},
+ status_code=200,
+ )
+ except KeyError as ke:
+ logging.error(f"Invalid Settings. {ke.args} configuration is missing.")
+ return func.HttpResponse(
+ "Invalid Settings. Configuration is missing.", status_code=500
+ )
+ except Exception as ex:
+ error_detail = traceback.format_exc()
+ logging.error(f"Exception Occurred: {str(ex)}, Traceback {error_detail}")
+ return func.HttpResponse("Internal Server Exception", status_code=500)
diff --git a/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/GetVMRayThreatIndicator/function.json b/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/GetVMRayThreatIndicator/function.json
new file mode 100644
index 00000000000..17fce6ffdb1
--- /dev/null
+++ b/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/GetVMRayThreatIndicator/function.json
@@ -0,0 +1,21 @@
+{
+ "scriptFile": "app.py",
+ "bindings": [
+ {
+ "authLevel": "function",
+ "type": "httpTrigger",
+ "direction": "in",
+ "name": "req",
+ "methods": [
+ "get",
+ "post"
+ ]
+ },
+ {
+ "type": "http",
+ "direction": "out",
+ "name": "$return"
+ }
+ ]
+ }
+
\ No newline at end of file
diff --git a/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/GetVMRayVTIs/__init__.py b/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/GetVMRayVTIs/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/GetVMRayVTIs/app.py b/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/GetVMRayVTIs/app.py
new file mode 100644
index 00000000000..d470cbb7d7c
--- /dev/null
+++ b/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/GetVMRayVTIs/app.py
@@ -0,0 +1,47 @@
+"""
+Main Function
+"""
+
+import logging
+
+# pylint: disable=logging-fstring-interpolation
+import traceback
+from json import dumps
+
+import azure.functions as func
+
+from vmray_api import VMRay
+
+
+def main(req: func.HttpRequest) -> func.HttpResponse:
+ """
+ This function handles VMRay API and returns HTTP response
+ :param req: func.HttpRequest
+ """
+ logging.info(f"Resource Requested: {func.HttpRequest}")
+
+ try:
+ sample_id = req.params.get("sample_id") or req.get_json().get("sample_id")
+ if not sample_id:
+ return func.HttpResponse(
+ "Invalid Request. Missing 'sample_id' parameter.", status_code=400
+ )
+ logging.info(f"sample_id {sample_id}")
+ vmray = VMRay(logging)
+ vmray.check_id(sample_id)
+ endpoint = f"/rest/sample/{sample_id}/vtis"
+ response = vmray.request_vmray_api("GET", endpoint)
+ return func.HttpResponse(
+ dumps(response),
+ headers={"Content-Type": "application/json"},
+ status_code=200,
+ )
+ except KeyError as ke:
+ logging.error(f"Invalid Settings. {ke.args} configuration is missing.")
+ return func.HttpResponse(
+ "Invalid Settings. Configuration is missing.", status_code=500
+ )
+ except Exception as ex:
+ error_detail = traceback.format_exc()
+ logging.error(f"Exception Occurred: {str(ex)}, Traceback {error_detail}")
+ return func.HttpResponse("Internal Server Exception", status_code=500)
diff --git a/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/GetVMRayVTIs/function.json b/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/GetVMRayVTIs/function.json
new file mode 100644
index 00000000000..17fce6ffdb1
--- /dev/null
+++ b/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/GetVMRayVTIs/function.json
@@ -0,0 +1,21 @@
+{
+ "scriptFile": "app.py",
+ "bindings": [
+ {
+ "authLevel": "function",
+ "type": "httpTrigger",
+ "direction": "in",
+ "name": "req",
+ "methods": [
+ "get",
+ "post"
+ ]
+ },
+ {
+ "type": "http",
+ "direction": "out",
+ "name": "$return"
+ }
+ ]
+ }
+
\ No newline at end of file
diff --git a/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/UplaodURL/__init__.py b/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/UplaodURL/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/UplaodURL/app.py b/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/UplaodURL/app.py
new file mode 100644
index 00000000000..9560e11c7db
--- /dev/null
+++ b/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/UplaodURL/app.py
@@ -0,0 +1,120 @@
+"""
+Main Function
+"""
+
+# pylint: disable=logging-fstring-interpolation
+import logging
+import traceback
+from json import dumps
+
+import azure.functions as func
+
+from vmray_api import VMRay
+
+
+def build_submission_data(data):
+ """Process a submission response from VMRay Platform
+
+ Args:
+ data: (dict): submission response
+ """
+ jobs_list = []
+ jobs = data.get("jobs", [])
+ for job in jobs:
+ if isinstance(job, dict):
+ job_entry = {}
+ job_entry["JobID"] = job.get("job_id")
+ job_entry["Created"] = job.get("job_created")
+ job_entry["SampleID"] = job.get("job_sample_id")
+ job_entry["VMName"] = job.get("job_vm_name")
+ job_entry["VMID"] = job.get("job_vm_id")
+ job_entry["JobRuleSampleType"] = job.get("job_jobrule_sampletype")
+ jobs_list.append(job_entry)
+
+ samples_list = []
+ samples = data.get("samples", [])
+ for sample in samples:
+ if isinstance(sample, dict):
+ sample_entry = {}
+ sample_entry["SampleID"] = sample.get("sample_id")
+ sample_entry["SampleURL"] = sample.get("sample_webif_url")
+ sample_entry["Created"] = sample.get("sample_created")
+ sample_entry["FileName"] = sample.get("submission_filename")
+ sample_entry["FileSize"] = sample.get("sample_filesize")
+ sample_entry["SSDeep"] = sample.get("sample_ssdeephash")
+ sample_entry["SHA1"] = sample.get("sample_sha1hash")
+ samples_list.append(sample_entry)
+
+ submissions_list = []
+ submissions = data.get("submissions", [])
+ for submission in submissions:
+ if isinstance(submission, dict):
+ submission_entry = {}
+ submission_entry["SubmissionID"] = submission.get("submission_id")
+ submission_entry["SubmissionURL"] = submission.get("submission_webif_url")
+ submission_entry["SampleID"] = submission.get("submission_sample_id")
+ submissions_list.append(submission_entry)
+
+ entry_context = {}
+ entry_context["vmray_job"] = jobs_list
+ entry_context["vmray_sample"] = samples_list
+ entry_context["vmray_submission"] = submissions_list
+
+ return entry_context
+
+
+def main(req: func.HttpRequest) -> func.HttpResponse:
+ """
+ This function handles VMRay API and returns HTTP response
+ :param req: func.HttpRequest
+ """
+ logging.info(f"Resource Requested: {func.HttpRequest}")
+
+ try:
+ url = req.params.get("url") or req.get_json().get("url")
+ shareable = req.params.get("shareable") or req.get_json().get("shareable")
+ max_jobs = req.params.get("max_jobs") or req.get_json().get("max_jobs")
+ tags = req.params.get("tags", []) or req.get_json().get("tags", [])
+ net_scheme_name = req.params.get("net_scheme_name", []) or req.get_json().get(
+ "net_scheme_name"
+ )
+
+ if not url:
+ return func.HttpResponse(
+ "Invalid Request. Missing 'url' parameter.", status_code=400
+ )
+ vmray = VMRay(logging)
+ params = {"shareable": shareable == "true"}
+ if max_jobs:
+ if (
+ isinstance(max_jobs, str)
+ and max_jobs.isdigit()
+ or isinstance(max_jobs, int)
+ ):
+ params["max_jobs"] = int(max_jobs)
+ else:
+ raise ValueError("max_jobs arguments isn't a number")
+ if tags:
+ params["tags"] = ",".join(tags)
+ if net_scheme_name:
+ params["user_config"] = (
+ '{"net_scheme_name": "' + str(net_scheme_name) + '"}'
+ )
+ endpoint = "/rest/sample/submit"
+ params["sample_url"] = url
+ response = vmray.request_vmray_api("POST", endpoint, params)
+ submission_data = build_submission_data(response)
+ return func.HttpResponse(
+ dumps(submission_data),
+ headers={"Content-Type": "application/json"},
+ status_code=200,
+ )
+ except KeyError as ke:
+ logging.error(f"Invalid Settings. {ke.args} configuration is missing.")
+ return func.HttpResponse(
+ "Invalid Settings. Configuration is missing.", status_code=500
+ )
+ except Exception as ex:
+ error_detail = traceback.format_exc()
+ logging.error(f"Exception Occurred: {str(ex)}, Traceback {error_detail}")
+ return func.HttpResponse("Internal Server Exception", status_code=500)
diff --git a/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/UplaodURL/function.json b/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/UplaodURL/function.json
new file mode 100644
index 00000000000..17fce6ffdb1
--- /dev/null
+++ b/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/UplaodURL/function.json
@@ -0,0 +1,21 @@
+{
+ "scriptFile": "app.py",
+ "bindings": [
+ {
+ "authLevel": "function",
+ "type": "httpTrigger",
+ "direction": "in",
+ "name": "req",
+ "methods": [
+ "get",
+ "post"
+ ]
+ },
+ {
+ "type": "http",
+ "direction": "out",
+ "name": "$return"
+ }
+ ]
+ }
+
\ No newline at end of file
diff --git a/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/VMRayEnrichment.zip b/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/VMRayEnrichment.zip
new file mode 100644
index 00000000000..2d0faa0b03c
Binary files /dev/null and b/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/VMRayEnrichment.zip differ
diff --git a/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/VMRayUploadSample/__init__.py b/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/VMRayUploadSample/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/VMRayUploadSample/app.py b/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/VMRayUploadSample/app.py
new file mode 100644
index 00000000000..175855899a5
--- /dev/null
+++ b/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/VMRayUploadSample/app.py
@@ -0,0 +1,144 @@
+"""
+Main Function
+"""
+
+# pylint: disable=logging-fstring-interpolation
+
+import base64
+import logging
+import traceback
+from io import BytesIO
+from json import dumps
+
+import azure.functions as func
+
+from vmray_api import VMRay
+
+
+def build_submission_data(data):
+ """Process a submission response from VMRay Platform
+
+ Args:
+ data: (dict): submission response
+ """
+ jobs_list = []
+ jobs = data.get("jobs", [])
+ for job in jobs:
+ if isinstance(job, dict):
+ job_entry = {
+ "JobID": job.get("job_id"),
+ "Created": job.get("job_created"),
+ "SampleID": job.get("job_sample_id"),
+ "VMName": job.get("job_vm_name"),
+ "VMID": job.get("job_vm_id"),
+ "JobRuleSampleType": job.get("job_jobrule_sampletype"),
+ }
+ jobs_list.append(job_entry)
+
+ samples_list = []
+ samples = data.get("samples", [])
+ for sample in samples:
+ if isinstance(sample, dict):
+ sample_entry = {
+ "SampleID": sample.get("sample_id"),
+ "SampleURL": sample.get("sample_webif_url"),
+ "Created": sample.get("sample_created"),
+ "FileName": sample.get("submission_filename"),
+ "FileSize": sample.get("sample_filesize"),
+ "SSDeep": sample.get("sample_ssdeephash"),
+ "SHA1": sample.get("sample_sha1hash"),
+ }
+ samples_list.append(sample_entry)
+
+ submissions_list = []
+ submissions = data.get("submissions", [])
+ for submission in submissions:
+ if isinstance(submission, dict):
+ submission_entry = {
+ "SubmissionID": submission.get("submission_id"),
+ "SubmissionURL": submission.get("submission_webif_url"),
+ "SampleID": submission.get("submission_sample_id"),
+ }
+ submissions_list.append(submission_entry)
+
+ entry_context = {}
+ entry_context["vmray_job"] = jobs_list
+ entry_context["vmray_sample"] = samples_list
+ entry_context["vmray_submission"] = submissions_list
+
+ return entry_context
+
+
+def main(req: func.HttpRequest) -> func.HttpResponse:
+ """
+ This function handles VMRay API and returns HTTP response
+ :param req: func.HttpRequest
+ """
+ logging.info(f"Resource Requested: {func.HttpRequest}")
+
+ try:
+ file = req.params.get("file") or req.get_json().get("file")
+ name = req.params.get("name") or req.get_json().get("name")
+ doc_pass = req.params.get("document_password") or req.get_json().get(
+ "document_password"
+ )
+ arch_pass = req.params.get("archive_password") or req.get_json().get(
+ "archive_password"
+ )
+ sample_type = req.params.get("sample_type") or req.get_json().get("sample_type")
+ shareable = req.params.get("shareable") or req.get_json().get("shareable")
+ max_jobs = req.params.get("max_jobs") or req.get_json().get("max_jobs")
+ tags = req.params.get("tags", []) or req.get_json().get("tags", [])
+ net_scheme_name = req.params.get("net_scheme_name", []) or req.get_json().get(
+ "net_scheme_name"
+ )
+
+ if not file:
+ return func.HttpResponse(
+ "Invalid Request. Missing 'file' parameter.", status_code=400
+ )
+ vmray = VMRay(logging)
+ binary_data = base64.b64decode(file)
+ file_object = BytesIO(binary_data)
+ file_object.name = name
+ params = {"shareable": shareable == "true"}
+ if doc_pass:
+ params["document_password"] = doc_pass
+ if arch_pass:
+ params["archive_password"] = arch_pass
+ if sample_type:
+ params["sample_type"] = sample_type
+ if max_jobs:
+ if (
+ isinstance(max_jobs, str)
+ and max_jobs.isdigit()
+ or isinstance(max_jobs, int)
+ ):
+ params["max_jobs"] = int(max_jobs)
+ else:
+ raise ValueError("max_jobs arguments isn't a number")
+ if tags:
+ params["tags"] = ",".join(tags)
+ if net_scheme_name:
+ params["user_config"] = (
+ '{"net_scheme_name": "' + str(net_scheme_name) + '"}'
+ )
+ endpoint = "/rest/sample/submit"
+ params["sample_file"] = file_object
+ response = vmray.request_vmray_api("POST", endpoint, params)
+ submission_data = build_submission_data(response)
+ return func.HttpResponse(
+ dumps(submission_data),
+ headers={"Content-Type": "application/json"},
+ status_code=200,
+ )
+
+ except KeyError as ke:
+ logging.error(f"Invalid Settings. {ke.args} configuration is missing.")
+ return func.HttpResponse(
+ "Invalid Settings. Configuration is missing.", status_code=500
+ )
+ except Exception as ex:
+ error_detail = traceback.format_exc()
+ logging.error(f"Exception Occurred: {str(ex)}, Traceback {error_detail}")
+ return func.HttpResponse("Internal Server Exception", status_code=500)
diff --git a/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/VMRayUploadSample/function.json b/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/VMRayUploadSample/function.json
new file mode 100644
index 00000000000..17fce6ffdb1
--- /dev/null
+++ b/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/VMRayUploadSample/function.json
@@ -0,0 +1,21 @@
+{
+ "scriptFile": "app.py",
+ "bindings": [
+ {
+ "authLevel": "function",
+ "type": "httpTrigger",
+ "direction": "in",
+ "name": "req",
+ "methods": [
+ "get",
+ "post"
+ ]
+ },
+ {
+ "type": "http",
+ "direction": "out",
+ "name": "$return"
+ }
+ ]
+ }
+
\ No newline at end of file
diff --git a/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/azuredeploy.json b/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/azuredeploy.json
new file mode 100644
index 00000000000..248cede9e46
--- /dev/null
+++ b/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/azuredeploy.json
@@ -0,0 +1,203 @@
+{
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "contentVersion": "1.0.0.0",
+ "parameters": {
+ "vmrayBaseURL": {
+ "type": "string",
+ "defaultValue": "https://us.cloud.vmray.com",
+ "minLength": 1
+ },
+ "vmrayAPIKey": {
+ "type": "securestring",
+ "defaultValue": "",
+ "minLength": 1
+ },
+ "Resubmit": {
+ "type": "bool",
+ "defaultValue": true,
+ "metadata": {
+ "description": "If true, the sample will be resubmitted to VMRay analyser, even if the sample hash was found in VMRay."
+ }
+ },
+ "FunctionAppName": {
+ "defaultValue": "vmrayenrich",
+ "type": "string"
+ },
+ "AppInsightsWorkspaceResourceID": {
+ "type": "string",
+ "metadata": {
+ "description": "Migrate Classic Application Insights to Log Analytic Workspace which is retiring by 29 Febraury 2024. Use 'Log Analytic Workspace-->Properties' blade having 'Resource ID' property value. This is a fully qualified resourceId which is in format '/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.OperationalInsights/workspaces/{workspaceName}'"
+ }
+ }
+ },
+ "variables": {
+ "FunctionName": "[concat(toLower(parameters('FunctionAppName')), take(uniqueString(resourceGroup().id), 3))]",
+ "StorageSuffix": "[environment().suffixes.storage]"
+ },
+ "resources": [
+ {
+ "type": "Microsoft.Insights/components",
+ "apiVersion": "2020-02-02",
+ "name": "[variables('FunctionName')]",
+ "location": "[resourceGroup().location]",
+ "kind": "web",
+ "properties": {
+ "Application_Type": "web",
+ "ApplicationId": "[variables('FunctionName')]",
+ "WorkspaceResourceId": "[parameters('AppInsightsWorkspaceResourceID')]"
+ }
+ },
+ {
+ "type": "Microsoft.Storage/storageAccounts",
+ "apiVersion": "2019-06-01",
+ "name": "[tolower(variables('FunctionName'))]",
+ "location": "[resourceGroup().location]",
+ "sku": {
+ "name": "Standard_LRS",
+ "tier": "Standard"
+ },
+ "kind": "StorageV2",
+ "properties": {
+ "networkAcls": {
+ "bypass": "AzureServices",
+ "virtualNetworkRules": [],
+ "ipRules": [],
+ "defaultAction": "Allow"
+ },
+ "supportsHttpsTrafficOnly": true,
+ "encryption": {
+ "services": {
+ "file": {
+ "keyType": "Account",
+ "enabled": true
+ },
+ "blob": {
+ "keyType": "Account",
+ "enabled": true
+ }
+ },
+ "keySource": "Microsoft.Storage"
+ }
+ }
+ },
+ {
+ "type": "Microsoft.Storage/storageAccounts/blobServices",
+ "apiVersion": "2019-06-01",
+ "name": "[concat(variables('FunctionName'), '/default')]",
+ "dependsOn": [
+ "[resourceId('Microsoft.Storage/storageAccounts', tolower(variables('FunctionName')))]"
+ ],
+ "sku": {
+ "name": "Standard_LRS",
+ "tier": "Standard"
+ },
+ "properties": {
+ "cors": {
+ "corsRules": []
+ },
+ "deleteRetentionPolicy": {
+ "enabled": false
+ }
+ }
+ },
+ {
+ "type": "Microsoft.Storage/storageAccounts/fileServices",
+ "apiVersion": "2019-06-01",
+ "name": "[concat(variables('FunctionName'), '/default')]",
+ "dependsOn": [
+ "[resourceId('Microsoft.Storage/storageAccounts', tolower(variables('FunctionName')))]"
+ ],
+ "sku": {
+ "name": "Standard_LRS",
+ "tier": "Standard"
+ },
+ "properties": {
+ "cors": {
+ "corsRules": []
+ }
+ }
+ },
+ {
+ "type": "Microsoft.Web/sites",
+ "apiVersion": "2018-11-01",
+ "name": "[variables('FunctionName')]",
+ "location": "[resourceGroup().location]",
+ "dependsOn": [
+ "[resourceId('Microsoft.Storage/storageAccounts', tolower(variables('FunctionName')))]",
+ "[resourceId('Microsoft.Insights/components', variables('FunctionName'))]"
+ ],
+ "kind": "functionapp,linux",
+ "identity": {
+ "type": "SystemAssigned"
+ },
+ "properties": {
+ "name": "[variables('FunctionName')]",
+ "httpsOnly": true,
+ "clientAffinityEnabled": true,
+ "alwaysOn": true,
+ "reserved": true,
+ "siteConfig": {
+ "linuxFxVersion": "python|3.11"
+ }
+ },
+ "resources": [
+ {
+ "apiVersion": "2018-11-01",
+ "type": "config",
+ "name": "appsettings",
+ "dependsOn": [
+ "[concat('Microsoft.Web/sites/', variables('FunctionName'))]"
+ ],
+ "properties": {
+ "FUNCTIONS_EXTENSION_VERSION": "~4",
+ "FUNCTIONS_WORKER_RUNTIME": "python",
+ "APPINSIGHTS_INSTRUMENTATIONKEY": "[reference(resourceId('Microsoft.insights/components', variables('FunctionName')), '2020-02-02').InstrumentationKey]",
+ "APPLICATIONINSIGHTS_CONNECTION_STRING": "[reference(resourceId('microsoft.insights/components', variables('FunctionName')), '2020-02-02').ConnectionString]",
+ "AzureWebJobsStorage": "[concat('DefaultEndpointsProtocol=https;AccountName=', toLower(variables('FunctionName')),';AccountKey=',listKeys(resourceId('Microsoft.Storage/storageAccounts', toLower(variables('FunctionName'))), '2023-04-01').keys[0].value, ';EndpointSuffix=',toLower(variables('StorageSuffix')))]",
+ "vmrayBaseURL": "[parameters('vmrayBaseURL')]",
+ "vmrayAPIKey": "[parameters('vmrayAPIKey')]",
+ "Resubmit": "[parameters('Resubmit')]",
+ "WEBSITE_RUN_FROM_PACKAGE": "https://github.com/vmray/ms-sentinel/raw/refs/heads/main/VMRayEnrichment/VMRayEnrichemntFuncApp.zip?raw=true"
+ }
+ }
+ ]
+ },
+ {
+ "type": "Microsoft.Storage/storageAccounts/blobServices/containers",
+ "apiVersion": "2019-06-01",
+ "name": "[concat(variables('FunctionName'), '/default/azure-webjobs-hosts')]",
+ "dependsOn": [
+ "[resourceId('Microsoft.Storage/storageAccounts/blobServices', variables('FunctionName'), 'default')]",
+ "[resourceId('Microsoft.Storage/storageAccounts', variables('FunctionName'))]"
+ ],
+ "properties": {
+ "publicAccess": "None"
+ }
+ },
+ {
+ "type": "Microsoft.Storage/storageAccounts/blobServices/containers",
+ "apiVersion": "2019-06-01",
+ "name": "[concat(variables('FunctionName'), '/default/azure-webjobs-secrets')]",
+ "dependsOn": [
+ "[resourceId('Microsoft.Storage/storageAccounts/blobServices', variables('FunctionName'), 'default')]",
+ "[resourceId('Microsoft.Storage/storageAccounts', variables('FunctionName'))]"
+ ],
+ "properties": {
+ "publicAccess": "None"
+ }
+ },
+ {
+ "type": "Microsoft.Storage/storageAccounts/fileServices/shares",
+ "apiVersion": "2019-06-01",
+ "name": "[concat(variables('FunctionName'), '/default/', tolower(variables('FunctionName')))]",
+ "dependsOn": [
+ "[resourceId('Microsoft.Storage/storageAccounts/fileServices', variables('FunctionName'), 'default')]",
+ "[resourceId('Microsoft.Storage/storageAccounts', variables('FunctionName'))]"
+ ],
+ "properties": {
+ "shareQuota": 5120
+ }
+ }
+
+ ]
+}
diff --git a/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/host.json b/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/host.json
new file mode 100644
index 00000000000..454e3cf5d88
--- /dev/null
+++ b/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/host.json
@@ -0,0 +1,16 @@
+{
+ "version": "2.0",
+ "logging": {
+ "applicationInsights": {
+ "samplingSettings": {
+ "isEnabled": true,
+ "excludedTypes": "Request"
+ }
+ }
+ },
+ "extensionBundle": {
+ "id": "Microsoft.Azure.Functions.ExtensionBundle",
+ "version": "[4.*, 5.0.0)"
+ },
+ "functionTimeout": "00:10:00"
+}
\ No newline at end of file
diff --git a/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/requirements.txt b/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/requirements.txt
new file mode 100644
index 00000000000..6d12fcf3a7d
--- /dev/null
+++ b/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/requirements.txt
@@ -0,0 +1,3 @@
+azure-functions
+requests
+vmray-rest-api
diff --git a/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/vmray_api.py b/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/vmray_api.py
new file mode 100644
index 00000000000..d06c1944299
--- /dev/null
+++ b/Solutions/VMRay/Playbooks/CustomConnector/VMRayEnrichment_FunctionAppConnector/vmray_api.py
@@ -0,0 +1,88 @@
+from os import environ
+from vmray.rest_api import VMRayRESTAPI, VMRayRESTAPIError
+
+vmray_api_key = environ["vmrayAPIKey"]
+vmray_base_url = environ["vmrayBaseURL"]
+
+
+class VMRay:
+ """
+ Wrapper class for VMRayRESTAPI modules and functions.
+ Import this class to submit samples and retrieve reports.
+ """
+
+ def __init__(self, log):
+ """
+ Initialize, authenticate and healthcheck the VMRay instance,
+ use VMRayConfig as configuration
+ :param log: logger instance
+ :return void
+ """
+ self.api = None
+ self.log = log
+
+ self.healthcheck()
+
+ def healthcheck(self):
+ """
+ Healthcheck for VMRay REST API, uses system_info endpoint
+ :raise: When healthcheck error occurred during the connection wih REST API
+ :return: boolean status of VMRay REST API
+ """
+ method = "GET"
+ url = "/rest/system_info"
+
+ try:
+ self.api = VMRayRESTAPI(
+ vmray_base_url,
+ vmray_api_key,
+ connector_name="VMRay-MSSentinel",
+ )
+ self.log.info("Successfully authenticated the VMRay API")
+ self.api.call(method, url)
+ self.log.info("VMRay Healthcheck is successfully.")
+ return True
+ except VMRayRESTAPIError as verr:
+ self.log.error(f"Healthcheck failed due to error in VMRay: {verr.args}")
+ raise
+ except Exception as err:
+ self.log.error("Healthcheck failed. Error: %s" % (err))
+ raise
+
+ def check_id(self, id_to_check: int | str) -> bool:
+ """Checks if parameter id_to_check is a number
+
+ Args:
+ id_to_check (int or str):
+
+ Returns:
+ bool: True if is a number, else returns error
+ """
+ if (
+ isinstance(id_to_check, int)
+ or isinstance(id_to_check, str)
+ and id_to_check.isdigit()
+ ):
+ return True
+ raise ValueError(f"Invalid ID `{id_to_check}` provided.")
+
+ def request_vmray_api(
+ self,
+ method,
+ url,
+ param=None,
+ ):
+ """
+ Retries the given API request in case of server errors or rate-limiting (HTTP 5xx or 429).
+
+ :param method: HTTP method (GET, POST, etc.)
+ :param url: URL to make the request to
+ :param param: Data to pass with the request (if applicable, e.g., for POST requests)
+ :return: Response object from the request or None if it fails after retries
+ """
+ try:
+ response = self.api.call(method, url, params=param)
+ return response
+ except VMRayRESTAPIError as err:
+ self.log.error(f"Error In VMRay: {err.args}")
+ raise Exception("An error occurred during retry request") from err
diff --git a/Solutions/VMRay/Playbooks/Submit-URL-VMRay-Analyzer/Images/incident_url_playbook.png b/Solutions/VMRay/Playbooks/Submit-URL-VMRay-Analyzer/Images/incident_url_playbook.png
new file mode 100644
index 00000000000..f668df92629
Binary files /dev/null and b/Solutions/VMRay/Playbooks/Submit-URL-VMRay-Analyzer/Images/incident_url_playbook.png differ
diff --git a/Solutions/VMRay/Playbooks/Submit-URL-VMRay-Analyzer/Images/url_comment.png b/Solutions/VMRay/Playbooks/Submit-URL-VMRay-Analyzer/Images/url_comment.png
new file mode 100644
index 00000000000..e56964b8375
Binary files /dev/null and b/Solutions/VMRay/Playbooks/Submit-URL-VMRay-Analyzer/Images/url_comment.png differ
diff --git a/Solutions/VMRay/Playbooks/Submit-URL-VMRay-Analyzer/azuredeploy.json b/Solutions/VMRay/Playbooks/Submit-URL-VMRay-Analyzer/azuredeploy.json
new file mode 100644
index 00000000000..74a13cba15b
--- /dev/null
+++ b/Solutions/VMRay/Playbooks/Submit-URL-VMRay-Analyzer/azuredeploy.json
@@ -0,0 +1,689 @@
+{
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "title": "VMRay URL Analyis",
+ "description": "Submits a url or set of urls associated with an incident to VMRay for Analyis.",
+ "prerequisites": "VMRay API Key.",
+ "prerequisitesDeployTemplateFile": "../VMRay/FunctionApp/azuredeploy.json",
+ "postDeploymentSteps": [
+ "None"
+ ],
+ "lastUpdateTime": "2025-02-20T00:00:00.000Z",
+ "entities": [
+ "url"
+ ],
+ "tags": [
+ "Enrichment"
+ ],
+ "support": {
+ "tier": "community"
+ },
+ "author": {
+ "name": "VMRay"
+ }
+ },
+ "parameters": {
+ "PlaybookName": {
+ "defaultValue": "Submit-URL-VMRay-Analyzer",
+ "type": "string",
+ "metadata": {
+ "description": "Name of the Logic App/Playbook"
+ }
+ },
+ "WorkspaceID": {
+ "defaultValue": "",
+ "type": "String",
+ "metadata": {
+ "description": "Log Analytics Workspace ID"
+ }
+ },
+ "FunctionAppName": {
+ "defaultValue": "vmrayenrich",
+ "type": "string",
+ "metadata": {
+ "description": "Name of the FunctionApp"
+ }
+ }
+ },
+ "variables": {
+ "functionappName": "[concat(toLower(parameters('FunctionAppName')), take(uniqueString(resourceGroup().id), 3))]",
+ "AzureSentinelConnectionName": "[concat('azuresentinel-', parameters('PlaybookName'))]"
+ },
+ "resources": [
+ {
+ "type": "Microsoft.Web/connections",
+ "apiVersion": "2016-06-01",
+ "name": "[variables('AzureSentinelConnectionName')]",
+ "location": "[resourceGroup().location]",
+ "kind": "V1",
+ "properties": {
+ "displayName": "[variables('AzureSentinelConnectionName')]",
+ "customParameterValues": {},
+ "parameterValueType": "Alternative",
+ "api": {
+ "id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/azuresentinel')]"
+ }
+ }
+ },
+ {
+ "type": "Microsoft.Logic/workflows",
+ "apiVersion": "2017-07-01",
+ "name": "[parameters('PlaybookName')]",
+ "location": "[resourceGroup().location]",
+ "identity": {
+ "type": "SystemAssigned"
+ },
+ "dependsOn": [
+ "[resourceId('Microsoft.Web/connections', variables('AzureSentinelConnectionName'))]"
+ ],
+ "properties": {
+ "state": "Enabled",
+ "definition": {
+ "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#",
+ "contentVersion": "1.0.0.0",
+ "parameters": {
+ "Indicator_Expiration_In_Days": {
+ "defaultValue": "30",
+ "type": "String"
+ },
+ "$connections": {
+ "type": "Object",
+ "defaultValue": {}
+ }
+ },
+ "triggers": {
+ "Microsoft_Sentinel_incident": {
+ "type": "ApiConnectionWebhook",
+ "inputs": {
+ "host": {
+ "connection": {
+ "name": "@parameters('$connections')['azuresentinel']['connectionId']"
+ }
+ },
+ "body": {
+ "callback_url": "@listCallbackUrl()"
+ },
+ "path": "/incident-creation"
+ }
+ }
+ },
+ "actions": {
+ "Entities_-_Get_URLs": {
+ "runAfter": {
+ "Sorted_VMRay_VTIS": [
+ "Succeeded"
+ ]
+ },
+ "type": "ApiConnection",
+ "inputs": {
+ "host": {
+ "connection": {
+ "name": "@parameters('$connections')['azuresentinel']['connectionId']"
+ }
+ },
+ "method": "post",
+ "body": "@triggerBody()?['object']?['properties']?['relatedEntities']",
+ "path": "/entities/url"
+ }
+ },
+ "For_each_URL": {
+ "foreach": "@body('Entities_-_Get_URLs')?['URLs']",
+ "actions": {
+ "UplaodURL": {
+ "type": "Function",
+ "inputs": {
+ "body": {
+ "url": "@items('For_each_URL')['url']",
+ "tags": ["url_submission_logic_app"]
+ },
+ "function": {
+ "id": "[concat('/subscriptions/', subscription().subscriptionId, '/resourceGroups/', resourceGroup().name, '/providers/Microsoft.Web/sites/', variables('Functionappname'), '/functions/UplaodURL')]"
+ }
+ }
+ },
+ "Check_if_the_status_is_200": {
+ "actions": {
+ "For_each_submission": {
+ "foreach": "@body('UplaodURL')?['vmray_submission']",
+ "actions": {
+ "Set_Submission_Status": {
+ "type": "SetVariable",
+ "inputs": {
+ "name": "submission_status",
+ "value": false
+ }
+ },
+ "Wait_untill_submission_is_completed": {
+ "actions": {
+ "Delay_By_2_minutes": {
+ "type": "Wait",
+ "inputs": {
+ "interval": {
+ "count": 2,
+ "unit": "Minute"
+ }
+ }
+ },
+ "GetVMRaySubmission": {
+ "runAfter": {
+ "Delay_By_2_minutes": [
+ "Succeeded"
+ ]
+ },
+ "type": "Function",
+ "inputs": {
+ "body": {
+ "submission_id": "@items('For_each_submission')['SubmissionID']"
+ },
+ "function": {
+ "id": "[concat('/subscriptions/', subscription().subscriptionId, '/resourceGroups/', resourceGroup().name, '/providers/Microsoft.Web/sites/', variables('Functionappname'), '/functions/GetVMRaySubmission')]"
+ }
+ }
+ },
+ "Check_if_submission_is_completed": {
+ "actions": {
+ "Set_Submission_Status_to_True": {
+ "type": "SetVariable",
+ "inputs": {
+ "name": "submission_status",
+ "value": true
+ }
+ },
+ "GetVMRaySample": {
+ "runAfter": {
+ "Set_Submission_Status_to_True": [
+ "Succeeded"
+ ]
+ },
+ "type": "Function",
+ "inputs": {
+ "body": {
+ "sample_id": "@body('GetVMRaySubmission')?['submission_sample_id']"
+ },
+ "function": {
+ "id": "[concat('/subscriptions/', subscription().subscriptionId, '/resourceGroups/', resourceGroup().name, '/providers/Microsoft.Web/sites/', variables('Functionappname'), '/functions/GetVMRaySample')]"
+ }
+ }
+ },
+ "Check_If_Sample_Verdict_is_Suspicious_or_Malicious": {
+ "actions": {
+ "Set_Submission_Report_Variable": {
+ "type": "SetVariable",
+ "inputs": {
+ "name": "submission_report",
+ "value": [
+ {
+ "Submission Status": "@body('GetVMRaySubmission')['submission_status']",
+ "Sample Verdict": "@body('GetVMRaySample')['sample_verdict']",
+ "Classification": "@body('GetVMRaySample')['sample_classifications']",
+ "Threat Names": "@body('GetVMRaySample')['sample_threat_names']",
+ "Link to the Full Report": "@body('GetVMRaySample')['sample_webif_url']",
+ "Submission ID": "@body('GetVMRaySubmission')['submission_id']",
+ "Sample ID": "@body('GetVMRaySample')['sample_id']",
+ "Notes": "@body('GetVMRaySample')['sample_verdict_reason_description']"
+ }
+ ]
+ }
+ },
+ "Create_HTML_table": {
+ "runAfter": {
+ "Set_Submission_Report_Variable": [
+ "Succeeded"
+ ]
+ },
+ "type": "Table",
+ "inputs": {
+ "from": "@variables('submission_report')",
+ "format": "HTML"
+ }
+ },
+ "Add_comment_to_incident_(V3)": {
+ "runAfter": {
+ "Create_HTML_table": [
+ "Succeeded"
+ ]
+ },
+ "type": "ApiConnection",
+ "inputs": {
+ "host": {
+ "connection": {
+ "name": "@parameters('$connections')['azuresentinel']['connectionId']"
+ }
+ },
+ "method": "post",
+ "body": {
+ "incidentArmId": "@triggerBody()?['object']?['id']",
+ "message": "VMRay Submission Details for URL: @{replace(replace(items('For_each_URL')?['url'],'https://',''),'http://','')}
@{body('Create_HTML_table')}@{body('Create_HTML_table_2')}
"
+ },
+ "path": "/Incidents/Comment"
+ }
+ },
+ "GetVMRayIOCs": {
+ "runAfter": {
+ "Add_comment_to_incident_(V3)": [
+ "Succeeded"
+ ]
+ },
+ "type": "Function",
+ "inputs": {
+ "body": {
+ "sample_id": "@body('GetVMRaySample')['sample_id']",
+ "submission_id": "@body('GetVMRaySubmission')['submission_id']",
+ "sample_verdict": [
+ "malicious",
+ "suspicious"
+ ],
+ "incident_id": "@triggerBody()?['object']?['properties']?['incidentNumber']",
+ "valid_until": "@parameters('Indicator_Expiration_In_Days')"
+ },
+ "function": {
+ "id": "[concat('/subscriptions/', subscription().subscriptionId, '/resourceGroups/', resourceGroup().name, '/providers/Microsoft.Web/sites/', variables('Functionappname'), '/functions/GetVMRayIOCs')]"
+ }
+ }
+ },
+ "Check_if_VMRay_has_any_indicators_for_the_sample": {
+ "type": "If",
+ "expression": {
+ "and": [
+ {
+ "greater": [
+ "@length(body('GetVMRayIOCs')['custom_resp'])",
+ 0
+ ]
+ }
+ ]
+ },
+ "actions": {
+ "Threat_Intelligence_-_Upload_Indicators_of_Compromise_(V2)_(Preview)": {
+ "type": "ApiConnection",
+ "inputs": {
+ "host": {
+ "connection": {
+ "name": "@parameters('$connections')['azuresentinel']['connectionId']"
+ }
+ },
+ "method": "post",
+ "body": {
+ "sourcesystem": "VMRayThreatIntelligence",
+ "indicators": "@body('GetVMRayIOCs')['custom_resp']"
+ },
+ "path": "/V2/ThreatIntelligence/@{encodeURIComponent(triggerBody()?['workspaceId'])}/UploadIndicators/"
+ }
+ }
+ },
+ "else": {
+ "actions": {}
+ },
+ "runAfter": {
+ "GetVMRayIOCs": [
+ "Succeeded"
+ ]
+ }
+ }
+ },
+ "runAfter": {
+ "Create_HTML_table_2": [
+ "Succeeded"
+ ]
+ },
+ "else": {
+ "actions": {
+ "Set_Submission_Report_Variable_01": {
+ "type": "SetVariable",
+ "inputs": {
+ "name": "submission_report",
+ "value": [
+ {
+ "Submission Status": "@body('GetVMRaySubmission')['submission_status']",
+ "Sample Verdict": "@body('GetVMRaySample')['sample_verdict']",
+ "Classification": "@body('GetVMRaySample')['sample_classifications']",
+ "Threat Names": "@body('GetVMRaySample')['sample_threat_names']",
+ "Link to the Full Report": "@body('GetVMRaySample')['sample_webif_url']",
+ "Submission ID": "@body('GetVMRaySubmission')['submission_id']",
+ "Sample ID": "@body('GetVMRaySample')['sample_id']",
+ "Notes": "@body('GetVMRaySample')['sample_verdict_reason_description']"
+ }
+ ]
+ }
+ },
+ "Create_HTML_table_1": {
+ "runAfter": {
+ "Set_Submission_Report_Variable_01": [
+ "Succeeded"
+ ]
+ },
+ "type": "Table",
+ "inputs": {
+ "from": "@variables('submission_report')",
+ "format": "HTML"
+ }
+ },
+ "Add_comment_to_incident_(V3)_1": {
+ "runAfter": {
+ "Create_HTML_table_1": [
+ "Succeeded"
+ ]
+ },
+ "type": "ApiConnection",
+ "inputs": {
+ "host": {
+ "connection": {
+ "name": "@parameters('$connections')['azuresentinel']['connectionId']"
+ }
+ },
+ "method": "post",
+ "body": {
+ "incidentArmId": "@triggerBody()?['object']?['id']",
+ "message": "VMRay Submission Details for URL: @{replace(replace(items('For_each_URL')?['url'],'https://',''),'http://','')} @{body('Create_HTML_table_1')}@{body('Create_HTML_table_2')}
"
+ },
+ "path": "/Incidents/Comment"
+ }
+ }
+ }
+ },
+ "expression": {
+ "and": [
+ {
+ "not": {
+ "equals": [
+ "@body('GetVMRaySample')['sample_verdict']",
+ "clean"
+ ]
+ }
+ }
+ ]
+ },
+ "type": "If"
+ },
+ "vmrayenrichcox-GetVMRayVTIs": {
+ "runAfter": {
+ "GetVMRaySample": [
+ "Succeeded"
+ ]
+ },
+ "type": "Function",
+ "inputs": {
+ "body": {
+ "sample_id": "@body('GetVMRaySubmission')?['submission_sample_id']"
+ },
+ "function": {
+ "id": "[concat('/subscriptions/', subscription().subscriptionId, '/resourceGroups/', resourceGroup().name, '/providers/Microsoft.Web/sites/', variables('Functionappname'), '/functions/GetVMRayVTIs')]"
+ }
+ }
+ },
+ "Set_VTIS": {
+ "runAfter": {
+ "vmrayenrichcox-GetVMRayVTIs": [
+ "Succeeded"
+ ]
+ },
+ "type": "SetVariable",
+ "inputs": {
+ "name": "sample_vtis",
+ "value": "@body('vmrayenrichcox-GetVMRayVTIs')['threat_indicators']"
+ }
+ },
+ "Create_HTML_table_2": {
+ "runAfter": {
+ "Set_variable": [
+ "Succeeded"
+ ]
+ },
+ "type": "Table",
+ "inputs": {
+ "from": "@reverse(variables('sorted_vmray_vtis'))",
+ "format": "HTML"
+ }
+ },
+ "For_each_VTI": {
+ "foreach": "@variables('sample_vtis')",
+ "actions": {
+ "Compose": {
+ "type": "Compose",
+ "inputs": {
+ "Severity": "@items('For_each_VTI')?['score']",
+ "Category": "@items('For_each_VTI')?['category']",
+ "Operation": "@items('For_each_VTI')?['operation']",
+ "Classifications": "@items('For_each_VTI')?['classifications']"
+ }
+ },
+ "Append_to_array_variable": {
+ "runAfter": {
+ "Compose": [
+ "Succeeded"
+ ]
+ },
+ "type": "AppendToArrayVariable",
+ "inputs": {
+ "name": "vmray_vtis",
+ "value": "@outputs('Compose')"
+ }
+ }
+ },
+ "runAfter": {
+ "Set_variable_2": [
+ "Succeeded"
+ ]
+ },
+ "type": "Foreach"
+ },
+ "Set_variable": {
+ "runAfter": {
+ "For_each_VTI": [
+ "Succeeded"
+ ]
+ },
+ "type": "SetVariable",
+ "inputs": {
+ "name": "sorted_vmray_vtis",
+ "value": "@sort(variables('vmray_vtis'), 'Severity')"
+ }
+ },
+ "Set_variable_1": {
+ "runAfter": {
+ "Set_VTIS": [
+ "Succeeded"
+ ]
+ },
+ "type": "SetVariable",
+ "inputs": {
+ "name": "vmray_vtis",
+ "value": "@null"
+ }
+ },
+ "Set_variable_2": {
+ "runAfter": {
+ "Set_variable_1": [
+ "Succeeded"
+ ]
+ },
+ "type": "SetVariable",
+ "inputs": {
+ "name": "sorted_vmray_vtis",
+ "value": "@null"
+ }
+ }
+ },
+ "runAfter": {
+ "GetVMRaySubmission": [
+ "Succeeded"
+ ]
+ },
+ "else": {
+ "actions": {}
+ },
+ "expression": {
+ "and": [
+ {
+ "equals": [
+ "@body('GetVMRaySubmission')?['submission_finished']",
+ true
+ ]
+ }
+ ]
+ },
+ "type": "If"
+ }
+ },
+ "runAfter": {
+ "Set_Submission_Status": [
+ "Succeeded"
+ ]
+ },
+ "expression": "@equals(variables('submission_status'),true)",
+ "limit": {
+ "count": 60,
+ "timeout": "PT1H"
+ },
+ "type": "Until"
+ }
+ },
+ "type": "Foreach"
+ }
+ },
+ "runAfter": {
+ "UplaodURL": [
+ "Succeeded"
+ ]
+ },
+ "else": {
+ "actions": {
+ "Add_Comment_to_incident": {
+ "type": "ApiConnection",
+ "inputs": {
+ "host": {
+ "connection": {
+ "name": "@parameters('$connections')['azuresentinel-1']['connectionId']"
+ }
+ },
+ "method": "post",
+ "body": {
+ "incidentArmId": "@triggerBody()?['object']?['id']",
+ "message": "Invalid API Response
"
+ },
+ "path": "/Incidents/Comment"
+ }
+ }
+ }
+ },
+ "expression": {
+ "and": [
+ {
+ "equals": [
+ "@outputs('UplaodURL')['statusCode']",
+ 200
+ ]
+ }
+ ]
+ },
+ "type": "If"
+ }
+ },
+ "runAfter": {
+ "Entities_-_Get_URLs": [
+ "Succeeded"
+ ]
+ },
+ "type": "Foreach"
+ },
+ "SubmissionReportforTable": {
+ "runAfter": {
+ "check_submission_status": [
+ "Succeeded"
+ ]
+ },
+ "type": "InitializeVariable",
+ "inputs": {
+ "variables": [
+ {
+ "name": "submission_report",
+ "type": "array"
+ }
+ ]
+ }
+ },
+ "check_submission_status": {
+ "runAfter": {},
+ "type": "InitializeVariable",
+ "inputs": {
+ "variables": [
+ {
+ "name": "submission_status",
+ "type": "boolean",
+ "value": false
+ }
+ ]
+ }
+ },
+ "Sample_VTIS": {
+ "runAfter": {
+ "SubmissionReportforTable": [
+ "Succeeded"
+ ]
+ },
+ "type": "InitializeVariable",
+ "inputs": {
+ "variables": [
+ {
+ "name": "sample_vtis",
+ "type": "array"
+ }
+ ]
+ }
+ },
+ "VMRay_VTIS": {
+ "runAfter": {
+ "Sample_VTIS": [
+ "Succeeded"
+ ]
+ },
+ "type": "InitializeVariable",
+ "inputs": {
+ "variables": [
+ {
+ "name": "vmray_vtis",
+ "type": "array"
+ }
+ ]
+ }
+ },
+ "Sorted_VMRay_VTIS": {
+ "runAfter": {
+ "VMRay_VTIS": [
+ "Succeeded"
+ ]
+ },
+ "type": "InitializeVariable",
+ "inputs": {
+ "variables": [
+ {
+ "name": "sorted_vmray_vtis",
+ "type": "array"
+ }
+ ]
+ }
+ }
+ },
+ "outputs": {}
+ },
+ "parameters": {
+ "$connections": {
+ "value": {
+ "azuresentinel": {
+ "connectionId": "[resourceId('Microsoft.Web/connections', variables('AzureSentinelConnectionName'))]",
+ "connectionName": "[variables('AzureSentinelConnectionName')]",
+ "id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/azuresentinel')]",
+ "connectionProperties": {
+ "authentication": {
+ "type": "ManagedServiceIdentity"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ ]
+}
diff --git a/Solutions/VMRay/Playbooks/VMRay-Sandbox_Outlook_Attachment/Images/outlook_attchment_playbook.png b/Solutions/VMRay/Playbooks/VMRay-Sandbox_Outlook_Attachment/Images/outlook_attchment_playbook.png
new file mode 100644
index 00000000000..e2dec200a89
Binary files /dev/null and b/Solutions/VMRay/Playbooks/VMRay-Sandbox_Outlook_Attachment/Images/outlook_attchment_playbook.png differ
diff --git a/Solutions/VMRay/Playbooks/VMRay-Sandbox_Outlook_Attachment/Images/outlook_comment.png b/Solutions/VMRay/Playbooks/VMRay-Sandbox_Outlook_Attachment/Images/outlook_comment.png
new file mode 100644
index 00000000000..2f02a5c628d
Binary files /dev/null and b/Solutions/VMRay/Playbooks/VMRay-Sandbox_Outlook_Attachment/Images/outlook_comment.png differ
diff --git a/Solutions/VMRay/Playbooks/VMRay-Sandbox_Outlook_Attachment/azuredeploy.json b/Solutions/VMRay/Playbooks/VMRay-Sandbox_Outlook_Attachment/azuredeploy.json
new file mode 100644
index 00000000000..48a190dfee8
--- /dev/null
+++ b/Solutions/VMRay/Playbooks/VMRay-Sandbox_Outlook_Attachment/azuredeploy.json
@@ -0,0 +1,607 @@
+{
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "contentVersion": "1.0.0.0",
+"metadata": {
+ "title": "VMRay Email Attachment Analyis",
+ "description": "Submits a attachment or set of attachment associated with an office 365 email to VMRay for Analyis.",
+ "prerequisites": "VMRay API Key.",
+ "prerequisitesDeployTemplateFile": "../VMRay/FunctionApp/azuredeploy.json",
+ "postDeploymentSteps": ["None"],
+ "lastUpdateTime": "2025-02-20T00:00:00.000Z",
+ "entities": [""],
+ "tags": ["Enrichment"],
+ "support": {
+ "tier": "community"
+ },
+ "author": {
+ "name": "VMRay"
+ }
+ },
+ "parameters": {
+ "PlaybookName": {
+ "defaultValue": "VMRay-Sandbox_Outlook_Attachment",
+ "type": "string",
+ "metadata": {
+ "description": "Name of the Logic App/Playbook"
+ }
+ },
+ "WorkspaceName": {
+ "defaultValue": "",
+ "type": "String",
+ "metadata": {
+ "description": "Log Analytics Workspace name"
+ }
+ },
+ "WorkspaceID": {
+ "defaultValue": "",
+ "type": "String",
+ "metadata": {
+ "description": "Log Analytics Workspace ID"
+ }
+ },
+ "FunctionAppName": {
+ "defaultValue": "vmrayenrich",
+ "type": "string",
+ "metadata": {
+ "description": "Name of the FunctionApp"
+ }
+ }
+ },
+ "variables": {
+ "functionappName": "[concat(toLower(parameters('FunctionAppName')), take(uniqueString(resourceGroup().id), 3))]",
+ "AzureSentinelConnectionName": "[concat('azuresentinel-', parameters('PlaybookName'))]",
+ "Office365V1": "[concat('office365-', parameters('PlaybookName'))]",
+ "subscription": "[last(split(subscription().id, '/'))]",
+ "resourceGroupName": "[resourceGroup().name]"
+ },
+ "resources": [
+ {
+ "type": "Microsoft.Web/connections",
+ "apiVersion": "2016-06-01",
+ "name": "[variables('Office365V1')]",
+ "location": "[resourceGroup().location]",
+ "properties": {
+ "displayName": "[variables('Office365V1')]",
+ "customParameterValues": {},
+ "api": {
+ "id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/office365')]"
+ }
+ }
+ },
+ {
+ "type": "Microsoft.Web/connections",
+ "apiVersion": "2016-06-01",
+ "name": "[variables('AzureSentinelConnectionName')]",
+ "location": "[resourceGroup().location]",
+ "kind": "V1",
+ "properties": {
+ "displayName": "[variables('AzureSentinelConnectionName')]",
+ "customParameterValues": {},
+ "parameterValueType": "Alternative",
+ "api": {
+ "id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/azuresentinel')]"
+ }
+ }
+ },
+ {
+ "type": "Microsoft.Logic/workflows",
+ "apiVersion": "2017-07-01",
+ "name": "[parameters('PlaybookName')]",
+ "location": "[resourceGroup().location]",
+ "identity": {
+ "type": "SystemAssigned"
+ },
+ "dependsOn": [
+ "[resourceId('Microsoft.Web/connections', variables('Office365V1'))]",
+ "[resourceId('Microsoft.Web/connections', variables('AzureSentinelConnectionName'))]"
+ ],
+ "properties": {
+ "state": "Disabled",
+ "definition": {
+ "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#",
+ "contentVersion": "1.0.0.0",
+ "parameters": {
+ "Indicator_Expiration_In_Days": {
+ "defaultValue": "30",
+ "type": "String"
+ },
+ "$connections": {
+ "type": "Object",
+ "defaultValue": {}
+ }
+ },
+ "triggers": {
+ "When_a_new_email_arrives_(V3)": {
+ "splitOn": "@triggerBody()?['value']",
+ "type": "ApiConnectionNotification",
+ "inputs": {
+ "host": {
+ "connection": {
+ "name": "@parameters('$connections')['office365']['connectionId']"
+ }
+ },
+ "fetch": {
+ "pathTemplate": {
+ "template": "/v3/Mail/OnNewEmail"
+ },
+ "method": "get",
+ "queries": {
+ "importance": "Any",
+ "fetchOnlyWithAttachment": true,
+ "includeAttachments": true,
+ "folderPath": "Inbox"
+ }
+ },
+ "subscribe": {
+ "body": {
+ "NotificationUrl": "@listCallbackUrl()"
+ },
+ "pathTemplate": {
+ "template": "/GraphMailSubscriptionPoke/$subscriptions"
+ },
+ "method": "post",
+ "queries": {
+ "importance": "Any",
+ "fetchOnlyWithAttachment": true,
+ "folderPath": "Inbox"
+ }
+ }
+ }
+ }
+ },
+ "actions": {
+ "Submission_Status": {
+ "runAfter": {
+ "Submission_Object": [
+ "Succeeded"
+ ]
+ },
+ "type": "InitializeVariable",
+ "inputs": {
+ "variables": [
+ {
+ "name": "submission_status",
+ "type": "boolean",
+ "value": false
+ }
+ ]
+ }
+ },
+ "Submission_Report": {
+ "runAfter": {
+ "Submission_Status": [
+ "Succeeded"
+ ]
+ },
+ "type": "InitializeVariable",
+ "inputs": {
+ "variables": [
+ {
+ "name": "submission_report",
+ "type": "array"
+ }
+ ]
+ }
+ },
+ "Email_Body": {
+ "runAfter": {
+ "Submission_Report": [
+ "Succeeded"
+ ]
+ },
+ "type": "InitializeVariable",
+ "inputs": {
+ "variables": [
+ {
+ "name": "email_body",
+ "type": "string"
+ }
+ ]
+ }
+ },
+ "File_Name": {
+ "runAfter": {
+ "Email_Body": [
+ "Succeeded"
+ ]
+ },
+ "type": "InitializeVariable",
+ "inputs": {
+ "variables": [
+ {
+ "name": "file_name",
+ "type": "string"
+ }
+ ]
+ }
+ },
+ "Submission_Object": {
+ "runAfter": {},
+ "type": "InitializeVariable",
+ "inputs": {
+ "variables": [
+ {
+ "name": "submissin_obj",
+ "type": "array"
+ }
+ ]
+ }
+ },
+ "For_Every_Attachment": {
+ "foreach": "@triggerBody()?['attachments']",
+ "actions": {
+ "VMRayUploadSample": {
+ "type": "Function",
+ "inputs": {
+ "body": {
+ "file": "@item()?['contentBytes']",
+ "name": "@item()?['name']",
+ "tags": ["outlook_attachment_logic_app_submission"]
+ },
+ "function": {
+ "id": "[concat('/subscriptions/', subscription().subscriptionId, '/resourceGroups/', resourceGroup().name, '/providers/Microsoft.Web/sites/', variables('Functionappname'), '/functions/VMRayUploadSample')]"
+ }
+ }
+ },
+ "Append_to_Submission_Object_to_Variable": {
+ "runAfter": {
+ "VMRayUploadSample": [
+ "Succeeded"
+ ]
+ },
+ "type": "AppendToArrayVariable",
+ "inputs": {
+ "name": "submissin_obj",
+ "value": {
+ "submission_id": "@body('VMRayUploadSample')['vmray_submission']",
+ "filename": "@variables('file_name')"
+ }
+ }
+ }
+ },
+ "runAfter": {
+ "File_Name": [
+ "Succeeded"
+ ]
+ },
+ "type": "Foreach"
+ },
+ "For_Every_Submission_Object": {
+ "foreach": "@variables('submissin_obj')",
+ "actions": {
+ "Set_Submission_Status_to_False": {
+ "type": "SetVariable",
+ "inputs": {
+ "name": "submission_status",
+ "value": false
+ }
+ },
+ "Wait_untill_submission_is_completed": {
+ "actions": {
+ "GetVMRaySubmission": {
+ "type": "Function",
+ "inputs": {
+ "body": {
+ "submission_id": "@items('For_Every_Submission_Object')['submission_id'][0]['SubmissionID']"
+ },
+ "function": {
+ "id": "[concat('/subscriptions/', subscription().subscriptionId, '/resourceGroups/', resourceGroup().name, '/providers/Microsoft.Web/sites/', variables('Functionappname'), '/functions/GetVMRaySubmission')]"
+ }
+ }
+ },
+ "Check_If_Submission_is_finished": {
+ "actions": {
+ "GetVMRaySample": {
+ "type": "Function",
+ "inputs": {
+ "body": {
+ "sample_id": "@body('GetVMRaySubmission')?['submission_sample_id']"
+ },
+ "function": {
+ "id": "[concat('/subscriptions/', subscription().subscriptionId, '/resourceGroups/', resourceGroup().name, '/providers/Microsoft.Web/sites/', variables('Functionappname'), '/functions/GetVMRaySample')]"
+ }
+ }
+ },
+ "Check_If_Sample_Verdict_is_Malicious_or_Suspicious": {
+ "actions": {
+ "Set_submission_report": {
+ "type": "SetVariable",
+ "inputs": {
+ "name": "submission_report",
+ "value": [
+ {
+ "Submission ID": "@body('GetVMRaySubmission')['submission_id']",
+ "Submission Status": "@body('GetVMRaySubmission')['submission_status']",
+ "Sample ID": "@body('GetVMRaySample')['sample_id']",
+ "Sample Classifications": "@body('GetVMRaySample')['sample_classifications']",
+ "Sample Threat Names": "@body('GetVMRaySample')['sample_threat_names']",
+ "Sample Webif URL": "@body('GetVMRaySample')['sample_webif_url']",
+ "Sample Verdict": "@body('GetVMRaySample')['sample_verdict']",
+ "Sample VTI Score": "@body('GetVMRaySample')['sample_vti_score']",
+ "Sample Verdict Reason Code": "@body('GetVMRaySample')['sample_verdict_reason_code']",
+ "Sample Verdict Reason Description": "@body('GetVMRaySample')['sample_verdict_reason_description']"
+ }
+ ]
+ }
+ },
+ "Create_HTML_table": {
+ "runAfter": {
+ "Set_submission_report": [
+ "Succeeded"
+ ]
+ },
+ "type": "Table",
+ "inputs": {
+ "from": "@variables('submission_report')",
+ "format": "HTML"
+ }
+ },
+ "Create_incident": {
+ "runAfter": {
+ "Create_HTML_table": [
+ "Succeeded"
+ ]
+ },
+ "type": "ApiConnection",
+ "inputs": {
+ "host": {
+ "connection": {
+ "name": "@parameters('$connections')['azuresentinel']['connectionId']"
+ }
+ },
+ "method": "put",
+ "body": {
+ "title": "VMRay Email Attachment Scan Submission Details for file - @{body('GetVMRaySample')['sample_filename']}",
+ "severity": "Medium",
+ "status": "New",
+ "description": "VMRay Email Attachment Scan . From:@{triggerBody()?['from']} . To: @{triggerBody()?['toRecipients']}"
+ },
+ "path": "[concat('/Incidents/subscriptions/',variables('subscription'),'/resourceGroups/',variables('resourceGroupName'),'/workspaces/',parameters('WorkspaceName'))]"
+ }
+ },
+ "Add_comment_to_incident_(V3)": {
+ "runAfter": {
+ "Create_incident": [
+ "Succeeded"
+ ]
+ },
+ "type": "ApiConnection",
+ "inputs": {
+ "host": {
+ "connection": {
+ "name": "@parameters('$connections')['azuresentinel']['connectionId']"
+ }
+ },
+ "method": "post",
+ "body": {
+ "incidentArmId": "@body('Create_incident')?['id']",
+ "message": "VMRay Submission for File - @{items('For_Every_Submission_Object')['filename']} @{body('Create_HTML_table')}
"
+ },
+ "path": "/Incidents/Comment"
+ }
+ },
+ "GetVMRayIOCs": {
+ "runAfter": {
+ "Add_comment_to_incident_(V3)": [
+ "Succeeded"
+ ]
+ },
+ "type": "Function",
+ "inputs": {
+ "body": {
+ "sample_id": "@body('GetVMRaySample')['sample_id']",
+ "submission_id": "@body('GetVMRaySubmission')['submission_id']",
+ "sample_verdict": [
+ "malicious",
+ "suspicious"
+ ],
+ "incident_id": "@body('Create_incident')?['properties']?['incidentNumber']",
+ "valid_until": "@parameters('Indicator_Expiration_In_Days')"
+ },
+ "function": {
+ "id": "[concat('/subscriptions/', subscription().subscriptionId, '/resourceGroups/', resourceGroup().name, '/providers/Microsoft.Web/sites/', variables('Functionappname'), '/functions/GetVMRayIOCs')]"
+ }
+ }
+ },
+ "Check_if_VMRay_has_any_indicators_for_the_sample": {
+ "actions": {
+ "Threat_Intelligence_-_Upload_Indicators_of_Compromise_(V2)_(Preview)": {
+ "type": "ApiConnection",
+ "inputs": {
+ "host": {
+ "connection": {
+ "name": "@parameters('$connections')['azuresentinel']['connectionId']"
+ }
+ },
+ "method": "post",
+ "body": {
+ "sourcesystem": "VMRayThreatIntelligence",
+ "indicators": "@body('GetVMRayIOCs')['custom_resp']"
+ },
+ "path": "[concat('/V2/ThreatIntelligence/',parameters('WorkspaceID'),'/UploadIndicators/')]"
+
+ }
+ }
+ },
+ "runAfter": {
+ "GetVMRayIOCs": [
+ "Succeeded"
+ ]
+ },
+ "else": {
+ "actions": {}
+ },
+ "expression": {
+ "and": [
+ {
+ "greater": [
+ "@length(body('GetVMRayIOCs')['custom_resp'])",
+ 0
+ ]
+ }
+ ]
+ },
+ "type": "If"
+ },
+ "Set_email_body": {
+ "runAfter": {
+ "Check_if_VMRay_has_any_indicators_for_the_sample": [
+ "Succeeded"
+ ]
+ },
+ "type": "SetVariable",
+ "inputs": {
+ "name": "email_body",
+ "value": "\n
\n\n\nSubmission ID \nSubmission Status \nSample ID \nSample Classifications \nSample Threat Names \nSample Webif URL \nSample Verdict \nSample VTI Score \nSample Verdict Reason Code \nSample Verdict Reason Description \n \n \n\n\n@{body('GetVMRaySubmission')['submission_id']} \n@{body('GetVMRaySubmission')['submission_status']} \n@{body('GetVMRaySample')['sample_id']} \n@{body('GetVMRaySample')['sample_classifications']} \n@{body('GetVMRaySample')['sample_threat_names']} \n@{body('GetVMRaySample')['sample_webif_url']} \n@{body('GetVMRaySample')['sample_verdict']} \n@{body('GetVMRaySample')['sample_vti_score']} \n@{body('GetVMRaySample')['sample_verdict_reason_code']} \n@{body('GetVMRaySample')['sample_verdict_reason_description']} \n \n \n
\n\n"
+ }
+ },
+ "Send_an_email_(V2)": {
+ "runAfter": {
+ "Set_email_body": [
+ "Succeeded"
+ ]
+ },
+ "type": "ApiConnection",
+ "inputs": {
+ "host": {
+ "connection": {
+ "name": "@parameters('$connections')['office365']['connectionId']"
+ }
+ },
+ "method": "post",
+ "body": {
+ "To": "abc@abc.com",
+ "Subject": "VMRay Email Attachment Scan for file - @{body('GetVMRaySample')['sample_filename']}",
+ "Body": "@{variables('email_body')}
",
+ "Importance": "Normal"
+ },
+ "path": "/v2/Mail"
+ }
+ },
+ "Set_Submission_Status_to_True": {
+ "runAfter": {
+ "Send_an_email_(V2)": [
+ "Succeeded"
+ ]
+ },
+ "type": "SetVariable",
+ "inputs": {
+ "name": "submission_status",
+ "value": true
+ }
+ }
+ },
+ "runAfter": {
+ "GetVMRaySample": [
+ "Succeeded"
+ ]
+ },
+ "else": {
+ "actions": {}
+ },
+ "expression": {
+ "and": [
+ {
+ "not": {
+ "equals": [
+ "@body('GetVMRaySample')['sample_verdict']",
+ "clean"
+ ]
+ }
+ }
+ ]
+ },
+ "type": "If"
+ }
+ },
+ "runAfter": {
+ "GetVMRaySubmission": [
+ "Succeeded"
+ ]
+ },
+ "else": {
+ "actions": {
+ "Delay_by_2_minutes": {
+ "type": "Wait",
+ "inputs": {
+ "interval": {
+ "count": 2,
+ "unit": "Minute"
+ }
+ }
+ },
+ "Set_submission_status_to_false_again": {
+ "runAfter": {
+ "Delay_by_2_minutes": [
+ "Succeeded"
+ ]
+ },
+ "type": "SetVariable",
+ "inputs": {
+ "name": "submission_status",
+ "value": false
+ }
+ }
+ }
+ },
+ "expression": {
+ "and": [
+ {
+ "equals": [
+ "@body('GetVMRaySubmission')['submission_finished']",
+ true
+ ]
+ }
+ ]
+ },
+ "type": "If"
+ }
+ },
+ "runAfter": {
+ "Set_Submission_Status_to_False": [
+ "Succeeded"
+ ]
+ },
+ "expression": "@equals(variables('submission_status'),true)",
+ "limit": {
+ "count": 60,
+ "timeout": "PT1H"
+ },
+ "type": "Until"
+ }
+ },
+ "runAfter": {
+ "For_Every_Attachment": [
+ "Succeeded"
+ ]
+ },
+ "type": "Foreach"
+ }
+ },
+ "outputs": {}
+ },
+ "parameters": {
+ "$connections": {
+ "value": {
+ "office365": {
+ "connectionId": "[resourceId('Microsoft.Web/connections', variables('Office365V1'))]",
+ "connectionName": "[variables('Office365V1')]",
+ "id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/office365')]"
+ },
+ "azuresentinel": {
+ "connectionId": "[resourceId('Microsoft.Web/connections', variables('AzureSentinelConnectionName'))]",
+ "connectionName": "[variables('AzureSentinelConnectionName')]",
+ "id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/azuresentinel')]",
+ "connectionProperties": {
+ "authentication": {
+ "type": "ManagedServiceIdentity"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ ]
+}
diff --git a/Solutions/VMRay/README.md b/Solutions/VMRay/README.md
new file mode 100644
index 00000000000..9fe05076fec
--- /dev/null
+++ b/Solutions/VMRay/README.md
@@ -0,0 +1,240 @@
+# VMRay Threat Intelligence Feed and Enrichment Integration - Microsoft Sentinel
+
+**Latest Version:** **1.0.0** - **Release Date:** **2025-07-23**
+
+## Overview
+
+
+## Requirements
+- Microsoft Sentinel.
+- VMRay Analyzer, VMRay FinalVerdict, VMRay TotalInsight.
+- Microsoft Azure
+ 1. Azure functions with Flex Consumption plan.
+ Reference: https://learn.microsoft.com/en-us/azure/azure-functions/flex-consumption-plan
+
+ **Note:** Flex Consumption plans are not available in all regions, please check if the region your are deploying the function is supported, if not we suggest you to deploy the function app with premium plan.
+ Reference: https://learn.microsoft.com/en-us/azure/azure-functions/flex-consumption-how-to?tabs=azure-cli%2Cvs-code-publish&pivots=programming-language-python#view-currently-supported-regions
+ 3. Azure functions Premium plan.
+ Reference: https://learn.microsoft.com/en-us/azure/azure-functions/functions-premium-plan
+ 4. Azure Logic App with Consumption plan.
+ Reference: https://learn.microsoft.com/en-us/azure/logic-apps/logic-apps-pricing#consumption-multitenant
+ 5. Azure storage with Standard general-purpose v2.
+
+## VMRay Configurations
+
+- In VMRay Console, you must create a Connector API key.Create it by following the steps below:
+
+ 1. Create a user dedicated for this API key (to avoid that the API key is deleted if an employee leaves)
+ 2. Create a role that allows to "View shared submission, analysis and sample" and "Submit sample, manage own jobs, reanalyse old analyses and regenerate analysis reports".
+ 3. Assign this role to the created user
+ 4. Login as this user and create an API key by opening Settings > Analysis > API Keys.
+ 5. Please save the keys, which will be used in configuring the Azure Function.
+
+
+## Microsoft Sentinel
+
+### Creating Application for API Access
+
+- Open [https://portal.azure.com/](https://portal.azure.com) and search `Microsoft Entra ID` service.
+
+
+
+- Click `Add->App registration`.
+
+
+
+- Enter the name of application and select supported account types and click on `Register`.
+
+
+
+- In the application overview you can see `Application Name`, `Application ID` and `Tenant ID`.
+
+
+
+- After creating the application, we need to set API permissions for connector. For this purpose,
+ - Click `Manage->API permissions` tab
+ - Click `Microsoft Graph` button
+ - Search `indicator` and click on the `ThreatIndicators.ReadWrite.OwnedBy`, click `Add permissions` button below.
+ - Click on `Grant admin consent`
+
+ 
+
+- We need secrets to access programmatically. For creating secrets
+ - Click `Manage->Certificates & secrets` tab
+ - Click `Client secrets` tab
+ - Click `New client secret` button
+ - Enter description and set expiration date for secret
+
+
+
+- Use Secret `Value` to configure connector.
+
+ 
+
+## Provide Permission To App Created Above
+
+- Open [https://portal.azure.com/](https://portal.azure.com) and search `Microsoft Sentinel` service.
+- Goto `Settings` -> `Workspace Setting`
+
+
+
+- Goto `Access Control(IAM)` -> `Add`
+
+
+
+- Search for `Microsoft Sentinel Contributor` and click `Next`
+
+
+
+- Select `User,group or service principle` and click on `select members`.
+- Search for the app name created above and click on `select`.
+- Click on `Next`
+
+
+
+- Click on `Review + assign`
+
+
+
+# Deploy VMRay Threat Intelligence Feed Function App Connector
+
+### Flex Consumption Plan
+- Click on below button to deploy with Flex Consumption plan:
+
+ [](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2FAzure%2FAzure-Sentinel%2Frefs%2Fheads%2Fmaster%2FSolutions%2FVMRay%2FData%20Connectors%2Fazuredeploy_VMRayThreatIntelligenceFuncApp_AzureFunction_flex.json)
+
+### Premium Plan
+- Click on below button to deploy with Premium plan:
+
+ [](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2FAzure%2FAzure-Sentinel%2Frefs%2Fheads%2Fmaster%2FSolutions%2FVMRay%2FData%20Connectors%2Fazuredeploy_VMRayThreatIntelligenceFuncApp_AzureFunction_premium.json)
+
+- It will redirect to feed Configuration page.
+ 
+- Please provide the values accordingly.
+
+| Fields | Description |
+|:---------------------|:--------------------
+| Subscription | Select the appropriate Azure Subscription |
+| Resource Group | Select the appropriate Resource Group |
+| Region | Based on Resource Group this will be uto populated |
+| Function Name | Please provide a function name if needed to change the default value|
+| Vmray Base URL | VMRay Base URL |
+| Vmray API Key | VMRay API Key |
+| Azure Client ID | Enter the Azure Client ID created in the App Registration Step |
+| Azure Client Secret | Enter the Azure Client Secret created in the App Registration Step |
+|Azure Tenant ID | Enter the Azure Tenant ID of the App Registration |
+| Azure Workspacse ID | Enter the Azure Workspacse ID. Go to `Log Analytics workspace -> Overview`, Copy `Workspace ID`, refer below image.|
+| App Insights Workspace Resource ID | Go to `Log Analytics workspace` -> `Settings` -> `Properties`, Copy `Resource ID` and paste here |
+
+
+
+- Once you provide the above values, please click on `Review + create` button.
+
+- Once the threat intelligence function app connector is succussefully deployed, the connector saves the IOCS into the Microsoft Sentinel Threat Intelligence.
+
+
+
+## Deploy VMRay Enrichment Function App Connector
+
+- Click on below button to deploy
+
+ [](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2raw.githubusercontent.com%2Azure%2Azure-Sentinel%2refs%2heads%2master%2Solutions%2VMRay%2Playbooks%2CustomConnector%2VMRayEnrichment_FunctionAppConnector%2azuredeploy.json)
+
+- It will redirect to feed Configuration page.
+
+
+
+- Please provide the values accordingly
+
+| Fields | Description |
+|:---------------------|:--------------------
+| Subscription | Select the appropriate Azure Subscription |
+| Resource Group | Select the appropriate Resource Group |
+| Region | Based on Resource Group this will be uto populated |
+| Function Name | Please provide a function name if needed to change the default value|
+| Vmray Base URL | VMRay Base URL |
+| Vmray API Key | VMRay API Key |
+| Resubmit | If true file will be resubmitted to VMRay |
+| App Insights Workspace Resource ID | Go to `Log Analytics workspace` -> `Settings` -> `Properties`, Copy `Resource ID` and paste here |
+
+- Once you provide the above values, please click on `Review + create` button.
+
+
+## Deploy VMRay Enrichment Logic Apps
+
+### `Submit-URL-VMRay-Analyzer` Logic App
+
+- This playbook can be used to enrich sentinel incidents, this playbook when configured to trigger on seninel incidents, the playbook will collect all the `URL` entities from the Incident and submits them to VMRay analyzer, once the submission is completed, it will add the VMRay Analysis report to the Incident and creates the IOCs in the microsoft seninel threat intelligence.
+
+- Click on below button to deploy
+
+[](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2raw.githubusercontent.com%2Azure%2Azure-Sentinel%2refs%2heads%2master%2Solutions%2VMRay%2Playbooks%2Submit-URL-VMRay-Analyzer%2azuredeploy.json)
+
+- It will redirect to configuration page
+
+
+
+- Please provide the values accordingly
+
+| Fields | Description |
+|:---------------------|:--------------------
+| Subscription | Select the appropriate Azure Subscription |
+| Resource Group | Select the appropriate Resource Group |
+| Region | Based on Resource Group this will be uto populated |
+| Playbook Name | Please provide a playbook name, if needed |
+| Workspace ID | Please provide Log Analytics Workspace ID |
+| Function App Name | Please provide the VMRay enrichment function app name |
+
+- Once you provide the above values, please click on `Review + create` button.
+
+
+### `VMRay-Sandbox_Outlook_Attachment` Logic App
+
+- This playbook can be used to enrich outlook attachements, this playbook when configured will collect all the `attachements` from the email and submits them to VMRay analyzer, once the submission is completed, it will add the VMRay Analysis report by creating an Incident and creates the IOCs in the microsoft seninel threat intelligence.
+
+
+- Click on below button to deploy
+
+[](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2raw.githubusercontent.com%2Azure%2Azure-Sentinel%2refs%2heads%2master%2Solutions%2VMRay%2Playbooks%2VMRay-Sandbox_Outlook_Attachment%2azuredeploy.json)
+
+- It will redirect to configuration page
+
+
+
+- Please provide the values accordingly
+
+| Fields | Description |
+|:---------------------|:--------------------
+| Subscription | Select the appropriate Azure Subscription |
+| Resource Group | Select the appropriate Resource Group |
+| Region | Based on Resource Group this will be uto populated |
+| Playbook Name | Please provide a playbook name, if needed |
+| Workspace Name | Please provide Log Analytics Workspace Name |
+| Workspace ID | Please provide Log Analytics Workspace ID |
+| Function App Name | Please provide the VMRay enrichment function app name |
+
+- Once you provide the above values, please click on `Review + create` button.
+
+## Provide Permission to Logic app
+
+- Open [https://portal.azure.com/](https://portal.azure.com) and search `Microsoft Sentinel` service.
+- Goto `Settings` -> `Workspace Setting`
+
+
+
+- Goto `Access Control(IAM)` -> `Add`
+
+
+
+- Search for `Microsoft Sentinel Contributor` and click `Next`
+
+
+
+- Select `Managed Identity` and click on `select members` .
+- Search for the Logic app name deployed above and click on `select`.
+- Click on `Next`
+
+
+
+- Click on `Review + assign`
+
diff --git a/Solutions/VMRay/ReleaseNotes.md b/Solutions/VMRay/ReleaseNotes.md
new file mode 100644
index 00000000000..62d46aa200f
--- /dev/null
+++ b/Solutions/VMRay/ReleaseNotes.md
@@ -0,0 +1,3 @@
+| **Version** | **Date Modified (DD-MM-YYYY)** | **Change History** |
+|-------------|--------------------------------|---------------------------------------------| |
+| 3.0.0 | 23-07-2025 | Initial Solution Release |
diff --git a/Solutions/VMRay/SolutionMetadata.json b/Solutions/VMRay/SolutionMetadata.json
new file mode 100644
index 00000000000..6244559f377
--- /dev/null
+++ b/Solutions/VMRay/SolutionMetadata.json
@@ -0,0 +1,19 @@
+{
+ "publisherId": "vmray",
+ "offerId": "microsoft-sentinel-solution-vmray",
+ "firstPublishDate": "2025-07-23",
+ "providers": [
+ "VMRay Inc"
+ ],
+ "categories": {
+ "domains": [
+ "Security - Automation (SOAR)", "Security - Threat Intelligence"
+ ]
+ },
+ "support": {
+ "name": "VMRay",
+ "email": "support@vmray.com",
+ "tier": "Partner",
+ "link": "https://www.vmray.com/contact/customer-support/"
+ }
+}