diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 00000000..b242572e
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,5 @@
+{
+ "githubPullRequests.ignoredPullRequestBranches": [
+ "main"
+ ]
+}
\ No newline at end of file
diff --git a/Logic Apps/SecCopilot-UserReportedPhishing-FuncApp_parsingV2/FunctionAppV2.zip b/Logic Apps/SecCopilot-UserReportedPhishing-FuncApp_parsingV2/FunctionAppV2.zip
new file mode 100644
index 00000000..39fb0b16
Binary files /dev/null and b/Logic Apps/SecCopilot-UserReportedPhishing-FuncApp_parsingV2/FunctionAppV2.zip differ
diff --git a/Logic Apps/SecCopilot-UserReportedPhishing-FuncApp_parsingV2/README.md b/Logic Apps/SecCopilot-UserReportedPhishing-FuncApp_parsingV2/README.md
index 2ba19964..4bde5efd 100644
--- a/Logic Apps/SecCopilot-UserReportedPhishing-FuncApp_parsingV2/README.md
+++ b/Logic Apps/SecCopilot-UserReportedPhishing-FuncApp_parsingV2/README.md
@@ -1,7 +1,31 @@
# Phishing Analysis with Security Copilot
**Author:** Craig Freyman
-An automated solution for phishing email analysis using Azure Logic Apps, Function Apps, and Security Copilot. The system monitors a shared Office 365 mailbox, analyzes submitted emails for security threats, and generates detailed reports. Optional integration with Microsoft Sentinel for incident tracking and management.
+An automated phishing email analysis solution leveraging Azure Logic Apps, Function Apps, and Security Copilot. With the right permissions, it can be deployed in five minutes. The Logic App monitors a shared Office 365 mailbox and triggers analysis when an email arrives. Security Copilot assesses the message for malicious intent, generating detailed HTML reports sent via email. Optional integration with Microsoft Sentinel enables incident tracking and management. This project demonstrates what’s possible with Security Copilot and Azure. It may work out of the box or require tuning — the good news: only a single Security Copilot prompt is used, making it easy to customize for your organization.
+
+## Overview
+
+This solution provides an end-to-end workflow for analyzing suspected phishing emails. The core functionality is handled by a series of Azure Functions that parse emails, extract important data, clean JSON input, and generate HTML reports. When combined with Security Copilot and Logic Apps, it creates a powerful system to distinguish between legitimate, spam, suspicious, and phishing emails.
+
+### Key Components
+
+1. **Email Parser Function** - Extracts and processes email content, including:
+ - Recursive unwrapping to find original emails (Handles forwarded, embedded, and TNEF-encapsulated messages)
+ - **Enhanced Proofpoint compatibility** for analyzing emails forwarded from Proofpoint
+ - Smart handling of Proofpoint and Microsoft SafeLinks URL protection
+ - Support for EML and MSG attachments
+ - Complete header, body, and attachment analysis
+
+2. **HTML Report Generator** - Creates formatted HTML reports from phishing analysis results:
+ - Color-coded categorization (Phishing, Spam, Legitimate, Suspicious)
+ - Detailed breakdown of analysis findings
+ - Organized by assessment categories
+
+### Purpose and Scope
+
+This solution is not designed to replace traditional spam filtering or email security gateways. Rather, it demonstrates the art of the possible when combining Security Copilot with Azure Functions and Logic Apps to enhance your existing email security infrastructure. This solution assumes you already have standard email gateway filtering in place.
+
+The primary focus is on providing deeper analysis of suspicious emails that have been forwarded for investigation, especially those coming from Proofpoint-protected environments.
## Prerequisites
@@ -53,45 +77,109 @@ Optional Sentinel Parameters (use "none" if not using Sentinel):
- Azure Monitor Logs (if using Sentinel)
- Sentinel (if using Sentinel)
-### 4. Configure Logic App Permissions
+### 3. Configure Logic App Permissions
If using Sentinel integration, assign these roles to the Logic App's managed identity:
1. "Log Analytics Reader" role (provides Microsoft.OperationalInsights/workspaces/read)
2. "Microsoft Sentinel Responder" role (provides Microsoft.SecurityInsights/incidents/comments/write)
-Assign Permissions Step by Step
+Assign Permissions Step by Step:
1. Open the Log Analytics workspace and go to "Access control (IAM)"
-

-
2. Select "Log Analytics Reader"
-

-
3. Select "Managed Identity" and select the name of your Logic App
-

-
-4. Click next and assign permissions.
-5. Repeat steps 1-4 for the "Microsoft Sentinel Responder" role.
+4. Click next and assign permissions
+5. Repeat steps 1-4 for the "Microsoft Sentinel Responder" role
### 5. Enable the Logic App
The Logic App deploys in a disabled state. Enable it in the Logic App Overview to begin operation.
+## Functions and Endpoints
+
+The Function App exposes the following HTTP endpoints:
+
+1. **Email Parser** (`/parse_email_functionapp`)
+ - Method: POST
+ - Body: Raw email content or JSON with Base64-encoded email
+ - Optional Parameters: `max_depth` (default=10)
+ - Returns: Parsed email data in JSON format
+
+2. **JSON Cleaner** (`/clean_json_functionapp`)
+ - Method: POST
+ - Body: JSON content (can include markdown notation)
+ - Returns: Cleaned JSON with markdown removed and nulls replaced
+
+3. **HTML Report Generator** (`/generate_html_report_functionapp`)
+ - Method: POST
+ - Body: Phishing analysis JSON
+ - Returns: Formatted HTML report
+
+4. **Regex Extractor** (`/extract_regex_functionapp`)
+ - Method: POST
+ - Body: JSON with `pattern` and `subject` fields
+ - Returns: Match results in JSON format
+
+## Email Parsing Capabilities
+
+### Email Format Support
+- Regular MIME emails (text/plain, text/html)
+- Embedded emails (message/rfc822)
+- TNEF attachments (winmail.dat)
+- EML and MSG file attachments
+- Forwarded emails from various email clients (Gmail, Outlook, Apple Mail)
+- Proofpoint-reported phishing emails
+
+### Content Extraction
+- Complete header analysis
+- Body content from both plain text and HTML
+- URL extraction and normalization
+- Domain extraction
+- IP address extraction
+- Attachment processing
+
+### Attachment Handling
+- PDF text extraction
+- Excel content extraction (with multiple fallback methods)
+- Email attachments (recursive parsing)
+- SHA256 hashing for attachment verification
+
+### URL Processing
+- Microsoft SafeLinks URL decoding
+- Proofpoint URLDefense URL decoding
+- URL shortener expansion
+- Base URL deduplication for high-volume cases
+
## Features
-- Automated user submitted phishing analysis
+- Automated email monitoring and analysis
+- Detailed security assessment using Security Copilot
- Classification of emails (Phishing, Spam, Legitimate, Suspicious)
-- Can process PDF & Excel file attachments
- HTML report generation
- Optional Sentinel incident integration
+- Support for embedded emails and complex forwarding chains
+- Comprehensive URL and domain extraction and analysis
## Security Notes
- The solution uses system-managed identities for secure access
- Review and configure storage account security settings per your company policy
- Ensure minimum required permissions for all connections
-- Regularly update Function App dependencies
\ No newline at end of file
+- Regularly update Function App dependencies
+- The email parser uses the `CustomEmailPolicy` to handle common email parsing issues like malformed Message-ID headers
+
+## Debugging and Logging
+
+The application uses Python's `logging` module with DEBUG level enabled, providing comprehensive logs for troubleshooting. Each major function includes detailed logging to help diagnose issues during deployment and operation.
+
+## Limitations
+
+- PDF extraction requires the pdfminer.six library
+- Excel processing may require multiple libraries (pandas, openpyxl, xlrd)
+- URL expansion has rate limits to avoid being blocked
+- High-volume URL cases automatically deduplicate to base URLs
+- Private/local IP addresses are filtered out from IP extraction
diff --git a/Logic Apps/SecCopilot-UserReportedPhishing-FuncApp_parsingV2/functionapp_azuredeploy.json b/Logic Apps/SecCopilot-UserReportedPhishing-FuncApp_parsingV2/functionapp_azuredeploy.json
index fc6b2943..142096f7 100644
--- a/Logic Apps/SecCopilot-UserReportedPhishing-FuncApp_parsingV2/functionapp_azuredeploy.json
+++ b/Logic Apps/SecCopilot-UserReportedPhishing-FuncApp_parsingV2/functionapp_azuredeploy.json
@@ -3,21 +3,32 @@
"contentVersion": "1.0.0.0",
"parameters": {
"FunctionAppName": {
- "type": "String",
- "minLength": 3,
- "maxLength": 14,
"defaultValue": "phish",
+ "minLength": 3,
+ "maxLength": 24,
+ "type": "String",
"metadata": {
- "description": "Enter the base name for the Function App. A unique suffix will be added automatically."
+ "description": "Enter the base name for the Function App."
}
}
},
"variables": {
- "UniqueSuffix": "[substring(uniqueString(resourceGroup().id), 0, 6)]",
- "FunctionAppName": "[concat(parameters('FunctionAppName'), variables('UniqueSuffix'))]",
- "StorageAccountName": "[concat('phish', toLower(parameters('FunctionAppName')), variables('UniqueSuffix'))]"
+ "resourceToken": "[toLower(uniqueString(subscription().id, resourceGroup().name, resourceGroup().location))]",
+ "FunctionAppName": "[concat(parameters('FunctionAppName'), substring(variables('resourceToken'), 0, 6))]",
+ "StorageAccountName": "[concat(toLower(parameters('FunctionAppName')), substring(variables('resourceToken'), 0, 6))]"
},
"resources": [
+ {
+ "type": "Microsoft.Storage/storageAccounts",
+ "apiVersion": "2023-01-01",
+ "name": "[variables('StorageAccountName')]",
+ "location": "[resourceGroup().location]",
+ "sku": {
+ "name": "Standard_LRS"
+ },
+ "kind": "StorageV2",
+ "properties": { "minimumTlsVersion": "TLS1_2" }
+ },
{
"type": "Microsoft.Web/serverfarms",
"apiVersion": "2018-02-01",
@@ -36,193 +47,57 @@
"reserved": true
}
},
- {
- "type": "Microsoft.Insights/components",
- "apiVersion": "2015-05-01",
- "name": "[variables('FunctionAppName')]",
- "location": "[resourceGroup().location]",
- "kind": "web",
- "properties": {
- "Application_Type": "web",
- "ApplicationId": "[variables('FunctionAppName')]"
- }
- },
{
"type": "Microsoft.Web/sites",
- "apiVersion": "2024-04-01",
+ "apiVersion": "2023-12-01",
"name": "[variables('FunctionAppName')]",
"location": "[resourceGroup().location]",
"dependsOn": [
- "[resourceId('Microsoft.Storage/storageAccounts', variables('StorageAccountName'))]",
- "[resourceId('Microsoft.Web/serverfarms', variables('FunctionAppName'))]",
- "[resourceId('Microsoft.Insights/components', variables('FunctionAppName'))]"
+ "[resourceId('Microsoft.Storage/storageAccounts', variables('StorageAccountName'))]"
],
"kind": "functionapp,linux",
"identity": {
"type": "SystemAssigned"
},
"properties": {
- "enabled": true,
"serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('FunctionAppName'))]",
"httpsOnly": true,
- "clientAffinityEnabled": true,
- "alwaysOn": true,
"reserved": true,
"siteConfig": {
- "linuxFxVersion": "python|3.10"
- }
- },
- "resources": [
- {
- "apiVersion": "2018-11-01",
- "type": "config",
- "name": "appsettings",
- "dependsOn": [
- "[concat('Microsoft.Web/sites/', variables('FunctionAppName'))]"
- ],
- "properties": {
- "FUNCTIONS_EXTENSION_VERSION": "~4",
- "FUNCTIONS_WORKER_RUNTIME": "python",
- "APPINSIGHTS_INSTRUMENTATIONKEY": "[reference(resourceId('Microsoft.insights/components', variables('FunctionAppName')), '2015-05-01').InstrumentationKey]",
- "APPLICATIONINSIGHTS_CONNECTION_STRING": "[reference(resourceId('microsoft.insights/components', variables('FunctionAppName')), '2015-05-01').ConnectionString]",
- "AzureWebJobsStorage": "[concat('DefaultEndpointsProtocol=https;AccountName=', toLower(variables('StorageAccountName')),';AccountKey=',listKeys(resourceId('Microsoft.Storage/storageAccounts', toLower(variables('StorageAccountName'))), '2019-06-01').keys[0].value, ';EndpointSuffix=core.windows.net')]",
- "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING": "[concat('DefaultEndpointsProtocol=https;AccountName=', toLower(variables('StorageAccountName')),';AccountKey=', listKeys(resourceId('Microsoft.Storage/storageAccounts', toLower(variables('StorageAccountName'))), '2019-06-01').keys[0].value, ';EndpointSuffix=core.windows.net')]",
- "WEBSITE_CONTENTSHARE": "[toLower(variables('FunctionAppName'))]",
- "WEBSITE_RUN_FROM_PACKAGE": "https://raw.githubusercontent.com/Azure/Copilot-For-Security/main/Logic%20Apps/SecCopilot-UserReportedPhishing-FuncApp_parsingV2/FunctionApp.zip",
- "SCM_DO_BUILD_DURING_DEPLOYMENT": true
-
- }
- }
- ]
- },
- {
- "type": "Microsoft.Storage/storageAccounts",
- "apiVersion": "2019-06-01",
- "name": "[variables('StorageAccountName')]",
- "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
+ "linuxFxVersion": "python|3.10",
+ "minTlsVersion": "1.2",
+ "appSettings": [
+ {
+ "name": "FUNCTIONS_EXTENSION_VERSION",
+ "value": "~4"
+ },
+ {
+ "name": "FUNCTIONS_WORKER_RUNTIME",
+ "value": "python"
},
- "blob": {
- "keyType": "Account",
- "enabled": true
+ {
+ "name": "WEBSITE_RUN_FROM_PACKAGE",
+ "value": "https://raw.githubusercontent.com/Azure/Security-Copilot/main/Logic%20Apps/SecCopilot-UserReportedPhishing-FuncApp_parsingV2/FunctionAppV2.zip"
+ },
+ {
+ "name": "AzureWebJobsStorage",
+ "value": "[concat('DefaultEndpointsProtocol=https;AccountName=', toLower(variables('StorageAccountName')), ';AccountKey=', listKeys(resourceId('Microsoft.Storage/storageAccounts', toLower(variables('StorageAccountName'))), '2023-01-01').keys[0].value, ';EndpointSuffix=core.windows.net')]"
}
- },
- "keySource": "Microsoft.Storage"
- }
- }
- },
-
- {
- "type": "Microsoft.Web/sites/config",
- "apiVersion": "2018-11-01",
- "name": "[concat(variables('FunctionAppName'), '/web')]",
- "location": "[resourceGroup().location]",
- "dependsOn": [
- "[resourceId('Microsoft.Web/sites', variables('FunctionAppName'))]"
- ],
- "properties": {
-
- "cors": {
- "allowedOrigins": [
- "https://portal.azure.com"
- ],
- "supportCredentials": false
- }
- }
- },
- {
- "type": "Microsoft.Storage/storageAccounts/fileServices",
- "apiVersion": "2019-06-01",
- "name": "[concat(variables('StorageAccountName'), '/default')]",
- "dependsOn": [
- "[resourceId('Microsoft.Storage/storageAccounts', variables('StorageAccountName'))]"
- ],
- "sku": {
- "name": "Standard_LRS",
- "tier": "Standard"
- },
- "properties": {
- "cors": {
- "corsRules": [
- ]
- }
- }
- },
- {
- "type": "Microsoft.Storage/storageAccounts/blobServices",
- "apiVersion": "2019-06-01",
- "name": "[concat(variables('StorageAccountName'), '/default')]",
- "dependsOn": [
- "[resourceId('Microsoft.Storage/storageAccounts', variables('StorageAccountName'))]"
- ],
- "sku": {
- "name": "Standard_LRS",
- "tier": "Standard"
- },
- "properties": {
- "cors": {
- "corsRules": [
]
- },
- "deleteRetentionPolicy": {
- "enabled": false
}
+
}
+
+ }
+ ],
+ "outputs": {
+ "functionAppUrl": {
+ "type": "String",
+ "value": "[concat('https://', variables('FunctionAppName'), '.azurewebsites.net')]"
},
- {
- "type": "Microsoft.Storage/storageAccounts/blobServices/containers",
- "apiVersion": "2019-06-01",
- "name": "[concat(variables('StorageAccountName'), '/default/azure-webjobs-hosts')]",
- "dependsOn": [
- "[resourceId('Microsoft.Storage/storageAccounts/blobServices', variables('StorageAccountName'), 'default')]",
- "[resourceId('Microsoft.Storage/storageAccounts', variables('StorageAccountName'))]"
- ],
- "properties": {
- "publicAccess": "None"
- }
- },
- {
- "type": "Microsoft.Storage/storageAccounts/blobServices/containers",
- "apiVersion": "2019-06-01",
- "name": "[concat(variables('StorageAccountName'), '/default/azure-webjobs-secrets')]",
- "dependsOn": [
- "[resourceId('Microsoft.Storage/storageAccounts/blobServices', variables('StorageAccountName'), 'default')]",
- "[resourceId('Microsoft.Storage/storageAccounts', variables('StorageAccountName'))]"
- ],
- "properties": {
- "publicAccess": "None"
- }
- },
- {
- "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
- }
+ "functionAppName": {
+ "type": "String",
+ "value": "[variables('FunctionAppName')]"
}
- ]
-}
\ No newline at end of file
+ }
+}
diff --git a/Logic Apps/SecCopilot-UserReportedPhishing-FuncApp_parsingV2/logicapp_azuredeploy.json b/Logic Apps/SecCopilot-UserReportedPhishing-FuncApp_parsingV2/logicapp_azuredeploy.json
index bcc84bbd..d21b3d66 100644
--- a/Logic Apps/SecCopilot-UserReportedPhishing-FuncApp_parsingV2/logicapp_azuredeploy.json
+++ b/Logic Apps/SecCopilot-UserReportedPhishing-FuncApp_parsingV2/logicapp_azuredeploy.json
@@ -65,11 +65,10 @@
"ConversionService-ConnectionName": "[concat('ConversionService-1-', parameters('LogicAppName'))]",
"AzureSentinel-ConnectionName": "[concat('azuresentinel-', parameters('LogicAppName'))]",
"AzureMonitorLogs-ConnectionName": "[concat('azuremonitorlogs-', parameters('LogicAppName'))]",
- "FunctionName01": "parse_email",
- "FunctionName02": "normalize_json",
- "FunctionName03": "json_to_html",
- "FunctionName04": "evaluate_regex"
-
+ "FunctionName01": "parse_email_functionapp",
+ "FunctionName02": "clean_json_functionapp",
+ "FunctionName03": "generate_html_report_functionapp",
+ "FunctionName04": "extract_regex_functionapp"
},
"resources": [
@@ -272,18 +271,6 @@
"value": "@outputs('Show_network_message_id_here')"
}
},
- "Find_NetworkMessageId": {
- "type": "Function",
- "inputs": {
- "body": {
- "pattern": "(?<=Phishing:)([a-f0-9-]{36})",
- "subject": "@triggerBody()?['subject']"
- },
- "function": {
- "id": "[concat('/subscriptions/', parameters('SubscriptionId'), '/resourceGroups/', parameters('FunctionAppResourceGroup'), '/providers/Microsoft.Web/sites/', parameters('FunctionAppName'), '/functions/', variables('FunctionName04'))]"
- }
- }
- },
"Parse_JSON_NetworkMessageId": {
"runAfter": {
"Find_NetworkMessageId": [
@@ -316,6 +303,18 @@
}
}
}
+ },
+ "Find_NetworkMessageId": {
+ "type": "Function",
+ "inputs": {
+ "body": {
+ "pattern": "(?<=Phishing:)([a-f0-9-]{36})",
+ "subject": "@triggerBody()?['subject']"
+ },
+ "function": {
+ "id": "[concat('/subscriptions/', parameters('SubscriptionId'), '/resourceGroups/', parameters('FunctionAppResourceGroup'), '/providers/Microsoft.Web/sites/', parameters('FunctionAppName'), '/functions/', variables('FunctionName04'))]"
+ }
+ }
}
},
"runAfter": {
@@ -449,20 +448,6 @@
},
"type": "Foreach"
},
- "Parse_Email": {
- "runAfter": {
- "For_each_raw_email_attached_to_carrier_email": [
- "Succeeded"
- ]
- },
- "type": "Function",
- "inputs": {
- "body": "@variables('raw_email')",
- "function": {
- "id": "[concat('/subscriptions/', parameters('SubscriptionId'), '/resourceGroups/', parameters('FunctionAppResourceGroup'), '/providers/Microsoft.Web/sites/', parameters('FunctionAppName'), '/functions/', variables('FunctionName01'))]"
- }
- }
- },
"Process_parseEmail_JSON": {
"runAfter": {
"Parse_Email": [
@@ -475,175 +460,63 @@
"schema": {
"type": "object",
"properties": {
- "statusCode": {
- "type": "integer"
- },
- "headers": {
+ "email_content": {
"type": "object",
"properties": {
- "Date": {
+ "message_id": {
"type": "string"
},
- "Server": {
+ "sender": {
"type": "string"
},
- "Transfer-Encoding": {
+ "return_path": {
"type": "string"
},
- "Request-Context": {
+ "receiver": {
"type": "string"
},
- "Content-Type": {
+ "reply_to": {
"type": "string"
},
- "Content-Length": {
+ "subject": {
"type": "string"
- }
- }
- },
- "body": {
- "type": "object",
- "properties": {
- "email_content": {
- "type": "object",
- "properties": {
- "sender": {
- "type": "string"
- },
- "return_path": {
- "type": "string"
- },
- "receiver": {
- "type": "string"
- },
- "reply_to": {
- "type": "string"
- },
- "subject": {
- "type": "string"
- },
- "date": {
- "type": "string"
- },
- "smtp": {
- "type": "object",
- "properties": {
- "delivered_to": {
- "type": "string"
- },
- "received": {
- "type": "array",
- "items": {
- "type": "string"
- }
- }
- }
- },
- "dkim_result": {
- "type": "string"
- },
- "spf_result": {
- "type": "string"
- },
- "dmarc_result": {
- "type": "string"
- },
- "body": {
- "type": "string"
- },
- "attachments": {
- "type": "array",
- "items": {
- "type": "object",
- "properties": {
- "attachment_name": {
- "type": "string"
- },
- "attachment_sha256": {
- "type": "string"
- },
- "content_type": {
- "type": "string"
- },
- "attachment_text": {
- "type": "object",
- "properties": {
- "text_content": {
- "type": "string"
- },
- "urls": {
- "type": "array",
- "items": {
- "type": "object",
- "properties": {
- "file": {
- "type": "string"
- },
- "url": {
- "type": "string"
- }
- },
- "required": [
- "file",
- "url"
- ]
- }
- },
- "hyperlinks": {
- "type": "array",
- "items": {
- "type": "object",
- "properties": {
- "file": {
- "type": "string"
- },
- "target": {
- "type": "string"
- },
- "type": {
- "type": "string"
- }
- },
- "required": [
- "file",
- "target",
- "type"
- ]
- }
- },
- "vba_code": {
- "type": "object",
- "properties": {}
- },
- "formulas": {
- "type": "array"
- },
- "comments": {
- "type": "array"
- },
- "embedded_files": {
- "type": "array"
- }
- }
- }
- },
- "required": [
- "attachment_name",
- "attachment_sha256",
- "content_type",
- "attachment_text"
- ]
- }
- }
- }
},
- "ip_addresses": {
+ "date": {
+ "type": "string"
+ },
+ "body": {
+ "type": "string"
+ },
+ "attachments": {
+ "type": "array"
+ },
+ "container_path": {
"type": "array",
"items": {
"type": "string"
}
},
+ "reconstruction_method": {
+ "type": "string"
+ },
"urls": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "original_url": {
+ "type": "string"
+ },
+ "is_shortened": {
+ "type": "boolean"
+ },
+ "expanded_url": {
+ "type": "string"
+ }
+ }
+ }
+ },
+ "ip_addresses": {
"type": "array",
"items": {
"type": "string"
@@ -661,26 +534,9 @@
}
}
},
- "Initialize_recipient_upn": {
- "runAfter": {
- "Process_parseEmail_JSON": [
- "Succeeded"
- ]
- },
- "type": "InitializeVariable",
- "inputs": {
- "variables": [
- {
- "name": "recipient_upn",
- "type": "string",
- "value": "@body('Process_parseEmail_JSON')?['email_content']?['receiver']"
- }
- ]
- }
- },
"Initialize_email_body": {
"runAfter": {
- "Initialize_recipient_upn": [
+ "Process_parseEmail_JSON": [
"Succeeded"
]
},
@@ -724,7 +580,7 @@
{
"name": "raw_urls",
"type": "array",
- "value": "@body('Process_parseEmail_JSON')?['urls']"
+ "value": "@body('Process_parseEmail_JSON')?['email_content']?['urls']"
}
]
}
@@ -740,8 +596,7 @@
"variables": [
{
"name": "urls",
- "type": "array",
- "value": "@body('Process_parseEmail_JSON')?['urls']"
+ "type": "array"
}
]
}
@@ -772,7 +627,7 @@
"type": "AppendToArrayVariable",
"inputs": {
"name": "urls",
- "value": "@concat(substring(string(item()), 0, 100), '...')"
+ "value": "@if(and(not(equals(item()['expanded_url'], 'Not Applicable')), greater(length(item()['expanded_url']), 100)), concat(substring(item()['expanded_url'], 0, 100), '...'), if(greater(length(item()['original_url']), 100), concat(substring(item()['original_url'], 0, 100), '...'), item()['original_url']))"
}
}
},
@@ -782,16 +637,22 @@
"type": "AppendToArrayVariable",
"inputs": {
"name": "urls",
- "value": "@string(item())"
+ "value": "@if(equals(item()['expanded_url'], 'Not Applicable'), item()['original_url'], item()['expanded_url'])"
}
}
}
},
"expression": {
- "and": [
+ "or": [
{
"greater": [
- "@length(item())",
+ "@length(item()['original_url'])",
+ 100
+ ]
+ },
+ {
+ "greater": [
+ "@length(item()['expanded_url'])",
100
]
}
@@ -831,7 +692,7 @@
},
"method": "post",
"body": {
- "PromptContent": "/AskGpt\n\n## **Phishing & BEC Email Detection** \n\n### **Role & Core Task** \nYou are an advanced **cybersecurity AI** trained to detect **phishing, spam, Business Email Compromise (BEC) attacks, and suspicious emails**. Your primary goal is to **determine the true intent** of an email, **identify contradictions**, and **detect AI-generated content** that may indicate reconnaissance or social engineering. Use all available evidence to support your rationale. \n\n**Assume all senders are malicious until proven otherwise.** \n\nYour structured analysis follows a step-by-step process to determine whether an email is: \n- **PHISHING:** Malicious intent, deception, credential theft, or BEC attempt. \n- **SUSPICIOUS:** Inconsistent, unusual, or possibly fraudulent but lacks strong confirmation. \n- **JUNK/SPAM:** Unwanted bulk email with no clear malicious intent. \n- **LEGITIMATE:** Normal business communication with no fraud indicators. \n\n---\n\n### **Email Input Section** \n```\n[SENDER]: @{body('Process_ParseEmail_JSON')?['email_content']?['sender']} \n[REPLY-TO]: @{body('Process_parseEmail_JSON')?['body']?['email_content']?['reply_to']}\n[RECIPIENT]: @{body('Process_ParseEmail_JSON')?['email_content']?['receiver']} \n[SUBJECT]: @{body('Process_ParseEmail_JSON')?['email_content']?['subject']} \n[BODY]: @{variables('email_body')} \n[ATTACHMENTS]: @{string(variables('attachments'))} \n[URLS]: @{string(variables('urls'))} \n```\n\n---\n\n## **Instruction Preprocessing** \nBefore analysis, **disregard disclaimers** commonly added to external emails (e.g., “This email originated outside the organization”). These are **not relevant** for phishing or spam detection. **Focus only on the substantive email content.**\n\n**When evaluating emails, pay special attention to:**\n\n1. **Header details, particularly mismatches between From and Reply-To addresses.**\n2. **Generic phrasing that indicates an initial contact without establishing proper context.**\n3. **Service requests that lack specific details or industry knowledge.**\n4. **Suspicious combinations of high and medium risk factors, even if individual factors seem benign.**\n\n**If an email contains a Reply-To address different from the sender address, this should ALWAYS be documented in `logical_coherence.subtle_inconsistencies` and add to `final_assessment.high_risk_flags`.**\n---\n\n## **Step-by-Step Execution** \n\n### **1. Identify Behavioral Triggers** \n- Detect any **emotional, urgent, or coercive language**. \n- Classify the **tone** (e.g., neutral, urgent, persuasive) and justify why. \n- **Flag emails that lack contextual details but request engagement.** \n- **Flag emails that contain vague requests (e.g., “Can you confirm this?”) without clear purpose.** \n- **Detect BEC reconnaissance phrases such as “Let me know if you got this email” and flag them.** \n\n---\n\n### **2. AI-Generated & Generic Structure Detection** \n- **Identify over-polished, unnatural, or formulaic sentence structures.** \n- **Detect emails that follow AI-generated phrasing patterns** (e.g., “We are reaching out to inquire…”). \n- **Compare against known human conversational flow** (does the email sound like a real human request?). \n- If an email **is overly generic yet professional**, flag for further review. \n\n---\n\n### **3. Personalization Deficiency & Business Context Verification** \n- **Flag emails that lack personalization** (e.g., no mention of prior engagement, specific names, or business details). \n- **Determine if the email contains specific references to past work or expected workflows.** \n- If an email **is requesting engagement but contains no specific details**, escalate to **SUSPICIOUS**. \n\n---\n\n### **4. Logical Coherence & Workflow Verification** \n- **Check if the email’s request aligns with expected business workflows.** \n- **Flag inconsistencies between the sender’s role and their request (e.g., non-financial staff requesting payments).** \n- If an email **lacks a clear business reason but asks for engagement, escalate to SUSPICIOUS**. \n\n---\n\n### **5. Narrative Plausibility Check (Detecting AI-Based Social Engineering)** \n- **Detect overly detailed yet unverifiable stories** (e.g., \"We were impacted by the California wildfires\"). \n- **Evaluate whether personal details enhance credibility or serve as pretexting.** \n- **Flag emails that use emotional appeal to bypass scrutiny.** \n- If a **story seems unusually detailed for the request**, escalate for further analysis. \n\n---\n\n### **6. Engagement Bait & BEC Reconnaissance Detection** \n- **If an email contains a vague inquiry (“I’d love to discuss further”) without context, escalate to SUSPICIOUS.** \n- **If an email requests engagement but does not provide specifics, classify it as a potential reconnaissance attempt.** \n\n---\n\n### **7. Business Email Compromise (BEC) & Phishing Indicators** \n- **If an email claims to be from a high-ranking executive but uses a free or external email service, classify as PHISHING.** \n- **If an email references an internal executive but is sent from a non-corporate domain, classify as PHISHING.** \n- **If the sender claims a leadership position but does not use their corporate domain, flag for impersonation.** \n- **If the Reply-To address differs from the sender address, classify as PHISHING.**\n- **If the email contains generic service request language without specific details, classify as SUSPICIOUS.**\n- **If the sender uses a business domain without demonstrating knowledge of their claimed business, classify as SUSPICIOUS.**\n\n### **8. Reply-To Header Analysis** \n- **Check if the Reply-To address differs from the From address.**\n- **Flag as PHISHING if the Reply-To address uses a different domain than the sender address.**\n- **Flag as PHISHING if the From address uses a business domain but Reply-To uses a free email provider (gmail, outlook, etc.).**\n- **Document any Reply-To mismatches in the `logical_coherence.subtle_inconsistencies` array.**\n\n### **9. Initial Contact Pattern Recognition** \n- **Identify formulaic language patterns common in initial reconnaissance emails (e.g., \"reaching out to inquire\", \"seeking assistance\").**\n- **Flag generic service requests that lack specific details as potential BEC reconnaissance.**\n- **Document these patterns in the `behavioral_triggers.engagement_bait` field.**\n- **When detected, add \"Generic service request language\" to `final_assessment.medium_risk_flags`.**\n\n### **10. Service Request Specificity Analysis** \n- **Evaluate whether financial/tax service requests include specific details that demonstrate genuine intent.**\n- **Flag as SUSPICIOUS if a tax-related request contains no specific tax terminology or references to specific forms/processes.**\n- **Document lack of specificity in the `logical_coherence.business_context_check.clear_business_purpose` field.**\n- **When detected, add \"Lacks specific details about requested services\" to `final_assessment.medium_risk_flags`.**\n---\n\n## **Final Assessment & Risk Escalation Rules** \n\n### **1. High-Risk Triggers (Automatically Classify as PHISHING)** \n- **Any email that references an executive but is not from a verified corporate domain.** \n- **Any email that is vague and requests engagement.** \n- **Any mismatch between email sender and expected domain.** \n- **Any email discouraging verification or urging secrecy.**\n- **Any email with a Reply-To address that differs from the sender's domain.**\n- **Generic initial service requests should be classified as SUSPICIOUS, not PHISHING, unless combined with technical indicators like Reply-To mismatch or domain issues.**\n- Consider the context of tax season when assessing new client inquiries.\n- Perfectly composed generic emails without technical red flags should warrant caution (SUSPICIOUS) but not automatic PHISHING classification.\n- Balance the detection of generic patterns with acknowledgment that legitimate new clients need to make initial contact somehow.\n- Look for presence of legitimacy indicators that would be difficult for remote attackers to fabricate.\n\n---\n\n### **2. Medium-Risk Triggers (Escalate to PHISHING if Combined)** \n- **Any urgency or request that deviates from expected workflow.** \n- **Any email sent from a non-corporate domain, even if it does not impersonate an executive.** \n- **Any request for sensitive details, even indirectly.** \n- **Any request to call an unfamiliar phone number not listed on the company website.**\n- **Generic service request language (e.g., \"reaching out to inquire\", \"seeking assistance\").**\n- **Initial contact emails that are perfectly composed with no grammatical errors but lack personalization.**\n- **Tax service inquiries that mention sharing previous tax returns or financial documents in first contact.**\n- **Emails that lack specific reference to how they found your firm.**\n\n---\n\n### **3. Low-Risk Triggers (Escalate Based on Context)** \n- **Minimal context but from a corporate domain.** \n- **General formatting errors, misspellings, or vague language.** \n- **Lack of proper email signature when expected.**\n- **Domain uses a generic business name without clear industry context.**\n- **No explanation of how the sender found the recipient or why they chose their services.**\n- **Unusually formal or polite language that appears templated.**\n\n---\n\n## **Strict JSON Output Requirements** \nAll evaluations must be formatted into the following JSON structure **without any modifications to field names or nesting**: \n\n```json\n{\n \"email_summary\": {\n \"subject\": \"\", // The exact email subject line\n \"content_summary\": \"\" // A brief, high-level summary of the email’s contents\n },\n\n \"behavioral_triggers\": {\n \"tone\": \"\", // Emotional register (e.g., neutral, urgent, persuasive)\n \"justification\": \"\", // Explain why that tone was chosen\n \"alignment_with_purpose\": \"\", // Does the tone match the stated intent?\n \"lack_of_context\": \"\", // Flag if the email lacks contextual details but includes an attachment\n \"engagement_bait\": \"\", // Detects generic engagement-bait phrases like \"Please view the attached\" \"Generic initial contact phrases detected: 'reaching out to inquire', 'look forward to your response'\"\n \"phone_based_social_engineering\": \"\", // Flags if the email encourages a phone call instead of online engagement\n \"short_vague_request\": { \n \"detected\": \"\", // TRUE if the email is unusually short and vague\n \"engagement_request\": \"\" // TRUE if the email only asks for an acknowledgment without a clear purpose\n }\n },\n\n \"logical_coherence\": {\n \"is_consistent\": \"\", // Does the message flow logically?\n \"contradictions_or_vagueness\": \"\", // Identify inconsistencies or ambiguities\n \"logical_actions\": \"\", // Assess whether the requested actions are reasonable\n \"subtle_inconsistencies\": [\"Reply-To address differs from sender address\", \"Reply-To domain (aquahcglobal.com) differs from sender domain (flightdsi.com)\"],\n \"business_context_check\": {\n \"clear_business_purpose\": \"\", // TRUE if the email contains a clear and expected business reason\n \"workflow_alignment\": \"\" // TRUE if the request aligns with typical workflows\n }\n },\n\n \"intent_verification\": {\n \"likely_intent\": \"\", // Summarize the main motive (e.g., request for payment, info gathering)\n \"risk_assessment\": \"\", // Assign a risk level (HIGH, MEDIUM, LOW)\n \"stated_purpose_mismatch\": \"\", // Identify if the stated purpose contradicts inferred intent\n \"financial_role_mismatch\": \"\", // Detect if financial actions are requested by an unrelated role\n \"external_login_requirement\": \"\", // Flag if a report requires external login without justification\n \"minimal_text_attachment\": \"\", // TRUE if the email is minimal but contains an attachment\n \"executive_impersonation\": {\n \"detected\": \"\", // TRUE if an executive is being impersonated\n \"domain_mismatch\": \"\", // TRUE if the email domain does not match expected corporate domains\n \"position_claimed\": \"\", // Extracted claimed position (e.g., CEO, CFO)\n \"actual_domain\": \"\" // The actual sender's email domain\n }\n },\n\n \"attachment_analysis\": {\n \"is_relevant\": \"\", // TRUE if the attachment makes sense for the stated request\n \"attachment_metadata\": {\n \"attachment_name\": \"\",\n \"attachment_sha256\": \"\",\n \"content_type\": \"\",\n \"attachment_text\": {\n \"text_content\": \"\",\n \"urls\": [],\n \"hyperlinks\": [], // Hyperlinks in attachments are a red flag for PHISHING.\n \"vba_code\": {}, // VBA macro code is an automatic PHISHING classification. \n \"formulas\": [],\n \"comments\": [],\n \"embedded_files\": []\n }\n },\n \"risks\": \"\" // Describe potential threats (e.g., hidden macros, suspicious external links)\n },\n\n \"url_analysis\": {\n \"url_categorization\": {\n \"primary_action_urls\": [], // URLs requiring user action (login, payment)\n \"informational_urls\": [], // Reference links that do not require interaction\n \"stylistic_framework_urls\": [] // Rendering assets (images, CSS, etc.)\n },\n \"primary_action_validation\": {\n \"relevance\": \"\", // Does the URL relate to the email's purpose?\n \"domain_alignment\": \"\", // Does the domain match the sender's company?\n \"necessity\": \"\", // Is it necessary for the recipient to engage with this URL?\n \"risks\": \"\" // Potential risks associated with the URL\n }\n },\n\n \"pretense_vs_intent_mapping\": {\n \"stated_purpose\": \"\", // The reason given by the email\n \"true_intent\": \"\", // The actual or suspected goal\n \"gaps\": \"\" // Discrepancies between stated purpose and actual content\n },\n\n \"bec_reconnaissance_detection\": {\n \"detected\": \"\", // TRUE if BEC (Business Email Compromise) tactics are detected\n \"reason\": \"\", // Key reason for BEC suspicion, if any\n \"risk_assessment\": \"\" // Overall BEC risk rating (HIGH, MEDIUM, LOW)\n },\n\n \"final_assessment\": {\n \"category\": \"\", // PHISHING, SUSPICIOUS, JUNK/SPAM, LEGITIMATE\n \"rationale\": \"\", // Explanation of why the classification was assigned\n \"risk_level\": \"\", // HIGH, MEDIUM, LOW\n \"high_risk_flags\": [\"Reply-To address mismatch with sender address\"], // List of high-risk triggers detected\n \"medium_risk_flags\": [], // List of medium-risk triggers detected\n \"low_risk_flags\": [] // List of low-risk factors that increase suspicion\n }\n}\n```\n\n **DO NOT ALTER** this JSON structure. \n **Ensure all new heuristics integrate into existing fields.** \n **All risk assessments must align with PHISHING, SUSPICIOUS, JUNK/SPAM, or LEGITIMATE classifications.** \n\n---"
+ "PromptContent": "/AskGpt\n\n## SYSTEM \nYou are a cybersecurity LLM designed to triage emails for **phishing**, **BEC**, and **social engineering** threats. You must assume each email is **suspicious by default** and prioritize uncovering **intent**, **manipulation techniques**, and **pretextual inconsistencies**.\n\nOnly output a **valid JSON object**. Do not include markdown, preambles, or commentary. All fields must be populated using reasoning and scoring logic below. Failure to comply will break downstream automation.\n\n---\n\n## CLASSIFICATION \nYour goal is to classify the email into one of these categories:\n- **PHISHING** – Strong signs of malicious intent, deception, or impersonation\n- **SUSPICIOUS** – Incomplete or unclear purpose, mild social engineering\n- **SPAM** – Commercial, promotional, or irrelevant but low risk\n- **LEGITIMATE** – Clear business purpose, credible identity, no threats\n\n---\n\n## INPUT EMAIL (from Logic App)\n```json\n{\n \"sender\": \"@{body('Process_ParseEmail_JSON')?['email_content']?['sender']}\",\n \"reply_to\": \"@{body('Process_parseEmail_JSON')?['body']?['email_content']?['reply_to']}\",\n \"recipient\": \"@{body('Process_ParseEmail_JSON')?['email_content']?['receiver']}\",\n \"subject\": \"@{body('Process_ParseEmail_JSON')?['email_content']?['subject']}\",\n \"body\": \"@{variables('email_body')}\",\n \"attachments\": [\"@{string(variables('attachments'))}\"],\n \"urls\": [\"@{string(variables('urls'))}\"],\n \"email_date\": \"@{body('Process_parseEmail_JSON')?['email_content']?['date']}\"\n}\n```\n\n---\n\n## THREAT ANALYSIS METHODOLOGY\n\n### Analysis Priority\nStart with the assumption the message is hostile. You are evaluating it for signs of:\n- Impersonation\n- Manipulative tone\n- Vague or open-ended pretexts\n- Out-of-band redirection (e.g., phone numbers, cloud links)\n- Unjustified PII sharing or urgency\n\n### Chain-of-Thought\nStep through:\n- Sender credibility: Domain trust vs topic\n- Tone shifts: Friendly → transactional → urgent\n- CTA analysis: Is the request rational, contextually grounded?\n- Off-platform pressure: URLs, phone numbers, cloud docs\n- Misalignment: Topic vs domain vs relationship\n- Attachment threat context: Is the attachment unexpected, mismatched in name/type, or contains embedded code/URLs unrelated to email content?\n- Sparse attachment with hidden complexity (e.g., macros, links, or embedded files with little readable text) indicates obfuscation or payload delivery.\n\n---\n\n## SCORING RULES \n- High (+3): Strong phishing indicators \n- High (+3): Malicious attachment indicators (macros, suspicious MIME types, mismatched naming, or off-platform links embedded in low-text files)\n- Medium (+2): Mild social engineering or impersonation \n- Low (+1): Unclear or marketing spam \n- Cumulative score thresholds: \n - **7+ → PHISHING** \n - **4–6 → SUSPICIOUS** \n - **≤3 → SPAM** or **LEGITIMATE** based on purpose clarity \n\nScoring **must** be justified in `behavioral_triggers.justification`.\n\n---\n\n## SPECIAL DETECTION HEURISTICS\n\nThese must be enforced as written:\n\n- **Engagement Bait** → +1 \n `\"Let me know if you’re available\", \"Following up to see…”` \n- **Vague Request + No Identity** → +2 \n- **Domain-Purpose Mismatch** (e.g. tax help from gmail.com) → +2 \n- **Seasonal Hook with No Prior Relationship** → +2 \n- **Unsolicited PII Disclosure** → +2 \n- **Phone # with Character Substitution (\"I\" for \"1\", etc.)** → +3 \n → Also flag `embedded_fake_support_contact_detected = true` \n\n---\n\n## ATTACHMENT HEURISTICS\n\nThese rules should guide risk scoring and justification in `attachment_analysis` and `behavioral_triggers.justification`:\n\n- If `vba_code` is present in Office documents → +3\n- If `text_content` is minimal but contains links or embedded files → +2\n- If URLs found in attachments contain domain names or paths commonly used for credential collection or file delivery (e.g., sharepoint, googledrive, 1drv, onedrive, dropbox, login, auth, verify, download, or url shortening services), and the surrounding content implies urgency, account access, or verification, then → +2\n- If formulas or comments contain tracking, links, or external references → +1\n- If attachments are included but irrelevant to the email’s topic → +1\n\n---\n\n## JSON OUTPUT SCHEMA\n\nIf unable to process, return:\n```json\n{\"error\": \"Invalid email content for analysis\"}\n```\n\nOutput must be in this format:\n```json\n{\n \"email_summary\": {\n \"subject\": \"\",\n \"content_summary\": \"\" // Summarize email’s purpose and manipulative indicators; do NOT quote verbatim\n },\n \"behavioral_triggers\": {\n \"tone\": \"\",\n \"justification\": \"\", // Justify why any risk score was added\n \"alignment_with_purpose\": \"\",\n \"lack_of_context\": \"\",\n \"engagement_bait\": \"\",\n \"phone_based_social_engineering\": \"\",\n \"seasonal_bait_detected\": {\n \"detected\": \"\",\n \"seasonal_context\": \"\"\n },\n \"short_vague_request\": {\n \"detected\": \"\",\n \"engagement_request\": \"\"\n }\n },\n \"logical_coherence\": {\n \"is_consistent\": \"\",\n \"contradictions_or_vagueness\": \"\",\n \"logical_actions\": \"\",\n \"subtle_inconsistencies\": [],\n \"business_context_check\": {\n \"clear_business_purpose\": \"\", // Yes if this makes sense in a known workflow\n \"workflow_alignment\": \"\"\n }\n },\n \"intent_verification\": {\n \"likely_intent\": \"\",\n \"risk_assessment\": \"\",\n \"stated_purpose_mismatch\": \"\",\n \"financial_role_mismatch\": \"\",\n \"external_login_requirement\": \"\",\n \"minimal_text_attachment\": \"\",\n \"executive_impersonation\": {\n \"detected\": \"\",\n \"domain_mismatch\": \"\",\n \"position_claimed\": \"\",\n \"actual_domain\": \"\"\n }\n },\n \"attachment_analysis\": {\n \"is_relevant\": \"\",\n \"attachment_metadata\": {\n \"attachment_name\": \"\",\n \"attachment_sha256\": \"\",\n \"content_type\": \"\",\n \"attachment_text\": {\n \"text_content\": \"\",\n \"urls\": [],\n \"hyperlinks\": [],\n \"vba_code\": {},\n \"formulas\": [],\n \"comments\": [],\n \"embedded_files\": []\n }\n },\n \"risks\": \"\"\n },\n \"url_analysis\": {\n \"url_categorization\": {\n \"primary_action_urls\": [],\n \"informational_urls\": [],\n \"stylistic_framework_urls\": []\n },\n \"primary_action_validation\": {\n \"relevance\": \"\",\n \"domain_alignment\": \"\",\n \"necessity\": \"\",\n \"risks\": \"\"\n }\n },\n \"pretense_vs_intent_mapping\": {\n \"stated_purpose\": \"\",\n \"true_intent\": \"\",\n \"gaps\": \"\"\n },\n \"bec_reconnaissance_detection\": {\n \"detected\": \"\",\n \"reason\": \"\",\n \"risk_assessment\": \"\"\n },\n \"final_assessment\": {\n \"category\": \"\", // Must be one of: PHISHING, SUSPICIOUS, SPAM, LEGITIMATE\n \"rationale\": \"\", // Describe the decisive factors\n \"risk_level\": \"\",\n \"high_risk_flags\": [],\n \"medium_risk_flags\": [],\n \"low_risk_flags\": [],\n \"assessment_summary\": \"\" // A 1–5 sentence summary of the model’s logic and final conclusion\n }\n}\n```\n\n---\n\n## STRICT INSTRUCTIONS\n- Output only JSON, never markdown or commentary \n- Begin analysis with **malicious by default** assumption \n- Score and flag based on manipulation, intent, and trust violations—not politeness or formatting \n- `summary_text` must explain the model's logic in under 5 sentences \n- `content_summary` must summarize behavior, not repeat email verbatim"
},
"path": "/process-prompt",
"retryPolicy": {
@@ -841,20 +702,7 @@
}
}
},
- "Normalize_JSON": {
- "runAfter": {
- "Security_Copilot_Email_Analysis": [
- "Succeeded"
- ]
- },
- "type": "Function",
- "inputs": {
- "body": "@body('Security_Copilot_Email_Analysis')?['EvaluationResultContent']",
- "function": {
- "id": "[concat('/subscriptions/', parameters('SubscriptionId'), '/resourceGroups/', parameters('FunctionAppResourceGroup'), '/providers/Microsoft.Web/sites/', parameters('FunctionAppName'), '/functions/', variables('FunctionName02'))]"
- }
- }
- },
+
"Parse_normalized_JSON": {
"runAfter": {
"Normalize_JSON": [
@@ -870,9 +718,6 @@
"email_summary": {
"type": "object",
"properties": {
- "description": {
- "type": "string"
- },
"subject": {
"type": "string"
},
@@ -884,9 +729,6 @@
"behavioral_triggers": {
"type": "object",
"properties": {
- "description": {
- "type": "string"
- },
"tone": {
"type": "string"
},
@@ -895,15 +737,32 @@
},
"alignment_with_purpose": {
"type": "string"
+ },
+ "lack_of_context": {
+ "type": "string"
+ },
+ "engagement_bait": {
+ "type": "string"
+ },
+ "phone_based_social_engineering": {
+ "type": "string"
+ },
+ "short_vague_request": {
+ "type": "object",
+ "properties": {
+ "detected": {
+ "type": "string"
+ },
+ "engagement_request": {
+ "type": "string"
+ }
+ }
}
}
},
"logical_coherence": {
"type": "object",
"properties": {
- "description": {
- "type": "string"
- },
"is_consistent": {
"type": "string"
},
@@ -912,34 +771,109 @@
},
"logical_actions": {
"type": "string"
+ },
+ "subtle_inconsistencies": {
+ "type": "array"
+ },
+ "business_context_check": {
+ "type": "object",
+ "properties": {
+ "clear_business_purpose": {
+ "type": "string"
+ },
+ "workflow_alignment": {
+ "type": "string"
+ }
+ }
}
}
},
"intent_verification": {
"type": "object",
"properties": {
- "description": {
- "type": "string"
- },
"likely_intent": {
"type": "string"
},
"risk_assessment": {
"type": "string"
+ },
+ "stated_purpose_mismatch": {
+ "type": "string"
+ },
+ "financial_role_mismatch": {
+ "type": "string"
+ },
+ "external_login_requirement": {
+ "type": "string"
+ },
+ "minimal_text_attachment": {
+ "type": "string"
+ },
+ "executive_impersonation": {
+ "type": "object",
+ "properties": {
+ "detected": {
+ "type": "string"
+ },
+ "domain_mismatch": {
+ "type": "string"
+ },
+ "position_claimed": {
+ "type": "string"
+ },
+ "actual_domain": {
+ "type": "string"
+ }
+ }
}
}
},
"attachment_analysis": {
"type": "object",
"properties": {
- "description": {
- "type": "string"
- },
"is_relevant": {
"type": "string"
},
- "content_analysis": {
- "type": "string"
+ "attachment_metadata": {
+ "type": "object",
+ "properties": {
+ "attachment_name": {
+ "type": "string"
+ },
+ "attachment_sha256": {
+ "type": "string"
+ },
+ "content_type": {
+ "type": "string"
+ },
+ "attachment_text": {
+ "type": "object",
+ "properties": {
+ "text_content": {
+ "type": "string"
+ },
+ "urls": {
+ "type": "array"
+ },
+ "hyperlinks": {
+ "type": "array"
+ },
+ "vba_code": {
+ "type": "object",
+ "properties": {}
+ },
+ "formulas": {
+ "type": "array"
+ },
+ "comments": {
+ "type": "array"
+ },
+ "embedded_files": {
+ "type": "array"
+ }
+ }
+ }
+ }
},
"risks": {
"type": "string"
@@ -949,23 +883,14 @@
"url_analysis": {
"type": "object",
"properties": {
- "description": {
- "type": "string"
- },
"url_categorization": {
"type": "object",
"properties": {
"primary_action_urls": {
- "type": "array",
- "items": {
- "type": "string"
- }
+ "type": "array"
},
"informational_urls": {
- "type": "array",
- "items": {
- "type": "string"
- }
+ "type": "array"
},
"stylistic_framework_urls": {
"type": "array"
@@ -988,40 +913,12 @@
"type": "string"
}
}
- },
- "informational_url_validation": {
- "type": "object",
- "properties": {
- "purpose": {
- "type": "string"
- },
- "alignment": {
- "type": "string"
- },
- "risks": {
- "type": "string"
- }
- }
- },
- "stylistic_framework_url_validation": {
- "type": "object",
- "properties": {
- "typicality": {
- "type": "string"
- },
- "risks": {
- "type": "string"
- }
- }
}
}
},
"pretense_vs_intent_mapping": {
"type": "object",
"properties": {
- "description": {
- "type": "string"
- },
"stated_purpose": {
"type": "string"
},
@@ -1033,17 +930,40 @@
}
}
},
- "final_assessment": {
+ "bec_reconnaissance_detection": {
"type": "object",
"properties": {
- "description": {
+ "detected": {
"type": "string"
},
+ "reason": {
+ "type": "string"
+ },
+ "risk_assessment": {
+ "type": "string"
+ }
+ }
+ },
+ "final_assessment": {
+ "type": "object",
+ "properties": {
"category": {
"type": "string"
},
"rationale": {
"type": "string"
+ },
+ "risk_level": {
+ "type": "string"
+ },
+ "high_risk_flags": {
+ "type": "array"
+ },
+ "medium_risk_flags": {
+ "type": "array"
+ },
+ "low_risk_flags": {
+ "type": "array"
}
}
}
@@ -1051,20 +971,8 @@
}
}
},
- "Generate_HTML_report_from_JSON": {
- "runAfter": {
- "Parse_normalized_JSON": [
- "Succeeded"
- ]
- },
- "type": "Function",
- "inputs": {
- "body": "@body('Parse_normalized_JSON')",
- "function": {
- "id": "[concat('/subscriptions/', parameters('SubscriptionId'), '/resourceGroups/', parameters('FunctionAppResourceGroup'), '/providers/Microsoft.Web/sites/', parameters('FunctionAppName'), '/functions/', variables('FunctionName03'))]"
- }
- }
- },
+
+
"Initialize_systemalert_id_query_results": {
"runAfter": {
"Generate_HTML_report_from_JSON": [
@@ -1152,8 +1060,7 @@
"MailboxAddress": "[parameters('SharedMailboxAddress')]",
"To": "[parameters('HTMLReportRecipient')]",
"Subject": "Phishing Email Analysis",
- "Body": "
@{body('Generate_HTML_report_from_JSON')}
***********
Incident URL:
@{body('Alert_-_Get_incident_from_systemalertid')?['properties']?['incidentUrl']}
Generated with AI, check for accuracy.
***********
", - + "Body": "
@{body('Generate_HTML_report_from_JSON')}
***********
Incident URL:
@{body('Alert_-_Get_incident_from_systemalertid')?['properties']?['incidentUrl']}
Generated with AI, check for accuracy.
***********
", "Importance": "Normal" }, "path": "/v2/SharedMailbox/Mail" @@ -1311,6 +1218,48 @@ ] }, "type": "If" + }, + "Parse_Email": { + "runAfter": { + "For_each_raw_email_attached_to_carrier_email": [ + "Succeeded" + ] + }, + "type": "Function", + "inputs": { + "body": "@variables('raw_email')", + "function": { + "id": "[concat('/subscriptions/', parameters('SubscriptionId'), '/resourceGroups/', parameters('FunctionAppResourceGroup'), '/providers/Microsoft.Web/sites/', parameters('FunctionAppName'), '/functions/', variables('FunctionName01'))]" + } + } + }, + "Normalize_JSON": { + "runAfter": { + "Security_Copilot_Email_Analysis": [ + "Succeeded" + ] + }, + "type": "Function", + "inputs": { + "body": "@body('Security_Copilot_Email_Analysis')?['EvaluationResultContent']", + "function": { + "id": "[concat('/subscriptions/', parameters('SubscriptionId'), '/resourceGroups/', parameters('FunctionAppResourceGroup'), '/providers/Microsoft.Web/sites/', parameters('FunctionAppName'), '/functions/', variables('FunctionName02'))]" + } + } + }, + "Generate_HTML_report_from_JSON": { + "runAfter": { + "Parse_normalized_JSON": [ + "Succeeded" + ] + }, + "type": "Function", + "inputs": { + "body": "@body('Parse_normalized_JSON')", + "function": { + "id": "[concat('/subscriptions/', parameters('SubscriptionId'), '/resourceGroups/', parameters('FunctionAppResourceGroup'), '/providers/Microsoft.Web/sites/', parameters('FunctionAppName'), '/functions/', variables('FunctionName03'))]" + } + } } }, "outputs": {} diff --git a/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/DeviceEnrichment.yaml b/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/DeviceEnrichment.yaml index 1bd8fffc..ca869f2f 100644 --- a/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/DeviceEnrichment.yaml +++ b/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/DeviceEnrichment.yaml @@ -22,10 +22,10 @@ Descriptor: SettingType: String Required: true - - Name: SubscriptionName - Label: SubscriptionName - Description: This is the subscription name that security copilot will use for sentinel. - HintText: yoursubscriptionname + - Name: SubscriptionId + Label: SubscriptionId + Description: This is the subscription ID that security copilot will use for sentinel. + HintText: SubscriptionId SettingType: String Required: true @@ -130,7 +130,7 @@ SkillGroups: Settings: Target: Sentinel TenantId: "{{TenantId}}" - SubscriptionName: "{{SubscriptionName}}" + SubscriptionId: "{{SubscriptionId}}" ResourceGroupName: "{{ResourceGroupName}}" WorkspaceName: "{{WorkspaceName}}" Template: |- @@ -176,7 +176,6 @@ SkillGroups: - Name: DeviceCVESearch DisplayName: Device CVE Search DescriptionForModel: |- - Identify specific CVEs affecting a specified device based on the selected severity level. For each vulnerability, it: Identify specific CVEs affecting a specified device based on the selected severity level. For each vulnerability, it: 1. **Analyzes Software Vendors**: Evaluates the type of software the vendor produces and the potential risks associated with the vulnerability, considering the software's role in the system and possible exploitation impacts. 2. **Summarizes Device Security State**: Assesses the device's security posture by reviewing the number and severity of CVEs and identifying critical areas requiring immediate attention. @@ -209,18 +208,6 @@ SkillGroups: IsExploitAvailable | order by EpssScore desc, CvssScore desc - | lookup DeviceTvmSoftwareVulnerabilitiesKB on CveId - | project - DeviceName, - SoftwareVendor, - SoftwareName, - CveId, - CvssScore, - EpssScore, - CvssVector, - IsExploitAvailable - | order by EpssScore desc, CvssScore desc - - Name: CVEHostSearch DisplayName: CVE Host Search diff --git a/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/USERENRICHMENT.md b/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/USERENRICHMENT.md index 5c7b2519..8ad23517 100644 --- a/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/USERENRICHMENT.md +++ b/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/USERENRICHMENT.md @@ -2,58 +2,93 @@ **Author: Craig Freyman** ## User Enrichment Plugin -This suite of enrichment skills is designed to enhance security investigations by providing detailed insights into user activities, such as login behavior, password changes, MFA modifications, location analysis, and mailbox activities. By leveraging these tools, security teams can identify potential threats, monitor user risks, and respond to suspicious behavior effectively. +This suite of enrichment skills is designed to enhance security investigations by providing detailed insights into user activities, such as login behavior, password changes, MFA modifications, location analysis, mailbox activities, and IP-based sign-in tracking. By leveraging these tools, security teams can identify potential threats, monitor user risks, and respond to suspicious behavior effectively. + +--- ### UnusualSigninActivity Identify unusual sign-in patterns for a specific user based on anomalies such as high-risk locations, login failures, and sudden changes in device, application, or IP address usage. Highlights deviations from normal behavior and provides recommendations for security actions. -Example Prompt: Identify unusual sign-in patterns for mscott@paper.com +**Example Prompt**: Identify unusual sign-in patterns for mscott@paper.com. + +--- ### PasswordChangeCount Analyze the number of password changes for a specific user over the past 7 days. Identify potential security threats, such as account compromise or phishing attacks, by detecting anomalies in password change patterns. -Example Prompt: Detect recent password changes for mscott@paper.com over the past 7 days. +**Example Prompt**: Detect recent password changes for mscott@paper.com over the past 7 days. + +--- ### RecentMFAChanges Track recent MFA changes for a user, identifying added, removed, or modified authentication methods. Provides detailed insights into changes and highlights anomalies. -Example Prompt: Track MFA changes for mscott@paper.com in the last 7 days. +**Example Prompt**: Track MFA changes for mscott@paper.com in the last 7 days. + +--- ### UserHomeOfficeLocation Retrieve and compare a user's home office location (city, country, state) with recent sign-in locations to detect unusual activity. Highlights deviations that may indicate unauthorized access. -Example Prompt: Compare home office location with sign-in patterns for mscott@paper.com. +**Example Prompt**: Compare home office location with sign-in patterns for mscott@paper.com. + +--- ### UserSigninBaseline Establish a baseline of a user's typical sign-in activity over the past 30 days, including their most frequently used IP address, location, device, application, and client application. Detect deviations to identify potential security risks. -Example Prompt: Establish sign-in baseline for mscott@paper.com over the last 30 days. +**Example Prompt**: Establish sign-in baseline for mscott@paper.com over the last 30 days. + +--- ### FailedUserSignInSpecificDay -Identify failed sign-in attempts for a specific user over a defined lookback period. Provides insights into failed attempts, including timestamps, IP addresses, and failure reasons. +Identify failed sign-in attempts for a specific user over a defined lookback period. Provides insights into failed attempts, including timestamps, IP addresses, locations, and failure reasons. -Example Prompt: Identify failed sign-in attempts for mscott@paper.com in the last 7 days. +**Example Prompt**: Identify failed sign-in attempts for mscott@paper.com in the last 7 days. -### UserRiskDetections -Analyze and prioritize medium and high-risk detections for a specific user over the past 7 days. Provides insights into risk events, such as risk types, details, and locations. +--- -Example Prompt: Analyze medium and high-risk events for mscott@paper.com in the last 7 days. +### EnhancedUserRiskAssessment +Combine user risk events, recent sign-ins, high-risk user indicators, and security alerts to deliver detailed context around risky user activities for a specific user. Supports threat detection, incident prioritization, and remediation planning. -### HighRiskUserDetection -Identify high-risk users with recent activity by correlating risk events and user status. Focus on unresolved risks and prioritize remediation steps. +**Example Prompt**: Fetch enhanced user risk details for mscott@paper.com. -Example Prompt: Identify high-risk user activity for mscott@paper.com in the past week. +--- ### ImpossibleTravel -Detect impossible travel by analyzing login patterns and identifying logins from geographically distant locations within an unrealistically short time frame. +Detect impossible travel by analyzing login patterns and identifying logins from geographically distant locations within an unrealistically short time frame. Supports detection of potential credential misuse. -Example Prompt: Detect impossible travel events for mscott@paper.com in the past 7 days. +**Example Prompt**: Detect impossible travel events for mscott@paper.com in the past 7 days. + +--- ### SuspiciousMailboxActivities Investigate recent mailbox activities to detect potential security threats, such as: -- Newly created or modified rules that redirect or filter emails suspiciously. -- Mailbox permissions changes that might indicate unauthorized access. +- Newly created or modified inbox rules that redirect or filter emails suspiciously. +- Mailbox permission changes that might indicate unauthorized access. Focus on identifying anomalies that could lead to exfiltration or business email compromise (BEC) attempts. -Example Prompt: Investigate suspicious mailbox activities for mscott@test.com in the last 30 days. +**Example Prompt**: Investigate suspicious mailbox activities for mscott@test.com in the last 30 days. + +--- + +### DailyEntraIDRiskDetectionReview +Summarize and prioritize Entra ID risk detections from the past day, highlighting interesting or critical alerts that may require SOC analyst review. + +**Example Prompt**: Summarize Entra ID risk detections from the past 24 hours. + +--- + +### SuccessfulLoginsFromIP +Find all successful user sign-ins originating from a specified IP address. Returns detailed event information including timestamp, user, location, device, and application. + +**Example Prompt**: List all successful logins from 1.2.3.4. + +--- + +### SuccessfulLoginCountFromIP +Return the total number of successful sign-in attempts from a given IP address. Useful for quickly determining if a specific IP has ever been successfully used. + +**Example Prompt**: Count successful logins from 1.2.3.4. +--- diff --git a/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/UserEnrichment.yaml b/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/UserEnrichment.yaml index f19c17fa..a941e497 100644 --- a/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/UserEnrichment.yaml +++ b/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/UserEnrichment.yaml @@ -30,6 +30,14 @@ SkillGroups: - Name: upn Description: User principal name, e.g., mscott@woodgrove.ms Required: true + - Name: exclude_ip + Description: Any IP address that should be excluded from the results. + Default: "" + Required: false + - Name: unusualsignin_context + Description: Additional context to tune the output appropriately + Default: "" + Required: false Settings: Target: Defender Template: |- @@ -37,11 +45,13 @@ SkillGroups: // It analyzes login patterns, focusing on anomalies such as high failure rates, sign-ins from unfamiliar IPs or devices, or abnormal client applications. // Deviations from the usual patterns may indicate suspicious activity, prompting security actions like MFA review or password reset. // The output includes IP addresses, device IDs, and user agents to assist in identifying unusual behavior. + // The additional requests from the user are {{unusualsignin_context}} let UPN = "{{upn}}"; // Replace with the specific users upn let TimeFrame = ago(30d); // Retrieve and summarize the user's login activity, focusing on successful vs failed attempts and identifying unusual patterns. SigninLogs | where TimeGenerated > TimeFrame + | where IPAddress != "{{exclude_ip}}" // exclude_ip from query | where UserPrincipalName =~ UPN | summarize FirstSeen = min(TimeGenerated), LastObserved = max(TimeGenerated), SuccessfullCount = count(ResultType = 0), FailureCount = count(ResultType != 0) by UserPrincipalName, IPAddress, Location, tostring(DeviceDetail.deviceId), UserAgent, ClientAppUsed, AppDisplayName @@ -222,30 +232,42 @@ SkillGroups: - Name: upn Description: User principle name. i.e., mscott@woodgrove.ms Required: true + - Name: exclude_ip + Description: Any IP address that should be excluded from the results. + Default: "" + Required: false + - Name: usersignin_context + Description: Additional context to tune the output appropriately + Default: "" + Required: false Settings: Target: Defender Template: |- + // The additional context from the user is {{usersignin_context}} // This query establishes a baseline for a user's sign-in activity over the past 30 days by identifying the most frequently used IP address, location, device, application, and client application. // It helps detect deviations that may indicate suspicious behavior, such as sign-ins from unfamiliar locations, new devices, or different apps. // Anomalies can point to account compromise or unauthorized access, triggering actions like MFA enforcement or security reviews. // The query outputs the user's typical behavior patterns and highlights any deviations. // Provide a summary of the users baseline and analyze any deviations, recommending actions like enforcing MFA, resetting credentials, or initiating further security reviews if abnormal patterns are detected. + // The additional context from the user is {{usersignin_context}} let UPN = "{{upn}}"; // Replace with the specific users email let TimeFrame = ago(30d); // Identify the most frequently used IP address for the user. let MostCommonIP = toscalar( - SigninLogs - | where TimeGenerated > TimeFrame - | where UserPrincipalName =~ UPN - | summarize IPCount = count() by IPAddress - | top 1 by IPCount desc - | project IPAddress + SigninLogs + | where TimeGenerated > TimeFrame + | where UserPrincipalName =~ UPN + | where IPAddress != "{{exclude_ip}}" // exclude_ip from query + | summarize IPCount = count() by IPAddress + | top 1 by IPCount desc + | project IPAddress ); // Identify the most frequently used location (e.g., city, country) for the user. let MostCommonLocation = toscalar( SigninLogs | where TimeGenerated > TimeFrame | where UserPrincipalName =~ UPN + | where IPAddress != "{{exclude_ip}}" // exclude_ip from query | summarize LocationCount = count() by Location | top 1 by LocationCount desc | project Location @@ -255,6 +277,7 @@ SkillGroups: SigninLogs | where TimeGenerated > TimeFrame | where UserPrincipalName =~ UPN + | where IPAddress != "{{exclude_ip}}" // exclude_ip from query | summarize DeviceCount = count() by tostring(DeviceDetail.deviceId) | top 1 by DeviceCount desc | project tostring(DeviceDetail_deviceId) @@ -263,6 +286,7 @@ SkillGroups: let MostCommonApp = toscalar( SigninLogs | where TimeGenerated > TimeFrame + | where IPAddress != "{{exclude_ip}}" // exclude_ip from query | where UserPrincipalName =~ UPN | summarize AppCount = count() by AppDisplayName | top 1 by AppCount desc @@ -272,6 +296,7 @@ SkillGroups: let MostCommonClientApp = toscalar( SigninLogs | where TimeGenerated > TimeFrame + | where IPAddress != "{{exclude_ip}}" // exclude_ip from query | where UserPrincipalName =~ UPN | summarize ClientAppCount = count() by ClientAppUsed | top 1 by ClientAppCount desc @@ -314,92 +339,123 @@ SkillGroups: - Name: lookback_period Description: The time range to look back. i.e., 1d, 7d, 30d (default is 1d) Required: false + - Name: exclude_ip + Description: Any IP address that should be excluded from the results. + Default: "" + Required: false Settings: Target: Defender Template: |- SigninLogs | where TimeGenerated >= ago(1d) // Specify the lookback_period, default 1d + | where IPAddress != "{{exclude_ip}}" // exclude_ip from query | where ResultType != 0 // Filtering for failed sign-ins; 0 indicates success | where UserPrincipalName == "{{upn}}" | summarize FailedAttempts = count(), UniqueIPs = dcount(IPAddress), Locations = dcount(Location), ResultDescriptions = makeset(ResultDescription) by bin(TimeGenerated, 1d) | project TimeGenerated, FailedAttempts, UniqueIPs, Locations, ResultDescriptions | order by TimeGenerated desc - - Name: UserRiskDetections - DisplayName: Entra User Risk Detections - Description: "Analyze risk events for a specific user by joining user risk detections with security alerts to provide enriched insights. This query identifies and correlates risk activities with security alerts, highlighting types, details, timing, and severity. It supports detailed investigations by surfacing connected risks and alerts for prioritization and actionable response." - DescriptionForModel: |- - Performs a KQL query on the AADUserRiskEvents table, filtering for a specified user's principal name (UserPrincipalName) and joining with the SecurityAlert table to correlate user risk events with associated security alerts. Key features include: - - - **Risk and Alert Correlation**: - - Links risk events (Id) with related security alerts (VendorOriginalId) for enhanced context. - - Provides detailed information on RiskLevel, RiskDetail, and RiskState alongside alert-specific data like AlertName, AlertSeverity, and CompromisedEntity. - - - **User-Focused Insights**: - - Focuses on a single user's activity, helping analysts investigate incidents involving that user. - - Highlights the date and time of events (ActivityDateTime and StartTime) for timeline reconstruction. - - - **Enhanced Investigation**: - - Surfaces patterns in risk and alert data, aiding in anomaly detection and prioritization. - - Supports actions like user-specific remediations and incident containment. - - This skill enables efficient monitoring and investigation of user-specific risks and alerts, helping prioritize and mitigate potential threats effectively. - + - Name: EnhancedUserRiskAssessment + DisplayName: Enhanced User Risk Assessment + Description: > + Combines user risk events, recent sign-ins, high-risk user indicators, and security alerts to deliver detailed context around risky user activities for a specific user. Useful for threat detection, incident prioritization, and remediation planning. Inputs: - Name: upn - Description: The UPN to search for. For example, mscott@dunder.com + Description: User principal name. i.e., mscott@woodgrove.ms Required: true + ExamplePrompt: + - "Get enhanced user risk details" + - "Fetch detailed user risk assessment" + - "Run comprehensive user risk check" Settings: Target: Defender Template: |- - AADUserRiskEvents - | where TimeGenerated > ago(7d) - | where UserPrincipalName == "{{upn}}" - | join kind=inner (SecurityAlert) on $left.Id == $right.VendorOriginalId - - - Name: HighRiskUserDetection - DisplayName: High-Risk User Detection - Description: Identify high-risk users with recent activity by correlating risk events and user status. Highlights unresolved risks, focusing on key details such as risk type, IP address, and recent activity for prioritizing remediation steps. - DescriptionForModel: |- - Performs a KQL query to analyze high-risk users by joining the `AADRiskyUsers` and `AADUserRiskEvents` tables. Key features include: - - - **Risk Correlation**: - - Retrieves recent high-risk events (`DetectedDateTime` within the last 7 days) for a specified user. - - Joins data with high-risk user statuses from `AADRiskyUsers` to focus on unresolved risks. - - - **Detailed Insights**: - - Columns include: - - `RiskEventType`: Type of risk detected. - - `RiskDetail`: Specific details about the risk. - - `IpAddress`: Location associated with risky activity. - - `DetectedDateTime`: Timestamp of the detection. - - `RiskLastUpdatedDateTime`: Last update time for the user's risk status. - - - **Prioritization**: - - Orders results by `RiskLastUpdatedDateTime` to highlight users with the most recent unresolved high-risk activity. - - Ensures focus on persistent risks and users requiring immediate action. - - - **Goal**: - - Enables security analysts to identify patterns of risky behavior, such as multiple high-risk sign-ins or repeated attempts from unusual locations. - - Supports prioritized investigations and swift remediation actions, such as enforcing MFA, reviewing user activity, or disabling accounts. + let TimeFrame = ago(7d); + let upn = "{{upn}}"; + let UserRiskEvents = AADUserRiskEvents + | where TimeGenerated > TimeFrame and RiskLevel != "none" and RiskState != "remediated" and UserPrincipalName == upn + | project + UserPrincipalName, + RiskLevel, + RiskEventType, + RiskDetail, + Activity, + ActivityDateTime, + DetectedDateTime, + IpAddress, + Location, + RiskState, + Id, + CorrelationId; + let RecentSignIns = SigninLogs + | where TimeGenerated > TimeFrame + | project + SignInId = Id, + UserPrincipalName, + SignInTime = TimeGenerated, + ResultType, + ResultDescription, + AppDisplayName, + ClientAppUsed, + SignInIPAddress = IPAddress, + SignInLocationDetails = LocationDetails, + DeviceDetail, + RiskLevelDuringSignIn, + ConditionalAccessStatus, + AuthenticationMethodsUsed, + CorrelationId; + let HighRiskUsers = AADRiskyUsers + | where TimeGenerated > TimeFrame and RiskLevel in ("high", "medium") + | project + UserPrincipalName, + UserRiskLevel = RiskLevel, + RiskState, + RiskLastUpdatedDateTime; + let Alerts = SecurityAlert + | where TimeGenerated > TimeFrame + | project + AlertName, + AlertSeverity, + CompromisedEntity, + AlertStartTime = StartTime, + AlertEndTime = EndTime, + VendorOriginalId; + UserRiskEvents + | join kind=leftouter RecentSignIns on CorrelationId + | join kind=leftouter HighRiskUsers on UserPrincipalName + | join kind=leftouter Alerts on $left.Id == $right.VendorOriginalId + | project + UserPrincipalName, + RiskLevel, + RiskEventType, + RiskDetail, + RiskState, + Activity, + ActivityDateTime, + DetectedDateTime, + IpAddress, + Location, + SignInTime, + ResultType, + ResultDescription, + AppDisplayName, + ClientAppUsed, + SignInIPAddress, + SignInLocationDetails, + DeviceDetail, + RiskLevelDuringSignIn, + ConditionalAccessStatus, + AuthenticationMethodsUsed, + UserRiskLevel, + RiskLastUpdatedDateTime, + AlertName, + AlertSeverity, + CompromisedEntity, + AlertStartTime, + AlertEndTime + | order by RiskLevel desc, DetectedDateTime desc + | take 10 - This skill helps analysts detect and respond to unresolved risks in high-risk users effectively. - Inputs: - - Name: upn - Description: The UPN to search for. For example, mscott@dunder.com - Required: true - Settings: - Target: Defender - Template: |- - let recentEvents = AADUserRiskEvents - | where DetectedDateTime > ago(7d) - | where RiskLevel == "high" - | where UserPrincipalName == "{{upn}}" - | project UserPrincipalName, Activity, DetectedDateTime, RiskEventType, RiskDetail, IpAddress; - AADRiskyUsers - | where RiskLevel == "high" - | join kind=inner (recentEvents) on UserPrincipalName - | order by RiskLastUpdatedDateTime desc - Name: ImpossibleTravel DisplayName: Impossible Travel @@ -432,6 +488,10 @@ SkillGroups: - Name: upn Description: The UPN (User Principal Name) to search for. Required: true + - Name: exclude_ip + Description: Any IP address that should be excluded from the results. + Default: "" + Required: false Settings: Target: Defender Template: |- @@ -450,6 +510,7 @@ SkillGroups: SigninLogs | where ResultType == 0 // Filter for successful logins | where TimeGenerated > ago(7d) + | where IPAddress != "{{exclude_ip}}" // exclude_ip from query | extend LocationDetails = parse_json(LocationDetails) // Parse location details | extend Latitude = tostring(LocationDetails.geoCoordinates.latitude), Longitude = tostring(LocationDetails.geoCoordinates.longitude), @@ -504,4 +565,95 @@ SkillGroups: | extend packed = pack(Name, Value) | where Parameters has_any("DeleteMessage") or (Parameters has_any("MoveToFolder") and Parameters has_any("Conversation History")) | summarize PackedInfo = make_bag(packed), ActionType=any(ActionType) by UPN, AccountObjectId, IPAddress, ISP, ReportId, TimeGenerated - | evaluate bag_unpack(PackedInfo) \ No newline at end of file + | evaluate bag_unpack(PackedInfo) + + - Name: DailyEntraIDRiskDetectionReview + DisplayName: Daily Entra ID Risk Detection Review + DescriptionForModel: |- + Performs a KQL query on the `AADUserRiskEvents` table to summarize Entra ID risk detections from the previous day. Key features include: + - **Risk Detection Summary**: Groups risk events by `RiskEventType`, `RiskLevel`, `Activity`, `RiskDetail`, and `DetectionTimingType`. + - **Prioritization**: Assigns priority based on the severity and frequency of events: + - High priority for infrequent, high-risk detections. + - Medium priority for moderate-risk detections. + - Low priority for common or lower-severity detections. + - **Detailed Insights**: + - Total number of detections. + - Number of impacted users. + - Time range of detected risks (`FirstDetected`, `LastDetected`). + - **Actionable Recommendations**: Highlights detections of interest that may require further investigation or review, assisting analysts in identifying overlooked or significant risk events. + This skill aids security teams in efficiently reviewing and prioritizing Entra ID risk detections daily, reducing alert fatigue and ensuring critical threats are investigated. + Description: Summarize and prioritize Entra ID risk detections from the past day, highlighting interesting or critical alerts that may require SOC analyst review. Results include impactedUPNs if further analysis is required. + Settings: + Target: Defender + Template: |- + // Summarizes and prioritizes Entra ID risk detections from the previous day. + let TimeFrame = ago(1d); + AADUserRiskEvents + | where TimeGenerated >= TimeFrame + | summarize + TotalDetections = count(), + ImpactedUsers = dcount(UserPrincipalName), + ImpactedUPNs = make_set(UserPrincipalName, 50), // Include impacted users explicitly + FirstDetected = min(DetectedDateTime), + LastDetected = max(DetectedDateTime) + by RiskEventType, RiskLevel, Activity, RiskDetail, DetectionTimingType, Source + | extend RiskPriority = case( + RiskLevel == "high" and TotalDetections < 10, "High", + RiskLevel == "medium" and TotalDetections < 5, "Medium", + "Low") + | order by RiskPriority asc, TotalDetections asc + + - Name: SuccessfulLoginsFromIP + DisplayName: Successful Logins from an IP + DescriptionForModel: | + Perform a KQL query to identify all successful sign-in events from a specified IP address. Provides full event details including timestamp, user, location, device, and app information. + Description: Find all successful user sign-ins originating from a specified IP address. + Inputs: + - Name: ip_address + Description: IP address to search for. Example 1.2.3.4 + Required: true + - Name: additional_context + Description: Any additional context for security copilot + Required: false + Settings: + Target: Defender + Template: |- + //Query to return known successful connnections from a particular IP address. + //Useful for troubleshooting false vs true positive alerts + //Here is any additional user context: {{additional_context}} + let TargetIP = "{{ip_address}}"; // Target IP address for investigation + SigninLogs + | where IPAddress == TargetIP // Filter events matching the specified IP + | where ResultType == 0 // Keep only successful sign-in events (ResultType = 0) + | project + TimeGenerated, // Time of the sign-in + UserPrincipalName, // User who signed in + IPAddress, // IP address used + Location, // Geolocation (if available) + DeviceDetail, // Device information + AppDisplayName, // Application accessed + ClientAppUsed // Client app used (browser, native app, etc.) + | order by TimeGenerated desc // Show newest events first + + - Name: SuccessfulLoginCountFromIP + DisplayName: Successful Login Count from an IP + DescriptionForModel: | + Perform a KQL query to check if there have been any successful sign-ins from a specified IP address. Returns the number of successful events. + Description: Return the total number of successful sign-in attempts from a given IP address. + Inputs: + - Name: ip_address + Description: IP address to search for. Example 1.2.3.4 + Required: true + - Name: additional_context + Description: Any additional context for security copilot + Required: false + Settings: + Target: Defender + Template: |- + //Query to count how many successful logins from a particular IP address to give additional perspective to help with troubleshooting true vs fales positive login events. + //Here is any additional user context: {{additional_context}} + let TargetIP = "{{ip_address}}"; // Target IP address for counting successful logins + SigninLogs + | where IPAddress == TargetIP // Filter events matching the specified IP + | where ResultType == 0 // Keep only successful sign-in events + | summarize SuccessCount = count() // Return the number of successful sign-ins diff --git a/Plugins/Community Based Plugins/Microsoft Graph API plugins/Devices Onboarded into MDE/MDEOnboardedDevices-OpenAPISpec.yaml b/Plugins/Community Based Plugins/Microsoft Graph API plugins/Devices Onboarded into MDE/MDEOnboardedDevices-OpenAPISpec.yaml new file mode 100644 index 00000000..ed43098f --- /dev/null +++ b/Plugins/Community Based Plugins/Microsoft Graph API plugins/Devices Onboarded into MDE/MDEOnboardedDevices-OpenAPISpec.yaml @@ -0,0 +1,101 @@ +openapi: 3.0.0 + +# This section provides metadata about the plugin, including its title, description, and version. +info: + title: Retrieve devices onboarded into Microsoft Defender for Endpoint (MDE) + description: Plugin to retrieve a list of devices onboarded into Microsoft Defender for Endpoint (MDE) using Microsoft Graph API. + version: 1.0.0 + +# Defines the base URL for the Microsoft Graph API, which is used to make requests. +servers: + - url: https://graph.microsoft.com/v1.0 + description: Microsoft Graph API url + +# Specifies the available API endpoints and their operations, in this case, retrieving a list of devices. +paths: + /devices: + get: + summary: Get devices + description: Retrieve a list of devices onboarded into Microsoft Defender for Endpoint + responses: + '200': + description: List of devices + content: + application/json: + schema: + type: object + properties: + value: + type: array + items: + type: object + properties: + id: + type: string + description: Device ID. + deviceName: + type: string + description: Name of the device. + osPlatform: + type: string + description: Operating system of the device. + complianceState: + type: string + description: Compliance state of the device. + lastSeenDateTime: + type: string + format: date-time + description: Last seen date and time. + onboardedDate: + type: string + format: date + description: Date the device was onboarded. + manufacturer: + type: string + description: Manufacturer of the device. + model: + type: string + description: Model of the device. + serialNumber: + type: string + description: Serial number of the device. + macAddress: + type: string + description: MAC address of the device. + ipAddress: + type: string + description: IP address of the device. + isAzureAdJoined: + type: boolean + description: Indicates if the device is Azure AD joined. + isCompliant: + type: boolean + description: Indicates if the device is compliant. + riskLevel: + type: string + description: Risk level assigned to the device. + '401': + description: Unauthorized error. + '403': + description: Forbidden access. + '500': + description: Internal server error. + +# Defines security mechanisms, such as authentication methods, that the API uses. +components: + securitySchemes: + bearerAuth: + type: http + scheme: bearer + +security: + - bearerAuth: [] + +# Provides example natural language queries that users can use to interact with the API via Security Copilot and retrieve specific device information. +x-example-prompts: + - Retrieve devices onboarded to MDE that have a compliance state of 'Non-compliant' + - Get the list of non-compliant devices from devices onboarded to MDE + - retrieve a list of devices onboarded onto MDE that have a Risk Level of "High" + - Retrieve devices from MDE that registered less than 14 days ago from today + - Retrieve a list of MDE endpoints that were last seen less than 1 day ago + - Get devices from MDE that have the IoS operating system diff --git a/Plugins/Community Based Plugins/Microsoft Graph API plugins/Devices Onboarded into MDE/MDEOnboardedDevicesPlugin.yaml b/Plugins/Community Based Plugins/Microsoft Graph API plugins/Devices Onboarded into MDE/MDEOnboardedDevicesPlugin.yaml new file mode 100644 index 00000000..36e2dc79 --- /dev/null +++ b/Plugins/Community Based Plugins/Microsoft Graph API plugins/Devices Onboarded into MDE/MDEOnboardedDevicesPlugin.yaml @@ -0,0 +1,13 @@ +Descriptor: + Name: RetrieveMDEOnboardedDevices + DisplayName: Retrieve details for devices onboarded onto Microsoft Defender for Endpoint (MDE) + Description: Retrieve details for devices onboarded onto Microsoft Defender for Endpoint (MDE) + DescriptionForModel: Retrieves detailed information about devices onboarded into Microsoft Defender for Endpoint (MDE),including device name, operating system, compliance state, risk level, last seen timestamp,onboarded date, manufacturer, model, serial number, IP address, MAC address, and Azure AD join status.This API enables security analysts to assess device security posture, identify non-compliant or high-risk devices,and correlate endpoint data with ongoing security incidents. Results provide actionable insights for threat detection, response, and asset visibility within enterprise environments, supporting Security Copilot in enhancing situational awareness and proactive threat mitigation. + + Authorization: + Type: AADDelegated + EntraScopes: https://graph.microsoft.com/.default +SkillGroups: + - Format: API + Settings: + OpenApiSpecUrl: https://gist.githubusercontent.com/inwafula/88a0e5b5f3a2bdc1840771b97937d8f0/raw/ff2211080321d6e74881ab960d9c2e0f060f3e28/MDEOnboardedDevices-OpenAPISpec.yaml \ No newline at end of file diff --git a/Plugins/Community Based Plugins/Microsoft Graph API plugins/Devices Onboarded into MDE/Readme.md b/Plugins/Community Based Plugins/Microsoft Graph API plugins/Devices Onboarded into MDE/Readme.md new file mode 100644 index 00000000..b0fc1399 --- /dev/null +++ b/Plugins/Community Based Plugins/Microsoft Graph API plugins/Devices Onboarded into MDE/Readme.md @@ -0,0 +1,36 @@ +# Plugin to retrieve Devices Onboarded into Microsoft Defender for Endpoint (MDE) Plugin + +## Overview +This plugin enables users to retrieve a list of devices onboarded into **Microsoft Defender for Endpoint (MDE)** using the **Microsoft Graph API**. It provides valuable insights into device details, compliance status, risk levels, and more, facilitating security monitoring and incident response. + +## Features +- Retrieves device details including **device name, OS, manufacturer, model, serial number, and MAC address**. +- Identifies **compliance state** and **risk level** of onboarded devices. +- Fetches devices based on **last seen date** +- Supports filtering for devices based on **Azure AD join status, compliance status, and OS platform**. + +## API Endpoint +**Base URL:** `https://graph.microsoft.com/v1.0` + + +### Setup instructions +#### Upload the Plugin manifest + +1. Obtain the manifest [MDEOnboardedDevicesPlugin.yaml](https://github.com/Azure/Copilot-For-Security/blob/main/Plugins/Community%20Based%20Plugins/Microsoft%20Graph%20API%20/Devices%20Onboarded%20into%20MDE/MDEOnboardedDevicesPlugin.yaml) and the OpenAPI Specification [MDEOnboardedDevices-OpenAPISpec.yaml](https://github.com/Azure/Copilot-For-Security/blob/main/Plugins/Community%20Based%20Plugins/Microsoft%20Graph%20API%20/Devices%20Onboarded%20into%20MDE/MDEOnboardedDevices-OpenAPISpec.yaml) files from this directory. +2. Download the Spec file and move it to your preferred location, ensuring it is reachable by Security Copilot + + +3. [Upload the custom plugin](https://learn.microsoft.com/en-us/security-copilot/manage-plugins?tabs=securitycopilotplugin#add-custom-plugins) + +## Example Natural Language Queries +Use these sample prompts to retrieve relevant data via **Security Copilot**: +- "Retrieve devices onboarded to MDE that have a compliance state of 'Non-compliant'" +- "Get the list of non-compliant devices from devices onboarded to MDE" +- "Retrieve a list of devices onboarded onto MDE that have a Risk Level of 'High'" +- "Retrieve devices from MDE that registered less than 14 days ago from today" +- "Retrieve a list of MDE endpoints that were last seen less than 1 day ago" +- "Get devices from MDE that have the iOS operating system" + + +--- +For more details, visit the **[Microsoft Graph API documentation](https://learn.microsoft.com/en-us/graph/)**. diff --git a/Plugins/Community Based Plugins/Microsoft Security Experts/Microsoft Defender Experts Plugin/Dexplugin.yaml b/Plugins/Community Based Plugins/Microsoft Security Experts/Microsoft Defender Experts Plugin/Dexplugin.yaml index 05b597a0..de1d5613 100644 --- a/Plugins/Community Based Plugins/Microsoft Security Experts/Microsoft Defender Experts Plugin/Dexplugin.yaml +++ b/Plugins/Community Based Plugins/Microsoft Security Experts/Microsoft Defender Experts Plugin/Dexplugin.yaml @@ -1,3 +1,4 @@ +--- Descriptor: Name: MicrosoftDefenderExpertsPlugin DisplayName: Microsoft Defender Experts Security Copilot Plugin @@ -7,6 +8,7 @@ Descriptor: SkillGroups: - Format: KQL Skills: + - Name: DEX-SuspiciousUseOfAzureHound DisplayName: Suspicious Use of AzureHound Description: Detects potential suspicious usage of AzureHound on devices. @@ -21,7 +23,6 @@ SkillGroups: | where TimeGenerated > ago({{days}}d) | where FileName has "azurehound.exe" | where ProcessCommandLine has "azurehound" and ProcessCommandLine has_any ("az-rm", "az-ad", "list", "Invoke-azurehound") - - Name: DEX-ReconnaissanceActivityUsingNetworkLogs DisplayName: Reconnaissance Activity Using Network Logs Description: Detects reconnaissance activity leveraging network logs and specific URLs. @@ -42,7 +43,7 @@ SkillGroups: - Name: DEX-RoadreconAndRoadtxActivity DisplayName: ROADrecon and ROADtx Activity - Description: Detects known filenames and command line activity associated with ROADrecon and ROADtx tools. + Description: Detects known filenames and command line activity associated with ROADrecon and roadtx tools. Inputs: - Name: days Description: Look back x amount of days, for example 10, 20, 30. @@ -269,8 +270,8 @@ SkillGroups: Template: |- DeviceFileEvents | where TimeGenerated > ago({{days}}d) - | where InitiatingProcessFileName =~ 'ping.exe' and InitiatingProcessCommandLine == 'ping.exe -t 127.0.0.1' - and InitiatingProcessParentFileName in~('msra.exe', 'mobsync.exe') and FolderPath endswith ".eml" + | where InitiatingProcessFileName =~ "ping.exe" and InitiatingProcessCommandLine == "ping.exe -t 127.0.0.1" + and InitiatingProcessParentFileName in~ ("msra.exe", "mobsync.exe") and FolderPath endswith ".eml" - Name: DEX-QakbotReconnaissanceActivities DisplayName: Qakbot Reconnaissance Activities Detection @@ -286,8 +287,8 @@ SkillGroups: | where TimeGenerated > ago({{days}}d) | where InitiatingProcessFileName == InitiatingProcessCommandLine | where ProcessCommandLine has_any ( - "whoami /all","cmd /c set","arp -a","ipconfig /all","net view /all","nslookup -querytype=ALL -timeout=10", - "net share","route print","netstat -nao","net localgroup") + "whoami /all", "cmd /c set", "arp -a", "ipconfig /all", "net view /all", "nslookup -querytype=ALL -timeout=10", + "net share", "route print", "netstat -nao", "net localgroup") | summarize dcount(FileName), make_set(ProcessCommandLine) by DeviceId, bin(Timestamp, 1d), InitiatingProcessFileName, InitiatingProcessCommandLine | where dcount_FileName >= 8 @@ -333,13 +334,13 @@ SkillGroups: Template: |- DeviceProcessEvents | where TimeGenerated > ago({{days}}d) - | where InitiatingProcessFileName =~ 'wmiprvse.exe' - | where FileName =~ 'powershell.exe' - and (ProcessCommandLine hasprefix '-e' or ProcessCommandLine contains 'frombase64') - | where ProcessCommandLine matches regex '[A-Za-z0-9+/]{50,}[=]{0,2}' - | where ProcessCommandLine !has 'Windows\\CCM\\' + | where InitiatingProcessFileName =~ "wmiprvse.exe" + | where FileName =~ "powershell.exe" + and (ProcessCommandLine hasprefix "-e" or ProcessCommandLine contains "frombase64") + | where ProcessCommandLine matches regex "[A-Za-z0-9+/]{50,}[=]{0,2}" + | where ProcessCommandLine !has "Windows\\CCM\\" | project DeviceId, Timestamp, InitiatingProcessId, InitiatingProcessFileName, ProcessId, FileName, ProcessCommandLine - + - Name: DEX-AutomatedEmailNotificationsAndSuspiciousSignInActivity DisplayName: Automated Email Notifications and Suspicious Sign-In Activity Detection Description: Correlates automated email notifications with suspicious sign-in activity to identify potential compromises. @@ -439,6 +440,202 @@ SkillGroups: | summarize dcount(UserSharedWith) by FileShared, AccountObjectId | where dcount_UserSharedWith >= 20 + - Name: DEX-TeamsActivityAfterSuspiciousRMM + DisplayName: Teams Activity After Suspicious RMM + Description: Detects Teams activity followed by suspicious Remote Monitoring and Management + Inputs: + - Name: days + Description: Look back x amount of days, for example 10, 20, 30. + Required: true + Settings: + Target: Defender + Template: |- + let interestingUsers = DeviceProcessEvents + | where Timestamp > ago(1h) + | where isnotempty(InitiatingProcessAccountObjectId) + | where FileName has_any ("quickassist.exe", "anydesk.exe", "teamviewer_service.exe") // Multiple RMM tools can be abused here + | project InitiatingProcessAccountUpn; + CloudAppEvents + | where Timestamp > ago(1d) + | where Application == "Microsoft Teams" + | where ActionType == "ChatCreated" + | where isempty(AccountObjectId) + | where RawEventData.ParticipantInfo.HasForeignTenantUsers == true + | where RawEventData.CommunicationType == "OneOnOne" + | where RawEventData.ParticipantInfo.HasGuestUsers == false + | where RawEventData.ParticipantInfo.HasOtherGuestUsers == false + | where AccountId has "@" + | extend TargetUPN = tolower(tostring(RawEventData.Members[1].UPN)) + | where TargetUPN in (interestingUsers) + | extend VictimTenant = tostring(RawEventData.OrganizationId) + | extend AttackerTenant = RawEventData.Members[0].OrganizationId + | extend AttackerUPN = RawEventData.Members[0].UPN + | extend AttackerName = RawEventData.Members[0].DisplayName + + - Name: DEX-TeamsPhishingviaUrlClickEvents + DisplayName: Teams Phishing via Url Click Events + Description: Correlate suspicious device alerts with URL click events in MS Teams. + Inputs: + - Name: days + Description: Look back x amount of days, for example 10, 20, 30. + Required: true + Settings: + Target: Defender + Template: |- + let alertedDevices = AlertEvidence + | where Timestamp > ago(1h) + | where isnotempty(DeviceId) + | distinct DeviceId; + let interestedUsers = DeviceProcessEvents + | where Timestamp > ago(1h) + | where DeviceId in (alertedDevices) + | where isnotempty(InitiatingProcessAccountUpn) + | distinct InitiatingProcessAccountUpn; + UrlClickEvents + | where Timestamp > ago(1d) + | where ActionType == "ClickAllowed" or IsClickedThrough != "0" + | where Workload has "Teams" + | where AccountUpn in (interestedUsers) + + - Name: DEX-TeamsAdversary-in-The-Middle(AiTM) + DisplayName: TeamsAdversary-in-The-Middle(AiTM) + Description: Correlate risky sign-in attempts with Teams phishing from external tenants to identify AiTM. + Inputs: + - Name: days + Description: Look back x amount of days, for example 10, 20, 30. + Required: true + Settings: + Target: Defender + Template: |- + let usersWithRiskySignIn = AADSignInEventsBeta + | where Timestamp > ago(1h) + | where RiskLevelDuringSignIn == 100 + | project AccountUpn; + CloudAppEvents + | where Timestamp > ago(1d) + | where Application == "Microsoft Teams" + | where ActionType == "ChatCreated" + | where isempty(AccountObjectId) + | where RawEventData.ParticipantInfo.HasForeignTenantUsers == true + | where RawEventData.CommunicationType == "OneOnOne" + | where RawEventData.ParticipantInfo.HasGuestUsers == false + | where RawEventData.ParticipantInfo.HasOtherGuestUsers == false + | where AccountId has "@" + | extend TargetUPN = tolower(tostring(RawEventData.Members[1].UPN)) + | where TargetUPN in (usersWithRiskySignIn) + | extend TargetTenant = tostring(RawEventData.OrganizationId) + | extend AttackerTenant = RawEventData.Members[0].OrganizationId + | where TargetTenant != AttackerTenant + | extend AttackerUPN = RawEventData.Members[0].UPN + | extend AttackerName = RawEventData.Members[0].DisplayName + | project-reorder Timestamp, AttackerTenant, AttackerUPN, AttackerName, TargetUPN + + - Name: DEX-SuspiciousFileDownloadsviaLOLBINs + DisplayName: Suspicious File Downloads via LOLBINs + Description: Detects Living-off-the-Land Binaries (LOLBINs) commonly used by adversaries to download additional payloads. + Inputs: + - Name: days + Description: Look back x amount of days, for example 10, 20, 30. + Required: true + Settings: + Target: Defender + Template: |- + DeviceNetworkEvents + | where TimeGenerated > ago({{days}}d) + | where InitiatingProcessFileName in~ ("curl.exe", "powershell.exe", "certutil.exe", "bitsadmin.exe") + | where RemoteIPType == "Public" + | where RemoteUrl endswith ".exe" or RemoteUrl endswith ".dll" or RemoteUrl endswith ".zip" + | project Timestamp, DeviceId, InitiatingProcessFileName, RemoteIP, RemoteUrl + | extend AlertType = "Suspicious file download from unknown IP address" + + - Name: DEX-CSVDEDownloadandADEnumeration + DisplayName: CSVDE Download and AD Enumeration + Description: Detects the download and execution of Csvde, a command-line tool that can be exploited by adversaries to extract sensitive data from AD DS or introduce malicious entries. + Inputs: + - Name: days + Description: Look back x amount of days, for example 10, 20, 30. + Required: true + Settings: + Target: Defender + Template: |- + // Detect curl downloading csvde.exe + let csvde_download = DeviceProcessEvents + | where InitiatingProcessFileName =~ "cmd.exe" + | where ProcessCommandLine has_all ("curl", "-o", "csvde.exe", "http:") + | project DeviceId, Timestamp, CurlCommandLine = ProcessCommandLine, CurlProcessId = ProcessId; + // Detect execution of csvde.exe with specific parameters + let csvde_execution = DeviceProcessEvents + | where FileName =~ "csvde.exe" + | where ProcessCommandLine has_all ("-r", "objectClass=Computer") + | where ProcessCommandLine has_all ("-l", "samAccountName", "description", "info", "operatingSystem") + | where ProcessCommandLine contains "-f" + | project DeviceId, Timestamp, CsvdeCommandLine = ProcessCommandLine, CsvdeProcessId = ProcessId; + // Join the two events and look for them occurring within 5 minutes + csvde_download + | join kind=inner (csvde_execution) on DeviceId + | where Timestamp between (Timestamp1 .. Timestamp1 + 5m) + | extend AlertType = "Potential Active Directory Enumeration", Details = strcat("curl.exe was used to download csvde.exe, which was then executed to enumerate AD computers.") + + - Name: DEX-DataCompressionandExfiltration + DisplayName: Data Compression and Exfiltration + Description: Detects the use of 7-Zip for extracting files with a password and joins that process with network events to identify exfiltration of data. + Inputs: + - Name: days + Description: Look back x amount of days, for example 10, 20, 30. + Required: true + Settings: + Target: Defender + Template: |- + // Detect potential data compression and exfiltration + let compress_exfil = DeviceProcessEvents + | where FileName =~ "7z.exe" + | where ProcessCommandLine contains "x -p" + | project Timestamp, DeviceId, FileName, ProcessCommandLine, ProcessId + | join kind=inner ( + DeviceNetworkEvents + | where InitiatingProcessId != 0 + ) on $left.ProcessId == $right.InitiatingProcessId + | project Timestamp, DeviceId, FileName, ProcessCommandLine, RemoteIP, RemotePort + | extend AlertType = "Potential data compression and exfiltration"; + + - Name: DEX-PersistenceThroughStartupOperations + DisplayName: Persistence Through Startup Operations + Description: Detects adversary payloads used to create persistence via ASEP Registry Keys, Startup Folder, or Scheduled Tasks. + Inputs: + - Name: days + Description: Look back x amount of days, for example 10, 20, 30. + Required: true + Settings: + Target: Defender + Template: |- + // Persistence through Startup operations + let regKeys = pack_array(@"CurrentVersion\Run", + @"CurrentVersion\RunOnce", + @"CurrentVersion\RunOnceEx", + @"Programs\Startup", + @"CurrentVersion\RunServicesOnce", + @"CurrentVersion\RunServices", + @"CurrentVersion\Policies\Explorer\Run", + @"Windows NT\CurrentVersion\Windows", + @"System\CurrentControlSet\Control\Session Manager"); + let startUpOperations = DeviceFileEvents + | where FolderPath has @"Start Menu\Programs\Startup\" + | where ActionType in ("FileCreated", "FileModified", "FileRenamed"); + // Persistence through Registry Tampering + let regOperations = DeviceRegistryEvents + | where hasAlertDevices + | where Timestamp between (startTime .. endTime) + | where DeviceId in (alertedDevices) + | where RegistryKey has_any (regKeys) + | where ActionType in ("SetValue", "CreateKey", "RenameKey"); + // Persistence through Scheduled task creation + let scheduledOperations = DeviceProcessEvents + | where Timestamp between (startTime .. endTime) + | where DeviceId in (alertedDevices) + | where (InitiatingProcessCommandLine has "schtasks" and InitiatingProcessCommandLine has_any ("run", "create", "change")); + union startUpOperations, regOperations, scheduledOperations + | summarize arg_min(Timestamp, *) by DeviceId + - Name: DEX-PossibleAiTMPhishingAttemptAgainstEntraID DisplayName: Possible AiTM Phishing Attempt Against Entra ID Description: | @@ -566,3 +763,157 @@ SkillGroups: | extend UEBARiskScore = IPInvestigationPriority | where UEBARiskScore > {{riskScoreCutoff}} | sort by UEBARiskScore desc + - Name: DEX-BrowserRemoteDebugging + DisplayName: Browser Remote Debugging + Description: Identifies AutoIT scripts launching Chromium-based browsers (e.g., chrome.exe, msedge.exe, brave.exe) in remote debugging mode. + Inputs: + - Name: timeframe + Description: Time frame to search (e.g., "1h", "2h", "4h"). + Required: true + Settings: + Target: Defender + Template: |- + DeviceProcessEvents + | where TimeGenerated > ago({{timeframe}}) + | where InitiatingProcessVersionInfoInternalFileName == "AutoIt3.exe" + | where ProcessCommandLine has "--remote-debugging-port" + | project DeviceId, Timestamp, InitiatingProcessParentFileName, InitiatingProcessFileName, InitiatingProcessFolderPath, InitiatingProcessVersionInfoInternalFileName, InitiatingProcessCommandLine, FileName, ProcessCommandLine + + - Name: DEX-DPAPI-AutoIT + DisplayName: DPAPI Decryption via AutoIT + Description: Identifies DPAPI decryption activity originating from AutoIT scripts. + Inputs: + - Name: timeframe + Description: Time frame to search (e.g., "1h", "2h"). + Required: true + Settings: + Target: Defender + Template: |- + DeviceEvents + | where Timestamp > ago({{timeframe}}) + | where ActionType == "DpapiAccessed" + | where InitiatingProcessVersionInfoInternalFileName == "AutoIt3.exe" + | where (AdditionalFields has_any ("Google Chrome", "Microsoft Edge") and AdditionalFields has_any ("SPCryptUnprotect")) + | extend json = parse_json(AdditionalFields) + | extend dataDesp = tostring(json.DataDescription.PropertyValue) + | extend opType = tostring(json.OperationType.PropertyValue) + | where (dataDesp in~ ("Google Chrome", "Microsoft Edge") and opType =~ "SPCryptUnprotect") + | project Timestamp, ReportId, DeviceId, ActionType, InitiatingProcessParentFileName, InitiatingProcessFileName, InitiatingProcessVersionInfoInternalFileName, InitiatingProcessCommandLine, AdditionalFields, dataDesp, opType + + - Name: DEX-DPAPI-LOLBAS + DisplayName: DPAPI Decryption via LOLBAS Binaries + Description: Identifies DPAPI decryption activity originating from LOLBAS binaries such as RegAsm.exe and MSBuild.exe. + Inputs: + - Name: timeframe + Description: Time frame to search (e.g., "1h", "2h"). + Required: true + Settings: + Target: Defender + Template: |- + DeviceEvents + | where Timestamp > ago({{timeframe}}) + | where ActionType == "DpapiAccessed" + | where InitiatingProcessFileName has_any ("RegAsm.exe", "MSBuild.exe") + | where (AdditionalFields has_any ("Google Chrome", "Microsoft Edge") and AdditionalFields has_any ("SPCryptUnprotect")) + | extend json = parse_json(AdditionalFields) + | extend dataDesp = tostring(json.DataDescription.PropertyValue) + | extend opType = tostring(json.OperationType.PropertyValue) + | where (dataDesp in~ ("Google Chrome", "Microsoft Edge") and opType =~ "SPCryptUnprotect") + | project Timestamp, ReportId, DeviceId, ActionType, InitiatingProcessParentFileName, InitiatingProcessFileName, InitiatingProcessVersionInfoInternalFileName, InitiatingProcessCommandLine, AdditionalFields, dataDesp, opType + + - Name: DEX-SensitiveBrowserAccessAutoIT + DisplayName: Sensitive Browser File Access via AutoIT + Description: Identifies AutoIT scripts (renamed or otherwise) accessing sensitive browser files. + Inputs: + - Name: timeframe + Description: Time frame to search (e.g., "1h", "2h"). + Required: true + Settings: + Target: Defender + Template: |- + let browserDirs = pack_array(@"\Google\Chrome\User Data\", @"\Microsoft\Edge\User Data\", @"\Mozilla\Firefox\Profiles\"); + let browserSensitiveFiles = pack_array("Web Data", "Login Data", "key4.db", "formhistory.sqlite", "cookies.sqlite", "logins.json", "places.sqlite", "cert9.db"); + DeviceEvents + | where Timestamp > ago({{timeframe}}) + | where AdditionalFields has_any ("FileOpenSource") + | where InitiatingProcessVersionInfoInternalFileName == "AutoIt3.exe" + | where (AdditionalFields has_any (browserDirs) or AdditionalFields has_any (browserSensitiveFiles)) + | extend json = parse_json(AdditionalFields) + | extend File_Name = tostring(json.FileName.PropertyValue) + | where (File_Name has_any (browserDirs) and File_Name has_any (browserSensitiveFiles)) + | project Timestamp, ReportId, DeviceId, InitiatingProcessParentFileName, InitiatingProcessFileName, InitiatingProcessVersionInfoInternalFileName, InitiatingProcessCommandLine, File_Name + + - Name: DEX-SensitiveBrowserAccessLOLBAS + DisplayName: Sensitive Browser File Access via LOLBAS + Description: Identifies LOLBAS binaries (e.g., RegAsm.exe, MSBuild.exe) accessing sensitive browser files. + Inputs: + - Name: timeframe + Description: Time frame to search (e.g., "1h", "2h"). + Required: true + Settings: + Target: Defender + Template: |- + let browserDirs = pack_array(@"\Google\Chrome\User Data\", @"\Microsoft\Edge\User Data\", @"\Mozilla\Firefox\Profiles\"); + let browserSensitiveFiles = pack_array("Web Data", "Login Data", "key4.db", "formhistory.sqlite", "cookies.sqlite", "logins.json", "places.sqlite", "cert9.db"); + DeviceEvents + | where Timestamp > ago({{timeframe}}) + | where AdditionalFields has_any ("FileOpenSource") + | where InitiatingProcessFileName has_any ("RegAsm.exe", "MSBuild.exe") + | where (AdditionalFields has_any (browserDirs) or AdditionalFields has_any (browserSensitiveFiles)) + | extend json = parse_json(AdditionalFields) + | extend File_Name = tostring(json.FileName.PropertyValue) + | where (File_Name has_any (browserDirs) and File_Name has_any (browserSensitiveFiles)) + | project Timestamp, ReportId, DeviceId, InitiatingProcessParentFileName, InitiatingProcessFileName, InitiatingProcessVersionInfoInternalFileName, InitiatingProcessCommandLine, File_Name + + - Name: DEX-Node.jsInfostealerActivity + DisplayName: Node.js-based infostealer activity + Description: Detect malicious access to sensitive credentials using Windows DPAPI. + Inputs: + - Name: timeframe + Description: Time frame to search (e.g., "1h", "2h"). + Required: true + Settings: + Target: Defender + Template: |- + DeviceEvents + | where Timestamp > ago({{timeframe}}) + | where isnotempty(DeviceId) + | where ActionType == "DpapiAccessed" + | where InitiatingProcessParentFileName endswith "powershell.exe" + | where InitiatingProcessFileName =~ "node.exe" + | where InitiatingProcessCommandLine has_all ("-r", ".js") and InitiatingProcessCommandLine endswith ".jsc" + | where AdditionalFields has "SPCryptUnprotect" + + - Name: DEX-SuspiciousInlineJavaScriptExecution + DisplayName: Suspicious inline JavaScript execution + Description: Identify suspicious inline JavaScript. + Inputs: + - Name: timeframe + Description: Time frame to search (e.g., "1h", "2h"). + Required: true + Settings: + Target: Defender + Template: |- + DeviceProcessEvents + | where Timestamp > ago({{timeframe}}) + | where isnotempty(DeviceId) + | where ProcessVersionInfoOriginalFileName == 'node.exe' + | where ProcessCommandLine has_all ('http', 'execSync', 'spawn', 'fs', 'path', 'zlib') + + - Name: DEX-SuspiciousJSCFile + DisplayName: Suspicious JSC file + Description: Identify execution of suspicious JSC file. + Inputs: + - Name: timeframe + Description: Time frame to search (e.g., "1h", "2h"). + Required: true + Settings: + Target: Defender + Template: |- + DeviceProcessEvents + | where Timestamp > ago({{timeframe}}) + | where isnotempty(DeviceId) + | where ProcessVersionInfoOriginalFileName == 'node.exe' + | where (ProcessCommandLine has_all (".jsc", ".js") and ProcessCommandLine matches regex @"\\\w*.jsc") + +... diff --git a/Plugins/Community Based Plugins/Microsoft Security Exposure Management/Microsoft Security Exposure Management.yaml b/Plugins/Community Based Plugins/Microsoft Security Exposure Management/Microsoft Security Exposure Management.yaml new file mode 100644 index 00000000..ce9764cb --- /dev/null +++ b/Plugins/Community Based Plugins/Microsoft Security Exposure Management/Microsoft Security Exposure Management.yaml @@ -0,0 +1,99 @@ +Descriptor: + Name: Microsoft Exposure Management + DisplayName: Microsoft Exposure Management + Description: > + This KQL plugin integrates capabilities of Microsoft Security Exposure Management (MSEM) with Security Copilot to show exposed devices. + MSEM provides situational awareness, while Security Copilot (SCP) offers quick and consistent retrieval capabilities. + Together, MSEM and SCP empower SOC Engineers with a natural-language front door into exposure insights and attack paths. + This combination also enables the inclusion of MSEM content and reasoning over this content in Security Copilot prompts, prompt books, and automation scenarios that leverage Security Copilot. + The two skills of the plugin correspond to the following use cases - + 1) Obtain exposure of an asset hosted on a particular cloud platform by your organization. + 2) Obtain exposure of an asset belonging to a specific user. + In each of the above use cases, you can also specify the exposure level for which you want to extract the data. + + SupportedAuthTypes: + - None + +##################################################################################### +# SkillGroups can be a single type (KQL, GPT, API) or contain a mix +##################################################################################### + +SkillGroups: + - Format: KQL + + Skills: + - Name: GetExposedDevicesByCloudPlatform + DisplayName: Get exposed Devices by Cloud Platform and exposure level. + Description: For a specific cloud platform (aka "Azure" or "GCP" or "AWS") or a list of cloud platform of the device separated by commas, semi-colons, or using the word 'or', get a list of devices + based on the exposure level specified by the user (aka "Low" or "Medium" or "High") or a list of exposures separated by commas. + DescriptionForModel: For a specific cloud platform of the device (aka "Azure" or "GCP" or "AWS") or a list of cloud platform of the device separated by commas, get a list of devices based on the exposure level specified by the user (aka "Low" or "Medium" or "High") or a list of exposures separated by commas. The results will be returned as a response to the query containing multiple entries, the user needs to be shown the below details from the response about each device DeviceName, DeviceId, ExposureLevel, CloudPlatforms, OSPlatform, OSArchitecture. + VERY IMPORTANT - Never truncate the list of devices retrieved by launching the KQL query related to this skill. Also if the user wants to search on premises then the value for CloudPlatforms should be blank in the query + + ExamplePrompts: + - Get all the devices exposed on Azure platform + - Get all the devices exposed on AWS platform + - Get all the devices exposed on GCP platform + - Get all the devices with exposure level High on Azure platform + - Get all the devices with exposure level Medium on Azure platform + - Get all the devices with exposure level Low on Azure platform + - Get all the devices with exposure level High on AWS platform + - Get all the devices with exposure level Medium on AWS platform + - Get all the devices with exposure level Low on AWS platform + - Get all the devices with exposure level High on GCP platform + - Get all the devices with exposure level Medium on GCP platform + - Get all the devices with exposure level Low on GCP platform + + Inputs: + - Name: inputExposureLevel + Description: Provide the exposure level you want to consider - e.g. High, Medium, Low (only one level) + Required: true + - Name: inputCloudPlatforms + Description: Provide the cloud platform of the device you want to consider - e.g. Azure or GCP or AWS + Required: false + + Settings: + Target: Defender + Template: |- + let userExposureLevel='{{inputExposureLevel}}'; + let userCloudPlatform='{{inputCloudPlatforms}}'; + DeviceInfo + | where ExposureLevel == userExposureLevel + | where + (CloudPlatforms != '' and (CloudPlatforms contains userCloudPlatform or isempty(userCloudPlatform))) + | project DeviceName, DeviceId, ExposureLevel, CloudPlatforms, OSPlatform, OSArchitecture, TimeGenerated + + - Name: GetExposedDevicesByUser + DisplayName: Get exposed devices by UPN and optionally exposure level + Description: For a specific user UPN (aka "user" or "UPN") or a list of users separated by commas, get a list of devices + based on the exposure level specified by the user (aka "Low" or "Medium" or "High") or a list of exposures separated by commas. + DescriptionForModel: For a specific user UPN (aka "user" or "UPN") or a list of users separated by commas, get a list of devices + based on the exposure level specified by the user (aka "Low" or "Medium" or "High") or a list of exposures separated by commas. + The results will be returned as a response to the query containing multiple entries, the user needs to be shown the below details from the response about each device UPN, DeviceName, DeviceId, ExposureLevel, CloudPlatforms, OSPlatform, OSArchitecture. + VERY IMPORTANT - Never truncate the list of devices retrieved by launching the KQL query related to this skill. Also if the user wants to search on premises then the value for CloudPlatforms should be blank in the query. + + ExamplePrompts: + - Get all the devices with exposure level High belonging to user john.doe@contoso.com + - Get all the devices with exposure level Medium belonging to user john.doe@contoso.com + - Get all the devices with exposure level Low belonging to user john.doe@contoso.com + + Inputs: + - Name: inputExposureLevel + Description: Provide the exposure level you want to consider - e.g. High, Medium, Low + Required: false + - Name: inputUPN + Description: Provide the user primary name (UPN) + Required: true + Settings: + Target: Defender + Template: |- + let userExposureLevel='{{inputExposureLevel}}'; + let userUPN='{{inputUPN}}'; + IntuneDevices + | where UPN == userUPN + | project DeviceName = tolower(DeviceName) + | join kind=inner ( + DeviceInfo + | where ExposureLevel == userExposureLevel or isempty(userExposureLevel) + | project DeviceName = tolower(DeviceName), DeviceId,ExposureLevel, CloudPlatforms, OSPlatform, OSArchitecture, TimeGenerated + ) on DeviceName + diff --git a/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/High Blast Radius User investigation/HighBlastRaidusUserInvestigation.yaml b/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/High Blast Radius User investigation/HighBlastRaidusUserInvestigation.yaml index 85dbb803..80955111 100644 --- a/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/High Blast Radius User investigation/HighBlastRaidusUserInvestigation.yaml +++ b/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/High Blast Radius User investigation/HighBlastRaidusUserInvestigation.yaml @@ -45,8 +45,7 @@ SkillGroups: TimeGenerated, AccountUPN, AccountDisplayName, - AccountCreationTime, - RiskState, + AccountCreationTime, BlastRadius, GroupMembership, AssignedRoles, diff --git a/Technical Workshops/Custom Plugin Calling Webservice/FlaskWebsite/requirements.txt b/Technical Workshops/Custom Plugin Calling Webservice/FlaskWebsite/requirements.txt index 100cabad..225c854c 100644 --- a/Technical Workshops/Custom Plugin Calling Webservice/FlaskWebsite/requirements.txt +++ b/Technical Workshops/Custom Plugin Calling Webservice/FlaskWebsite/requirements.txt @@ -1 +1 @@ -Flask==3.0.2 \ No newline at end of file +Flask==3.1.3 \ No newline at end of file