diff --git a/.gitignore b/.gitignore index 1a02111a..ee0b85ed 100644 --- a/.gitignore +++ b/.gitignore @@ -397,3 +397,4 @@ FodyWeavers.xsd # JetBrains Rider *.sln.iml .DS_Store +.vscode/* 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/Images/Community Plugins/RedactPII/RedactPIIPrompt.png b/Images/Community Plugins/RedactPII/RedactPIIPrompt.png new file mode 100644 index 00000000..e0f580aa Binary files /dev/null and b/Images/Community Plugins/RedactPII/RedactPIIPrompt.png differ diff --git a/Images/Community Plugins/SecurityCopilotLogins/LoginsAndAnomalies.png b/Images/Community Plugins/SecurityCopilotLogins/LoginsAndAnomalies.png new file mode 100644 index 00000000..9f4f2c02 Binary files /dev/null and b/Images/Community Plugins/SecurityCopilotLogins/LoginsAndAnomalies.png differ diff --git a/Images/Community Plugins/SentinelIncidentComments/SentinelIncidentComments.png b/Images/Community Plugins/SentinelIncidentComments/SentinelIncidentComments.png new file mode 100644 index 00000000..e3137be1 Binary files /dev/null and b/Images/Community Plugins/SentinelIncidentComments/SentinelIncidentComments.png differ diff --git a/Images/Community Plugins/WatchlistKQL/TrustedUserWatchlist.png b/Images/Community Plugins/WatchlistKQL/TrustedUserWatchlist.png new file mode 100644 index 00000000..5c63cc27 Binary files /dev/null and b/Images/Community Plugins/WatchlistKQL/TrustedUserWatchlist.png differ diff --git a/Logic Apps/CfS-SendPromptbookResultsByEmail/CfS-SendPromptbookResultsByEmail.json b/Logic Apps/CfS-SendPromptbookResultsByEmail/CfS-SendPromptbookResultsByEmail.json new file mode 100644 index 00000000..92d0cd62 --- /dev/null +++ b/Logic Apps/CfS-SendPromptbookResultsByEmail/CfS-SendPromptbookResultsByEmail.json @@ -0,0 +1,1257 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "title": "Send the results of Security Copilot's Promptbook by email", + "description": "This Logic App executes the specified Promptbook in Security Copilot (a.k.a. Copilot for Security). It then converts each prompt's response, written in the simplified markdown language of Security Copilot, into HTML. Using these HTML excerpts or plain text responses and starting from a specified HTML template, it constructs the HTML body of an email and finally sends that email to the specified recipients. It is possible to exclude one or more of the promptbook responses from the email by their position index (1, 2, 3, etc.)", + "prerequisites": "", + "postDeployment": [ + "1. Set the credentials for the OAuth API connection to Security Copilot and Office 365 (the connection to O365 is used to send the email).", + "2. Edit the workflow and set the promptbook name and related parameters in the run-prompt action (after the deployment, they are empty).", + "3. In the workflow editor, if needed, modify the trigger type and/or parameters (e.g., change the recurrence date and time or replace the trigger with the Microsoft Sentinel Incident trigger).", + "4. In the workflow editor, open the parameters and set the desired email title, subject, and recipients.", + "5. In the workflow editor, in the parameters list, if needed, specify the indexes of the prompts that should not be included in the email (use the SkipPromptsInOutput parameter).", + "6. In the workflow editor, in the parameters list, if needed, specify the prompts that should be replaced in the email (use the ReplacePromptsInOutput parameter).", + "7. In the workflow editor, if needed, further customize the email subject in the send email action (e.g., if you have changed the trigger to 'MS Sentinel Incident', you may want to dynamically include the incident ID or title at the end of the email subject; this cannot be done in the paramteres).", + "8. Enable the Logic App (the template deploys it in the disabled state)." + ], + "prerequisitesDeployTemplateFile": "", + "lastUpdateTime": "", + "entities": [ + ], + "tags": [ + ], + "support": { + "tier": "community", + "armtemplate": "Generated from https://github.com/Azure/Azure-Sentinel/tree/master/Tools/Playbook-ARM-Template-Generator" + }, + "author": { + "name": "Stefano Pescosolido (https://www.linkedin.com/in/stefanopescosolido/)" + } + }, + "parameters": { + "PlaybookName": { + "defaultValue": "CfS-SendPromptbookResultsByEmail", + "type": "string" + }, + "HtmlBodyHeader": { + "defaultValue": "

 

", + "type": "String", + "metadata": { + "description": "Enter the template header of the HTML email body. You can modify it later in the Logic App paramters list. The HTML must contain the text-placehoder ***EMAILTITLE***" + } + }, + "HtmlBodyRow": { + "defaultValue": "", + "type": "String", + "metadata": { + "description": "Enter the template row of the HTML email body. You can modify it later in the Logic App paramters list. The HTML must contain the text-placehoders ***CONTENT1*** and ***CONTENT2***." + } + }, + "HtmlBodyFooter": { + "defaultValue": "

***EMAILTITLE***

***CONTENT1***
***CONTENT2***

 

The content of this email was generated by Artificial Intelligence and may not be accurate. Please verify its accuracy..
To access Security Copilot, click HERE.

", + "type": "String", + "metadata": { + "description": "Enter the template footer of the HTML email body. You can modify it later in the Logic App paramters list." + } + }, + "EmailTitle": { + "defaultValue": "", + "type": "String", + "metadata": { + "description": "Enter value for the title to be specified in the HTML header of the email. You can modify it later in the Logic App paramters list." + } + }, + "Email Subject": { + "defaultValue": "", + "type": "String", + "metadata": { + "description": "Enter value for email subject. You can modify it later in the Logic App paramters list." + } + }, + "EmailRecipients": { + "defaultValue": "", + "type": "String", + "metadata": { + "description": "Enter value for email recipients. You can enter multiple email addresses separated by commas. You can modify it later in the Logic App paramters list." + } + }, + "SkipPromptsInOutput": { + "defaultValue": [], + "type": "Array", + "metadata": { + "description": "Enter comma separated list of the indexes of the prompts in the promptbook that should not be included in the email or leave '[]' for an empty array. You can modify it later in the Logic App paramters list. The index number starts from 1." + } + }, + "ReplacePromptsInOutput": { + "defaultValue": [], + "type": "Array", + "metadata": { + "description": "Enter a comma-separated list of replacements for the prompts that should be written in the email, or leave '[]' for an empty array. You can modify it later in the Logic App parameters list. The prompts are replaced by their position in the array and the corresponding position in the promptbook. Leave an empty string for the prompts that do not need to be replaced." + } + } + }, + "variables": { + "SecuritycopilotConnectionName": "[concat('Securitycopilot-', parameters('PlaybookName'))]", + "Office365ConnectionName": "[concat('Office365-', parameters('PlaybookName'))]" + }, + "resources": [ + { + "properties": { + "provisioningState": "Succeeded", + "state": "Disabled", + "definition": { + "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "$connections": { + "defaultValue": { + }, + "type": "Object" + }, + "HtmlBodyHeader": { + "defaultValue": "[parameters('HtmlBodyHeader')]", + "type": "String", + "metadata": { + "description": "Enter the template header of the HTML email body. The HTML must contain the text-placehoder ***EMAILTITLE***" + } + }, + "HtmlBodyRow": { + "defaultValue": "[parameters('HtmlBodyRow')]", + "type": "String", + "metadata": { + "description": "Enter the template row of the HTML email body. The HTML must contain the text-placehoders ***CONTENT1*** and ***CONTENT2***." + } + }, + "HtmlBodyFooter": { + "defaultValue": "[parameters('HtmlBodyFooter')]", + "type": "String", + "metadata": { + "description": "Enter the template footer of the HTML email body." + } + }, + "EmailTitle": { + "defaultValue": "[parameters('EmailTitle')]", + "type": "String", + "metadata": { + "description": "Enter value for the title to be specified in the HTML header of the email." + } + }, + "Email Subject": { + "defaultValue": "[parameters('Email Subject')]", + "type": "String", + "metadata": { + "description": "Enter value for email subject. " + } + }, + "EmailRecipients": { + "defaultValue": "[parameters('EmailRecipients')]", + "type": "String", + "metadata": { + "description": "Enter value for email recipients. You can enter multiple email addresses separated by commas." + } + }, + "SkipPromptsInOutput": { + "defaultValue": "[parameters('SkipPromptsInOutput')]", + "type": "Array", + "metadata": { + "description": "Enter comma separated list of the indexes of the prompts in the promptbook that should not be included in the email or leave '[]' for an empty array. The index number starts from 1." + } + }, + "ReplacePromptsInOutput": { + "defaultValue": "[parameters('ReplacePromptsInOutput')]", + "type": "Array", + "metadata": { + "description": "Enter a comma-separated list of replacements for the prompts that should be written in the email, or leave '[]' for an empty array. The prompts are replaced by their position in the array and the corresponding position in the promptbook. Leave an empty string for the prompts that do not need to be replaced." + } + } + }, + "triggers": { + "Recurrence": { + "recurrence": { + "interval": 16, + "frequency": "Month" + }, + "evaluatedRecurrence": { + "interval": 16, + "frequency": "Month" + }, + "type": "Recurrence" + } + }, + "actions": { + "Run_a_Security_Copilot_promptbook": { + "runAfter": { + "Initialize_variable_-_PromptReplacement": [ + "Succeeded" + ] + }, + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['securitycopilot']['connectionId']" + } + }, + "method": "post", + "body": { + "PromptbookId": "", + "PromptbookInputs": {} + }, + "path": "/run-promptbook" + } + }, + "Parse_JSON_-_Pb_execution_result": { + "runAfter": { + "Run_a_Security_Copilot_promptbook": [ + "Succeeded", + "TimedOut", + "Skipped", + "Failed" + ] + }, + "type": "ParseJson", + "inputs": { + "content": "@outputs('Run_a_Security_Copilot_promptbook')", + "schema": { + "type": "object", + "properties": { + "content": { + "type": "object", + "properties": { + "statusCode": { + "type": "integer" + }, + "headers": { + }, + "body": { + "type": "object", + "properties": { + "error": { + "type": [ + "object", + "null" + ], + "message": [ + "string", + "null" + ] + }, + "SessionId": { + "type": [ + "string", + "null" + ] + }, + "evaluationResults": { + "type": [ + "array", + "null" + ], + "items": { + "type": [ + "object", + "null" + ], + "properties": { + "skillName": { + "type": [ + "string", + "null" + ] + }, + "promptContent": { + "type": [ + "string", + "null" + ] + }, + "evaluationResultType": { + "type": [ + "string", + "null" + ] + }, + "evaluationResultContent": { + "type": [ + "string", + "null" + ] + }, + "skillSources": { + "type": [ + "array", + "null" + ], + "items": { + "type": [ + "object", + "null" + ], + "properties": { + "displayName": { + "type": [ + "string", + "null" + ] + }, + "sourceType": { + "type": [ + "string", + "null" + ] + }, + "sourceContent": { + "type": [ + "string", + "null" + ] + }, + "sourceSerializedData": { + } + }, + "required": [ + "displayName", + "sourceType", + "sourceContent", + "sourceSerializedData" + ] + } + } + }, + "required": [ + "skillName", + "promptContent", + "evaluationResultType", + "evaluationResultContent", + "skillSources" + ] + } + } + } + } + } + } + } + } + } + }, + "Initialize_variable_-_Result": { + "runAfter": { + "Initialize_variable_-_ErrorMsg": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "SkillSources", + "type": "string" + } + ] + } + }, + "Switch_-_Pb_statusCode": { + "runAfter": { + "Parse_JSON_-_Pb_execution_result": [ + "Succeeded" + ] + }, + "cases": { + "Case_-_Pb_statusCode_-_200": { + "case": 200, + "actions": { + "For_each_-_evaluationResults": { + "foreach": "@body('Parse_JSON_-_Pb_execution_result')?['body']?['evaluationResults']", + "actions": { + "Parse_JSON_-_evaluationResult": { + "runAfter": { + "Increment_variable_-_LoopIndex": [ + "Succeeded" + ] + }, + "type": "ParseJson", + "inputs": { + "content": "@items('For_each_-_evaluationResults')", + "schema": { + "type": "object", + "properties": { + "skillName": { + "type": [ + "string", + "null" + ] + }, + "promptContent": { + "type": "string" + }, + "evaluationResultType": { + "type": "string" + }, + "evaluationResultContent": { + "type": "string" + }, + "skillSources": { + "type": [ + "array", + "null" + ], + "items": { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "sourceType": { + "type": "string" + }, + "sourceContent": { + "type": "string" + }, + "sourceSerializedData": { + } + }, + "required": [ + "displayName", + "sourceType", + "sourceContent", + "sourceSerializedData" + ] + } + } + } + } + } + }, + "Set_variable_-_Reset_HtmlRow": { + "runAfter": { + "Parse_JSON_-_evaluationResult": [ + "Succeeded" + ] + }, + "type": "SetVariable", + "inputs": { + "name": "HtmlRow", + "value": " " + } + }, + "Increment_variable_-_LoopIndex": { + "type": "IncrementVariable", + "inputs": { + "name": "LoopIndex", + "value": 1 + } + }, + "Condition_-_Do_not_skip": { + "actions": { + "Append_to_string_variable_-_HtmlBody": { + "runAfter": { + "Condition_-_evaluationResultType": [ + "Succeeded" + ] + }, + "type": "AppendToStringVariable", + "inputs": { + "name": "HtmlBody", + "value": "@{variables('HtmlRow')}\n\n" + } + }, + "Condition_-_evaluationResultType": { + "actions": { + "Condition_-_evaluationResultContent_is_MD": { + "actions": { + "CfS_-_Convert_MD_response_to_HTML": { + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['securitycopilot']['connectionId']" + } + }, + "method": "post", + "body": { + "PromptContent": "Convert the following markup string to HTML. Do not include the doctype tag and the header. Write only the content of the HTML body, without the opening and closing \"body\" tag. Do not write any character before and after that HTML content. This is the string written in lightweight markup language to be converted in HTML format: @{body('Parse_JSON_-_evaluationResult')?['evaluationResultContent']}" + }, + "path": "/process-prompt" + } + }, + "Parse_JSON_-_MdToHtml_Response": { + "runAfter": { + "CfS_-_Convert_MD_response_to_HTML": [ + "Succeeded", + "TimedOut", + "Skipped", + "Failed" + ] + }, + "type": "ParseJson", + "inputs": { + "content": "@outputs('CfS_-_Convert_MD_response_to_HTML')", + "schema": { + "type": "object", + "properties": { + "statusCode": { + "type": "integer" + }, + "headers": { + }, + "body": { + "type": "object", + "properties": { + "sessionId": { + "type": "string" + }, + "skillName": { + "type": [ + "string", + "null" + ] + }, + "promptContent": { + "type": [ + "string", + "null" + ] + }, + "evaluationResultType": { + "type": [ + "string", + "null" + ] + }, + "evaluationResultContent": { + "type": [ + "string", + "null" + ] + }, + "skillSources": { + "type": [ + "array", + "null" + ] + }, + "status": { + "type": [ + "integer", + "null" + ] + }, + "errors": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "debugInfo": { + "type": [ + "string", + "null" + ] + } + } + } + } + } + } + }, + "Switch_-_MdToHtml_statusCode": { + "runAfter": { + "Parse_JSON_-_MdToHtml_Response": [ + "Succeeded" + ] + }, + "cases": { + "Case_-_MdToHtml_statusCode_-_200": { + "case": 200, + "actions": { + "Compose_-_Html_Row_-_200": { + "runAfter": { + "For_each_-_skillSources": [ + "Succeeded" + ] + }, + "type": "Compose", + "inputs": "@replace(replace(parameters('HtmlBodyRow'),'***CONTENT1***',variables('PromptInOutput')),'***CONTENT2***',concat(body('Parse_JSON_-_MdToHtml_Response')?['body']?['evaluationResultContent'],variables('SkillSources')))" + }, + "Set_variable_-_HtmlRow_-_200": { + "runAfter": { + "Compose_-_Html_Row_-_200": [ + "Succeeded" + ] + }, + "type": "SetVariable", + "inputs": { + "name": "HtmlRow", + "value": "@outputs('Compose_-_Html_Row_-_200')" + } + }, + "For_each_-_skillSources": { + "foreach": "@body('Parse_JSON_-_evaluationResult')?['skillSources']", + "actions": { + "Parse_JSON_-_skillSource": { + "type": "ParseJson", + "inputs": { + "content": "@items('For_each_-_skillSources')", + "schema": { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "sourceType": { + "type": "string" + }, + "sourceContent": { + "type": "string" + }, + "sourceSerializedData": { + } + } + } + } + }, + "Append_to_string_variable_-_Result_-_skillSource": { + "runAfter": { + "Parse_JSON_-_skillSource": [ + "Succeeded" + ] + }, + "type": "AppendToStringVariable", + "inputs": { + "name": "SkillSources", + "value": "\u003cP\u003e\n\u003cB\u003eSource: \u003c/B\u003e\u003cA href=\"@{body('Parse_JSON_-_skillSource')?['sourceContent']}\"\u003e@{body('Parse_JSON_-_skillSource')?['displayName']}\u003c/A\u003e\n\u003c/P\u003e" + } + } + }, + "type": "Foreach" + } + } + }, + "Case_-_MdToHtml_statusCode_-_404": { + "case": 404, + "actions": { + "Compose_-_Html_Row_-_404": { + "type": "Compose", + "inputs": "@replace(replace(parameters('HtmlBodyRow'),'***CONTENT1***',variables('PromptInOutput')),'***CONTENT2***',concat('\u003cp style=\"color: red;\"\u003eERROR: Copilot not available!\u003c/P\u003eDETAILS:',body('Parse_JSON_-_MdToHtml_Response')?['body']?['errors']))" + }, + "Set_variable_-_HtmlRow_-_404": { + "runAfter": { + "Compose_-_Html_Row_-_404": [ + "Succeeded" + ] + }, + "type": "SetVariable", + "inputs": { + "name": "HtmlRow", + "value": "@outputs('Compose_-_Html_Row_-_404')" + } + } + } + }, + "Case_-_MdToHtml_statusCode_-_429": { + "case": 429, + "actions": { + "Compose_-_Html_Row_-_429": { + "type": "Compose", + "inputs": "@replace(replace(parameters('HtmlBodyRow'),'***CONTENT1***',variables('PromptInOutput')),'***CONTENT2***',concat('\u003cp style=\"color: red;\"\u003eERROR: Copilot is no longer available due to exceeding capacity!\u003c/P\u003eDETAILS:',body('Parse_JSON_-_MdToHtml_Response')?['body']?['errors']))" + }, + "Set_variable_-_HtmlRow_-_429": { + "runAfter": { + "Compose_-_Html_Row_-_429": [ + "Succeeded" + ] + }, + "type": "SetVariable", + "inputs": { + "name": "HtmlRow", + "value": "@outputs('Compose_-_Html_Row_-_429')" + } + } + } + } + }, + "default": { + "actions": { + "Compose_-_Html_Row_-_Default": { + "type": "Compose", + "inputs": "@replace(replace(parameters('HtmlBodyRow'),'***CONTENT1***',variables('PromptInOutput')),'***CONTENT2***',concat('\u003cp style=\"color: red;\"\u003eERROR while calling Copilot! Error number: ',body('Parse_JSON_-_MdToHtml_Response')?['statusCode'],'\u003c/P\u003eDETAILS:',body('Parse_JSON_-_MdToHtml_Response')?['body']?['errors']))" + }, + "Set_variable_-_HtmlRow_-_Default": { + "runAfter": { + "Compose_-_Html_Row_-_Default": [ + "Succeeded" + ] + }, + "type": "SetVariable", + "inputs": { + "name": "HtmlRow", + "value": "@outputs('Compose_-_Html_Row_-_Default')" + } + } + } + }, + "expression": "@body('Parse_JSON_-_MdToHtml_Response')?['statusCode']", + "type": "Switch" + } + }, + "else": { + "actions": { + "Compose_-_Html_Row_-_Plain_text": { + "type": "Compose", + "inputs": "@replace(replace(parameters('HtmlBodyRow'),'***CONTENT1***',variables('PromptInOutput')),'***CONTENT2***',body('Parse_JSON_-_evaluationResult')?['evaluationResultContent'])" + }, + "Set_variable_-_HtmlRow_-_Plain_text": { + "runAfter": { + "Compose_-_Html_Row_-_Plain_text": [ + "Succeeded" + ] + }, + "type": "SetVariable", + "inputs": { + "name": "HtmlRow", + "value": "@outputs('Compose_-_Html_Row_-_Plain_text')" + } + } + } + }, + "expression": { + "or": [ + { + "contains": [ + "@body('Parse_JSON_-_evaluationResult')?['evaluationResultContent']", + "#" + ] + }, + { + "contains": [ + "@body('Parse_JSON_-_evaluationResult')?['evaluationResultContent']", + "*" + ] + }, + { + "contains": [ + "@body('Parse_JSON_-_evaluationResult')?['evaluationResultContent']", + "+" + ] + }, + { + "contains": [ + "@body('Parse_JSON_-_evaluationResult')?['evaluationResultContent']", + "\\n" + ] + }, + { + "contains": [ + "@body('Parse_JSON_-_evaluationResult')?['evaluationResultContent']", + "[" + ] + }, + { + "contains": [ + "@body('Parse_JSON_-_evaluationResult')?['evaluationResultContent']", + "|" + ] + }, + { + "contains": [ + "@body('Parse_JSON_-_evaluationResult')?['evaluationResultContent']", + "`" + ] + } + ] + }, + "type": "If" + } + }, + "else": { + "actions": { + "Compose_-_Html_Row_-_Not_Success": { + "type": "Compose", + "inputs": "@replace(replace(parameters('HtmlBodyRow'),'***CONTENT1***',variables('PromptInOutput')),'***CONTENT2***',concat('\u003cp style=\"color: orange;\"\u003eWARNING! The prompt was not successful. Response type: ',body('Parse_JSON_-_evaluationResult')?['evaluationResultType'],'\u003c/P\u003eDETAILS:',body('Parse_JSON_-_evaluationResult')?['evaluationResultContent']))" + }, + "Set_variable_-_HtmlRow_-_Not_Success": { + "runAfter": { + "Compose_-_Html_Row_-_Not_Success": [ + "Succeeded" + ] + }, + "type": "SetVariable", + "inputs": { + "name": "HtmlRow", + "value": "@outputs('Compose_-_Html_Row_-_Not_Success')" + } + } + } + }, + "expression": { + "and": [ + { + "equals": [ + "@body('Parse_JSON_-_evaluationResult')?['evaluationResultType']", + "Success" + ] + } + ] + }, + "type": "If" + } + }, + "runAfter": { + "Set_variable_-_SkipPrompt": [ + "Succeeded" + ] + }, + "else": { + "actions": { + } + }, + "expression": { + "and": [ + { + "equals": [ + "@variables('SkipPrompt')", + false + ] + } + ] + }, + "type": "If" + }, + "Set_variable_-_SkipPrompt": { + "runAfter": { + "Condition_-_PromptReplacement_is_empty": [ + "Succeeded" + ] + }, + "type": "SetVariable", + "inputs": { + "name": "SkipPrompt", + "value": "@contains(parameters('SkipPromptsInOutput'),variables('LoopIndex'))" + } + }, + "Set_variable_-_Reset_SkillSources": { + "runAfter": { + "Set_variable_-_Reset_HtmlRow": [ + "Succeeded" + ] + }, + "type": "SetVariable", + "inputs": { + "name": "SkillSources", + "value": " " + } + }, + "Condition_-_PromptReplacement_is_empty": { + "actions": { + "Set_variable_-_PromptInOutput_-_Original": { + "type": "SetVariable", + "inputs": { + "name": "PromptInOutput", + "value": "@body('Parse_JSON_-_evaluationResult')?['promptContent']" + } + } + }, + "runAfter": { + "Set_variable_-_Read_PromptReplacement": [ + "Succeeded", + "Failed" + ] + }, + "else": { + "actions": { + "Set_variable_-_PromptInOutput_-_Replaced": { + "type": "SetVariable", + "inputs": { + "name": "PromptInOutput", + "value": "@variables('PromptReplacement')" + } + } + } + }, + "expression": { + "and": [ + { + "equals": [ + "@variables('PromptReplacement')", + "@trim('')" + ] + } + ] + }, + "type": "If" + }, + "Set_variable_-_Read_PromptReplacement": { + "runAfter": { + "Set_variable_-_Reset_PromptReplacement": [ + "Succeeded" + ] + }, + "type": "SetVariable", + "inputs": { + "name": "PromptReplacement", + "value": "@trim(parameters('ReplacePromptsInOutput')[sub(variables('LoopIndex'),1)])" + } + }, + "Set_variable_-_Reset_PromptReplacement": { + "runAfter": { + "Set_variable_-_Reset_SkillSources": [ + "Succeeded" + ] + }, + "type": "SetVariable", + "inputs": { + "name": "PromptReplacement", + "value": "@trim('')" + } + } + }, + "type": "Foreach", + "runtimeConfiguration": { + "concurrency": { + "repetitions": 1 + } + } + } + } + }, + "Case_-_Pb_statusCode_-_404": { + "case": 404, + "actions": { + "For_each_-_404_-_Errors": { + "foreach": "@body('Parse_JSON_-_Pb_execution_result')?['body']?['errors']", + "actions": { + "Append_-_ErrorMsg_-_404": { + "type": "AppendToStringVariable", + "inputs": { + "name": "ErrorMsg", + "value": "@items('For_each_-_404_-_Errors')" + } + } + }, + "type": "Foreach" + }, + "Compose_-_Html_Row_-_Pb_-_404": { + "runAfter": { + "For_each_-_404_-_Errors": [ + "Succeeded" + ] + }, + "type": "Compose", + "inputs": "@replace(replace(parameters('HtmlBodyRow'),'***CONTENT1***','Promptbook execution'),'***CONTENT2***',concat('\u003cp style=\"color: red;\"\u003eERROR: Copilot not available!\u003c/P\u003eDETAILS:',body('Parse_JSON_-_Pb_execution_result')?['body']?['errors']))" + }, + "Append_-_HtmlBody_-_Pb_-_404": { + "runAfter": { + "Compose_-_Html_Row_-_Pb_-_404": [ + "Succeeded" + ] + }, + "type": "AppendToStringVariable", + "inputs": { + "name": "HtmlBody", + "value": "@outputs('Compose_-_Html_Row_-_Pb_-_404')" + } + } + } + }, + "Case_-_Pb_statusCode_-_429": { + "case": 429, + "actions": { + "For_each_-_429_-_Errors": { + "foreach": "@body('Parse_JSON_-_Pb_execution_result')?['body']?['errors']", + "actions": { + "Append_-_ErrorMsg_-_429": { + "type": "AppendToStringVariable", + "inputs": { + "name": "ErrorMsg", + "value": "@items('For_each_-_429_-_Errors')" + } + } + }, + "type": "Foreach" + }, + "Compose_-_Html_Row_-_Pb_-_429": { + "runAfter": { + "For_each_-_429_-_Errors": [ + "Succeeded" + ] + }, + "type": "Compose", + "inputs": "@replace(replace(parameters('HtmlBodyRow'),'***CONTENT1***','Promptbook execution'),'***CONTENT2***',concat('\u003cp style=\"color: red;\"\u003eCopilot is no longer available due to exceeding capacity!\u003c/P\u003eDETAILS:',body('Parse_JSON_-_Pb_execution_result')?['body']?['errors']))" + }, + "Append_-_HtmlBody_-_Pb_-_429": { + "runAfter": { + "Compose_-_Html_Row_-_Pb_-_429": [ + "Succeeded" + ] + }, + "type": "AppendToStringVariable", + "inputs": { + "name": "HtmlBody", + "value": "@outputs('Compose_-_Html_Row_-_Pb_-_429')" + } + } + } + } + }, + "default": { + "actions": { + "For_each_-_Default_error": { + "foreach": "@body('Parse_JSON_-_Pb_execution_result')?['content']?['body']?['error']", + "actions": { + "Append_to_string_variable_-_Default_error": { + "type": "AppendToStringVariable", + "inputs": { + "name": "ErrorMsg", + "value": "Error: @{items('For_each_-_Default_error')}" + } + } + }, + "type": "Foreach" + }, + "Append_-_HtmlBody_-_Pb_-_Default_error": { + "runAfter": { + "Compose_-_Html_Row_-_Pb_-_Default_error": [ + "Succeeded" + ] + }, + "type": "AppendToStringVariable", + "inputs": { + "name": "HtmlBody", + "value": "@outputs('Compose_-_Html_Row_-_Pb_-_Default_error')" + } + }, + "Compose_-_Html_Row_-_Pb_-_Default_error": { + "runAfter": { + "For_each_-_Default_error": [ + "Succeeded" + ] + }, + "type": "Compose", + "inputs": "@replace(replace(parameters('HtmlBodyRow'),'***CONTENT1***','Promptbook execution'),'***CONTENT2***',concat('\u003cp style=\"color: red;\"\u003eERROR while calling Copilot! Error number: ',body('Parse_JSON_-_Pb_execution_result')?['statusCode'],'\u003c/P\u003eDETAILS:',variables('ErrorMsg')))" + } + } + }, + "expression": "@body('Parse_JSON_-_Pb_execution_result')?['statusCode']", + "type": "Switch" + }, + "Append_-_HtmlBody_-_Footer": { + "runAfter": { + "Switch_-_Pb_statusCode": [ + "Succeeded" + ] + }, + "type": "AppendToStringVariable", + "inputs": { + "name": "HtmlBody", + "value": "@parameters('HtmlBodyFooter')" + } + }, + "Initialize_variable_-_HtmlBody": { + "runAfter": { + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "HtmlBody", + "type": "string", + "value": "@replace(parameters('HtmlBodyHeader'),'***EMAILTITLE***',parameters('EmailTitle'))" + } + ] + } + }, + "Initialize_variable_-_HtmlRow": { + "runAfter": { + "Initialize_variable_-_HtmlBody": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "HtmlRow", + "type": "string" + } + ] + } + }, + "Initialize_variable_-_ErrorMsg": { + "runAfter": { + "Initialize_variable_-_HtmlRow": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "ErrorMsg", + "type": "string" + } + ] + } + }, + "Send_an_email_(V2)": { + "runAfter": { + "Append_-_HtmlBody_-_Footer": [ + "Succeeded" + ] + }, + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['office365']['connectionId']" + } + }, + "method": "post", + "body": { + "To": "@parameters('EmailRecipients')", + "Subject": "@parameters('Email Subject')", + "Body": "\u003cp class=\"editor-paragraph\"\u003e@{variables('HtmlBody')}\u003c/p\u003e", + "Importance": "Normal" + }, + "path": "/v2/Mail" + } + }, + "Initialize_variable_-_LoopIndex": { + "runAfter": { + "Initialize_variable_-_Result": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "LoopIndex", + "type": "integer", + "value": 0 + } + ] + } + }, + "Initialize_variable_-_SkipPrompt": { + "runAfter": { + "Initialize_variable_-_LoopIndex": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "SkipPrompt", + "type": "boolean", + "value": false + } + ] + } + }, + "Initialize_variable_-_PromptInOutput": { + "runAfter": { + "Initialize_variable_-_SkipPrompt": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "PromptInOutput", + "type": "string" + } + ] + } + }, + "Initialize_variable_-_PromptReplacement": { + "runAfter": { + "Initialize_variable_-_PromptInOutput": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "PromptReplacement", + "type": "string" + } + ] + } + } + }, + "outputs": { + } + }, + "parameters": { + "$connections": { + "value": { + "securitycopilot": { + "connectionId": "[resourceId('Microsoft.Web/connections', variables('SecuritycopilotConnectionName'))]", + "connectionName": "[variables('SecuritycopilotConnectionName')]", + "id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/Securitycopilot')]" + }, + "office365": { + "connectionId": "[resourceId('Microsoft.Web/connections', variables('Office365ConnectionName'))]", + "connectionName": "[variables('Office365ConnectionName')]", + "id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/Office365')]" + } + } + } + } + }, + "name": "[parameters('PlaybookName')]", + "type": "Microsoft.Logic/workflows", + "location": "[resourceGroup().location]", + "tags": { + "hidden-SentinelTemplateName": "CfS-SendPromptbookResultsByEmail", + "hidden-SentinelTemplateVersion": "1.0" + }, + "identity": { + "type": "SystemAssigned" + }, + "apiVersion": "2017-07-01", + "dependsOn": [ + "[resourceId('Microsoft.Web/connections', variables('SecuritycopilotConnectionName'))]", + "[resourceId('Microsoft.Web/connections', variables('Office365ConnectionName'))]" + ] + }, + { + "type": "Microsoft.Web/connections", + "apiVersion": "2016-06-01", + "name": "[variables('SecuritycopilotConnectionName')]", + "location": "[resourceGroup().location]", + "kind": "V1", + "properties": { + "displayName": "[variables('SecuritycopilotConnectionName')]", + "customParameterValues": { + }, + "api": { + "id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/Securitycopilot')]" + } + } + }, + { + "type": "Microsoft.Web/connections", + "apiVersion": "2016-06-01", + "name": "[variables('Office365ConnectionName')]", + "location": "[resourceGroup().location]", + "kind": "V1", + "properties": { + "displayName": "[variables('Office365ConnectionName')]", + "customParameterValues": { + }, + "api": { + "id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/Office365')]" + } + } + } + ] +} diff --git a/Logic Apps/CfS-SendPromptbookResultsByEmail/Example of email customization.png b/Logic Apps/CfS-SendPromptbookResultsByEmail/Example of email customization.png new file mode 100644 index 00000000..8b61e3fa Binary files /dev/null and b/Logic Apps/CfS-SendPromptbookResultsByEmail/Example of email customization.png differ diff --git a/Logic Apps/CfS-SendPromptbookResultsByEmail/readme.md b/Logic Apps/CfS-SendPromptbookResultsByEmail/readme.md new file mode 100644 index 00000000..28bbcb38 --- /dev/null +++ b/Logic Apps/CfS-SendPromptbookResultsByEmail/readme.md @@ -0,0 +1,30 @@ +# Send the results of Security Copilot's Promptbook by email + +This Logic App executes the specified Promptbook in Security Copilot (Copilot for Security). It then converts each prompt's response, written in the simplified markdown language of Security Copilot, into HTML. Using these HTML excerpts or plain text responses and starting from a customizable HTML template, it constructs the HTML body of an email and finally sends that email to the specified recipients. It is possible to exclude one or more of the promptbook responses from the email by their position index (1, 2, 3, etc.); it is also possible to replace the text of one or more prompts in the email for the sake of readability. + +Please refer to this article for further details and for a video on how to deploy and use it: https://www.linkedin.com/pulse/sending-security-copilot-promptbook-responses-via-stefano-pescosolido-wcalf. + +### Prerequisites: +* This Logic App is useful when you have active Security Compute Units (SCUs) for Security Copilot + + +### Post deployment actions: +1. Set the credentials for the OAuth API connection to Security Copilot and Office 365 (the connection to O365 is used to send the email). +2. Edit the workflow and set the promptbook name and related parameters in the run-prompt action (after the deployment, they are empty). +3. In the workflow editor, if needed, modify the trigger type and/or parameters (e.g., change the recurrence date and time or replace the trigger with the Microsoft Sentinel Incident trigger). +4. In the workflow editor, open the parameters and set the desired email title, subject, and recipients. +5. In the workflow editor, in the parameters list, if needed, specify the indexes of the prompts that should not be included in the email (use the SkipPromptsInOutput parameter). +6. In the workflow editor, if needed, further customize the email subject (e.g., if you have changed the trigger to MS Sentinel Incident, you may want to include the incident ID at the end of the email subject). +7. Enable the Logic App (the template deploys it in disabled state). + +## Deployment button +[![Deploy to Azure](https://aka.ms/deploytoazurebutton)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2FAzure%2FSecurity-Copilot%2Frefs%2Fheads%2Fmain%2FLogic%2520Apps%2FCfS-SendPromptbookResultsByEmail%2FCfS-SendPromptbookResultsByEmail.json) + +## Example of email customization: +The following image shows: +1. The standard promptbook "Vulnerability impact assessment" used as an example +2. The email delivered using the default HTML template +3. The customization of 3 parameters: HtmlBodyHeader (for changing the colors), SkipPromptsInOutput (for hiding the second prompt and response in the email) and ReplacePromptsInOutput (for replacing, in the email, the text of the first and third prompt. Note: the second prompt in the array is left as empty string "", meaning that there should be no replacement; the last prompt is not even included, which also means no replacement) +4. The email delivered using that customization + +![Example of email customization](./Example%20of%20email%20customization.png) diff --git a/Logic Apps/KQL-Migrator/AQL.csv b/Logic Apps/KQL-Migrator/AQL.csv new file mode 100644 index 00000000..ec52ed27 --- /dev/null +++ b/Logic Apps/KQL-Migrator/AQL.csv @@ -0,0 +1,2 @@ +UseCase,AQL +Spawning Windows Shell,"SELECT UTF8(payload) as search_payload from events where (((LOGSOURCETYPENAME(devicetype) ilike 'Microsoft Windows Security Event Log')) and ((""EventID""='4688' and (search_payload ilike '%\cmd.exe' or search_payload ilike '%\powershell.exe' or search_payload ilike '%\wscript.exe' or search_payload ilike '%\cscript.exe'or search_payload ilike '%\sh.exe' or search_payload ilike '%\bash.exe' or search_payload ilike '%\scrcons.exe' or search_payload ilike '%\schtasks.exe' or search_payload ilike '%\regsvr32.exe' or search_payload ilike '%\mshta.exe' or search_payload ilike '%\rundll32.exe' or search_payload ilike '%\msiexec.exe')))) GROUP BY sourceip LAST 3 DAYS" diff --git a/Logic Apps/KQL-Migrator/KQLMigratorPlaybook-SecCopilot.json b/Logic Apps/KQL-Migrator/KQLMigratorPlaybook-SecCopilot.json new file mode 100644 index 00000000..bc023fb0 --- /dev/null +++ b/Logic Apps/KQL-Migrator/KQLMigratorPlaybook-SecCopilot.json @@ -0,0 +1,271 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "title": "", + "description": "", + "prerequisites": "", + "postDeployment": [ + ], + "prerequisitesDeployTemplateFile": "", + "lastUpdateTime": "", + "entities": [ + ], + "tags": [ + ], + "support": { + "tier": "community", + "armtemplate": "Generated from https://github.com/Azure/Azure-Sentinel/tree/master/Tools/Playbook-ARM-Template-Generator" + }, + "author": { + "name": "" + } + }, + "parameters": { + "PlaybookName": { + "defaultValue": "KQL-CFS-Migrator", + "type": "string" + }, + "SubscriptionID": { + "type": "String", + "metadata": { + "description": "Enter value for SubscriptionID" + } + }, + "Resourcegroup": { + "type": "String", + "metadata": { + "description": "Enter value for Resourcegroup" + } + }, + "Resourcename": { + "type": "String", + "metadata": { + "description": "Enter value for Resourcename" + } + } + }, + "variables": { + "AzuremonitorlogsConnectionName": "[concat('Azuremonitorlogs-', parameters('PlaybookName'))]", + "SecuritycopilotConnectionName": "[concat('Securitycopilot-', parameters('PlaybookName'))]" + }, + "resources": [ + { + "properties": { + "provisioningState": "Succeeded", + "state": "Disabled", + "definition": { + "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "$connections": { + "defaultValue": { + }, + "type": "Object" + }, + "SubscriptionID": { + "defaultValue": "[parameters('SubscriptionID')]", + "type": "String" + }, + "Resourcegroup": { + "defaultValue": "[parameters('Resourcegroup')]", + "type": "String" + }, + "Resourcename": { + "defaultValue": "[parameters('Resourcename')]", + "type": "String" + } + }, + "triggers": { + "Recurrence": { + "recurrence": { + "interval": 1, + "frequency": "Day" + }, + "evaluatedRecurrence": { + "interval": 1, + "frequency": "Day" + }, + "type": "Recurrence" + } + }, + "actions": { + "UseCase-Var": { + "runAfter": { + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "UseCase", + "type": "string" + } + ] + } + }, + "Query-Var": { + "runAfter": { + "UseCase-Var": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "Query", + "type": "string" + } + ] + } + }, + "Get_Watchlist_Items": { + "runAfter": { + "Query-Var": [ + "Succeeded" + ] + }, + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['azuremonitorlogs']['connectionId']" + } + }, + "method": "post", + "body": "_GetWatchlist('AQLTOKQL')", + "path": "/queryData", + "queries": { + "subscriptions": "@parameters('SubscriptionID')", + "resourcegroups": "@parameters('Resourcegroup')", + "resourcetype": "Log Analytics Workspace", + "resourcename": "@parameters('Resourcename')", + "timerange": "@addDays(utcNow(),-10000)" + } + } + }, + "For_each": { + "foreach": "@body('Get_Watchlist_Items')?['value']", + "actions": { + "UseCase-Set_variable": { + "type": "SetVariable", + "inputs": { + "name": "UseCase", + "value": "@items('For_each')['UseCase']" + } + }, + "Query-Set_variable": { + "runAfter": { + "UseCase-Set_variable": [ + "Succeeded" + ] + }, + "type": "SetVariable", + "inputs": { + "name": "Query", + "value": "@items('For_each')['AQL']" + } + }, + "KQL_MIgrator_Prompt_book": { + "runAfter": { + "Query-Set_variable": [ + "Succeeded" + ] + }, + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['securitycopilot']['connectionId']" + } + }, + "method": "post", + "body": { + "PromptbookId": "5a846832-ae56-48ff-b625-5b70174b55fd", + "PromptbookInputs": { + "inputQuery": "@variables('Query')" + } + }, + "path": "/run-promptbook" + } + } + }, + "runAfter": { + "Get_Watchlist_Items": [ + "Succeeded" + ] + }, + "type": "Foreach" + } + }, + "outputs": { + } + }, + "parameters": { + "$connections": { + "value": { + "azuremonitorlogs": { + "connectionId": "[resourceId('Microsoft.Web/connections', variables('AzuremonitorlogsConnectionName'))]", + "connectionName": "[variables('AzuremonitorlogsConnectionName')]", + "id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/Azuremonitorlogs')]" + }, + "securitycopilot": { + "connectionId": "[resourceId('Microsoft.Web/connections', variables('SecuritycopilotConnectionName'))]", + "connectionName": "[variables('SecuritycopilotConnectionName')]", + "id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/Securitycopilot')]" + } + } + } + } + }, + "name": "[parameters('PlaybookName')]", + "type": "Microsoft.Logic/workflows", + "location": "[resourceGroup().location]", + "tags": { + "CreatedDate": "12/18/2024 1:05:02 PM", + "Created By": "Hesham Abdelaal", + "CreatorUPN": "", + "hidden-SentinelTemplateName": "KQL-CFS-Migrator", + "hidden-SentinelTemplateVersion": "1.0" + }, + "identity": { + "type": "SystemAssigned" + }, + "apiVersion": "2017-07-01", + "dependsOn": [ + "[resourceId('Microsoft.Web/connections', variables('AzuremonitorlogsConnectionName'))]", + "[resourceId('Microsoft.Web/connections', variables('SecuritycopilotConnectionName'))]" + ] + }, + { + "type": "Microsoft.Web/connections", + "apiVersion": "2016-06-01", + "name": "[variables('AzuremonitorlogsConnectionName')]", + "location": "[resourceGroup().location]", + "kind": "V1", + "properties": { + "displayName": "[variables('AzuremonitorlogsConnectionName')]", + "customParameterValues": { + }, + "api": { + "id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/Azuremonitorlogs')]" + } + } + }, + { + "type": "Microsoft.Web/connections", + "apiVersion": "2016-06-01", + "name": "[variables('SecuritycopilotConnectionName')]", + "location": "[resourceGroup().location]", + "kind": "V1", + "properties": { + "displayName": "[variables('SecuritycopilotConnectionName')]", + "customParameterValues": { + }, + "api": { + "id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/Securitycopilot')]" + } + } + } + ] +} \ No newline at end of file diff --git a/Logic Apps/KQL-Migrator/KQLMigratorPlaybook.png b/Logic Apps/KQL-Migrator/KQLMigratorPlaybook.png new file mode 100644 index 00000000..5d0851ee Binary files /dev/null and b/Logic Apps/KQL-Migrator/KQLMigratorPlaybook.png differ diff --git a/Logic Apps/KQL-Migrator/YaraUseCasesFinal.csv b/Logic Apps/KQL-Migrator/YaraUseCasesFinal.csv new file mode 100644 index 00000000..519d6b64 --- /dev/null +++ b/Logic Apps/KQL-Migrator/YaraUseCasesFinal.csv @@ -0,0 +1,20 @@ +UseCase,Yara +APT Cobalt,"rule Cobalt_functions +{ + + meta: + + author=""@userx"" + description=""Detect functions coded with ROR edi,D; Detect CobaltStrike used by differents groups APT"" + + strings: + + $h1={58 A4 53 E5} // VirtualAllocEx + $h2={4C 77 26 07} // LoadLibraryEx + $h3={6A C9 9C C9} // DNSQuery_UTF8 + $h4={44 F0 35 E0} // Sleep + $h5={F4 00 8E CC} // lstrlen + + condition: + 2 of ( $h* ) +}" diff --git a/Logic Apps/KQL-Migrator/readme.md b/Logic Apps/KQL-Migrator/readme.md new file mode 100644 index 00000000..b85d8003 --- /dev/null +++ b/Logic Apps/KQL-Migrator/readme.md @@ -0,0 +1,35 @@ +# Security-Copilot-KQLMigrator +Author: Hiten Sharma and Hesham Abdelaal +## Overview +This playbook emphasizes the capability to convert and migrate custom SIEM, EDR/XDR queries and rules to KQL. + +## Pre-deployment Instructions + +Prior to beginning the installation process, it's crucial to confirm that you have met the following prerequisites: +- The user that will deploy this Logic app need to have a Contributor Role. +- You enabled the Security Copilot license on your tenant +- The user authenticated within the CoPilot logic app action and has permission to access M365 Defender / Microsoft Sentinel environment +- The user authenticated within the CoPilot logic app action and has permission to create and manage Analytics rules in Microsoft Sentinel + + +## Deployment + + + + + +you environement settings. + + +### Post-Deployment Instructions + +1. Select the frequency on how many times you want the Logic App to run +2. Authenticate the Copilot Prompts with a User that has Access to Security Copilot +3. Make sure you upload and define the watchlist item from Microsoft Sentinel (two examples provided one for AQL queries and the other for YARA rules) you want to leverage in the playbook and app registeration if you want to automate creating Microsoft Sentinel analytics rules via the playbook, remember that you might need to edit the Logic App playbook with the watchlist required ones. +3. Proceed to run the Logic app and Prepare for the results + + +Results of the Logic APP +![KQLMigrator](https://github.com/Azure/Security-Copilot/blob/main/Logic%20Apps/KQL-Migrator/KQLMigratorPlaybook.png) + + diff --git a/Logic Apps/LatestCISAVulnerabilities/CISALogicApp.json b/Logic Apps/LatestCISAVulnerabilities/CISALogicApp.json new file mode 100644 index 00000000..cc20cc5d --- /dev/null +++ b/Logic Apps/LatestCISAVulnerabilities/CISALogicApp.json @@ -0,0 +1,339 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "PlaybookName": { + "defaultValue": "LatestCISAVulnerabilities", + "type": "string" + } + }, + "variables": { + "SecuritycopilotConnectionName": "[concat('Securitycopilot-', parameters('PlaybookName'))]", + "Office365ConnectionName": "[concat('Office365-', parameters('PlaybookName'))]" + }, + "resources": [ + { + "properties": { + "state": "Enabled", + "definition": { + "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "ToEmail": { + "defaultValue": "youremail@company.com", + "type": "String" + }, + "$connections": { + "defaultValue": {}, + "type": "Object" + } + }, + "triggers": { + "Recurrence": { + "recurrence": { + "frequency": "Hour", + "interval": 24 + }, + "evaluatedRecurrence": { + "frequency": "Hour", + "interval": 24 + }, + "type": "Recurrence" + } + }, + "actions": { + "For_each": { + "foreach": "@body('Parse_JSON')?['vulnerabilities']", + "actions": { + "Condition": { + "actions": { + "Send_an_email_(V2)": { + "runAfter": { + "Submit_a_Copilot_for_Security_prompt_2": [ + "Succeeded" + ] + }, + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['office365']['connectionId']" + } + }, + "method": "post", + "body": { + "To": "@parameters('ToEmail')", + "Subject": "CISA @{items('For_each')?['cveID']}", + "Body": "

Name of CVE: @{items('For_each')?['cveID']}


CVE Date: @{items('For_each')?['dateAdded']}

Impacted Devices: @{body('Submit_a_Copilot_for_Security_prompt_2')?['EvaluationResultContent']}

Impacted Vendors: @{items('For_each')?['vendorProject']}

Details: @{body('Submit_a_Copilot_for_Security_prompt')?['EvaluationResultContent']}

Exploitation Status: @{items('For_each')?['knownRansomwareCampaignUse']}

Mediation Steps: @{items('For_each')?['requiredAction']}

", + "Importance": "Normal" + }, + "path": "/v2/Mail" + } + }, + "Submit_a_Copilot_for_Security_prompt": { + "runAfter": { + "Parse_JSON_1": [ + "Succeeded" + ] + }, + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['securitycopilot']['connectionId']" + } + }, + "method": "post", + "body": { + "PromptContent": "Using Defender Threat Intelligence could you write a summary with no headings for @{body('Parse_JSON_1')?['cveID']}, include the CVSS score of @{body('Parse_JSON_1')?['cveID']} and also summarize@{body('Parse_JSON_1')?['shortDescription']} in a short paragraph with no more than 500 words" + }, + "path": "/process-prompt" + } + }, + "Submit_a_Copilot_for_Security_prompt_2": { + "runAfter": { + "Submit_a_Copilot_for_Security_prompt": [ + "Succeeded" + ] + }, + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['securitycopilot']['connectionId']" + } + }, + "method": "post", + "body": { + "PromptContent": "How many devices are effected by @{body('Parse_JSON_1')?['cveID']}", + "SessionId": "@body('Submit_a_Copilot_for_Security_prompt')?['sessionId']", + "SkillName": "GetCvesByIdsDti" + }, + "path": "/process-prompt" + } + }, + "Parse_JSON_1": { + "type": "ParseJson", + "inputs": { + "content": "@items('For_each')", + "schema": { + "type": "object", + "properties": { + "cveID": { + "type": "string" + }, + "vendorProject": { + "type": "string" + }, + "product": { + "type": "string" + }, + "vulnerabilityName": { + "type": "string" + }, + "dateAdded": { + "type": "string" + }, + "shortDescription": { + "type": "string" + }, + "requiredAction": { + "type": "string" + }, + "dueDate": { + "type": "string" + }, + "knownRansomwareCampaignUse": { + "type": "string" + }, + "notes": { + "type": "string" + }, + "cwes": { + "type": "array" + } + } + } + } + } + }, + "else": { + "actions": {} + }, + "expression": { + "and": [ + { + "greater": [ + "@ticks(items('For_each')?['dateAdded'])", + "@ticks(addDays(utcnow(),-1))" + ] + } + ] + }, + "type": "If" + } + }, + "runAfter": { + "Parse_JSON": [ + "Succeeded" + ] + }, + "type": "Foreach" + }, + "HTTP": { + "runAfter": {}, + "type": "Http", + "inputs": { + "uri": "https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json", + "method": "GET" + } + }, + "Parse_JSON": { + "runAfter": { + "HTTP": [ + "Succeeded" + ] + }, + "type": "ParseJson", + "inputs": { + "content": "@body('HTTP')", + "schema": { + "properties": { + "catalogVersion": { + "type": "string" + }, + "count": { + "type": "integer" + }, + "dateReleased": { + "type": "string" + }, + "title": { + "type": "string" + }, + "vulnerabilities": { + "items": { + "properties": { + "cveID": { + "type": "string" + }, + "cwes": { + "type": "array" + }, + "dateAdded": { + "type": "string" + }, + "dueDate": { + "type": "string" + }, + "knownRansomwareCampaignUse": { + "type": "string" + }, + "notes": { + "type": "string" + }, + "product": { + "type": "string" + }, + "requiredAction": { + "type": "string" + }, + "shortDescription": { + "type": "string" + }, + "vendorProject": { + "type": "string" + }, + "vulnerabilityName": { + "type": "string" + } + }, + "required": [ + "cveID", + "vendorProject", + "product", + "vulnerabilityName", + "dateAdded", + "shortDescription", + "requiredAction", + "dueDate", + "knownRansomwareCampaignUse", + "notes", + "cwes" + ], + "type": "object" + }, + "type": "array" + } + }, + "type": "object" + } + } + } + }, + "outputs": {} + }, + "parameters": { + "$connections": { + "value": { + "securitycopilot": { + "connectionId": "[resourceId('Microsoft.Web/connections', variables('SecuritycopilotConnectionName'))]", + "connectionName": "[variables('SecuritycopilotConnectionName')]", + "id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/Securitycopilot')]" + }, + "office365": { + "connectionId": "[resourceId('Microsoft.Web/connections', variables('Office365ConnectionName'))]", + "connectionName": "[variables('Office365ConnectionName')]", + "id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/Office365')]" + } + } + } + } + }, + "type": "Microsoft.Logic/workflows", + "apiVersion": "2017-07-01", + "name": "[parameters('PlaybookName')]", + "location": "[resourceGroup().location]", + "tags": { + "Created By": "Jamil Mirza", + "CreatedDate": "8/8/2024 10:52:27 PM" + }, + "apiVersion": "2017-07-01", + "dependsOn": [ + "[resourceId('Microsoft.Web/connections', variables('Office365ConnectionName'))]", + "[resourceId('Microsoft.Web/connections', variables('SecuritycopilotConnectionName'))]" + ] + }, + { + "type": "Microsoft.Web/connections", + "apiVersion": "2016-06-01", + "name": "[variables('Office365ConnectionName')]", + "location": "[resourceGroup().location]", + "kind": "V1", + "properties": { + "displayName": "[variables('Office365ConnectionName')]", + "customParameterValues": { + }, + "api": { + "id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/Office365')]" + } + } + }, + { + "type": "Microsoft.Web/connections", + "apiVersion": "2016-06-01", + "name": "[variables('SecuritycopilotConnectionName')]", + "location": "[resourceGroup().location]", + "kind": "V1", + "properties": { + "displayName": "[variables('SecuritycopilotConnectionName')]", + "customParameterValues": { + }, + "api": { + "id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/Securitycopilot')]" + } + } + } + + ] +} diff --git a/Logic Apps/LatestCISAVulnerabilities/Readme.md b/Logic Apps/LatestCISAVulnerabilities/Readme.md new file mode 100644 index 00000000..09cb6625 --- /dev/null +++ b/Logic Apps/LatestCISAVulnerabilities/Readme.md @@ -0,0 +1,40 @@ +# Get the latest CISA vulnerabilities and check devices within my organization +Author: Jamil Mirza +This solution checks the latest CISA feed every 24 hours and queries the CVE finding against devices within Microsoft Defender for Endpoint. Security Copilot will check for remediation actions and enrich the description. It will also tell you how many (if any) devices in your organization are vulnerable to the reported CVE. Finally, an email with the findings is submitted to a mailbox which can be edited after the install of the Logic App. + +## Deploy the Solution + +### Step 1: Deploy the Logic App + +Click the button below to deploy the Logic App. Select the resource group and change the Playbook name (if needed). You will enter this information in the deployment screen. + +[![Deploy to Azure](https://aka.ms/deploytoazurebutton)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2FAzure%2FSecurity-Copilot%2Fmain%2FLogic%2520Apps%2FLatestCISAVulnerabilities%2FCISALogicApp.json) + +### Step 2: Initialize O365 and Security Copilot API connections + +Open your new Logic App. + +Click "API Connections" + +![alt text](image-2.png) + +Authorize both of the API connections. +![alt text](image-1.png) + +### Step 3: Change the email address + +Open your new Logic App. + +1. Click "Logic App Designer" (located under "Development Tools") + +2. Click "Parameters" + +3. Edit the default value to an email address of your choice. + +![alt text](image-3.png) + +### Step 4: Email Notification + +Below is an example of the email received for each finding. + +![alt text](image-5.png) diff --git a/Logic Apps/LatestCISAVulnerabilities/image-1.png b/Logic Apps/LatestCISAVulnerabilities/image-1.png new file mode 100644 index 00000000..e62256f7 Binary files /dev/null and b/Logic Apps/LatestCISAVulnerabilities/image-1.png differ diff --git a/Logic Apps/LatestCISAVulnerabilities/image-2.png b/Logic Apps/LatestCISAVulnerabilities/image-2.png new file mode 100644 index 00000000..400034c3 Binary files /dev/null and b/Logic Apps/LatestCISAVulnerabilities/image-2.png differ diff --git a/Logic Apps/LatestCISAVulnerabilities/image-3.png b/Logic Apps/LatestCISAVulnerabilities/image-3.png new file mode 100644 index 00000000..73c9fbd5 Binary files /dev/null and b/Logic Apps/LatestCISAVulnerabilities/image-3.png differ diff --git a/Logic Apps/LatestCISAVulnerabilities/image-4.png b/Logic Apps/LatestCISAVulnerabilities/image-4.png new file mode 100644 index 00000000..18626b26 Binary files /dev/null and b/Logic Apps/LatestCISAVulnerabilities/image-4.png differ diff --git a/Logic Apps/LatestCISAVulnerabilities/image-5.png b/Logic Apps/LatestCISAVulnerabilities/image-5.png new file mode 100644 index 00000000..bb4a8a35 Binary files /dev/null and b/Logic Apps/LatestCISAVulnerabilities/image-5.png differ diff --git a/Logic Apps/Readme.md b/Logic Apps/Readme.md index f7c80be5..c06441e7 100644 --- a/Logic Apps/Readme.md +++ b/Logic Apps/Readme.md @@ -29,4 +29,4 @@ Here you will find an array of differnt playbooks aligned with CyberSec scenario **Attention Customers!** -When utilizing Logic Apps on Copilot, please be aware that it may result in increased consumption of Secure Compute Units (SCUs). We recommend closely monitoring SCU usage to optimize resource management. +When utilizing Logic Apps on Copilot, please be aware that it may result in increased consumption of Secure Compute Units (SCUs). We recommend closely monitoring SCU usage to optimize resource management. \ No newline at end of file diff --git a/Logic Apps/SecCopilot-UserReportedPhishing-FuncApp_parsingV2/FunctionApp.zip b/Logic Apps/SecCopilot-UserReportedPhishing-FuncApp_parsingV2/FunctionApp.zip new file mode 100644 index 00000000..12b129c3 Binary files /dev/null and b/Logic Apps/SecCopilot-UserReportedPhishing-FuncApp_parsingV2/FunctionApp.zip differ 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 new file mode 100644 index 00000000..4bde5efd --- /dev/null +++ b/Logic Apps/SecCopilot-UserReportedPhishing-FuncApp_parsingV2/README.md @@ -0,0 +1,185 @@ +# Phishing Analysis with Security Copilot +**Author:** Craig Freyman + +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 + +1. **Azure Resources:** + - Azure subscription + - Contributor rights to the deployment target resource group + - [Security Copilot Security Compute Units](https://learn.microsoft.com/en-us/copilot/security/get-started-security-copilot) + - Microsoft Sentinel workspace (optional) + +2. **Email Configuration:** + - [Office 365 shared mailbox for monitoring](https://learn.microsoft.com/en-us/microsoft-365/admin/email/create-a-shared-mailbox?view=o365-worldwide) + - Permissions to manage email configurations + +## Deployment Steps + +### 1. Deploy Function App + +[![Deploy to Azure](https://aka.ms/deploytoazurebutton)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2FAzure%2FCopilot-For-Security%2Fmain%2FLogic%20Apps%2FSecCopilot-UserReportedPhishing-FuncApp_parsingV2%2Ffunctionapp_azuredeploy.json) + +Required Parameters: +- FunctionAppName (name is prepended with "phish" and random characters are appended) +- FunctionAppResourceGroup + +Wait for the Function App to fully deploy before moving on to step 2. + +### 2. Deploy Logic App + +[![Deploy to Azure](https://aka.ms/deploytoazurebutton)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2FAzure%2FCopilot-For-Security%2Fmain%2FLogic%20Apps%2FSecCopilot-UserReportedPhishing-FuncApp_parsingV2%2Flogicapp_azuredeploy.json) + +Required Parameters: +- SubscriptionId +- LogicAppName +- FunctionAppName (step 1) +- FunctionAppResourceGroup (step 1) +- SharedMailboxAddress +- HTMLReportRecipient + +Optional Sentinel Parameters (use "none" if not using Sentinel): +- LogAnalyticsWorkspaceName +- LogAnalyticsWorkspaceId +- LogAnalyticsResourceGroup + +### 3. Configure API Connections + +1. Open the Logic App in Azure Portal +2. Authorize these connections: + - Office 365 (shared mailbox access) + - Security Copilot + - Azure Monitor Logs (if using Sentinel) + - Sentinel (if using Sentinel) + +### 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: + +1. Open the Log Analytics workspace and go to "Access control (IAM)" +![alt text](image.png) +2. Select "Log Analytics Reader" +![alt text](image-1.png) +3. Select "Managed Identity" and select the name of your Logic App +![alt text](image-2.png) +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 email monitoring and analysis +- Detailed security assessment using Security Copilot +- Classification of emails (Phishing, Spam, Legitimate, Suspicious) +- 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 +- 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 new file mode 100644 index 00000000..142096f7 --- /dev/null +++ b/Logic Apps/SecCopilot-UserReportedPhishing-FuncApp_parsingV2/functionapp_azuredeploy.json @@ -0,0 +1,103 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "FunctionAppName": { + "defaultValue": "phish", + "minLength": 3, + "maxLength": 24, + "type": "String", + "metadata": { + "description": "Enter the base name for the Function App." + } + } + }, + "variables": { + "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", + "name": "[variables('FunctionAppName')]", + "location": "[resourceGroup().location]", + "sku": { + "name": "Y1", + "tier": "Dynamic" + }, + "kind": "functionapp,linux", + "properties": { + "name": "[variables('FunctionAppName')]", + "workerSize": "0", + "workerSizeId": "0", + "numberOfWorkers": "1", + "reserved": true + } + }, + { + "type": "Microsoft.Web/sites", + "apiVersion": "2023-12-01", + "name": "[variables('FunctionAppName')]", + "location": "[resourceGroup().location]", + "dependsOn": [ + "[resourceId('Microsoft.Storage/storageAccounts', variables('StorageAccountName'))]" + ], + "kind": "functionapp,linux", + "identity": { + "type": "SystemAssigned" + }, + "properties": { + "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('FunctionAppName'))]", + "httpsOnly": true, + "reserved": true, + "siteConfig": { + "linuxFxVersion": "python|3.10", + "minTlsVersion": "1.2", + "appSettings": [ + { + "name": "FUNCTIONS_EXTENSION_VERSION", + "value": "~4" + }, + { + "name": "FUNCTIONS_WORKER_RUNTIME", + "value": "python" + }, + { + "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')]" + } + ] + } + + } + + } + ], + "outputs": { + "functionAppUrl": { + "type": "String", + "value": "[concat('https://', variables('FunctionAppName'), '.azurewebsites.net')]" + }, + "functionAppName": { + "type": "String", + "value": "[variables('FunctionAppName')]" + } + } +} diff --git a/Logic Apps/SecCopilot-UserReportedPhishing-FuncApp_parsingV2/image-1.png b/Logic Apps/SecCopilot-UserReportedPhishing-FuncApp_parsingV2/image-1.png new file mode 100644 index 00000000..6d6624d3 Binary files /dev/null and b/Logic Apps/SecCopilot-UserReportedPhishing-FuncApp_parsingV2/image-1.png differ diff --git a/Logic Apps/SecCopilot-UserReportedPhishing-FuncApp_parsingV2/image-2.png b/Logic Apps/SecCopilot-UserReportedPhishing-FuncApp_parsingV2/image-2.png new file mode 100644 index 00000000..f741aecc Binary files /dev/null and b/Logic Apps/SecCopilot-UserReportedPhishing-FuncApp_parsingV2/image-2.png differ diff --git a/Logic Apps/SecCopilot-UserReportedPhishing-FuncApp_parsingV2/image.png b/Logic Apps/SecCopilot-UserReportedPhishing-FuncApp_parsingV2/image.png new file mode 100644 index 00000000..b80eb6ff Binary files /dev/null and b/Logic Apps/SecCopilot-UserReportedPhishing-FuncApp_parsingV2/image.png differ diff --git a/Logic Apps/SecCopilot-UserReportedPhishing-FuncApp_parsingV2/logicapp_azuredeploy.json b/Logic Apps/SecCopilot-UserReportedPhishing-FuncApp_parsingV2/logicapp_azuredeploy.json new file mode 100644 index 00000000..d21b3d66 --- /dev/null +++ b/Logic Apps/SecCopilot-UserReportedPhishing-FuncApp_parsingV2/logicapp_azuredeploy.json @@ -0,0 +1,1313 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "SubscriptionId": { + "type": "String", + "metadata": { + "description": "The subscription ID where resources will be deployed." + } + }, + "LogicAppName": { + "type": "String", + "metadata": { + "description": "The name of the Logic App to be deployed." + } + }, + "FunctionAppName": { + "type": "String", + "metadata": { + "description": "The name of the Azure Function App created during previous Function App deployment." + } + }, + "FunctionAppResourceGroup": { + "type": "String", + "metadata": { + "description": "The name of the resource group where the Function App is deployed." + } + }, + "LogAnalyticsWorkspaceName": { + "type": "String", + "metadata": { + "description": "The name of the Log Analytics Workspace for where Sentinel lives." + } + }, + "LogAnalyticsWorkspId": { + "type": "String", + "metadata": { + "description": "The workspace ID of the Log Analytics Sentinel Workspace." + } + }, + "LogAnalyticsResourceGroup": { + "type": "String", + "metadata": { + "description": "The resource group name of the Log Analytics Sentinel Workspace." + } + }, + "SharedMailboxAddress": { + "type": "String", + "metadata": { + "description": "Email address of the shared O365 mailbox." + } + }, + "HTMLReportRecipient": { + "type": "String", + "metadata": { + "description": "Where to send the HTML report." + } + } + + + }, + "variables": { + "Office365-ConnectionName": "[concat('Office365-1-', parameters('LogicAppName'))]", + "Securitycopilot-ConnectionName": "[concat('Securitycopilot-1-', parameters('LogicAppName'))]", + "ConversionService-ConnectionName": "[concat('ConversionService-1-', parameters('LogicAppName'))]", + "AzureSentinel-ConnectionName": "[concat('azuresentinel-', parameters('LogicAppName'))]", + "AzureMonitorLogs-ConnectionName": "[concat('azuremonitorlogs-', parameters('LogicAppName'))]", + "FunctionName01": "parse_email_functionapp", + "FunctionName02": "clean_json_functionapp", + "FunctionName03": "generate_html_report_functionapp", + "FunctionName04": "extract_regex_functionapp" + + }, + "resources": [ + { + "type": "Microsoft.Web/connections", + "apiVersion": "2016-06-01", + "name": "[variables('AzureMonitorLogs-ConnectionName')]", + "location": "[resourceGroup().location]", + "kind": "V1", + "properties": { + "displayName": "[variables('AzureMonitorLogs-ConnectionName')]", + "parameterValueSet": { + "name": "managedIdentityAuth", + "values": {} + }, + "api": { + "category": "Standard", + "id": "[concat('/subscriptions/', parameters('SubscriptionId'), '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/azuremonitorlogs')]", + "type": "Microsoft.Web/locations/managedApis" + }, + "customParameterValues": {} + + } + }, + { + "type": "Microsoft.Web/connections", + "apiVersion": "2016-06-01", + "name": "[variables('AzureSentinel-ConnectionName')]", + "location": "[resourceGroup().location]", + "kind": "V1", + "properties": { + "displayName": "[variables('AzureSentinel-ConnectionName')]", + "parameterValueType": "Alternative", + "customParameterValues": {}, + "api": { + + "id": "[concat('/subscriptions/', parameters('SubscriptionId'), '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/azuresentinel')]" + } + } + }, + + { + "type": "Microsoft.Web/connections", + "apiVersion": "2016-06-01", + "name": "[variables('ConversionService-ConnectionName')]", + "location": "[resourceGroup().location]", + "kind": "V1", + "properties": { + "displayName": "[variables('ConversionService-ConnectionName')]", + "api": { + "id": "[concat('/subscriptions/', parameters('SubscriptionId'), '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/conversionservice')]" + } + } + }, + { + + "type": "Microsoft.Web/connections", + "apiVersion": "2016-06-01", + "name": "[variables('Office365-ConnectionName')]", + "location": "[resourceGroup().location]", + "kind": "V1", + "properties": { + "displayName": "[variables('Office365-ConnectionName')]", + "api": { + "id": "[concat('/subscriptions/', parameters('SubscriptionId'), '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/office365')]" + } + } + }, + { + "type": "Microsoft.Web/connections", + "apiVersion": "2016-06-01", + "name": "[variables('Securitycopilot-ConnectionName')]", + "location": "[resourceGroup().location]", + "kind": "V1", + "properties": { + "displayName": "[variables('Securitycopilot-ConnectionName')]", + "api": { + "id": "[concat('/subscriptions/', parameters('SubscriptionId'), '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/Securitycopilot')]" + } + } + }, + + { + "type": "Microsoft.Logic/workflows", + "apiVersion": "2017-07-01", + "name": "[parameters('LogicAppName')]", + "location": "[resourceGroup().location]", + "dependsOn": [ + "[resourceId('Microsoft.Web/connections', variables('Office365-ConnectionName'))]", + "[resourceId('Microsoft.Web/connections', variables('Securitycopilot-ConnectionName'))]", + "[resourceId('Microsoft.Web/connections', variables('ConversionService-ConnectionName'))]", + "[resourceId('Microsoft.Web/connections', variables('AzureMonitorLogs-ConnectionName'))]", + "[resourceId('Microsoft.Web/connections', variables('AzureSentinel-ConnectionName'))]" + ], + "identity": { + "type": "SystemAssigned" + }, + "properties": { + "state": "Disabled", + "definition": { + "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "$connections": { + "defaultValue": {}, + "type": "Object" + } + }, + "triggers": { + "When_a_new_email_arrives_in_a_shared_mailbox_(V2)": { + "recurrence": { + "interval": 3, + "frequency": "Minute" + }, + "evaluatedRecurrence": { + "interval": 3, + "frequency": "Minute" + }, + "splitOn": "@triggerBody()?['value']", + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['office365-1']['connectionId']" + } + }, + "method": "get", + "path": "/v2/SharedMailbox/Mail/OnNewEmail", + "queries": { + "mailboxAddress": "[parameters('SharedMailboxAddress')]", + "importance": "Any", + "hasAttachments": false, + "includeAttachments": true, + "folderId": "Inbox" + } + } + } + }, + "actions": { + "Initialize_is_defender_incident": { + "runAfter": {}, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "is_defender_incident", + "type": "boolean" + } + ] + } + }, + "Initialize_network_message_id": { + "runAfter": { + "Initialize_is_defender_incident": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "network_message_id", + "type": "string" + } + ] + } + }, + "If_email_subject_contains_phishing": { + "actions": { + "Show_network_message_id_here": { + "runAfter": { + "Parse_JSON_NetworkMessageId": [ + "Succeeded" + ] + }, + "type": "Compose", + "inputs": "@body('Parse_JSON_NetworkMessageId')?['matched_value']" + }, + "Set_variable_defender_incident_true": { + "runAfter": { + "Set_variable_network_message_id": [ + "Succeeded" + ] + }, + "type": "SetVariable", + "inputs": { + "name": "is_defender_incident", + "value": true + } + }, + "Set_variable_network_message_id": { + "runAfter": { + "Show_network_message_id_here": [ + "Succeeded" + ] + }, + "type": "SetVariable", + "inputs": { + "name": "network_message_id", + "value": "@outputs('Show_network_message_id_here')" + } + }, + "Parse_JSON_NetworkMessageId": { + "runAfter": { + "Find_NetworkMessageId": [ + "Succeeded" + ] + }, + "type": "ParseJson", + "inputs": { + "content": "@body('Find_NetworkMessageId')", + "schema": { + "type": "object", + "properties": { + "input": { + "type": "object", + "properties": { + "pattern": { + "type": "string" + }, + "subject": { + "type": "string" + } + } + }, + "match_found": { + "type": "boolean" + }, + "matched_value": { + "type": "string" + } + } + } + } + }, + "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": { + "Initialize_network_message_id": [ + "Succeeded" + ] + }, + "else": { + "actions": { + "Set_variable_is_defender_incident_false": { + "type": "SetVariable", + "inputs": { + "name": "is_defender_incident", + "value": false + } + } + } + }, + "expression": { + "and": [ + { + "contains": [ + "@triggerBody()?['subject']", + "Phishing:" + ] + } + ] + }, + "type": "If" + }, + "Export_email_(V2)": { + "runAfter": { + "If_email_subject_contains_phishing": [ + "Succeeded" + ] + }, + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['office365-1']['connectionId']" + } + }, + "method": "get", + "path": "/codeless/beta/me/messages/@{encodeURIComponent(triggerBody()?['id'])}/$value", + "queries": { + "mailboxAddress": "[parameters('SharedMailboxAddress')]" + } + } + }, + "Initialize_raw_email": { + "runAfter": { + "Export_email_(V2)": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "raw_email", + "type": "string", + "value": "@{body('Export_email_(V2)')}" + } + ] + } + }, + "For_each_raw_email_attached_to_carrier_email": { + "foreach": "@triggerBody()?['attachments']", + "actions": { + "Check_if_msg_in_binary_format": { + "actions": { + "Decode_Base64_ContentBytes": { + "type": "Compose", + "inputs": "@base64ToString(item()?['contentBytes'])" + }, + "Set_variable_raw_email_msg_binary_decoded": { + "runAfter": { + "Decode_Base64_ContentBytes": [ + "Succeeded" + ] + }, + "type": "SetVariable", + "inputs": { + "name": "raw_email", + "value": "@string(outputs('Decode_Base64_ContentBytes'))" + } + } + }, + "runAfter": { + "Show_current_email_here": [ + "Succeeded" + ] + }, + "else": { + "actions": {} + }, + "expression": { + "or": [ + { + "equals": [ + "@outputs('Show_current_email_here')?['contentType']", + "message/rfc822" + ] + }, + { + "equals": [ + "@outputs('Show_current_email_here')?['contentType']", + "application/octet-stream" + ] + }, + { + "equals": [ + "@outputs('Show_current_email_here')?['contentType']", + "application/vnd.ms-outlook" + ] + } + ] + }, + "type": "If" + }, + "Show_current_email_here": { + "type": "Compose", + "inputs": "@item()" + } + }, + "runAfter": { + "Initialize_raw_email": [ + "Succeeded" + ] + }, + "type": "Foreach" + }, + "Process_parseEmail_JSON": { + "runAfter": { + "Parse_Email": [ + "Succeeded" + ] + }, + "type": "ParseJson", + "inputs": { + "content": "@body('Parse_Email')", + "schema": { + "type": "object", + "properties": { + "email_content": { + "type": "object", + "properties": { + "message_id": { + "type": "string" + }, + "sender": { + "type": "string" + }, + "return_path": { + "type": "string" + }, + "receiver": { + "type": "string" + }, + "reply_to": { + "type": "string" + }, + "subject": { + "type": "string" + }, + "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" + } + }, + "domains": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + } + } + }, + "Initialize_email_body": { + "runAfter": { + "Process_parseEmail_JSON": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "email_body", + "type": "string", + "value": "@body('Process_parseEmail_JSON')?['email_content']?['body']" + } + ] + } + }, + "Initialize_attachments": { + "runAfter": { + "Initialize_email_body": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "attachments", + "type": "array", + "value": "@body('Process_parseEmail_JSON')?['email_content']?['attachments']" + } + ] + } + }, + "Initialize_raw_urls": { + "runAfter": { + "Initialize_attachments": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "raw_urls", + "type": "array", + "value": "@body('Process_parseEmail_JSON')?['email_content']?['urls']" + } + ] + } + }, + "Initialize_urls": { + "runAfter": { + "Initialize_raw_urls": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "urls", + "type": "array" + } + ] + } + }, + "Initialize_domains": { + "runAfter": { + "Initialize_urls": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "domains", + "type": "array", + "value": "@body('Process_parseEmail_JSON')?['domains']" + } + ] + } + }, + "For_each_raw_url_truncate_if_over_100_char": { + "foreach": "@variables('raw_urls')", + "actions": { + "Condition": { + "actions": { + "Append_shortened_url_to_url_array": { + "type": "AppendToArrayVariable", + "inputs": { + "name": "urls", + "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']))" + } + } + }, + "else": { + "actions": { + "Append_original_url_to_url_array": { + "type": "AppendToArrayVariable", + "inputs": { + "name": "urls", + "value": "@if(equals(item()['expanded_url'], 'Not Applicable'), item()['original_url'], item()['expanded_url'])" + } + } + } + }, + "expression": { + "or": [ + { + "greater": [ + "@length(item()['original_url'])", + 100 + ] + }, + { + "greater": [ + "@length(item()['expanded_url'])", + 100 + ] + } + ] + }, + "type": "If" + } + }, + "runAfter": { + "Initialize_domains": [ + "Succeeded" + ] + }, + "type": "Foreach" + }, + "Show_URL_array_here": { + "runAfter": { + "For_each_raw_url_truncate_if_over_100_char": [ + "Succeeded" + ] + }, + "type": "Compose", + "inputs": "@variables('urls')" + }, + "Security_Copilot_Email_Analysis": { + "runAfter": { + "Show_URL_array_here": [ + "Succeeded" + ] + }, + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['securitycopilot-1']['connectionId']" + } + }, + "method": "post", + "body": { + "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": { + "type": "fixed", + "count": 10, + "interval": "PT10M" + } + } + }, + + "Parse_normalized_JSON": { + "runAfter": { + "Normalize_JSON": [ + "Succeeded" + ] + }, + "type": "ParseJson", + "inputs": { + "content": "@body('Normalize_JSON')", + "schema": { + "type": "object", + "properties": { + "email_summary": { + "type": "object", + "properties": { + "subject": { + "type": "string" + }, + "content_summary": { + "type": "string" + } + } + }, + "behavioral_triggers": { + "type": "object", + "properties": { + "tone": { + "type": "string" + }, + "justification": { + "type": "string" + }, + "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": { + "is_consistent": { + "type": "string" + }, + "contradictions_or_vagueness": { + "type": "string" + }, + "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": { + "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": { + "is_relevant": { + "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" + } + } + }, + "url_analysis": { + "type": "object", + "properties": { + "url_categorization": { + "type": "object", + "properties": { + "primary_action_urls": { + "type": "array" + }, + "informational_urls": { + "type": "array" + }, + "stylistic_framework_urls": { + "type": "array" + } + } + }, + "primary_action_validation": { + "type": "object", + "properties": { + "relevance": { + "type": "string" + }, + "domain_alignment": { + "type": "string" + }, + "necessity": { + "type": "string" + }, + "risks": { + "type": "string" + } + } + } + } + }, + "pretense_vs_intent_mapping": { + "type": "object", + "properties": { + "stated_purpose": { + "type": "string" + }, + "true_intent": { + "type": "string" + }, + "gaps": { + "type": "string" + } + } + }, + "bec_reconnaissance_detection": { + "type": "object", + "properties": { + "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" + } + } + } + } + } + } + }, + + + "Initialize_systemalert_id_query_results": { + "runAfter": { + "Generate_HTML_report_from_JSON": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "systemalertid_query_results", + "type": "array" + } + ] + } + }, + "If_is_defender_incident_true": { + "actions": { + "Html_to_text": { + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['conversionservice-1']['connectionId']" + } + }, + "method": "post", + "body": "

@{body('Generate_HTML_report_from_JSON')}

", + "path": "/html2text" + } + }, + "Alert_-_Get_incident_from_systemalertid": { + "runAfter": { + "If_empty_results_wait_15_minutes_for_logs_to_show_up": [ + "Succeeded" + ] + }, + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['azuresentinel']['connectionId']" + } + }, + "method": "get", + "path": "[concat('/Incidents/subscriptions/@{encodeURIComponent(''', parameters('SubscriptionId'), ''')}/resourceGroups/@{encodeURIComponent(''', parameters('LogAnalyticsResourceGroup'),''')}/workspaces/@{encodeURIComponent(''', parameters('LogAnalyticsWorkspId') ,''')}/alerts/@{encodeURIComponent(variables(''systemalertid_query_results'')[0][''SystemAlertId''])}')]" + } + }, + "Add_comment_to_incident_(V3)_armid": { + "runAfter": { + "Alert_-_Get_incident_from_systemalertid": [ + "Succeeded" + ] + }, + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['azuresentinel']['connectionId']" + } + }, + "method": "post", + "body": { + "incidentArmId": "@body('Alert_-_Get_incident_from_systemalertid')?['id']", + "message": "

********** Phishing Analysis Generated by Security Copilot **********

@{body('Html_to_text')}


---
Report generated: @{utcNow()}
Generated with AI, check for accuracy.


" + }, + "path": "/Incidents/Comment" + } + }, + "Send_an_email_from_a_shared_mailbox_(V2)_with_incident_url": { + "runAfter": { + "Add_comment_to_incident_(V3)_armid": [ + "Succeeded" + ] + }, + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['office365-1']['connectionId']" + } + }, + "method": "post", + "body": { + "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.

***********

", + "Importance": "Normal" + }, + "path": "/v2/SharedMailbox/Mail" + } + }, + "If_empty_results_wait_15_minutes_for_logs_to_show_up": { + "actions": { + "Delay_15_min": { + "type": "Wait", + "inputs": { + "interval": { + "count": 15, + "unit": "Minute" + } + } + }, + "Second_try_set_variable_systemalertid_query_results": { + "runAfter": { + "Query_to_get_systemalertid_second_try": [ + "Succeeded" + ] + }, + "type": "SetVariable", + "inputs": { + "name": "systemalertid_query_results", + "value": "@body('Query_to_get_systemalertid_second_try')?['value']" + } + }, + "Query_to_get_systemalertid_second_try": { + "runAfter": { + "Delay_15_min": [ + "Succeeded" + ] + }, + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['azuremonitorlogs']['connectionId']" + } + }, + "method": "post", + "body": "SecurityAlert\n| where isnotempty(Entities)\n| extend MailEntities = parse_json(Entities)\n| mv-expand MailEntities\n| where MailEntities.Type == \"mailMessage\"\n| extend NetworkMessageId = tostring(MailEntities.NetworkMessageId)\n| where NetworkMessageId == \"@{variables('network_message_id')}\"\n| distinct SystemAlertId", + "path": "/queryData", + "queries": { + "subscriptions": "[parameters('SubscriptionId')]", + "resourcegroups": "[parameters('LogAnalyticsResourceGroup')]", + "resourcetype": "Log Analytics Workspace", + "resourcename": "[parameters('LogAnalyticsWorkspaceName')]", + "timerange": "14d" + } + } + } + }, + "runAfter": { + "Set_variable_systemalertid_query_results": [ + "Succeeded" + ] + }, + "else": { + "actions": {} + }, + "expression": { + "and": [ + { + "less": [ + "@length(body('Query_to_get_systemalertid')?['value'])", + 1 + ] + } + ] + }, + "type": "If" + }, + "Set_variable_systemalertid_query_results": { + "runAfter": { + "Query_to_get_systemalertid": [ + "Succeeded" + ] + }, + "type": "SetVariable", + "inputs": { + "name": "systemalertid_query_results", + "value": "@body('Query_to_get_systemalertid')?['value']" + } + }, + "Query_to_get_systemalertid": { + "runAfter": { + "Html_to_text": [ + "Succeeded" + ] + }, + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['azuremonitorlogs']['connectionId']" + } + }, + "method": "post", + "body": "SecurityAlert\n| where isnotempty(Entities)\n| extend MailEntities = parse_json(Entities)\n| mv-expand MailEntities\n| where MailEntities.Type == \"mailMessage\"\n| extend NetworkMessageId = tostring(MailEntities.NetworkMessageId)\n| where NetworkMessageId == \"@{variables('network_message_id')}\"\n| distinct SystemAlertId", + "path": "/queryData", + "queries": { + "subscriptions": "[parameters('SubscriptionId')]", + "resourcegroups": "[parameters('LogAnalyticsResourceGroup')]", + "resourcetype": "Log Analytics Workspace", + "resourcename": "[parameters('LogAnalyticsWorkspaceName')]", + "timerange": "14d" + } + } + } + }, + "runAfter": { + "Initialize_systemalert_id_query_results": [ + "Succeeded" + ] + }, + "else": { + "actions": { + "Send_an_email_from_a_shared_mailbox_(V2)_without_incident_url": { + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['office365-1']['connectionId']" + } + }, + "method": "post", + "body": { + "MailboxAddress": "[parameters('SharedMailboxAddress')]", + "To": "[parameters('HTMLReportRecipient')]", + "Subject": "Phishing Email Analysis", + "Body": "


@{body('Generate_HTML_report_from_JSON')}

***********

Generated with AI, check for accuracy.

***********

", + "Attachments": [ + { + "Name": "original_email.eml", + "ContentBytes": "@base64(body('Export_email_(V2)'))" + } + ], + "Importance": "Normal" + }, + "path": "/v2/SharedMailbox/Mail" + } + } + } + }, + "expression": { + "and": [ + { + "equals": [ + "@variables('is_defender_incident')", + true + ] + } + ] + }, + "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": {} + }, + "parameters": { + "$connections": { + "value": { + "office365-1": { + "id": "[concat('/subscriptions/', parameters('SubscriptionId'), '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/office365')]", + "connectionId": "[resourceId('Microsoft.Web/connections', variables('Office365-ConnectionName'))]", + "connectionName": "[variables('Office365-ConnectionName')]" + }, + + "securitycopilot-1": { + "id": "[concat('/subscriptions/', parameters('SubscriptionId'), '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/securitycopilot')]", + "connectionId": "[resourceId('Microsoft.Web/connections', variables('Securitycopilot-ConnectionName'))]", + "connectionName": "[variables('Securitycopilot-ConnectionName')]" + }, + + "azuresentinel": { + "id": "[concat('/subscriptions/', parameters('SubscriptionId'), '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/azuresentinel')]", + "connectionId": "[resourceId('Microsoft.Web/connections', variables('AzureSentinel-ConnectionName'))]", + "connectionName": "[variables('AzureSentinel-ConnectionName')]", + "connectionProperties": { + "authentication": { + "type": "ManagedServiceIdentity" + } + } + }, + "azuremonitorlogs": { + "id": "[concat('/subscriptions/', parameters('SubscriptionId'), '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/azuremonitorlogs')]", + "connectionId": "[resourceId('Microsoft.Web/connections', variables('AzureMonitorLogs-ConnectionName'))]", + "connectionName": "[variables('AzureMonitorLogs-ConnectionName')]", + "connectionProperties": { + "authentication": { + "type": "ManagedServiceIdentity" + } + } + }, + "conversionservice-1": { + "id": "[concat('/subscriptions/', parameters('SubscriptionId'), '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/conversionservice')]", + "connectionId": "[resourceId('Microsoft.Web/connections', variables('ConversionService-ConnectionName'))]", + "connectionName": "[variables('ConversionService-ConnectionName')]" + } + } + } + } + } + } + ] +} \ No newline at end of file diff --git a/Logic Apps/SecCopilot-UserReportedPhishing/DefenderKqlPlugins_automation.yaml b/Logic Apps/SecCopilot-UserReportedPhishing/DefenderKqlPlugins_automation.yaml index 6446d4ea..d4d5ec68 100644 --- a/Logic Apps/SecCopilot-UserReportedPhishing/DefenderKqlPlugins_automation.yaml +++ b/Logic Apps/SecCopilot-UserReportedPhishing/DefenderKqlPlugins_automation.yaml @@ -13,8 +13,8 @@ SkillGroups: Description: The subject of the email Required: true Settings: - Target: Defender - Template: |- + Target: Defender + Template: |- EmailEvents | where Subject == '{{subject}}' - Name: GetUrlClicksBySubjectAndSenderEmail @@ -28,8 +28,8 @@ SkillGroups: Description: The sender of the email Required: true Settings: - Target: Defender - Template: |- + Target: Defender + Template: |- let email = EmailEvents | where Subject == '{{subject}}' and SenderFromAddress == '{{senderEmail}}' | project NetworkMessageId; diff --git a/Logic Apps/SecurityCopilot-Sentinel-Incident-Investigation/azuredeploy.json b/Logic Apps/SecurityCopilot-Sentinel-Incident-Investigation/azuredeploy.json new file mode 100644 index 00000000..3101ea3a --- /dev/null +++ b/Logic Apps/SecurityCopilot-Sentinel-Incident-Investigation/azuredeploy.json @@ -0,0 +1,3020 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "title": "SecurityCopilot-Sentinel-Incident-Investigation", + "description": "Logic App automating incident investigation within Microsoft Sentinel using Security Copilot", + "prerequisites": "", + "postDeployment": [ + ], + "prerequisitesDeployTemplateFile": "", + "lastUpdateTime": "", + "entities": [ + ], + "tags": [ + ], + "support": { + "tier": "community", + "armtemplate": "Generated from https://github.com/Azure/Azure-Sentinel/tree/master/Tools/Playbook-ARM-Template-Generator" + }, + "author": { + "name": "" + } + }, + "parameters": { + "PlaybookName": { + "defaultValue": "SecurityCopilot-Sentinel-Incident-Investigation", + "type": "string" + }, + "CallScpForIPsWithAbuseIPDB": { + "type": "Bool", + "metadata": { + "description": "Leave 'true' if you want Security Copilot to investigate the IP addresses on AbuseIPDB. Set 'false' otherwise." + }, + "defaultValue": true + }, + "CallScpForIPsWithMDTI": { + "type": "Bool", + "metadata": { + "description": "Leave 'true' if you want Security Copilot to investigate the IP addresses on MDTI. Set 'false' otherwise." + }, + "defaultValue": true + }, + "CallScpForAccounts": { + "type": "Bool", + "metadata": { + "description": "Leave 'true' if you want Security Copilot to investigate the accounts on MS Entra. Set 'false' otherwise." + }, + "defaultValue": true + }, + "CallScpForUserRisk": { + "type": "Bool", + "metadata": { + "description": "Leave 'true' if you want Security Copilot to check the risk level of the users on MS Entra. Set 'false' otherwise." + }, + "defaultValue": true + }, + "CallScpForHosts": { + "type": "Bool", + "metadata": { + "description": "Leave 'true' if you want Security Copilot to investigate the devices on Intune. Set 'false' otherwise." + }, + "defaultValue": true + }, + "CallScpForCommandLines": { + "type": "Bool", + "metadata": { + "description": "Leave 'true' if you want Security Copilot to investigate the command lines by using its script analysis capability. Set 'false' otherwise." + }, + "defaultValue": true + }, + "CallScpForFilehashesWithMDTI": { + "type": "Bool", + "metadata": { + "description": "Leave 'true' if you want Security Copilot to investigate the file hashes on MDTI. Set 'false' otherwise." + }, + "defaultValue": true + }, + "CallScpForUrlsWithMDTI": { + "type": "Bool", + "metadata": { + "description": "Leave 'true' if you want Security Copilot to investigate the Urls on MDTI. Set 'false' otherwise." + }, + "defaultValue": true + }, + "CallScpForDomainsWithMDTI": { + "type": "Bool", + "metadata": { + "description": "Leave 'true' if you want Security Copilot to investigate the domains on MDTI. Set 'false' otherwise." + }, + "defaultValue": true + }, + "SkipIPs": { + "type": "Array", + "metadata": { + "description": "Enter, as an array of strings, the IPs that you do not want Security Copilot to investigate" + }, + "defaultValue": [] + }, + "SkipAccounts": { + "type": "Array", + "metadata": { + "description": "Enter, as an array of strings, the accounts that you do not want Security Copilot to investigate" + }, + "defaultValue": [] + }, + "SkipHosts": { + "type": "Array", + "metadata": { + "description": "Enter, as an array of strings, the devices that you do not want Security Copilot to investigate" + }, + "defaultValue": [] + }, + "SkipCommandLines": { + "type": "Array", + "metadata": { + "description": "Enter, as an array of strings, the command lines that you do not want Security Copilot to investigate" + }, + "defaultValue": ["cmd.exe","explorer.exe"] + }, + "SkipFilehashes": { + "type": "Array", + "metadata": { + "description": "Enter, as an array of strings, the file hashes that you do not want Security Copilot to investigate" + }, + "defaultValue": [] + }, + "SkipUrls": { + "type": "Array", + "metadata": { + "description": "Enter, as an array of strings, the urls that you do not want Security Copilot to investigate" + }, + "defaultValue": [] + }, + "SkipDomains": { + "type": "Array", + "metadata": { + "description": "Enter, as an array of strings, the domains that you do not want Security Copilot to investigate" + }, + "defaultValue": [] + }, + "MaxMdtiOutputLenghtForPreSummarization": { + "type": "Int", + "metadata": { + "description": "When the overall length of the aggregated MDTI results report is greater than the number specified here, an additional call is made to Security Copilot to pre-summarize this specific report. This pre-summarization reduces the risk that the final summarization gets an error because the overall input text is too long. " + }, + "defaultValue": 2000 + }, + "WriteUnsummarizedResultsToIncident": { + "type": "Bool", + "metadata": { + "description": "Leave true if you want that the raw results are saved as comment to the Sentinel incident. The raw results are the results before the final summarization. Set false otherwise. " + }, + "defaultValue": true + }, + "WriteFinalSummaryToIncident": { + "type": "Bool", + "metadata": { + "description": "Leave true if you want that the summarized results are saved as comment to the Sentinel incident. Set false otherwise. " + }, + "defaultValue": true + }, + "RecipientEmail": { + "type": "String", + "metadata": { + "description": "Enter the email addrees of the recipient for the emails sent by this automation." + }, + "defaultValue": "" + }, + "SendUnsummarizedResultsByEmail": { + "type": "Bool", + "metadata": { + "description": "Leave true if you want that the raw results are sent by email to the specified recipient. The raw results are the results before the final summarization. Set false otherwise. " + }, + "defaultValue": true + }, + "SendFinalSummaryByEmail": { + "type": "Bool", + "metadata": { + "description": "Leave true if you want that the summarized results are sent by email to the specified recipient. Set false otherwise. " + }, + "defaultValue": true + } + }, + "variables": { + "MicrosoftSentinelConnectionName": "[concat('MicrosoftSentinel-', parameters('PlaybookName'))]", + "SecuritycopilotConnectionName": "[concat('Securitycopilot-', parameters('PlaybookName'))]", + "Office365ConnectionName": "[concat('Office365-', parameters('PlaybookName'))]" + }, + "resources": [ + { + "properties": { + "provisioningState": "Succeeded", + "state": "Enabled", + "definition": { + "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "$connections": { + "defaultValue": { + }, + "type": "Object" + }, + "CallScpForIPsWithAbuseIPDB": { + "defaultValue": "[parameters('CallScpForIPsWithAbuseIPDB')]", + "type": "Bool" + }, + "CallScpForIPsWithMDTI": { + "defaultValue": "[parameters('CallScpForIPsWithMDTI')]", + "type": "Bool" + }, + "CallScpForAccounts": { + "defaultValue": "[parameters('CallScpForAccounts')]", + "type": "Bool" + }, + "CallScpForUserRisk": { + "defaultValue": "[parameters('CallScpForUserRisk')]", + "type": "Bool" + }, + "CallScpForHosts": { + "defaultValue": "[parameters('CallScpForHosts')]", + "type": "Bool" + }, + "CallScpForCommandLines": { + "defaultValue": "[parameters('CallScpForCommandLines')]", + "type": "Bool" + }, + "CallScpForFilehashesWithMDTI": { + "defaultValue": "[parameters('CallScpForFilehashesWithMDTI')]", + "type": "Bool" + }, + "CallScpForUrlsWithMDTI": { + "defaultValue": "[parameters('CallScpForUrlsWithMDTI')]", + "type": "Bool" + }, + "CallScpForDomainsWithMDTI": { + "defaultValue": "[parameters('CallScpForDomainsWithMDTI')]", + "type": "Bool" + }, + "SkipIPs": { + "defaultValue": "[parameters('SkipIPs')]", + "type": "Array" + }, + "SkipAccounts": { + "defaultValue": "[parameters('SkipAccounts')]", + "type": "Array" + }, + "SkipHosts": { + "defaultValue": "[parameters('SkipHosts')]", + "type": "Array" + }, + "SkipCommandLines": { + "defaultValue": "[parameters('SkipCommandLines')]", + "type": "Array" + }, + "SkipFilehashes": { + "defaultValue": "[parameters('SkipFilehashes')]", + "type": "Array" + }, + "SkipUrls": { + "defaultValue": "[parameters('SkipUrls')]", + "type": "Array" + }, + "SkipDomains": { + "defaultValue": "[parameters('SkipDomains')]", + "type": "Array" + }, + "MaxMdtiOutputLenghtForPreSummarization": { + "defaultValue": "[parameters('MaxMdtiOutputLenghtForPreSummarization')]", + "type": "Int" + }, + "WriteUnsummarizedResultsToIncident": { + "defaultValue": "[parameters('WriteUnsummarizedResultsToIncident')]", + "type": "Bool" + }, + "WriteFinalSummaryToIncident": { + "defaultValue": "[parameters('WriteFinalSummaryToIncident')]", + "type": "Bool" + }, + "RecipientEmail": { + "defaultValue": "[parameters('RecipientEmail')]", + "type": "String" + }, + "SendUnsummarizedResultsByEmail": { + "defaultValue": "[parameters('SendUnsummarizedResultsByEmail')]", + "type": "Bool" + }, + "SendFinalSummaryByEmail": { + "defaultValue": "[parameters('SendFinalSummaryByEmail')]", + "type": "Bool" + } + }, + "triggers": { + "Microsoft_Sentinel_incident": { + "type": "ApiConnectionWebhook", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['azuresentinel']['connectionId']" + } + }, + "body": { + "callback_url": "@{listCallbackUrl()}" + }, + "path": "/incident-creation" + } + } + }, + "actions": { + "Compose_Incident_Entities": { + "runAfter": { + }, + "type": "Compose", + "inputs": "@triggerBody()?['object']?['properties']?['relatedEntities']" + }, + "Condition_-_Validate_Entities": { + "actions": { + "For_Each_Entity": { + "foreach": "@outputs('Compose_Incident_Entities')", + "actions": { + "Switch": { + "runAfter": { + "Show_item_for_debug_purposes": [ + "Succeeded" + ] + }, + "cases": { + "Case_-_Ip_Address": { + "case": "Ip", + "actions": { + "Condition_-_IP": { + "actions": { + "Condition_-_IP_-_Use_AbuseIPDB": { + "actions": { + "Append_to_array_forScp-IPs": { + "type": "AppendToArrayVariable", + "inputs": { + "name": "forScp-IPs", + "value": "@items('For_Each_Entity')?['properties']?['address']" + } + } + }, + "runAfter": { + "Append_IP_to_ProcessedItems": [ + "Succeeded" + ] + }, + "else": { + "actions": { + } + }, + "expression": { + "and": [ + { + "equals": [ + "@parameters('CallScpForIPsWithAbuseIPDB')", + true + ] + } + ] + }, + "type": "If" + }, + "Condition_-_IP_-_Use_MDTI": { + "actions": { + "Append_IP_to_array_variable_IndicatorsForMDTI": { + "type": "AppendToArrayVariable", + "inputs": { + "name": "forScp-Indicators", + "value": "@items('For_Each_Entity')?['properties']?['address']" + } + } + }, + "runAfter": { + "Append_IP_to_ProcessedItems": [ + "Succeeded" + ] + }, + "else": { + "actions": { + } + }, + "expression": { + "and": [ + { + "equals": [ + "@parameters('CallScpForIPsWithMDTI')", + true + ] + } + ] + }, + "type": "If" + }, + "Append_IP_to_ProcessedItems": { + "type": "AppendToArrayVariable", + "inputs": { + "name": "ProcessedItems", + "value": "@concat('IP:',trim(toLower(items('For_Each_Entity')?['properties']?['address'])))" + } + } + }, + "else": { + "actions": { + } + }, + "expression": { + "and": [ + { + "or": [ + { + "equals": [ + "@parameters('CallScpForIPsWithAbuseIPDB')", + "ture" + ] + }, + { + "equals": [ + "@parameters('CallScpForIPsWithMDTI')", + true + ] + } + ] + }, + { + "equals": [ + "@empty(items('For_Each_Entity')?['properties']?['address'])", + false + ] + }, + { + "not": { + "contains": [ + "@parameters('SkipIPs')", + "@toLower(items('For_Each_Entity')?['properties']?['address'])" + ] + } + }, + { + "not": { + "equals": [ + "@items('For_Each_Entity')?['properties']?['address']", + "127.0.0.1" + ] + } + }, + { + "not": { + "equals": [ + "@items('For_Each_Entity')?['properties']?['address']", + "::1" + ] + } + }, + { + "not": { + "startsWith": [ + "@items('For_Each_Entity')?['properties']?['address']", + "10." + ] + } + }, + { + "not": { + "startsWith": [ + "@items('For_Each_Entity')?['properties']?['address']", + "192.168." + ] + } + }, + { + "not": { + "startsWith": [ + "@items('For_Each_Entity')?['properties']?['address']", + "172.16." + ] + } + }, + { + "not": { + "startsWith": [ + "@items('For_Each_Entity')?['properties']?['address']", + "172.17." + ] + } + }, + { + "not": { + "startsWith": [ + "@items('For_Each_Entity')?['properties']?['address']", + "172.18." + ] + } + }, + { + "not": { + "startsWith": [ + "@items('For_Each_Entity')?['properties']?['address']", + "172.19." + ] + } + }, + { + "not": { + "startsWith": [ + "@items('For_Each_Entity')?['properties']?['address']", + "172.20." + ] + } + }, + { + "not": { + "startsWith": [ + "@items('For_Each_Entity')?['properties']?['address']", + "172.21." + ] + } + }, + { + "not": { + "startsWith": [ + "@items('For_Each_Entity')?['properties']?['address']", + "172.22." + ] + } + }, + { + "not": { + "startsWith": [ + "@items('For_Each_Entity')?['properties']?['address']", + "172.23." + ] + } + }, + { + "not": { + "startsWith": [ + "@items('For_Each_Entity')?['properties']?['address']", + "172.24." + ] + } + }, + { + "not": { + "startsWith": [ + "@items('For_Each_Entity')?['properties']?['address']", + "172.25." + ] + } + }, + { + "not": { + "endsWith": [ + "@items('For_Each_Entity')?['properties']?['address']", + "172.26." + ] + } + }, + { + "not": { + "startsWith": [ + "@items('For_Each_Entity')?['properties']?['address']", + "172.27." + ] + } + }, + { + "not": { + "startsWith": [ + "@items('For_Each_Entity')?['properties']?['address']", + "172.28." + ] + } + }, + { + "not": { + "startsWith": [ + "@items('For_Each_Entity')?['properties']?['address']", + "172.29." + ] + } + }, + { + "not": { + "startsWith": [ + "@items('For_Each_Entity')?['properties']?['address']", + "172.30." + ] + } + }, + { + "not": { + "startsWith": [ + "@items('For_Each_Entity')?['properties']?['address']", + "172.31." + ] + } + }, + { + "not": { + "startsWith": [ + "@toLower(items('For_Each_Entity')?['properties']?['address'])", + "fd00::" + ] + } + }, + { + "not": { + "startsWith": [ + "@toLower(items('For_Each_Entity')?['properties']?['address'])", + "fc00::" + ] + } + }, + { + "not": { + "startsWith": [ + "@toLower(items('For_Each_Entity')?['properties']?['address'])", + "ff00::" + ] + } + }, + { + "not": { + "startsWith": [ + "@toLower(items('For_Each_Entity')?['properties']?['address'])", + "fe80::" + ] + } + }, + { + "not": { + "startsWith": [ + "@toLower(items('For_Each_Entity')?['properties']?['address'])", + "::ffff:" + ] + } + } + ] + }, + "type": "If" + } + } + }, + "Case_-_Account": { + "case": "Account", + "actions": { + "Condition_-_Account": { + "actions": { + "Append_User_Upn_to_ProcessedItems": { + "type": "AppendToArrayVariable", + "inputs": { + "name": "ProcessedItems", + "value": "@concat('Account:',trim(toLower(items('For_Each_Entity')?['properties']?['additionalData']?['UserPrincipalName'])))" + } + }, + "old_Condition_-_Account_-_Get_User_Details": { + "actions": { + "Append_to_array_forScp-UserForDetails": { + "type": "AppendToArrayVariable", + "inputs": { + "name": "forScp-UsersForDetails", + "value": "@items('For_Each_Entity')?['properties']?['additionalData']?['UserPrincipalName']" + } + } + }, + "runAfter": { + "Append_User_Upn_to_ProcessedItems": [ + "Succeeded" + ] + }, + "else": { + "actions": { + } + }, + "expression": { + "and": [ + { + "equals": [ + "@parameters('CallScpForAccounts')", + true + ] + } + ] + }, + "type": "If" + }, + "Condition_-_Account_-_Get_User_Risk": { + "actions": { + "Append_UPN_to_array_variable_forScp-UsersForRiskAnalysis": { + "type": "AppendToArrayVariable", + "inputs": { + "name": "forScp-UsersForRiskAnalysis", + "value": "@items('For_Each_Entity')?['properties']?['additionalData']?['UserPrincipalName']" + } + } + }, + "runAfter": { + "Append_User_Upn_to_ProcessedItems": [ + "Succeeded" + ] + }, + "else": { + "actions": { + } + }, + "expression": { + "and": [ + { + "equals": [ + "@parameters('CallScpForUserRisk')", + true + ] + } + ] + }, + "type": "If" + } + }, + "else": { + "actions": { + } + }, + "expression": { + "and": [ + { + "or": [ + { + "equals": [ + "@parameters('CallScpForAccounts')", + true + ] + }, + { + "equals": [ + "@parameters('CallScpForUserRisk')", + true + ] + } + ] + }, + { + "equals": [ + "@empty(items('For_Each_Entity')?['properties']?['accountName'])", + false + ] + }, + { + "equals": [ + "@empty(items('For_Each_Entity')?['properties']?['additionalData']?['UserPrincipalName'])", + false + ] + }, + { + "not": { + "contains": [ + "@parameters('SkipAccounts')", + "@toLower(items('For_Each_Entity')?['properties']?['accountName'])" + ] + } + }, + { + "not": { + "endsWith": [ + "@items('For_Each_Entity')?['properties']?['accountName']", + "$" + ] + } + } + ] + }, + "type": "If" + } + } + }, + "Case_-_Host": { + "case": "Host", + "actions": { + "Condition_-_Host": { + "actions": { + "Append_Device_to_ProcessedItems": { + "type": "AppendToArrayVariable", + "inputs": { + "name": "ProcessedItems", + "value": "@concat('Device:',trim(toLower(items('For_Each_Entity')?['properties']?['hostName'])))" + } + }, + "Append_to_array_forScp-Devices": { + "runAfter": { + "Append_Device_to_ProcessedItems": [ + "Succeeded" + ] + }, + "type": "AppendToArrayVariable", + "inputs": { + "name": "forScp-Devices", + "value": "@items('For_Each_Entity')?['properties']?['hostName']" + } + } + }, + "else": { + "actions": { + } + }, + "expression": { + "and": [ + { + "equals": [ + "@empty(items('For_Each_Entity')?['properties']?['hostName'])", + false + ] + }, + { + "equals": [ + "@parameters('CallScpForHosts')", + true + ] + }, + { + "not": { + "contains": [ + "@parameters('SkipHosts')", + "@toLower(items('For_Each_Entity')?['properties']?['hostName'])" + ] + } + } + ] + }, + "type": "If" + } + } + }, + "Case_-_Process": { + "case": "Process", + "actions": { + "Condition_-_Command_Line": { + "actions": { + "Set_variable_CommandLineNormalized": { + "type": "SetVariable", + "inputs": { + "name": "CommandLineNormalized", + "value": "@tolower(trim(replace(replace(items('For_Each_Entity')?['properties']?['commandLine'],' ',' '),'net1 ','net ')))" + } + }, + "Append_CommandLineNormalized_to_ProcessedItem": { + "runAfter": { + "Set_variable_CommandLineNormalized": [ + "Succeeded" + ] + }, + "type": "AppendToArrayVariable", + "inputs": { + "name": "ProcessedItems", + "value": "@concat('CommandLine:',variables('CommandLineNormalized'))" + } + }, + "Append_to_array_variable_forScp-CommandLines": { + "runAfter": { + "Append_CommandLineNormalized_to_ProcessedItem": [ + "Succeeded" + ] + }, + "type": "AppendToArrayVariable", + "inputs": { + "name": "forScp-CommandLines", + "value": "@variables('CommandLineNormalized')" + } + } + }, + "else": { + "actions": { + } + }, + "expression": { + "and": [ + { + "equals": [ + "@empty(items('For_Each_Entity')?['properties']?['commandLine'])", + false + ] + }, + { + "equals": [ + "@parameters('CallScpForCommandLines')", + true + ] + }, + { + "not": { + "contains": [ + "@parameters('SkipCommandLines')", + "@toLower(items('For_Each_Entity')?['properties']?['commandLine'])" + ] + } + } + ] + }, + "type": "If" + } + } + }, + "Case_-_FileHash": { + "case": "FileHash", + "actions": { + "Condition_-_FileHash": { + "actions": { + "Append_Filehash_to_array_variable_IndicatorsForMDTI": { + "runAfter": { + "Append_FileHash_to_ProcessedItems": [ + "Succeeded" + ] + }, + "type": "AppendToArrayVariable", + "inputs": { + "name": "forScp-Indicators", + "value": "@toLower(items('For_Each_Entity')?['properties']?['hashValue'])" + } + }, + "Append_FileHash_to_ProcessedItems": { + "type": "AppendToArrayVariable", + "inputs": { + "name": "ProcessedItems", + "value": "@concat('FileHash:',trim(toLower(items('For_Each_Entity')?['properties']?['hashValue'])))" + } + } + }, + "else": { + "actions": { + } + }, + "expression": { + "and": [ + { + "equals": [ + "@empty(items('For_Each_Entity')?['properties']?['hashValue'])", + false + ] + }, + { + "equals": [ + "@parameters('CallScpForFilehashesWithMDTI')", + true + ] + }, + { + "not": { + "contains": [ + "@parameters('SkipFilehashes')", + "" + ] + } + } + ] + }, + "type": "If" + } + } + }, + "Case_Url": { + "case": "Url", + "actions": { + "Condition_-_Url": { + "actions": { + "Append_Url_to_array_variable_IndicatorsForMDTI": { + "runAfter": { + "Append_URL_to_ProcessedItems": [ + "Succeeded" + ] + }, + "type": "AppendToArrayVariable", + "inputs": { + "name": "forScp-Indicators", + "value": "@toLower(split(items('For_Each_Entity')?['properties']?['url'],'?')[0])" + } + }, + "Append_URL_to_ProcessedItems": { + "type": "AppendToArrayVariable", + "inputs": { + "name": "ProcessedItems", + "value": "@concat('URL:',trim(toLower(split(items('For_Each_Entity')?['properties']?['url'],'?')[0])))" + } + } + }, + "else": { + "actions": { + } + }, + "expression": { + "and": [ + { + "equals": [ + "@empty(items('For_Each_Entity')?['properties']?['url'])", + false + ] + }, + { + "equals": [ + "@parameters('CallScpForUrlsWithMDTI')", + true + ] + }, + { + "not": { + "contains": [ + "@parameters('SkipUrls')", + "@toLower(items('For_Each_Entity')?['properties']?['url'])" + ] + } + } + ] + }, + "type": "If" + } + } + }, + "Case_DNS": { + "case": "DNS", + "actions": { + "Condition_-_DNS": { + "actions": { + "Append_Domain_to_array_variable_IndicatorsForMDTI": { + "runAfter": { + "Append_Domain_to_ProcessedItems": [ + "Succeeded" + ] + }, + "type": "AppendToArrayVariable", + "inputs": { + "name": "forScp-Indicators", + "value": "@toLower(items('For_Each_Entity')?['properties']?['domainName'])" + } + }, + "Append_Domain_to_ProcessedItems": { + "type": "AppendToArrayVariable", + "inputs": { + "name": "ProcessedItems", + "value": "@concat('Domain:',trim(toLower(items('For_Each_Entity')?['properties']?['domainName'])))" + } + } + }, + "else": { + "actions": { + } + }, + "expression": { + "and": [ + { + "equals": [ + "@empty(items('For_Each_Entity')?['properties']?['domainName'])", + false + ] + }, + { + "equals": [ + "@parameters('CallScpForDomainsWithMDTI')", + true + ] + }, + { + "not": { + "contains": [ + "@parameters('SkipDomains')", + "@toLower(items('For_Each_Entity')?['properties']?['domainName'])" + ] + } + } + ] + }, + "type": "If" + } + } + } + }, + "default": { + "actions": { + } + }, + "expression": "@items('For_Each_Entity')?['kind']", + "type": "Switch" + }, + "Show_item_for_debug_purposes": { + "type": "Compose", + "inputs": "@items('For_Each_Entity')" + } + }, + "type": "Foreach" + }, + "Condition_Call_for_IP_Addresses": { + "actions": { + "For_each_forScp-IPs": { + "foreach": "@variables('Unique-forScp-IPs')", + "actions": { + "Security_Copilot_prompt_for_IP": { + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['securitycopilot']['connectionId']" + } + }, + "method": "post", + "body": { + "PromptContent": "I'm curious about any abuseipdb records for the IP address @{items('For_each_forScp-IPs')}. Can you look that up for me for the last 30 days?" + }, + "path": "/process-prompt" + } + }, + "Condition_-_SCP_-_Call_for_IPs_with_AbuseIPBD_was_successful": { + "actions": { + "Set_variable_ResultsAvailable_-_IPs": { + "runAfter": { + "Append_to_Results-IPs_Success": [ + "Succeeded" + ] + }, + "type": "SetVariable", + "inputs": { + "name": "ResultsAvailable", + "value": true + } + }, + "Append_to_Results-IPs_Success": { + "type": "AppendToArrayVariable", + "inputs": { + "name": "Results-IPs", + "value": "\nResults of the analysis for the IP '@{items('For_each_forScp-IPs')}':\n\n@{body('Security_Copilot_prompt_for_IP')?['EvaluationResultContent']}\n " + } + } + }, + "runAfter": { + "Security_Copilot_prompt_for_IP": [ + "Succeeded", + "TimedOut", + "Skipped", + "Failed" + ] + }, + "else": { + "actions": { + "Append_to_Results-IPs_Error": { + "type": "AppendToArrayVariable", + "inputs": { + "name": "Results-IPs", + "value": "\nResults of the analysis for the IP '@{items('For_each_forScp-IPs')}':\n\nError while retrieving the results: @{body('Security_Copilot_prompt_for_IP')?['EvaluationResultContent']}\n " + } + } + } + }, + "expression": { + "and": [ + { + "equals": [ + "@body('Security_Copilot_prompt_for_IP')?['evaluationResultType']", + "Success" + ] + } + ] + }, + "type": "If" + } + }, + "runAfter": { + "Show_Unique-forScp-IPs_for_debug_purposes": [ + "Succeeded" + ] + }, + "type": "Foreach" + }, + "Show_Unique-forScp-IPs_for_debug_purposes": { + "runAfter": { + "Set_variable_-_Unique-forScp-IPs": [ + "Succeeded" + ] + }, + "type": "Compose", + "inputs": "@variables('Unique-forScp-IPs')" + }, + "Set_variable_-_Unique-forScp-IPs": { + "type": "SetVariable", + "inputs": { + "name": "Unique-forScp-IPs", + "value": "@sort(union(variables('forScp-IPs'),variables('forScp-IPs')))" + } + } + }, + "runAfter": { + "For_Each_Entity": [ + "Succeeded" + ] + }, + "else": { + "actions": { + } + }, + "expression": { + "and": [ + { + "equals": [ + "@and(not(equals(variables('forScp-IPs'),null)), greater(length(variables('forScp-IPs')), 0))", + true + ] + } + ] + }, + "type": "If" + }, + "Condition_Call_SCP_for_Users_Details": { + "actions": { + "For_each_forScp-UsersForDetails": { + "foreach": "@variables('Unique-forScp-UsersForDetails')", + "actions": { + "Security_Copilot_prompt_for_Account_": { + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['securitycopilot']['connectionId']" + } + }, + "method": "post", + "body": { + "PromptContent": "Give me all usr details for @{items('For_each_forScp-UsersForDetails')}. Does have any registered devices in Microsoft Entra? List the recent risky sign-ins for @{items('For_each_forScp-UsersForDetails')}. Get Microsoft Entra audit logs for @{items('For_each_forScp-UsersForDetails')} for the past 72 hours.", + "SkillName": "GetEntraUserDetailsV1" + }, + "path": "/process-prompt" + } + }, + "Condition_-_SCP_-_Call_for_Account_on_Entra_was_successful": { + "actions": { + "Set_variable_-_ResultsAvailable_-_Accounts": { + "runAfter": { + "Append_to_Results-UsersDetails_-_Success": [ + "Succeeded" + ] + }, + "type": "SetVariable", + "inputs": { + "name": "ResultsAvailable", + "value": true + } + }, + "Append_to_Results-UsersDetails_-_Success": { + "type": "AppendToArrayVariable", + "inputs": { + "name": "Results-UsersDetails", + "value": "\nResults of the analysis for the account '@{items('For_each_forScp-UsersForDetails')}':\n\n@{body('Security_Copilot_prompt_for_Account_')?['EvaluationResultContent']}\n " + } + } + }, + "runAfter": { + "Security_Copilot_prompt_for_Account_": [ + "Succeeded", + "TimedOut", + "Skipped", + "Failed" + ] + }, + "else": { + "actions": { + "Append_to_Results-UsersDetails_-_Error": { + "type": "AppendToArrayVariable", + "inputs": { + "name": "Results-UsersDetails", + "value": "\nResults of the analysis for the account '@{items('For_each_forScp-UsersForDetails')}':\n\nError while retrieving the results: @{body('Security_Copilot_prompt_for_Account_')?['EvaluationResultContent']}\n " + } + } + } + }, + "expression": { + "and": [ + { + "equals": [ + "@body('Security_Copilot_prompt_for_Account_')?['evaluationResultType']", + "Success" + ] + } + ] + }, + "type": "If" + } + }, + "runAfter": { + "Show_Unique-forScp-UsersForDetails_for_debug_purposes": [ + "Succeeded" + ] + }, + "type": "Foreach" + }, + "Show_Unique-forScp-UsersForDetails_for_debug_purposes": { + "runAfter": { + "Set_variable_-_Unique-forScp-UsersForDetails": [ + "Succeeded" + ] + }, + "type": "Compose", + "inputs": "@variables('Unique-forScp-UsersForDetails')" + }, + "Set_variable_-_Unique-forScp-UsersForDetails": { + "type": "SetVariable", + "inputs": { + "name": "Unique-forScp-UsersForDetails", + "value": "@sort(union(variables('forScp-UsersForDetails'),variables('forScp-UsersForDetails')))" + } + } + }, + "runAfter": { + "For_Each_Entity": [ + "Succeeded" + ] + }, + "else": { + "actions": { + } + }, + "expression": { + "and": [ + { + "equals": [ + "@and(not(equals(variables('forScp-UsersForDetails'),null)), greater(length(variables('forScp-UsersForDetails')), 0))", + true + ] + } + ] + }, + "type": "If" + }, + "Condition_Call_SCP_for_Users_Risk": { + "actions": { + "Security_Copilot_prompt_for_Users_Risk": { + "runAfter": { + "Show_Unique-forScp-UsersForRiskAnalysis_for_debug_purposes": [ + "Succeeded" + ] + }, + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['securitycopilot']['connectionId']" + } + }, + "method": "post", + "body": { + "PromptContent": "@join(variables('Unique-forScp-UsersForRiskAnalysis'),', ')", + "SkillName": "GetEntraRiskyUsers" + }, + "path": "/process-prompt" + } + }, + "Condition_-_Users_Risk_retrieved": { + "actions": { + "Compose_Users_Risk": { + "type": "Compose", + "inputs": "@body('Security_Copilot_prompt_for_Users_Risk')?['EvaluationResultContent']" + }, + "Set_variable_-_ResultsAvailable_-_Users_Risk": { + "runAfter": { + "Append_to_Results-UsersRisk_-_Success": [ + "Succeeded" + ] + }, + "type": "SetVariable", + "inputs": { + "name": "ResultsAvailable", + "value": true + } + }, + "Append_to_Results-UsersRisk_-_Success": { + "runAfter": { + "Compose_Users_Risk": [ + "Succeeded" + ] + }, + "type": "AppendToArrayVariable", + "inputs": { + "name": "Results-UsersRisk", + "value": "@body('Security_Copilot_prompt_for_Users_Risk')?['EvaluationResultContent']" + } + } + }, + "runAfter": { + "Security_Copilot_prompt_for_Users_Risk": [ + "Succeeded", + "TimedOut", + "Skipped", + "Failed" + ] + }, + "else": { + "actions": { + "Append_to_Results-UsersRisk_-_Error": { + "type": "AppendToArrayVariable", + "inputs": { + "name": "Results-UsersRisk", + "value": "Error while retrieving the users risk: '@{body('Security_Copilot_prompt_for_Users_Risk')?['EvaluationResultContent']}'" + } + } + } + }, + "expression": { + "and": [ + { + "equals": [ + "@body('Security_Copilot_prompt_for_Users_Risk')?['evaluationResultType']", + "Success" + ] + } + ] + }, + "type": "If" + }, + "Show_Unique-forScp-UsersForRiskAnalysis_for_debug_purposes": { + "runAfter": { + "Set_variable_Unique-forScp-UsersForRiskAnalysis": [ + "Succeeded" + ] + }, + "type": "Compose", + "inputs": "@variables('Unique-forScp-UsersForRiskAnalysis')" + }, + "Set_variable_Unique-forScp-UsersForRiskAnalysis": { + "type": "SetVariable", + "inputs": { + "name": "Unique-forScp-UsersForRiskAnalysis", + "value": "@sort(union(variables('forScp-UsersForRiskAnalysis'),variables('forScp-UsersForRiskAnalysis')))" + } + } + }, + "runAfter": { + "For_Each_Entity": [ + "Succeeded" + ] + }, + "else": { + "actions": { + } + }, + "expression": { + "and": [ + { + "equals": [ + "@and(not(equals(variables('forScp-UsersForRiskAnalysis'),null)), greater(length(variables('forScp-UsersForRiskAnalysis')), 0))", + true + ] + } + ] + }, + "type": "If" + }, + "Condition_Call_SCP_for_Devices_Details": { + "actions": { + "For_each_forScp-Devices": { + "foreach": "@variables('Unique-forScp-Devices')", + "actions": { + "Security_Copilot_prompt_for_Device": { + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['securitycopilot']['connectionId']" + } + }, + "method": "post", + "body": { + "PromptContent": "Tell me about device @{items('For_each_forScp-Devices')}.", + "SkillName": "GetIntuneDevices" + }, + "path": "/process-prompt" + } + }, + "Condition_-_SCP_-_Call_for_devices_on_Intune_was_successful": { + "actions": { + "Set_variable_-_ResultsAvailable_-_Devices": { + "runAfter": { + "Append_to_Results-Devices-Success": [ + "Succeeded" + ] + }, + "type": "SetVariable", + "inputs": { + "name": "ResultsAvailable", + "value": true + } + }, + "Append_to_Results-Devices-Success": { + "type": "AppendToArrayVariable", + "inputs": { + "name": "Results-Devices", + "value": "\nResults of the analysis for the device '@{items('For_each_forScp-Devices')}':\n\n@{body('Security_Copilot_prompt_for_Device')?['EvaluationResultContent']}\n" + } + } + }, + "runAfter": { + "Security_Copilot_prompt_for_Device": [ + "Succeeded", + "TimedOut", + "Skipped", + "Failed" + ] + }, + "else": { + "actions": { + "Append_to_results-Devices-Error": { + "type": "AppendToArrayVariable", + "inputs": { + "name": "Results-Devices", + "value": "\nResults of the analysis for the device '@{items('For_each_forScp-Devices')}': \n\nError while retrieving the results: '@{body('Security_Copilot_prompt_for_Device')?['EvaluationResultContent']}'\n" + } + } + } + }, + "expression": { + "and": [ + { + "equals": [ + "@body('Security_Copilot_prompt_for_Device')?['evaluationResultType']", + "Success" + ] + } + ] + }, + "type": "If" + } + }, + "runAfter": { + "Show_Unique-forScp-Devices_for_debug_purposes": [ + "Succeeded" + ] + }, + "type": "Foreach" + }, + "Show_Unique-forScp-Devices_for_debug_purposes": { + "runAfter": { + "Set_variable_Unique-forScp-Devices": [ + "Succeeded" + ] + }, + "type": "Compose", + "inputs": "@variables('Unique-forScp-Devices')" + }, + "Set_variable_Unique-forScp-Devices": { + "type": "SetVariable", + "inputs": { + "name": "Unique-forScp-Devices", + "value": "@sort(union(variables('forScp-Devices'),variables('forScp-Devices')))" + } + } + }, + "runAfter": { + "For_Each_Entity": [ + "Succeeded" + ] + }, + "else": { + "actions": { + } + }, + "expression": { + "and": [ + { + "equals": [ + "@and(not(equals(variables('forScp-Devices'),null)), greater(length(variables('forScp-Devices')), 0))", + true + ] + } + ] + }, + "type": "If" + }, + "Condition_Call_SCP_for_Command_Lines": { + "actions": { + "Security_Copilot_prompt_for_Unique_Processes": { + "runAfter": { + "Show_Unique-forScp-CommandLines_for_debug_purposes": [ + "Succeeded" + ] + }, + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['securitycopilot']['connectionId']" + } + }, + "method": "post", + "body": { + "PromptContent": "From the following list, extract the command lines and related descriptions containing commands that are most likely DIRECTLY CORRELATED to malicious activities. Remove all command lines containing only the names of well-known portable executable files that are part of the Windows or Linux operating system, where no additional parameters are specified. For example, remove 'notepad.exe' and 'cmd.exe' but keep 'notepad.exe -param1' or 'cmd.exe -paramX' or 'unknownfile.exe'. The list starts here:\n \n@{join(variables('Unique-forScp-CommandLines'),'\n')}", + "SkillName": "ScriptAnalyzer" + }, + "path": "/process-prompt" + } + }, + "Condition_-_SCP_-_Call_for_Processes_was_successful": { + "actions": { + "Compose_Process_Analysis": { + "type": "Compose", + "inputs": "@body('Security_Copilot_prompt_for_Unique_Processes')?['EvaluationResultContent']" + }, + "Set_variable_-_ResultsAvailable_-_Command_Lines": { + "runAfter": { + "Append_to_Results-CommandLines_-_Success": [ + "Succeeded" + ] + }, + "type": "SetVariable", + "inputs": { + "name": "ResultsAvailable", + "value": true + } + }, + "Append_to_Results-CommandLines_-_Success": { + "runAfter": { + "Compose_Process_Analysis": [ + "Succeeded" + ] + }, + "type": "AppendToArrayVariable", + "inputs": { + "name": "Results-CommandLines", + "value": "@body('Security_Copilot_prompt_for_Unique_Processes')?['EvaluationResultContent']" + } + } + }, + "runAfter": { + "Security_Copilot_prompt_for_Unique_Processes": [ + "Succeeded", + "TimedOut", + "Skipped", + "Failed" + ] + }, + "else": { + "actions": { + "Append_to_Results-CommandLines_-_Error": { + "type": "AppendToArrayVariable", + "inputs": { + "name": "Results-CommandLines", + "value": "Error while analyzing the command lines: '@{body('Security_Copilot_prompt_for_Unique_Processes')?['EvaluationResultContent']}'" + } + } + } + }, + "expression": { + "and": [ + { + "equals": [ + "@body('Security_Copilot_prompt_for_Unique_Processes')?['evaluationResultType']", + "Success" + ] + } + ] + }, + "type": "If" + }, + "Show_Unique-forScp-CommandLines_for_debug_purposes": { + "runAfter": { + "Set_variable_Unique-forScp-CommandLines": [ + "Succeeded" + ] + }, + "type": "Compose", + "inputs": "@variables('Unique-forScp-CommandLines')" + }, + "Set_variable_Unique-forScp-CommandLines": { + "type": "SetVariable", + "inputs": { + "name": "Unique-forScp-CommandLines", + "value": "@sort(union(variables('forScp-CommandLines'),variables('forScp-CommandLines')))" + } + } + }, + "runAfter": { + "For_Each_Entity": [ + "Succeeded" + ] + }, + "else": { + "actions": { + } + }, + "expression": { + "and": [ + { + "equals": [ + "@and(not(equals(variables('forScp-CommandLines'), null)), greater(length(variables('forScp-CommandLines')), 0))", + true + ] + } + ] + }, + "type": "If" + }, + "Condition_Call_SCP_for_MDTI_indicators": { + "actions": { + "Until": { + "actions": { + "Set_variable_SubarrayIndicatorsForMDTI": { + "type": "SetVariable", + "inputs": { + "name": "SubarrayIndicatorsForMDTI", + "value": "@take(skip(variables('Unique-forScp-Indicators'),variables('CounterIndicatorsForMDTI')),10)" + } + }, + "Set_variable_CsvIndicatorsForMDTI": { + "runAfter": { + "Set_variable_SubarrayIndicatorsForMDTI": [ + "Succeeded" + ] + }, + "type": "SetVariable", + "inputs": { + "name": "CsvIndicatorsForMDTI", + "value": "@join(variables('SubarrayIndicatorsForMDTI'),', ')" + } + }, + "Security_Copilot_prompt_for_IoC_to_MDTI": { + "runAfter": { + "Set_variable_CsvIndicatorsForMDTI": [ + "Succeeded" + ] + }, + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['securitycopilot']['connectionId']" + } + }, + "method": "post", + "body": { + "PromptContent": "By looking at MDTI, tell me the reputation of these indicators: @{variables('CsvIndicatorsForMDTI')} ", + "SkillName": "GetReputationsForIndicators" + }, + "path": "/process-prompt" + } + }, + "Compose_New_Counter_Value": { + "runAfter": { + "Condition_-_SCP_-_Call_for_indicators_was_successful": [ + "Succeeded" + ] + }, + "type": "Compose", + "inputs": "@add(variables('CounterIndicatorsForMDTI'),10)" + }, + "Set_variable_Counter": { + "runAfter": { + "Compose_New_Counter_Value": [ + "Succeeded" + ] + }, + "type": "SetVariable", + "inputs": { + "name": "CounterIndicatorsForMDTI", + "value": "@outputs('Compose_New_Counter_Value')" + } + }, + "Condition_-_SCP_-_Call_for_indicators_was_successful": { + "actions": { + "Compose_IoC_from_MDTI": { + "type": "Compose", + "inputs": "@body('Security_Copilot_prompt_for_IoC_to_MDTI')?['EvaluationResultContent']" + }, + "Append_to_array_variable_ResponsesFromMDTI_-_Success": { + "runAfter": { + "Compose_IoC_from_MDTI": [ + "Succeeded" + ] + }, + "type": "AppendToArrayVariable", + "inputs": { + "name": "ResponsesFromMDTI", + "value": "@body('Security_Copilot_prompt_for_IoC_to_MDTI')?['EvaluationResultContent']" + } + } + }, + "runAfter": { + "Security_Copilot_prompt_for_IoC_to_MDTI": [ + "Succeeded", + "TimedOut", + "Skipped", + "Failed" + ] + }, + "else": { + "actions": { + "Append_to_array_variable_ResponsesFromMDTI_-_Error": { + "type": "AppendToArrayVariable", + "inputs": { + "name": "ResponsesFromMDTI", + "value": "Error during the iteration @{add(div(variables('CounterIndicatorsForMDTI'),10),1)} wirh MDTI: '@{body('Security_Copilot_prompt_for_IoC_to_MDTI')?['EvaluationResultContent']}'" + } + } + } + }, + "expression": { + "and": [ + { + "equals": [ + "@body('Security_Copilot_prompt_for_IoC_to_MDTI')?['evaluationResultType']", + "Success" + ] + } + ] + }, + "type": "If" + } + }, + "runAfter": { + "Show_Unique-forScp-Indicators_for_debug_purposes": [ + "Succeeded" + ] + }, + "expression": "@equals(greaterOrEquals(length(variables('Unique-forScp-Indicators')),variables('CounterIndicatorsForMDTI')),false)", + "limit": { + "count": 60, + "timeout": "PT1H" + }, + "type": "Until" + }, + "Set_variable_OutputLength_-_MDTI_Output": { + "runAfter": { + "Set_variable_original_Response-MDTI": [ + "Succeeded" + ] + }, + "type": "SetVariable", + "inputs": { + "name": "OutputLengthMDTI", + "value": "@length(variables('Response-MDTI'))" + } + }, + "Condition_-_MDTI_Output_Length": { + "actions": { + "Submit_a_Security_Copilot_prompt_-_Summarize_MDTI_response": { + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['securitycopilot']['connectionId']" + } + }, + "method": "post", + "body": { + "PromptContent": "From the following detailed list containing the results of the analysis of Indicators of Compromise (IoC) made by MDTI, extract suspicious and malicious indicators with the all the related details on reputation and explanations. Do not return unknown indicators. The list starts here: @{variables('Response-MDTI')}\n", + "SkillName": "SummarizeData" + }, + "path": "/process-prompt" + } + }, + "Condition_-_SCP_-_Call_for_indicator_summarization_was_successful": { + "actions": { + "Set_variable_summarized_Response-MDTI_-_Success": { + "type": "SetVariable", + "inputs": { + "name": "Response-MDTI", + "value": "@body('Submit_a_Security_Copilot_prompt_-_Summarize_MDTI_response')?['EvaluationResultContent']" + } + } + }, + "runAfter": { + "Submit_a_Security_Copilot_prompt_-_Summarize_MDTI_response": [ + "Succeeded", + "TimedOut", + "Skipped", + "Failed" + ] + }, + "else": { + "actions": { + "Set_variable_summarized_Response-MDTI_-_Error": { + "type": "SetVariable", + "inputs": { + "name": "Response-MDTI", + "value": "Error while summarizing the results retrieved from MDTI: '@{body('Submit_a_Security_Copilot_prompt_-_Summarize_MDTI_response')?['EvaluationResultContent']}'" + } + } + } + }, + "expression": { + "and": [ + { + "equals": [ + "", + "" + ] + } + ] + }, + "type": "If" + } + }, + "runAfter": { + "Condition_-_MDTI_Results_available": [ + "Succeeded" + ] + }, + "else": { + "actions": { + } + }, + "expression": { + "and": [ + { + "greater": [ + "@variables('OutputLengthMDTI')", + "@parameters('MaxMdtiOutputLenghtForPreSummarization')" + ] + } + ] + }, + "type": "If" + }, + "Set_variable_original_Response-MDTI": { + "runAfter": { + "Until": [ + "Succeeded" + ] + }, + "type": "SetVariable", + "inputs": { + "name": "Response-MDTI", + "value": "@join(variables('ResponsesFromMDTI'),' \n\n\n')" + } + }, + "Show_Unique-forScp-Indicators_for_debug_purposes": { + "runAfter": { + "Set_variable_Unique-forScp-Indicators": [ + "Succeeded" + ] + }, + "type": "Compose", + "inputs": "@variables('Unique-forScp-Indicators')" + }, + "Set_variable_Unique-forScp-Indicators": { + "type": "SetVariable", + "inputs": { + "name": "Unique-forScp-Indicators", + "value": "@sort(union(variables('forScp-Indicators'),variables('forScp-Indicators')))" + } + }, + "Condition_-_MDTI_Results_available": { + "actions": { + "Set_variable_-_ResultsAvailable_-_MDTI": { + "type": "SetVariable", + "inputs": { + "name": "ResultsAvailable", + "value": true + } + } + }, + "runAfter": { + "Set_variable_OutputLength_-_MDTI_Output": [ + "Succeeded" + ] + }, + "else": { + "actions": { + } + }, + "expression": { + "and": [ + { + "greater": [ + "@variables('OutputLengthMDTI')", + 0 + ] + } + ] + }, + "type": "If" + } + }, + "runAfter": { + "For_Each_Entity": [ + "Succeeded" + ] + }, + "else": { + "actions": { + } + }, + "expression": { + "or": [ + { + "equals": [ + "@and(not(equals(variables('forScp-Indicators'),null)), greater(length(variables('forScp-Indicators')), 0))", + true + ] + } + ] + }, + "type": "If" + } + }, + "runAfter": { + "Condition_-_Validate_Parameters": [ + "Succeeded" + ] + }, + "else": { + "actions": { + "No_Entities_Found_-_Cancel_Execution": { + "type": "Terminate", + "inputs": { + "runStatus": "Cancelled" + } + } + } + }, + "expression": { + "and": [ + { + "greater": [ + "@length(outputs('Compose_Incident_Entities'))", + 0 + ] + } + ] + }, + "type": "If" + }, + "Initialize_variable_CommandLineNormalized": { + "runAfter": { + "Compose_Incident_Entities": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "CommandLineNormalized", + "type": "string" + } + ] + } + }, + "Initialize_variable_forScp-CommandLines": { + "runAfter": { + "Initialize_variable_ProcessedCommandLines": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "forScp-CommandLines", + "type": "array" + } + ] + } + }, + "Initialize_variable_ProcessedCommandLines": { + "runAfter": { + "Initialize_variable_CommandLineNormalized": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "ProcessedCommandLines", + "type": "array" + } + ] + } + }, + "Initialize_variable_forScp-Indicators": { + "runAfter": { + "Initialize_variable_Results-CommandLines": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "forScp-Indicators", + "type": "array" + } + ] + } + }, + "Initialize_variable_CounterIndicatorsForMDTI": { + "runAfter": { + "Initialize_variable_Unique-forScp-Indicators": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "CounterIndicatorsForMDTI", + "type": "integer", + "value": 0 + } + ] + } + }, + "Initialize_variable_SubarrayIndicatorsForMDTI": { + "runAfter": { + "Initialize_variable_CounterIndicatorsForMDTI": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "SubarrayIndicatorsForMDTI", + "type": "array" + } + ] + } + }, + "Initialize_variable_CsvIndicatorsForMDTI": { + "runAfter": { + "Initialize_variable_SubarrayIndicatorsForMDTI": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "CsvIndicatorsForMDTI", + "type": "string" + } + ] + } + }, + "Initialize_variable_ResponsesFromMDTI": { + "runAfter": { + "Initialize_variable_CsvIndicatorsForMDTI": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "ResponsesFromMDTI", + "type": "array" + } + ] + } + }, + "Initialize_variable_OutputLength": { + "runAfter": { + "Initialize_variable_Results-IPs": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "OutputLengthMDTI", + "type": "integer" + } + ] + } + }, + "Initialize_variable_Response-MDTI": { + "runAfter": { + "Initialize_variable_ResponsesFromMDTI": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "Response-MDTI", + "type": "string" + } + ] + } + }, + "Initialize_variable_forScp-UsersForRiskAnalysis": { + "runAfter": { + "Initialize_variable_Results-UsersDetails": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "forScp-UsersForRiskAnalysis", + "type": "array" + } + ] + } + }, + "Initialize_variable_ProcessedItems": { + "runAfter": { + "Initialize_variable_OutputLength": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "ProcessedItems", + "type": "array" + } + ] + } + }, + "Initialize_variable_forScp-UsersForDetails": { + "runAfter": { + "Initialize_variable_Response-MDTI": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "forScp-UsersForDetails", + "type": "array" + } + ] + } + }, + "Initialize_variable_forScp-Devices": { + "runAfter": { + "Initialize_variable_Results-UsersRisk": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "forScp-Devices", + "type": "array" + } + ] + } + }, + "Initialize_variable_forScp-IPs": { + "runAfter": { + "Initialize_variable_Unique-forScp-Devices": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "forScp-IPs", + "type": "array" + } + ] + } + }, + "Initialize_variable_Unique-forScp-CommandLines": { + "runAfter": { + "Initialize_variable_forScp-CommandLines": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "Unique-forScp-CommandLines", + "type": "array", + "value": [ + ] + } + ] + } + }, + "Initialize_variable_Unique-forScp-Indicators": { + "runAfter": { + "Initialize_variable_forScp-Indicators": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "Unique-forScp-Indicators", + "type": "array", + "value": [ + ] + } + ] + } + }, + "Initialize_variable_Unique-forScp-UsersForDetails": { + "runAfter": { + "Initialize_variable_forScp-UsersForDetails": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "Unique-forScp-UsersForDetails", + "type": "array", + "value": [ + ] + } + ] + } + }, + "Initialize_variable_Unique-forScp-UsersForRiskAnalysis": { + "runAfter": { + "Initialize_variable_forScp-UsersForRiskAnalysis": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "Unique-forScp-UsersForRiskAnalysis", + "type": "array", + "value": [ + ] + } + ] + } + }, + "Initialize_variable_Unique-forScp-Devices": { + "runAfter": { + "Initialize_variable_Results-Devices": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "Unique-forScp-Devices", + "type": "array", + "value": [ + ] + } + ] + } + }, + "Initialize_variable_Unique-forScp-IPs": { + "runAfter": { + "Initialize_variable_forScp-IPs": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "Unique-forScp-IPs", + "type": "array", + "value": [ + ] + } + ] + } + }, + "Initialize_variable_ResultsAvailable": { + "runAfter": { + "Initialize_variable_ProcessedItems": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "ResultsAvailable", + "type": "boolean", + "value": false + } + ] + } + }, + "Condition_-_Results_Available": { + "actions": { + "Compose_-_Incident_Data": { + "type": "Compose", + "inputs": "Incident Ttile: \n@{triggerBody()?['object']?['properties']?['title']},\n\nIncident Description: \n@{triggerBody()?['object']?['properties']?['description']},\n\nIncident Severity: \n@{triggerBody()?['object']?['properties']?['severity']},\n\nIncident Tactics: \n@{triggerBody()?['object']?['properties']?['additionalData']?['tactics']},\n\n______________________________________\nIp Addresses Analysis: \n@{if(parameters('CallScpForIPsWithAbuseIPDB'),variables('Results-IPs'),'skipped...')},\n\n______________________________________\nAccounts Analysis: \n@{if(parameters('CallScpForAccounts'),variables('Results-UsersDetails'),'skipped...')},\n\n______________________________________\nUsers Risk Analysis: \n@{if(parameters('CallScpForUserRisk'),outputs('Compose_Users_Risk'),'skipped...')},\n\n______________________________________\nDevices Analysis: \n@{if(parameters('CallScpForHosts'),variables('Results-Devices'),'skipped...')},\n\n______________________________________\nProcesses Analysis: \n@{if(parameters('CallScpForCommandLines'),outputs('Compose_Process_Analysis'),'skipped...')}\n\n______________________________________\nIndicators Analysis from MDTI: \n@{if(or(parameters('CallScpForIPsWithMDTI'),parameters('CallScpForFilehashesWithMDTI'),parameters('CallScpForUrlsWithMDTI'),parameters('CallScpForDomainsWithMDTI')),variables('Response-MDTI'),'skipped...')}" + }, + "Compose_-_Normalized_Incident_Data": { + "runAfter": { + "Compose_-_Incident_Data": [ + "Succeeded" + ] + }, + "type": "Compose", + "inputs": "@replace(replace(outputs('Compose_-_Incident_Data'),',null',''),'[null,','')" + }, + "Condition_-_Send_unsummarized_results_by_email": { + "actions": { + "Send_an_email_(V2)_-_Enrichment_Details": { + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['office365']['connectionId']" + } + }, + "method": "post", + "body": { + "To": "@trim(parameters('RecipientEmail'))", + "Subject": "Incident @{triggerBody()?['object']?['properties']?['providerIncidentId']} - Enrichment Details", + "Body": "\u003cp class=\"editor-paragraph\"\u003e\u003cb\u003e\u003cstrong class=\"editor-text-bold\"\u003e--- Incident Analysis - Details---\u003c/strong\u003e\u003c/b\u003e\u003c/p\u003e\u003cp class=\"editor-paragraph\"\u003eIncident URL: @{concat('https://security.microsoft.com/incident2/',triggerBody()?['object']?['properties']?['providerIncidentId'],'/overview')}\u003c/p\u003e\u003cbr\u003e\u003cp class=\"editor-paragraph\"\u003e@{outputs('Compose_-_Details_-_Replace_NewLine_char')}\u003c/p\u003e", + "Importance": "Normal" + }, + "path": "/v2/Mail" + } + } + }, + "runAfter": { + "Compose_-_Details_-_Replace_NewLine_char": [ + "Succeeded" + ] + }, + "else": { + "actions": { + } + }, + "expression": { + "and": [ + { + "contains": [ + "@parameters('RecipientEmail')", + "@" + ] + } + ] + }, + "type": "If" + }, + "Condition_-_Write_unsummarized_results_on_incident": { + "actions": { + "Add_unsummarized_results_to_incident": { + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['azuresentinel']['connectionId']" + } + }, + "method": "post", + "body": { + "incidentArmId": "@triggerBody()?['object']?['id']", + "message": "\u003cp class=\"editor-paragraph\"\u003e\u003cb\u003e\u003cstrong class=\"editor-text-bold\"\u003e--- Incident Analysis - Details---\u003c/strong\u003e\u003c/b\u003e\u003c/p\u003e\u003cbr\u003e\u003cp class=\"editor-paragraph\"\u003e@{outputs('Compose_-_Details_-_Replace_NewLine_char')}\u003c/p\u003e\u003cbr\u003e" + }, + "path": "/Incidents/Comment" + } + } + }, + "runAfter": { + "Compose_-_Details_-_Replace_NewLine_char": [ + "Succeeded" + ] + }, + "else": { + "actions": { + } + }, + "expression": { + "and": [ + { + "equals": [ + "@parameters('WriteUnsummarizedResultsToIncident')", + true + ] + } + ] + }, + "type": "If" + }, + "Security_Copilot_summarize_incident_investigation": { + "runAfter": { + "Condition_-_Send_unsummarized_results_by_email": [ + "Succeeded", + "TimedOut", + "Skipped", + "Failed" + ], + "Condition_-_Write_unsummarized_results_on_incident": [ + "Succeeded", + "TimedOut", + "Skipped", + "Failed" + ] + }, + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['securitycopilot']['connectionId']" + } + }, + "method": "post", + "body": { + "PromptContent": "Here is the security incident detail: @{outputs('Compose_-_Normalized_Incident_Data')}\n\nPerform investigation on security incident in following order. Incident Overview, Incident Description, Ip Addresses Analysis , Account Analysis, Users Risk Analysis, Devices Analysis, Processes Analysis, Indicators Analysis from MDTI, Possible mitigation steps and Conclusion. Ignore all error messages", + "SkillName": "SummarizeData" + }, + "path": "/process-prompt" + } + }, + "Condition_-_SCP_-_Final_prompt_was_successful": { + "actions": { + "Compose_-_Incident_Investigation": { + "type": "Compose", + "inputs": "@body('Security_Copilot_summarize_incident_investigation')?['EvaluationResultContent']" + }, + "Condition_-_Write_final_summary_to_incident_-_Success": { + "actions": { + "Add_Investigation_Final_summary_to_Incident": { + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['azuresentinel']['connectionId']" + } + }, + "method": "post", + "body": { + "incidentArmId": "@triggerBody()?['object']?['id']", + "message": "\u003cp class=\"editor-paragraph\"\u003e\u003cb\u003e\u003cstrong class=\"editor-text-bold\"\u003e--- Incident Analysis - Summary ---\u003c/strong\u003e\u003c/b\u003e\u003c/p\u003e\u003cp class=\"editor-paragraph\"\u003e\u003cbr\u003e@{outputs('Compose_-_Incident_Investigation')}\u003c/p\u003e\u003cbr\u003e" + }, + "path": "/Incidents/Comment" + } + } + }, + "runAfter": { + "Compose_-_Incident_Investigation": [ + "Succeeded" + ] + }, + "else": { + "actions": { + } + }, + "expression": { + "and": [ + { + "equals": [ + "@parameters('WriteFinalSummaryToIncident')", + true + ] + } + ] + }, + "type": "If" + }, + "Condition_-_RecipientEmail_-_Final_-_Success": { + "actions": { + "Send_an_email_(V2)_-_Enrichment_Summary_-_Success": { + "runAfter": { + "Compose_-_Summary_-_Success_-_Replace_NewLine_char": [ + "Succeeded" + ] + }, + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['office365']['connectionId']" + } + }, + "method": "post", + "body": { + "To": "@trim(parameters('RecipientEmail'))", + "Subject": "Incident @{triggerBody()?['object']?['properties']?['providerIncidentId']} - Enrichment Summary", + "Body": "\u003cp class=\"editor-paragraph\"\u003e\u003cb\u003e\u003cstrong class=\"editor-text-bold\"\u003e--- Incident Analysis - Summary ---\u003c/strong\u003e\u003c/b\u003e\u003cbr\u003eIncident URL: @{concat('https://security.microsoft.com/incident2/',triggerBody()?['object']?['properties']?['providerIncidentId'],'/overview')}\u003c/p\u003e\u003cbr\u003e\u003cp class=\"editor-paragraph\"\u003e@{outputs('Compose_-_Summary_-_Success_-_Replace_NewLine_char')}\u003c/p\u003e", + "Importance": "Normal" + }, + "path": "/v2/Mail" + } + }, + "Compose_-_Summary_-_Success_-_Replace_NewLine_char": { + "type": "Compose", + "inputs": "@replace(replace(outputs('Compose_-_Incident_Investigation'),'\\n','\u003cbr\u003e'),'\n','\u003cbr\u003e')" + } + }, + "runAfter": { + "Compose_-_Incident_Investigation": [ + "Succeeded" + ] + }, + "else": { + "actions": { + } + }, + "expression": { + "and": [ + { + "contains": [ + "@parameters('RecipientEmail')", + "@" + ] + } + ] + }, + "type": "If" + }, + "Terminate_-_Success": { + "runAfter": { + "Condition_-_Write_final_summary_to_incident_-_Success": [ + "Succeeded" + ], + "Condition_-_RecipientEmail_-_Final_-_Success": [ + "Succeeded" + ] + }, + "type": "Terminate", + "inputs": { + "runStatus": "Succeeded" + } + } + }, + "runAfter": { + "Security_Copilot_summarize_incident_investigation": [ + "Succeeded", + "TimedOut", + "Skipped", + "Failed" + ] + }, + "else": { + "actions": { + "Condition_-_Unsummarized_results_not_already_written_to_incident": { + "actions": { + "Add_Investigation_Final_comment_to_Incident": { + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['azuresentinel']['connectionId']" + } + }, + "method": "post", + "body": { + "incidentArmId": "@triggerBody()?['object']?['id']", + "message": "\u003cp class=\"editor-paragraph\"\u003e\u003cb\u003e\u003cstrong class=\"editor-text-bold\"\u003e--- Incident Analysis - Evidences ---\u003c/strong\u003e\u003c/b\u003e\u003c/p\u003e\u003cp class=\"editor-paragraph\"\u003e\u003ci\u003e\u003cem class=\"editor-text-italic\"\u003e(Content not summarized)\u003c/em\u003e\u003c/i\u003e\u003c/p\u003e\u003cbr\u003e\u003cp class=\"editor-paragraph\"\u003e@{outputs('Compose_-_Details_-_Replace_NewLine_char')}\u003c/p\u003e" + }, + "path": "/Incidents/Comment" + } + } + }, + "else": { + "actions": { + } + }, + "expression": { + "and": [ + { + "equals": [ + "@parameters('WriteUnsummarizedResultsToIncident')", + false + ] + }, + { + "equals": [ + "@parameters('WriteFinalSummaryToIncident')", + true + ] + } + ] + }, + "type": "If" + }, + "Terminate_-_Failed": { + "runAfter": { + "Condition_-_Unsummarized_results_not_already_written_to_incident": [ + "Succeeded" + ], + "Condition_-_Unsummarized_results_not_already_sent_by_email": [ + "Succeeded" + ] + }, + "type": "Terminate", + "inputs": { + "runStatus": "Failed", + "runError": { + "message": "Final summarization failed" + } + } + }, + "Condition_-_Unsummarized_results_not_already_sent_by_email": { + "actions": { + "Send_an_email_(V2)_-_Enrichment_Summary_-_Failure": { + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['office365']['connectionId']" + } + }, + "method": "post", + "body": { + "To": "@trim(parameters('RecipientEmail'))", + "Subject": "Incident @{triggerBody()?['object']?['properties']?['providerIncidentId']} - Enrichment Summary - Failure", + "Body": "\u003cp class=\"editor-paragraph\"\u003e\u003cb\u003e\u003cstrong class=\"editor-text-bold\"\u003e--- Incident Analysis - Evidences ---\u003c/strong\u003e\u003c/b\u003e\u003cbr\u003e\u003ci\u003e\u003cem class=\"editor-text-italic\"\u003e(Content not summarized)\u003c/em\u003e\u003c/i\u003e\u003cbr\u003e\u003cbr\u003e@{outputs('Compose_-_Details_-_Replace_NewLine_char')}\u003c/p\u003e", + "Importance": "Normal" + }, + "path": "/v2/Mail" + } + } + }, + "else": { + "actions": { + } + }, + "expression": { + "and": [ + { + "equals": [ + "@parameters('SendUnsummarizedResultsByEmail')", + false + ] + }, + { + "equals": [ + "@parameters('SendFinalSummaryByEmail')", + true + ] + } + ] + }, + "type": "If" + } + } + }, + "expression": { + "and": [ + { + "equals": [ + "@body('Security_Copilot_summarize_incident_investigation')?['evaluationResultType']", + "Success" + ] + } + ] + }, + "type": "If" + }, + "Compose_-_Details_-_Replace_NewLine_char": { + "runAfter": { + "Compose_-_Normalized_Incident_Data": [ + "Succeeded" + ] + }, + "type": "Compose", + "inputs": "@replace(replace(outputs('Compose_-_Normalized_Incident_Data'),'\\n','\u003cbr\u003e'),'\n','\u003cbr\u003e')" + } + }, + "runAfter": { + "Condition_-_Validate_Entities": [ + "Succeeded" + ] + }, + "else": { + "actions": { + "No_results_available": { + "type": "Terminate", + "inputs": { + "runStatus": "Cancelled" + } + } + } + }, + "expression": { + "and": [ + { + "equals": [ + "@variables('ResultsAvailable')", + true + ] + } + ] + }, + "type": "If" + }, + "Condition_-_Validate_Parameters": { + "actions": { + }, + "runAfter": { + "Initialize_variable_ResultsAvailable": [ + "Succeeded" + ] + }, + "else": { + "actions": { + "Invalid_Parameters": { + "type": "Terminate", + "inputs": { + "runStatus": "Cancelled" + } + } + } + }, + "expression": { + "and": [ + { + "contains": [ + "@parameters('RecipientEmail')", + "@" + ] + }, + { + "or": [ + { + "equals": [ + "@parameters('SendUnsummarizedResultsByEmail')", + true + ] + }, + { + "equals": [ + "@parameters('SendFinalSummaryByEmail')", + true + ] + } + ] + } + ] + }, + "type": "If" + }, + "Initialize_variable_Results-Devices": { + "runAfter": { + "Initialize_variable_forScp-Devices": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "Results-Devices", + "type": "array", + "value": [ + ] + } + ] + } + }, + "Initialize_variable_Results-IPs": { + "runAfter": { + "Initialize_variable_Unique-forScp-IPs": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "Results-IPs", + "type": "array", + "value": [ + ] + } + ] + } + }, + "Initialize_variable_Results-UsersDetails": { + "runAfter": { + "Initialize_variable_Unique-forScp-UsersForDetails": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "Results-UsersDetails", + "type": "array", + "value": [ + ] + } + ] + } + }, + "Initialize_variable_Results-UsersRisk": { + "runAfter": { + "Initialize_variable_Unique-forScp-UsersForRiskAnalysis": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "Results-UsersRisk", + "type": "array", + "value": [ + ] + } + ] + } + }, + "Initialize_variable_Results-CommandLines": { + "runAfter": { + "Initialize_variable_Unique-forScp-CommandLines": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "Results-CommandLines", + "type": "array", + "value": [ + ] + } + ] + } + } + }, + "outputs": { + } + }, + "parameters": { + "$connections": { + "value": { + "azuresentinel": { + "connectionId": "[resourceId('Microsoft.Web/connections', variables('MicrosoftSentinelConnectionName'))]", + "connectionName": "[variables('MicrosoftSentinelConnectionName')]", + "id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/Azuresentinel')]", + "connectionProperties": { + "authentication": { + "type": "ManagedServiceIdentity" + } + } + }, + "securitycopilot": { + "connectionId": "[resourceId('Microsoft.Web/connections', variables('SecuritycopilotConnectionName'))]", + "connectionName": "[variables('SecuritycopilotConnectionName')]", + "id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/Securitycopilot')]" + }, + "office365": { + "connectionId": "[resourceId('Microsoft.Web/connections', variables('Office365ConnectionName'))]", + "connectionName": "[variables('Office365ConnectionName')]", + "id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/Office365')]" + } + } + } + } + }, + "name": "[parameters('PlaybookName')]", + "type": "Microsoft.Logic/workflows", + "location": "[resourceGroup().location]", + "tags": { + "hidden-SentinelTemplateName": "SecurityCopilot-Sentinel-Incident-Investigation", + "hidden-SentinelTemplateVersion": "1.0" + }, + "identity": { + "type": "SystemAssigned" + }, + "apiVersion": "2017-07-01", + "dependsOn": [ + "[resourceId('Microsoft.Web/connections', variables('MicrosoftSentinelConnectionName'))]", + "[resourceId('Microsoft.Web/connections', variables('SecuritycopilotConnectionName'))]", + "[resourceId('Microsoft.Web/connections', variables('Office365ConnectionName'))]" + ] + }, + { + "type": "Microsoft.Web/connections", + "apiVersion": "2016-06-01", + "name": "[variables('MicrosoftSentinelConnectionName')]", + "location": "[resourceGroup().location]", + "kind": "V1", + "properties": { + "displayName": "[variables('MicrosoftSentinelConnectionName')]", + "customParameterValues": { + }, + "parameterValueType": "Alternative", + "api": { + "id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/Azuresentinel')]" + } + } + }, + { + "type": "Microsoft.Web/connections", + "apiVersion": "2016-06-01", + "name": "[variables('SecuritycopilotConnectionName')]", + "location": "[resourceGroup().location]", + "kind": "V1", + "properties": { + "displayName": "[variables('SecuritycopilotConnectionName')]", + "customParameterValues": { + }, + "api": { + "id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/Securitycopilot')]" + } + } + }, + { + "type": "Microsoft.Web/connections", + "apiVersion": "2016-06-01", + "name": "[variables('Office365ConnectionName')]", + "location": "[resourceGroup().location]", + "kind": "V1", + "properties": { + "displayName": "[variables('Office365ConnectionName')]", + "customParameterValues": { + }, + "api": { + "id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/Office365')]" + } + } + } + ] +} diff --git a/Logic Apps/SecurityCopilot-Sentinel-Incident-Investigation/images/copilot-for-security-authentication.png b/Logic Apps/SecurityCopilot-Sentinel-Incident-Investigation/images/copilot-for-security-authentication.png new file mode 100644 index 00000000..a93e6f4f Binary files /dev/null and b/Logic Apps/SecurityCopilot-Sentinel-Incident-Investigation/images/copilot-for-security-authentication.png differ diff --git a/Logic Apps/SecurityCopilot-Sentinel-Incident-Investigation/images/copilot-for-security-investigation-prompt.png b/Logic Apps/SecurityCopilot-Sentinel-Incident-Investigation/images/copilot-for-security-investigation-prompt.png new file mode 100644 index 00000000..797871ac Binary files /dev/null and b/Logic Apps/SecurityCopilot-Sentinel-Incident-Investigation/images/copilot-for-security-investigation-prompt.png differ diff --git a/Logic Apps/SecurityCopilot-Sentinel-Incident-Investigation/images/params.png b/Logic Apps/SecurityCopilot-Sentinel-Incident-Investigation/images/params.png new file mode 100644 index 00000000..e97f11ba Binary files /dev/null and b/Logic Apps/SecurityCopilot-Sentinel-Incident-Investigation/images/params.png differ diff --git a/Logic Apps/SecurityCopilot-Sentinel-Incident-Investigation/images/sentinel-trigger-authentication.png b/Logic Apps/SecurityCopilot-Sentinel-Incident-Investigation/images/sentinel-trigger-authentication.png new file mode 100644 index 00000000..5301b4a9 Binary files /dev/null and b/Logic Apps/SecurityCopilot-Sentinel-Incident-Investigation/images/sentinel-trigger-authentication.png differ diff --git a/Logic Apps/SecurityCopilot-Sentinel-Incident-Investigation/readme.md b/Logic Apps/SecurityCopilot-Sentinel-Incident-Investigation/readme.md new file mode 100644 index 00000000..1dad3e5d --- /dev/null +++ b/Logic Apps/SecurityCopilot-Sentinel-Incident-Investigation/readme.md @@ -0,0 +1,74 @@ +# SecurityCopilot-Sentinel-Incident-Investigation +**Author: Pratik Lal, Innocent Wafula, Stefano Pescosolido** + +## Overview + +This Logic App automates incident investigation within Microsoft Sentinel using the Microsoft Security Copilot prompt and built-in skills. It is designed to: +- Read Microsoft Sentinel incident entities. +- Process and normalize the entities. +- Submit prompts to Security Copilot to analyze each entity type. +- Summarize the outputs in a predefined format. +- Update the Microsoft Sentinel incident comments for the incident investigator to review (writing to the Sentinel incident can be disabled for facilitating the initial testing). +- Optionally send the result of the investigation by email. + +By leveraging this Logic App, Security Operations Center (SOC) and incident response teams can significantly reduce investigation time, gain consistent analysis outputs, and enhance their incident response process. + +## Prerequisites +- The user or service principal deploying this logic app should have Contributor role on the Azure Resource Group. +- Microsoft Security Copilot should be enabled on the Azure tenant. +- The user should have access to Microsoft Security Copilot to submit prompts by authenticating to Microsoft Security Copilot connector within the logic app. +- Microsoft Sentinel workspace should be set up. +- Optionally get AbuseIPDB API key to perform IP address reputation check. [Learn more.](https://learn.microsoft.com/en-us/copilot/security/plugin-abuseipdb) +- Enable Defender, Sentinel, Entra, Intune, MDTI and AbuseIPDB plugins from copilot settings for the user account configured on the API connection of the Logic App to Security Copilot. + + +## Deployment + + + + + +
+ +## Post-Deployment +- Authenticate Microsoft Sentinel and Security Copilot connectors within the logic app. You can choose Oauth, Service principal or Managed identity based authentication for Microsoft Sentinel trigger. +
+
+- Choose Client Certificate Auth or Oauth (using user account that can submit prompts) to create connection with Microsoft Security Copilot logic app connector. +
+ +
+- Important: the account selected for the authentication to Security Copilot must have the Contributor role on Security Copilot and reader privileges on Defender, Sentinel, Entra and Intune. +- Similarly, authenticate the API connection to Exchange Online (the correct setup of connection is needed even if the Logic App is configured to not send emails) +- Assign the role of 'Microsoft Sentinel Responder' to the Managed Identity of the Logic App within the Resource Group that contains the Sentinel's Log Analytics workspace. +- Set the values for the Logic App parameters + +- Automate Sentinel incident investigation by adding the Logic App to Microsoft Sentinel automation rule. Alternatively, this logic app can be called on-demand by right-clicking on a Sentinel incident, choose **Run Playbook** option. + +## Microsoft Security Copilot skills +- Following built-in skills are being used to perform investigation on a given incident entity type + +| Incident entity | Security Copilot skill | +| --------------- | -------------------------- | +| Process name / commandline | ProcessAnalyzer | +| Account name | GetEntraUserDetails | +| Account names | GetEntraRiskyUsers | +| Device name | GetIntuneDevices | +| IP address | AbuseIPDB | +| Indicator of Compromise (IoC) | GetReputationsForIndicators | + + +## Incident Investigation Template +- Automated incident investigation summary is structured in following order. Modify the prompt to re-order or change the investigation template. + - Incident Overview, + - Incident Description, + - IP Addresses Analysis, + - Account Analysis, + - Users Risk Analysis, + - Devices Analysis, + - Processes Analysis, + - Indicators Analysis from MDTI, + - Possible mitigation steps and Conclusion +
+ +
diff --git a/Logic Apps/ciso-reporting/ciso-get-users-status.json b/Logic Apps/ciso-reporting/ciso-get-users-status.json new file mode 100644 index 00000000..28e8a1a4 --- /dev/null +++ b/Logic Apps/ciso-reporting/ciso-get-users-status.json @@ -0,0 +1,344 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "title": "Get the status of a set of users in MS Entra", + "description": "This Logic App return the status, in Microsoft Entra, of the users specified in input as comma separated values of User Principal Names (UPNs). For each user it returns if the user is Enabled or Disabled and the level of risk High, Medium or Low or Unknown", + "prerequisites": "", + "prerequisitesDeployTemplateFile": "", + "lastUpdateTime": "", + "entities": [ + ], + "tags": [ + ], + "support": { + "tier": "community", + "armtemplate": "Generated from https://github.com/Azure/Azure-Sentinel/tree/master/Tools/Playbook-ARM-Template-Generator" + }, + "author": { + "name": "Stefano Pescosolido (https://www.linkedin.com/in/stefanopescosolido/)" + } + }, + "parameters": { + "PlaybookName": { + "defaultValue": "CfS-CisoGetUserStatus", + "type": "string" + } + }, + "variables": { + "SecuritycopilotConnectionName": "[concat('Securitycopilot-', parameters('PlaybookName'))]" + }, + "resources": [ + { + "properties": { + "provisioningState": "Succeeded", + "state": "Enabled", + "definition": { + "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "$connections": { + "defaultValue": { + }, + "type": "Object" + } + }, + "triggers": { + "AnalyzeUPNsCall": { + "type": "Request", + "kind": "Http", + "inputs": { + "method": "POST", + "schema": { + "type": "object", + "properties": { + "users_upn": { + "type": "string" + } + } + } + }, + "conditions": [ + ] + } + }, + "actions": { + "For_each_-_UPN": { + "foreach": "@variables('UPNs')", + "actions": { + "Submit_a_Security_Copilot_prompt_-_Enablement": { + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['securitycopilot']['connectionId']" + } + }, + "method": "post", + "body": { + "PromptContent": "User UPN: @{trim(items('For_each_-_UPN'))}. Respond with only 1 word: \"Enabled\" or \"Disabled\", depending on the current value of the \"Account enabled\" attribute, or \"Unknown\" if you can't read the value of this attribute", + "SkillName": "GetEntraUserDetailsV1" + }, + "path": "/process-prompt" + } + }, + "Condition_-_evaluationResultType_-_Enablement": { + "actions": { + "Append_to_array_variable_-_Enablement_-_Success": { + "type": "AppendToArrayVariable", + "inputs": { + "name": "ResponseArray", + "value": { + "UPN": "@{trim(items('For_each_-_UPN'))}", + "Enablement Status": "@{trim(body('Submit_a_Security_Copilot_prompt_-_Enablement')?['EvaluationResultContent'])}" + } + } + } + }, + "runAfter": { + "Submit_a_Security_Copilot_prompt_-_Enablement": [ + "Succeeded" + ] + }, + "else": { + "actions": { + "Append_to_array_variable_-_Enablement_-_Error": { + "type": "AppendToArrayVariable", + "inputs": { + "name": "ResponseArray", + "value": { + "UPN": "@{trim(items('For_each_-_UPN'))}", + "Enablement Status": "Error" + } + } + } + } + }, + "expression": { + "and": [ + { + "equals": [ + "@body('Submit_a_Security_Copilot_prompt_-_Enablement')?['evaluationResultType']", + "Success" + ] + } + ] + }, + "type": "If" + }, + "Submit_a_Security_Copilot_prompt_-_Entra_Risk": { + "runAfter": { + "Condition_-_evaluationResultType_-_Enablement": [ + "Succeeded" + ] + }, + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['securitycopilot']['connectionId']" + } + }, + "method": "post", + "body": { + "PromptContent": "Check if the user @{trim(items('For_each_-_UPN'))} has a Risk Level associated. Respond with only 1 of these possible 5 word: \"High\", \"Medium\", \"Low\", according to the Risk Level of the user account, \"None\" if no risk level is assigned to the user account or \"Unknown\" if it was not possible to read the risk level for the user account.", + "SkillName": "GetEntraRiskyUsers" + }, + "path": "/process-prompt" + } + }, + "Condition_-_evaluationResultType_-_Entra_Risk": { + "actions": { + "Append_to_array_variable_-_Risk_-_Success": { + "type": "AppendToArrayVariable", + "inputs": { + "name": "ResponseArray", + "value": { + "UPN": "@{trim(items('For_each_-_UPN'))}", + "Risk Status": "@{trim(body('Submit_a_Security_Copilot_prompt_-_Entra_Risk')?['EvaluationResultContent'])}" + } + } + } + }, + "runAfter": { + "Submit_a_Security_Copilot_prompt_-_Entra_Risk": [ + "Succeeded", + "TimedOut", + "Failed" + ] + }, + "else": { + "actions": { + "Append_to_array_variable_-_Risk_-_Error": { + "type": "AppendToArrayVariable", + "inputs": { + "name": "ResponseArray", + "value": { + "UPN": "@{trim(items('For_each_-_UPN'))}", + "Risk Status": "Error" + } + } + } + } + }, + "expression": { + "and": [ + { + "equals": [ + "@body('Submit_a_Security_Copilot_prompt_-_Entra_Risk')?['evaluationResultType']", + "Success" + ] + } + ] + }, + "type": "If" + } + }, + "runAfter": { + "Initialize_variable_-_ResponseArray": [ + "Succeeded" + ] + }, + "type": "Foreach", + "runtimeConfiguration": { + "concurrency": { + "repetitions": 20 + } + } + }, + "Initialize_variable_-_UPNs": { + "runAfter": { + "Initialize_variable_-_SessionID_-_Use_it_in_Copilot_calls_if_needed": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "UPNs", + "type": "array", + "value": "@split(triggerBody()?['users_upn'],',')" + } + ] + } + }, + "Initialize_variable_-_CallResponse": { + "runAfter": { + "Initialize_variable_-_UPNs": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "CallResponse", + "type": "string" + } + ] + } + }, + "Initialize_variable_-_SessionID_-_Use_it_in_Copilot_calls_if_needed": { + "runAfter": { + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "SessionID", + "type": "string", + "value": "@triggerOutputs()?['headers']?['x-securitycopilot-sessionid']" + } + ] + } + }, + "Response_-_Return_the_users_status_to_the_caller": { + "runAfter": { + "Set_variable_-_Call_Response": [ + "Succeeded" + ] + }, + "type": "Response", + "kind": "Http", + "inputs": { + "statusCode": 200, + "body": "@variables('CallResponse')" + } + }, + "Initialize_variable_-_ResponseArray": { + "runAfter": { + "Initialize_variable_-_CallResponse": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "ResponseArray", + "type": "array" + } + ] + } + }, + "Set_variable_-_Call_Response": { + "runAfter": { + "For_each_-_UPN": [ + "Succeeded" + ] + }, + "type": "SetVariable", + "inputs": { + "name": "CallResponse", + "value": "@concat('[',join(variables('ResponseArray'),','),']')" + } + } + }, + "outputs": { + } + }, + "parameters": { + "$connections": { + "value": { + "securitycopilot": { + "connectionId": "[resourceId('Microsoft.Web/connections', variables('SecuritycopilotConnectionName'))]", + "connectionName": "[variables('SecuritycopilotConnectionName')]", + "id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/Securitycopilot')]" + } + } + } + } + }, + "name": "[parameters('PlaybookName')]", + "type": "Microsoft.Logic/workflows", + "location": "[resourceGroup().location]", + "tags": { + "hidden-SentinelTemplateName": "CfS-CisoGetUserStatus", + "hidden-SentinelTemplateVersion": "1.0" + }, + "identity": { + "type": "SystemAssigned" + }, + "apiVersion": "2017-07-01", + "dependsOn": [ + "[resourceId('Microsoft.Web/connections', variables('SecuritycopilotConnectionName'))]" + ] + }, + { + "type": "Microsoft.Web/connections", + "apiVersion": "2016-06-01", + "name": "[variables('SecuritycopilotConnectionName')]", + "location": "[resourceGroup().location]", + "kind": "V1", + "properties": { + "displayName": "[variables('SecuritycopilotConnectionName')]", + "customParameterValues": { + }, + "api": { + "id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/Securitycopilot')]" + } + } + } + ] +} diff --git a/Logic Apps/ciso-reporting/ciso-incidents-summary-api-spec.yaml b/Logic Apps/ciso-reporting/ciso-incidents-summary-api-spec.yaml new file mode 100644 index 00000000..7e0652ad --- /dev/null +++ b/Logic Apps/ciso-reporting/ciso-incidents-summary-api-spec.yaml @@ -0,0 +1,44 @@ +openapi: 3.0.0 + +info: + title: CISO Incidents Summary + description: Helper skills used to get specific summary info on the incidents in Defender + version: "0.0.1" + +servers: + - url: + +paths: + /workflows//triggers/AnalyzeUPNsCall/paths/invoke: + post: + operationId: CisoGetUsersStatus + summary: Get the status of a list of users in terms of enablement and risk level in Entra. + parameters: + - name: api-version + in: query + default: 2016-10-01 + schema: + type: string + - name: sp + in: query + default: /triggers/AnalyzeUPNsCall/run + schema: + type: string + - name: sv + in: query + default: 1.0 + schema: + type: string + requestBody: + description: Comma separated list of the User Principal Names (UPNs) of the user to be analyzed + required: true + content: + application/json: + schema: + type: object + properties: + users_upn: + type: string + responses: + "200": + description: Status of the users diff --git a/Logic Apps/ciso-reporting/ciso-incidents-summary-man.yaml b/Logic Apps/ciso-reporting/ciso-incidents-summary-man.yaml new file mode 100644 index 00000000..bdab9c01 --- /dev/null +++ b/Logic Apps/ciso-reporting/ciso-incidents-summary-man.yaml @@ -0,0 +1,282 @@ +Descriptor: + Name: CisoIncidentsSummary + DisplayName: CISO Incidents Summary + Description: Skills to get a summary view on the status of the incidents in Defender + + SupportedAuthTypes: + - ApiKey + + Authorization: + Type: APIKey + Key: sig + Location: QueryParams + AuthScheme: '' + +SkillGroups: + + - Format: API + Settings: + OpenApiSpecUrl: /ciso-incidents-summary-api-spec.yaml + + - Format: KQL + Skills: + - Name: CisoIncidentsStatusSummary + DisplayName: CISO - Incidents Status Summary + Description: Get a summary of the incidents status in Defender + DescriptionForModel : Get a summary of the incidents status in Defender. + If severities are specified in the prompt (e.g. High and Medium), concatenate them as a comma separated list of double-quoted strings with the first letter in uppercase (like "High","Medium"). + If start date and end date are not specified, consider the last 7 days. + If the top number of results is not specified, consider 5 incidents titles. + If csv_of_severities is not specified, consider only "High". + Inputs: + - Name: start_date + Description: The first date of the considered time window + Required: true + - Name: end_date + Description: The end date of the considered time window + Required: true + - Name: top_results_number + Description: The number of top incidents to be considered + Required: true + - Name: csv_of_severities + Description: Comma separated list of severities. E.g. "High","Medium" + Required: true + Settings: + Target: Defender + Template: |- + let start_time = datetime({{start_date}}); + let end_time = datetime({{end_date}}); + let top_incs_number = int({{top_results_number}}); + let severities = dynamic([{{csv_of_severities}}]); + let top_incidents = SecurityIncident + | summarize arg_max(TimeGenerated, *) by IncidentName + | where Status <> "Closed" or not(Status == "Closed" and Classification == "Undetermined" and ModifiedBy == "Microsoft XDR" ) + | where Severity in (severities) and CreatedTime between (start_time .. end_time) + | extend SecInc = strcat("[",Severity,"] ",Title) + | summarize TotalCount = count() by SecInc + | top top_incs_number by TotalCount; + let closed_incidents = SecurityIncident + | summarize arg_max(TimeGenerated, *) by IncidentName + | where Status <> "Closed" or not(Status == "Closed" and Classification == "Undetermined" and ModifiedBy == "Microsoft XDR" ) + | where Severity in (severities) and not(isnull(ClosedTime)) and CreatedTime between (start_time .. end_time) + | extend SecInc = strcat("[",Severity,"] ",Title) + | summarize ClosedCount = count() by SecInc; + let classified_incidents = SecurityIncident + | summarize arg_max(TimeGenerated, *) by IncidentName + | where Status <> "Closed" or not(Status == "Closed" and Classification == "Undetermined" and ModifiedBy == "Microsoft XDR" ) + | where Severity in (severities) and CreatedTime between (start_time .. end_time) + | extend SecInc = strcat("[",Severity,"] ",Title) + | summarize BenignPositiveCount = countif(Classification == "BenignPositive"), + FalsePositiveCount = countif(Classification == "FalsePositive"), + TruePositiveCount = countif(Classification == "TruePositive"), + UndeterminedCount = countif(Classification == "Undetermined") by SecInc; + top_incidents + | join kind=leftouter (closed_incidents) on SecInc + | join kind=leftouter (classified_incidents) on SecInc + | project SecInc, TotalCount, ClosedCount, BenignPositiveCount, FalsePositiveCount, TruePositiveCount, UndeterminedCount + | order by TotalCount desc, ClosedCount desc, TruePositiveCount desc + + + - Name: CisoTruePositiveIncidentsTTAnalysis + DisplayName: CISO - True Positive Incidents Tactics and Techniques Analysis + Description: Get a summary of the tactics and techniques used in the incidents that were classified as true positives + DescriptionForModel : Get a summary of the tactics and techniques used in the incidents that were classified as true positives + If severities are specified in the prompt (e.g. High and Medium), concatenate them as a comma separated list of double-quoted strings with the first letter in uppercase (like "High","Medium"). + If start date and end date are not specified, consider the last 7 days. + If severities is not specified, consider only "High". + Inputs: + - Name: start_date + Description: The first date of the considered time window + Required: true + - Name: end_date + Description: The end date of the considered time window + Required: true + - Name: csv_of_severities + Description: Comma separated list of severities. E.g. "High","Medium" + Required: true + Settings: + Target: Defender + Template: |- + let start_time = datetime({{start_date}}); + let end_time = datetime({{end_date}}); + let severities = dynamic([{{csv_of_severities}}]); + SecurityIncident + | summarize arg_max(TimeGenerated, *) by IncidentName + | where Status <> "Closed" or not(Status == "Closed" and Classification == "Undetermined" and ModifiedBy == "Microsoft XDR" ) + | where Severity in (severities) and CreatedTime between (start_time .. end_time) + | where Classification == "TruePositive" + | extend SecInc = strcat("[",Severity,"] ",Title) + | extend AdditionalDataParsed = parse_json(AdditionalData) + | summarize Occurrences=count(), Tactics=make_set(AdditionalDataParsed.tactics), Techniques=make_set(AdditionalDataParsed.techniques) by SecInc + | order by Occurrences desc + + + - Name: CisoMTTR + DisplayName: CISO - Get Mean Time To Resolve (MTTR) + Description: Calculate the Mean Time To Resolve (MTTR), in minutes, of the incidents closed in the specified period and compare it to its value in the previous month + DescriptionForModel : + Calculate the Mean Time To Resolve (MTTR), in minutes, for incidents closed within the specified period and compare it to the MTTR from the entire previous month. + The MTTR is defined as the average time between the creation and closure of incidents, for the incidents closed within the specified time window. + If the start and / or end dates are not specified, consider as time window the last 7 days. + The response is a table that includes the MTTR in the specified time window, the MTTR in the previous month, the percentage variation between the two MTTR values, + the first day of the specified time window, the last day of the specified time window, the first day of the previous month, and the last day of the previous month. + Inputs: + - Name: start_date + Description: The first date of the considered time window + Required: true + - Name: end_date + Description: The end date of the considered time window + Required: true + Settings: + Target: Defender + Template: |- + let start_time = datetime({{start_date}}); + let end_time = datetime({{end_date}}); + let lastMonthStart = startofmonth(datetime_add('month', -1, now())); + let lastMonthEnd = endofmonth(datetime_add('month', -1, now())); + let timeWindowMTTR = SecurityIncident + | summarize arg_max(TimeGenerated, *) by IncidentName + | where Status <> "Closed" or not(Status == "Closed" and Classification == "Undetermined" and ModifiedBy == "Microsoft XDR" ) + | where ClosedTime between (start_time .. end_time) + | extend ResolveTime = datetime_diff('minute', ClosedTime, CreatedTime) + | summarize MTTR = round(avg(ResolveTime),0) + | extend j = "forcedjoin" + | project timeWindowMTTR = MTTR, j; + let lastMonthMTTR = SecurityIncident + | summarize arg_max(TimeGenerated, *) by IncidentName + | where Status <> "Closed" or not(Status == "Closed" and Classification == "Undetermined" and ModifiedBy == "Microsoft XDR" ) + | where CreatedTime between (lastMonthStart .. lastMonthEnd) + | extend ResolveTime = datetime_diff('minute', ClosedTime, CreatedTime) + | summarize MTTR = round(avg(ResolveTime),0) + | extend j = "forcedjoin" + | project lastMonthMTTR = MTTR, j; + timeWindowMTTR + | join lastMonthMTTR on j + | extend PercentageChange = round(((timeWindowMTTR - lastMonthMTTR) * 100.0 / lastMonthMTTR), 0) + | project timeWindowMTTR, lastMonthMTTR, PercentageChange, start_time, end_time, lastMonthStart, lastMonthEnd + + + + - Name: CisoMTTA + DisplayName: CISO - Get Mean Time To Acknowledge (MTTA) + Description: Calculate the Mean Time To Acknowledge (MTTA), in minutes, of the incidents created in the specified period and compare it to its value in the previous month + DescriptionForModel : + Calculate the Mean Time To Acknowledge (MTTA), in minutes, for incidents created in the specified period and compare it to the MTTA from the entire previous month. + The MTTA is defined as the average time between the creation and first modification of incidents, for the incidents created within the specified time window. + If the start and / or end dates are not specified, consider as time window the last 7 days. + The response is a table that includes the MTTA in the specified time window, the MTTA in the previous month, the percentage variation between the two MTTA values, + the first day of the specified time window, the last day of the specified time window, the first day of the previous month, and the last day of the previous month. + Inputs: + - Name: start_date + Description: The first date of the considered time window + Required: true + - Name: end_date + Description: The end date of the considered time window + Required: true + Settings: + Target: Defender + Template: |- + let start_time = datetime({{start_date}}); + let end_time = datetime({{end_date}}); + let lastMonthStart = startofmonth(datetime_add('month', -1, now())); + let lastMonthEnd = endofmonth(datetime_add('month', -1, now())); + let timeWindowMTTA = SecurityIncident + | summarize arg_max(TimeGenerated, *) by IncidentName + | where Status <> "Closed" or not(Status == "Closed" and Classification == "Undetermined" and ModifiedBy == "Microsoft XDR" ) + | where CreatedTime between (start_time .. end_time) + | extend AcknowledgmentTime = datetime_diff('minute', FirstModifiedTime, CreatedTime) + | where isnotnull(AcknowledgmentTime) + | summarize MTTA = round(avg(AcknowledgmentTime), 0) + | extend j = "forcedjoin" + | project timeWindowMTTA = MTTA, j; + let lastMonthMTTA = SecurityIncident + | summarize arg_max(TimeGenerated, *) by IncidentName + | where Status <> "Closed" or not(Status == "Closed" and Classification == "Undetermined" and ModifiedBy == "Microsoft XDR" ) + | where CreatedTime between (lastMonthStart .. lastMonthEnd) + | extend AcknowledgmentTime = datetime_diff('minute', FirstModifiedTime, CreatedTime) + | where isnotnull(AcknowledgmentTime) + | summarize MTTA = round(avg(AcknowledgmentTime), 0) + | extend j = "forcedjoin" + | project lastMonthMTTA = MTTA, j; + timeWindowMTTA + | join lastMonthMTTA on j + | extend PercentageChange = round(((timeWindowMTTA - lastMonthMTTA) * 100.0 / lastMonthMTTA), 0) + | project timeWindowMTTA, lastMonthMTTA, PercentageChange, start_time, end_time, lastMonthStart, lastMonthEnd + + + + - Name: CisoIncidentsOwners + DisplayName: CISO - Get Top Incident Owners + Description: Get the list of top owners for the incidents created in the specified period + DescriptionForModel : + Get the list of top owners for the incidents created in the specified period. + If the start and / or end dates are not specified, consider as time window the last 7 days. + If the number of top owners is not specified, consider the top 10 owners. + Inputs: + - Name: start_date + Description: The first date of the considered time window + Required: true + - Name: end_date + Description: The end date of the considered time window + Required: true + - Name: top_owners_number + Description: The number of top owners to be considered + Required: true + Settings: + Target: Defender + Template: |- + let start_time = datetime({{start_date}}); + let end_time = datetime({{end_date}}); + let top_owners_number = int({{top_owners_number}}); + SecurityIncident + | summarize arg_max(TimeGenerated, *) by IncidentName + | where Status <> "Closed" or not(Status == "Closed" and Classification == "Undetermined" and ModifiedBy == "Microsoft XDR" ) + | where CreatedTime between (start_time .. end_time) + | where isnotempty(Owner) + | extend OwnerUPN = tostring(parse_json(Owner).userPrincipalName) + | where isnotempty(OwnerUPN) + | summarize IncidentCount = count() by OwnerUPN + | top top_owners_number by IncidentCount + | project OwnerUPN, IncidentCount + | order by IncidentCount desc + + + + - Name: CisoMostImpactedUsers + DisplayName: CISO - Get Most Impacted Users + Description: Get the list of the users impacted by the biggest number of alerts related to incidents created in the specified period and not closed as 'false positive' + DescriptionForModel : + Get the list of the users impacted by the biggest number of alerts related to incidents created in the specified period and not closed as 'false positive'. + If the start and / or end dates are not specified, consider as time window the last 7 days. + If the number of top impacted users is not specified, consider the top 10 impacted users. + Inputs: + - Name: start_date + Description: The first date of the considered time window + Required: true + - Name: end_date + Description: The end date of the considered time window + Required: true + - Name: top_impacted_users_number + Description: The number of top impacted users to be considered + Required: true + Settings: + Target: Defender + Template: |- + let start_time = datetime({{start_date}}); + let end_time = datetime({{end_date}}); + let top_owners_number = int({{top_impacted_users_number}}); + SecurityIncident + | summarize arg_max(TimeGenerated, *) by IncidentName + | where Status <> "Closed" or not(Status == "Closed" and Classification == "Undetermined" and ModifiedBy == "Microsoft XDR" ) + | where CreatedTime between (start_time .. end_time) and Classification <> "FalsePositive" + | mv-expand AlertIds + | extend aid = tostring(AlertIds) + | join kind=inner SecurityAlert on $left.aid == $right.SystemAlertId + | project aid, Entities + | extend Entities = parse_json(Entities) + | mv-expand Entities + | where not(isempty(Entities.UserPrincipalName)) + | distinct aid, UserPrincipalName = tostring(Entities.UserPrincipalName) + | summarize AlertNumber = count() by UserPrincipalName + | order by AlertNumber desc, UserPrincipalName + | top {{top_impacted_users_number}} by AlertNumber diff --git a/Logic Apps/ciso-reporting/ciso-posture-summary-man.yaml b/Logic Apps/ciso-reporting/ciso-posture-summary-man.yaml new file mode 100644 index 00000000..2a78fbdf --- /dev/null +++ b/Logic Apps/ciso-reporting/ciso-posture-summary-man.yaml @@ -0,0 +1,44 @@ +Descriptor: + Name: CisoPostureSummary + DisplayName: CISO Posture Summary + Description: Skills to get a summary view on the status of the posture in Defender for Cloud and Exposure Management + + +SkillGroups: + + - Format: KQL + Skills: + - Name: CisoRecommendationsBySeverity + DisplayName: CISO - Recommendations by Severity + Description: Get the list of the active recommendations by Severity (work in progress) + DescriptionForModel : Get the list of the active recommendations by Severity + If severities are specified in the prompt (e.g. High and Medium), concatenate them as a comma separated list of double-quoted strings with the first letter in uppercase (like "High","Medium"). + Inputs: + - Name: days_back + Description: The number of days to be considered in the past, starting from today + Required: true + - Name: top_results_number + Description: The number of top recommendations to be considered in terms of number of occurrences + Required: true + - Name: csv_of_severities + Description: Comma separated list of severities. E.g. "High","Medium" + Required: true + Settings: + Target: Defender + Template: |- + let today = now(); + let days_back = {{days_back}}; + let top_results_number = int({{top_results_number}}); + let severities = dynamic([{{csv_of_severities}}]); + let isInt = not(isnull(isnan(toreal(days_back)))); + let pastDays = toint( + iif(isInt and (days_back!=0),toint(days_back),1) + ); + let dateStart = datetime_add('day',-pastDays,today); + SecurityRecommendation + | where not(RecommendationState == "Healthy") + | where RecommendationSeverity in (severities) and TimeGenerated between (dateStart .. today) + | extend SevName = strcat("[", RecommendationSeverity, "] ", RecommendationDisplayName) + | summarize Occurrences = count() by SevName + | order by Occurrences desc + | top top_results_number by Occurrences diff --git a/Logic Apps/ciso-reporting/images/yt.png b/Logic Apps/ciso-reporting/images/yt.png new file mode 100644 index 00000000..0bf5879d Binary files /dev/null and b/Logic Apps/ciso-reporting/images/yt.png differ diff --git a/Logic Apps/ciso-reporting/install-guide.md b/Logic Apps/ciso-reporting/install-guide.md new file mode 100644 index 00000000..4faf3266 --- /dev/null +++ b/Logic Apps/ciso-reporting/install-guide.md @@ -0,0 +1,190 @@ + +# Prerequisites + +> YouTube Mini-video showing the prerequisites (2m): [CISO Reporting with Security Copilot - Prototype setup - **#0** Prerequisites] + +1. The approach and logic used in this solution can be applied with Security Copilot connected to any data source. + However, the sample custom plugins and promptbooks provided are designed to work in an environment where security is managed in the Unified SOC portal (Defender + Sentinel). + +2. This procedure assumes that you already have some experience with Security Copilot and an existing baseline compute capacity deployed. + If this is not the case, before starting the steps described below, create a minimum capacity of at least 1 SCU, which will be needed to access and configure Security Copilot. + Later in the procedure, you will be instructed to increase the number of SCUs for the initial test and for scheduled execution. + +4. The custom Logic Apps that invoke Security Copilot access it through an OAuth connection authenticated with user credentials. + Security Copilot accesses the target source systems through an impersonated access model; therefore, the credentials used in the OAuth connection from the Logic App + must have the right to read the security data in the target source systems. In the promptbooks provided in this solution, the sources are Defender, Entra, and Sentinel. + To my knowledge, at the time of this writing, it is not possible to specify 'Managed Identities' for the connection from Logic Apps to Security Copilot. + 'User credentials' are then required, even though these credentials will generally not be used by humans but only by this Logic App. + As a prerequisite for proceeding with this installation, then, it is necessary to create user credentials with the following minimum 'active' permissions + (note that 'eligible' permissions in PIM are not adequate for use in automation): + - Security Reader at tenant level (for reading data in Defender and Entra) + - Microsoft Sentinel Reader at the level of the Sentinel's resource group (for reading data in the Sentinel workspace) + - Contributor on Microsoft Security Copilot (for accessing and interacting with Security Copilot) + + These credentials also need to have an Microsoft 365 Exchange Online license assigned because they will be used to send the scheduled report email. + It is recommended to create and use a dedicated user with a clear name (such as 'Security Copilot Automation Account') and to monitor its usage to ensure that + any anomalous logon attempts with these credentials generate an alert. + Before starting this setup procedure, it is highly recommended to prepare a browser session authenticated with the credentials of the new Security Copilot Automation Account, + where you have already successfully accessed: + - The user's mailbox in Exchange Online with Outlook web access + - The Unfied SOC Portal (Defender + Sentinel, security.microsoft.com) + - The Sentinel blade in Azure (portal.azure.com) + - The Security Copilot portal (at the very first access, it requires you to acknowledge some introductory topics) + +6. If you want to generate a report on the recommendations identified by Defender for Cloud (as in the third prompt in the sample Posture Analysis promptbook documented and shared here), + you also need to enable Continuous Export from Defender for Cloud (in the relevant Azure subscriptions) to Log Analytics/Sentinel. + +7. Before starting this setup procedure, make sure to have the following information available: + - Credentials for the "Security Copilot Automation Account" + - Identification parameters for Sentinel (Subscription ID, Resource Group Name, Sentinel Workspace Name) +# Setup Procedure + +## [**STEP 1**] Create the promptbooks + + > YouTube Mini-video showing the promptbooks creation (3m): [CISO Reporting with Security Copilot - Prototype setup - **#1** Create promptbooks] + +On the Security Copilot portal, accessed with credentials with the Owner role: +- [promptbook-incident-analysis](https://github.com/Azure/Security-Copilot/blob/main/Logic%20Apps/ciso-reporting/promptbook-incident-analysis.md) +- [OPTIONAL] [promptbook-posture-analysis](https://github.com/Azure/Security-Copilot/blob/main/Logic%20Apps/ciso-reporting/promptbook-posture-analysis.md)\ + NOTES: + - This promptbook for posture analysis and the related automation are in a very preliminary draft state. + Not only are these prompts not optimized in terms of compute capacity consumption, but the overall promptbook is also very incomplete. + Please refer to [this article](https://www.linkedin.com/pulse/periodic-reporting-security-managers-cisos-using-stefano-pescosolido-fm80f/) + for further details. + - If you have not enabled the Continuous Export feature in Defender for Cloud, you should not include the third prompt in this sample promptbook. + +## [**STEP 2**] Deploy the Logic App [ciso-get-users-status.json](https://github.com/Azure/Security-Copilot/blob/main/Logic%20Apps/ciso-reporting/ciso-get-users-status.json) used by one of the skills in the custom plugin for Incidents Analysis + + > YouTube Mini-video showing the creation of the Logic App **ciso-get-users-status** (>2m): [CISO Reporting with Security Copilot - Prototype setup - **#2** Install GetUsersStatus] + +On the Azure portal, accessed with credentials with an administrative role: + - NOTE: Currently, it has no "Deploy in Azure" button; it is necessary to perform a manual deployment. + - Edit its connection to Security Copilot and authenticate with the credentials of the Security Copilot Automation Account + +## [**STEP 3**] Deploy the Logic App [CfS-SendPromptbookResultsByEmail](https://github.com/Azure/Security-Copilot/tree/main/Logic%20Apps/CfS-SendPromptbookResultsByEmail) to create the "**CISO Incidents Analysis**" automation + + > YouTube Mini-video showing the creation of the Logic App **CISO Incidents Analysis** (>5m): [CISO Reporting with Security Copilot - Prototype setup - **#3** Install Incidents Analysis Automation] + +On the Azure portal, accessed with credentials with an administrative role: + - Use the "Deploy in Azure" button + - Edit its connection to Security Copilot and authenticate with the credentials of the Security Copilot Automation Account + - Edit its connection to Office 365 and authenticate with the credentials of the Security Copilot Automation Account + - Chose the promptbook "promptbook-incident-analysis" and specify its parameters. Typically you may want to specify: + - TOP_INCIDENTS_NUMBER = 10 + - SEVERITIES = High and Medium + - TIME_FRAME = last week (NOTE: if you change this value, you have to modify the content of the ReplacePromptsInOutput workflow paramter accordingly) + - RESPONSE_LANGUAGE = set a language supported by Security Copilot + - RESPONSE_LANGUAGE = 10 + - TOP_IMPACTED_USERS_NUMBER = 5 (the value of this number has an important impact on the consumption - and, then, costs - of the solution.) + - Set all the Logic App parameters. Specifically, ensure that you have set: + - The Email Title and Email Subject + - The EmailRecipients + - The SkipPromptsInOutput and ReplacePromptsInOutput as specified at the end of the file + [promptbook-incident-analysis](https://github.com/Azure/Security-Copilot/blob/main/Logic%20Apps/ciso-reporting/promptbook-incident-analysis.md) + - Set the recurrence. Typically it has to be set to be run once per week, like on Monday morning at 6 am. + - For the time being, keep the Logic App disabled (we will enable it when all the required configurations are in place) + + +## [**STEP 4**] [OPTIONAL] Deploy the Logic App [CfS-SendPromptbookResultsByEmail](https://github.com/Azure/Security-Copilot/tree/main/Logic%20Apps/CfS-SendPromptbookResultsByEmail) to create the "**CISO Posture Analysis**" automation + NOTE: this automation and the related promptbook are in a very preliminary draft state. + Not only are these prompts not optimized in terms of compute capacity consumption, but the overall promptbook is also very incomplete. + Please refer to [this article](https://www.linkedin.com/pulse/periodic-reporting-security-managers-cisos-using-stefano-pescosolido-fm80f/) + for further details on how this promptbook should be improved. + + > YouTube Mini-video showing the creation of the Logic App **CISO Posture Analysis** (~4m): [CISO Reporting with Security Copilot - Prototype setup - **#4** Install Posture Analysis Automation] + + - Use the "Deploy in Azure" button + - Edit its connection to Security Copilot and authenticate with the credentials of the Security Copilot Automation Account + - Edit its connection to Office 365 and authenticate with the credentials of the Security Copilot Automation Account + - Chose the promptbook "promptbook-posture-analysis" and specify its parameters. Typically you may want to specify: + - WORDS_NUMBER = 50 + - NUMBER_OF_DAYS = 30 + - NUMBER_OF_RECOMMENDATIONS = 10 + - SEVERITIES = High and Medium + - Set all the Logic App parameters. Specifically, ensure that you have set: + - The HtmlBodyHeader to change the color shade of the email + (see [promptbook-posture-analysis](https://github.com/Azure/Security-Copilot/blob/main/Logic%20Apps/ciso-reporting/promptbook-posture-analysis.md)) + - The Email Title and Email Subject + - The EmailRecipients + - Set the recurrence. Typically it has to be set to be run once per week, like on Monday morning at 6 am. + - For the time being, keep the Logic App disabled (we will enable it when all the required configurations are in place) + +## [**STEP 5**] Deploy the Custom Plugins + + > YouTube Mini-video showing the creation of the Custom Plugins (8m): [CISO Reporting with Security Copilot - Prototype setup - **#5** Custom plugins] + + - Prepare and deploy the [ciso-incidents-summary-man](https://github.com/Azure/Security-Copilot/blob/main/Logic%20Apps/ciso-reporting/ciso-incidents-summary-man.yaml) + custom plugin (used by the "**CISO Incidents Analysis**" automation) + NOTES: + - This custom plugin loops through the users specified in the input and queries Entra for their enablement status and risk status. + Due to the existance of the loop, it may consume a high level of compute units. You should decide if this evidence adds value to your report. + If you want to include this evidence in the output report, it is recommended to limit the number of users included in the loop + by setting a low value number in the TOP_IMPACTED_USERS_NUMBER parameter specified in the "**CISO Posture Analysis**" Logic App for the call to the + [promptbook-incident-analysis](https://github.com/Azure/Security-Copilot/blob/main/Logic%20Apps/ciso-reporting/promptbook-incident-analysis.md) + promptbook. + If, instead, you decide to remove the call to that loop, you should not only remove the related prompt in the promptbook but also reorganize + accordingly the values for the SkipPromptsInOutput and ReplacePromptsInOutput parameters in the same "**CISO Posture Analysis**" Logic App. + - In this first draft release of this solution, the OpenAPI specification file + [ciso-incidents-summary-api-spec.yaml](https://github.com/Azure/Security-Copilot/blob/main/Logic%20Apps/ciso-reporting/ciso-incidents-summary-api-spec.yaml) + contains two placeholders whose value must be replaced accorinding to the components of the full URL of the trigger for the deployed Logic App + [ciso-get-users-status.json](https://github.com/Azure/Security-Copilot/blob/main/Logic%20Apps/ciso-reporting/ciso-get-users-status.json). + Specifically, you need to set: + - equal to the base path of that URL (e.g. https://prod-32.northeurope.logic.azure.com:443) + - equal to the Workload ID in that URL (e.g. 836c758b842f46549f57c861ca856c7b) + You have then to save the edited file in a public storage on the Internet that is accessible to Security Copilot, for example in another public GitHub repo. + You then have to modifiy your copy of the file [ciso-incidents-summary-man.yaml](https://github.com/Azure/Security-Copilot/blob/main/Logic%20Apps/ciso-reporting/ciso-incidents-summary-man.yaml) + by replacing the placehoder in the OpenApiSpecUrl parameter with the public URL of your modified OpenAPI specification file. + - [OPTIONAL] Deploy the [ciso-posture-summary-man](https://github.com/Azure/Security-Copilot/blob/main/Logic%20Apps/ciso-reporting/ciso-incidents-summary-man.yaml) + custom plugin (used by the "**CISO Posture Analysis**" automation). This deployment deployment does not require any specific customization: just download the + file from the template in GitHub and upload it in your Security Copilot. + - [OPTIONAL] Deploy the [Sentinel-SOC-Optimization-CustomPlugin] https://github.com/stefanpems/cfs/tree/main/Sentinel-SOC-Optimization-CustomPlugin + custom plugin (used by the "**CISO Posture Analysis**" automation). This deployment deployment does not require any specific customization: just download the + file from the template in GitHub and upload it in your Security Copilot. + NOTES: + - While adding this custom plugin, you need to provide the Identification parameters for your Sentinel (Subscroption ID, Resource Group Name, Sentinel Workspace Name) + - If you do not want to install this custom plugin, you need to remove the second prompt from the + [promptbook-posture-analysis](https://github.com/Azure/Security-Copilot/blob/main/Logic%20Apps/ciso-reporting/promptbook-posture-analysis.md) that you deployed in one of the previous steps. + + +## [**STEP 6**] Enable the "**CISO Incidents Analysis**" automation and wait for the completion of its first automatic execution + + > YouTube Mini-video showing the first execution of the Incidents Analysis automation (4m): [CISO Reporting with Security Copilot - Prototype setup - **#6** Run Incidents Analysis] + + On the Security Copilot portal, accessed with credentials with the Security Copilot Automation Account: + - [**STEP 6.1**] Ensure that all the custom plugins just installed are enabled. + Optionally, test the [ciso-get-users-status.json](https://github.com/Azure/Security-Copilot/blob/main/Logic%20Apps/ciso-reporting/ciso-get-users-status.json) Logic App + by invoking the "CISO Get Users Staus" skill in the [ciso-incidents-summary-man](https://github.com/Azure/Security-Copilot/blob/main/Logic%20Apps/ciso-reporting/ciso-incidents-summary-man.yaml). + For this purpose, use the following inputs: + - api-version = 2016-10-01 + - sp = /triggers/AnalyzeUPNsCall/run + - sv = 1.0 + - users_upn = one or more comma separated UPNs of users in your tenant + + + On the Security Copilot portal, accessed with credentials with the Owner role: + - [**STEP 6.2**] Increment temporarily the number of SCUs to ensure the first "test" executions can be completed successfully. + + On the Azure portal, accessed with credentials with an administrative role: + - [**STEP 6.3**] Enable the "**CISO Incidents Analysis**" automation and wait its first automatic execution + +## [**STEP 7**] Enable the "**CISO Posture Analysis**" automation and wait for the completion of its first automatic execution + + > YouTube Mini-video showing the first execution of the Incidents Posture automation (~1m): [CISO Reporting with Security Copilot - Prototype setup - **#7** Run Posture Analysis] + + On the Azure portal, accessed with credentials with an administrative role: + - Enable the "**CISO Posture Analysis**" automation and wait its first automatic execution + +## [**STEP 8**] Schedule the execution of the reporting together with the required periodic increment of SCUs + + > YouTube Mini-video showing the setup of the periodic assignment of SCUs and of the recurrence for the reporting automations (>8m): [CISO Reporting with Security Copilot - Prototype setup - **#8** Scheduling SCUs and reporting] + + - Set the recurrence on the "**CISO Incidents Analysis**" Logic App + - Set the recurrence on the "**CISO Posture Analysis**" Logic App + - Schedule the automatic deployment of the compute units (SCUs) dedicated to the execution of these automations + to ensure that there is enough capacity (on top of your "baseline" capacity used by your users and other automations) everytime that the are executed. + For example, deploy 2 instances of the [cfs-set-X-SCUs](https://github.com/stefanpems/cfs/tree/main/cfs-set-X-SCUs) Logic App: + - One to increase the capacity by N SCUs over the baseline + - One to decrease the capacity by N SCUs when the execution of these automations is completed + + + diff --git a/Logic Apps/ciso-reporting/promptbook-incident-analysis.md b/Logic Apps/ciso-reporting/promptbook-incident-analysis.md new file mode 100644 index 00000000..07236a6d --- /dev/null +++ b/Logic Apps/ciso-reporting/promptbook-incident-analysis.md @@ -0,0 +1,138 @@ +# Prompts in the Incident Analysis promptbook + +This promptbook, containing a (sample) series of prompts for reporting the high-level status of the Security Incident Management process for the current organization, has been designed to be invoked from the [CfS-SendPromptbookResultsByEmail](https://github.com/Azure/Security-Copilot/tree/main/Logic%20Apps/CfS-SendPromptbookResultsByEmail) Logic App and requires the [CISO Incidents Summary](https://github.com/Azure/Security-Copilot/blob/main/Logic%20Apps/ciso-reporting/ciso-incidents-summary-man.yaml) Custom Plugin as a prerequisite. + +## Prompt 1 +``` +/CisoIncidentsStatusSummary Consider the top most occurring incidents in the period of . For the input parameter named "csv_of_severities" specify the following word or words, each surrounded by double quotes, with only the first letter in uppercase and, if more than one, separated by commas: . +Respond by applying ALL these 4 indications to this specific response only: +1. Start the response with the following first sentence: "This is the list of Incidents with severity created in the requested period ". Then add, within round brackets, exactly the first day and the last day considered for , as used for invoking the CisoIncidentsStatusSummary skill; use the long date format. +2. Do not return a table: return the results in paragraphs instead; highlight the incident title and write as bulleted list the numbers by status. Do not write classifications where the number is zero. Write in bold underlined the incident classified as true positive. +3. Format the ENTIRE response in HTML including the very first sentence; do not include the doctype tag and the header: write only the content of the HTML body, without the opening and closing "body" tag and DO NOT write any character before and after that HTML content. +4. Translate this ENTIRE response in from its very first to its very last word, but the titles of the incidents. +``` + +## Prompt 2 +``` +/CisoTruePositiveIncidentsTTAnalysis Consider only the incidents occurred . For the input parameter named "csv_of_severities" specify the following word or words, each surrounded by double quotes, with only the first letter in uppercase and, if more than one, separated by commas: . +Respond by applying BOTH these 2 indications to this specific response only: +1. Start the response with the following first sentence: "These are the MITRE Tactics and Techniques for the Incidents closed as 'True Positives' in the requested period ". Then add, within round brackets, exactly the first day and the last day considered for , as used for invoking the CisoTruePositiveIncidentsTTAnalysis skill; use the long date format. +2. Do not return a table: return the results in paragraphs instead; highlight the incident title and write in a bullet the the number of occurrences, in a second bullet the list of tactics and in a third bullet the list of techniques. +``` + +## Prompt 3 +``` +/askGPT Respond by applying ALL these 3 indications to this specific response only: +1. Create a copy of the list returned by the previous prompt and, in that copy, replace the value of each listed Tactic with its link to the MITRE Att&ck Framework web site describing that specific tactic. Also replace the value of each listed Techniques with its link to the MITRE Att&ck Framework web site describing that specific technique; concatenate the technique code with its title. Ensure that the links are correct. +2. Format the ENTIRE response in HTML including the very first sentence; do not include the doctype tag and the header: write only the content of the HTML body, without the opening and closing "body" tag and DO NOT write any character before and after that HTML content. +3. Translate this ENTIRE response in from its very first to its very last word. Keep in English only the titles of the incidents and the names of the Tactics and Techniques. +``` + +## Prompt 4 +``` +/CisoMTTR Set the start_date and end_date input parameters to the first and last day of . In the response, include all the content returned by the KQL call. +``` + +## Prompt 5 +``` +/askGPT Respond by applying ALL these 6 indications to this specific response only: +1. Start the response with the following sentence: "This is the MTTR, Mean Time To Resolve, for the incidents closed .". +2. After that sentence, write a table with two columns, Metric and Value, and three rows containing the first three numbers of the previous response. Write the first two numbers in minutes (with no decimal places) and in hours (in round brackets, with only 1 decimal place); write explicitly that these are minutes and hours. +3. After that first table, write the sentence "The MTTR is defined as the average time between the creation and closure of incidents, for the incidents closed within the specified time window, as shown in the following table:". +4. After that sentence, write explicitly, in a second table, what are the first and last days considered for both the two calculated MTTR values, as returned by the previous response. The second table should contain these three columns: Period, Start Date, End Date. +5. Format the dates as weekday, day, month, year. Format this ENTIRE response in HTML, from the very first to the very last word; for any table, use a border 1px with color light gray, spacing 1px and width 100%; do not include the doctype tag and the header: write only the content of the HTML body, without the opening and closing "body" tag and DO NOT write any character before and after that HTML content. +6. Translate this ENTIRE response in from its very first to its very last word. Keep in English only the acronym MTTR and the name "Mean Time To Resolve" but add its translation after that name in round brackets. +``` + +## Prompt 6 +``` +/CisoMTTA Set the start_date and end_date input parameters to the first and last day of . In the response, include all the content returned by the KQL call. +``` + +## Prompt 7 +``` +/askGPT Respond by applying ALL these 6 indications to this specific response only: +1. Start the response with the following sentence: "This is the MTTA, Mean Time To Acknowledge, for the incidents created .". +2. After that sentence, write a table with two columns, Metric and Value, and three rows containing the first three numbers of the previous response. Write the first two numbers in minutes (with no decimal places) and in hours (in round brackets, with only 1 decimal place); write explicitly that these are minutes and hours. +3. After that first table, write the sentence "The MTTA is defined as the average time between the creation and first modification of incidents, for the incidents created within the specified time window, as shown in the following table:". +4. After that sentence, write explicitly, in a second table, what are the first and last days considered for both the two calculated MTTA values, as returned by the previous response. The second table should contain these three columns: Period, Start Date, End Date. +5. Format the dates as weekday, day, month, year. Format this ENTIRE response in HTML, from the very first to the very last word; for any table, use a border 1px with color light gray, spacing 1px and width 100%; do not include the doctype tag and the header: write only the content of the HTML body, without the opening and closing "body" tag and DO NOT write any character before and after that HTML content. +6. Translate this ENTIRE response in from its very first to its very last word. Keep in English only the acronym MTTA and the name "Mean Time To Acknowledge" but add its translation after that name in round brackets. +``` + +## Prompt 8 +``` +/CisoIncidentsOwners Set the start_date and end_date input parameters to the first and last day of . Set the top_owners_number input parameter to . +Respond by applying ALL these 4 indications to this specific response only: +1. Start the response with the following sentence: "This is the list of owners of incidents with the biggest number of assigned incidents considering only the incidents created :". +2. Return the list in bullets of "user principal names" with the number of assigned incidents after the name, in round brackets. +3. Format the ENTIRE response in HTML including the very first sentence; do not include the doctype tag and the header: write only the content of the HTML body, without the opening and closing "body" tag and DO NOT write any character before and after that HTML content. +4. Translate this ENTIRE response in from its very first to its very last word. +``` + +## Prompt 9 +``` +/CisoMostImpactedUsers Set the start_date and end_date input parameters to the first and last day of . Set the top_impacted_users_number input parameter to impacted users. +Respond by returning the list of "user principal names" of the identified impacted users, with the related number of associated alerts +``` + +## Prompt 10 +``` +/askGPT extract in a single string the comma separated list of User Principal Names included in the previous response. +``` + +## Prompt 11 +``` +Call the CisoGetUsersStatus skill of the CisoIncidentsSummaryApiHelper plugin. Pass these values to the input parameters: api-version=2016-10-01 , sp=/triggers/AnalyzeUPNsCall/run , sv=1.0. Pass to the users_upn input parameter the comma separated list of UPNs that you returned in the previous response. Read in the body of the response of this call and return the values of EntraStatus and RiskStatus for these users. +``` + +## Prompt 12 +``` +/askGPT Consolidate in a single table the number of incidents, the EntraStatus and the RiskStatus for ALL the impacted users identified in the previous responses. Respond by applying ALL these 4 indications to this specific response only: 1. Start the response with the following sentence: "This is the list of the users most impacted by incidents created and not already closed as 'false positive'. The list includes the number of their incidents and their status and risk level in Entra.". 2. Return the list as table. 3. Format the ENTIRE response in HTML including the very first sentence; for the HTML table, use a border 1px with color light gray, spacing 1px and width 100%; do not include the doctype tag and the header: write only the content of the HTML body, without the opening and closing "body" tag and DO NOT write any character before and after that HTML content. +4. Translate this ENTIRE response in from its very first to its very last word. +``` + +--- + +# Parameters for the Logic App that invoke the Incident Analysis promptbook and sends the results by email +(Logic App template: https://github.com/Azure/Security-Copilot/tree/main/Logic%20Apps/CfS-SendPromptbookResultsByEmail) + + +## Paramters related to the responses of the promptbook + + +### Value for the Logic App Paramter 'SkipPromptsInOutput' +Indexes of the prompts whose responses should not be included in the delivered email. +``` +[2,4,6,9,10,11] +``` + +### Value for the Logic App Paramter 'ReplacePromptsInOutput' +Text to be used for replacing the prompts in the delivered email. +NOTE: It may include words and numbers that should be consistent with the values specified for the input parameters of the promptbook. +``` +["List the names of incidents opened last week with high or medium severity. Consider only the 10 most frequent incidents. Report the number of occurrences and their status.","","For incidents closed last week as 'true positives', report the related tactics and techniques defined in the MITRE ATT&CK Framework.","","Report the Mean Time To Resolution (MTTR) of incidents closed last week. Compare it with the value recorded last month.","","Report the Mean Time To Acknowledgment (MTTA) of incidents created last week. Compare it with the value recorded last month.","List the 10 operators with the most incidents assigned among the incidents created last week.","","","","List the 5 users impacted by the highest number of alerts related to incidents created last week and not closed as false positives. Report their status in Entra."] +``` + + +## Paramters related to the HTML formatting of the delivered email + + +### Value for the Logic App Paramter 'HtmlBodyHeader' +(Shades of **blue** - Same as in the Logic App template) +``` +

 

+``` + +### Value for the Logic App Paramter 'HtmlBodyRow' +(same as in the Logic App template) +``` + +``` + + +Value for the Logic App Paramter 'HtmlBodyRow' +(same as in the Logic App template) +``` +
***CONTENT1***
***CONTENT2***
+``` diff --git a/Logic Apps/ciso-reporting/promptbook-posture-analysis.md b/Logic Apps/ciso-reporting/promptbook-posture-analysis.md new file mode 100644 index 00000000..2329ed01 --- /dev/null +++ b/Logic Apps/ciso-reporting/promptbook-posture-analysis.md @@ -0,0 +1,67 @@ +# Prompts in the Incident Analysis promptbook + +This promptbook, containing a (sample) series of prompts for reporting the high-level status of the security Posture for the current organization, has been designed to be invoked from the [CfS-SendPromptbookResultsByEmail](https://github.com/Azure/Security-Copilot/tree/main/Logic%20Apps/CfS-SendPromptbookResultsByEmail) Logic App and requires the [CISO Posture Summary](https://github.com/Azure/Security-Copilot/blob/main/Logic%20Apps/ciso-reporting/ciso-posture-summary-man.yaml) Custom Plugin as a prerequisite. + +NOTE: This promptbook is in a very preliminary draft state. Not only are these prompts not optimized in terms of compute capacity consumption, but it is also very incomplete. Please refer to [this article](https://www.linkedin.com/pulse/periodic-reporting-security-managers-cisos-using-stefano-pescosolido-fm80f/) for further details on how this promptbook should be improved. + +## Prompt 1 +``` +Which threats should I focus on based on their exposure scores? For each returned threat, include a brief summary (no more that words), the Exposure Score, the Last Updated date, the number of related Alerts, the number of Misconfigured Devices and the number of Vulnerable Devices. +``` + +## Prompt 2 +``` +/GetSentinelSOCOptimizationRecommendations Is my Sentinel environment configured appropriately with reference to threat coverage and data utilization? Summarize the results in 3 paragraphs: Threat Coverage Recommendations (for each threat, show a bulleted list of: Threat Name, Recommended Action, Number of Active Detections, Number of Recommended Detections), Data Utilization Recommendations (for each table, show a bulleted list of: Table Name, Recommendations, Number of Suggested Rules) and Detection Tuning Recommendations (for each detection, show a bulleted list of: Detection ID, Recommended Action, Recommended Entities). For Data Utilization, show only the recommendations with 1 or more Suggested Rules +``` + +## Prompt 3 +``` +/CisoRecommendationsBySeverity List the top active Recommendations created in the last days. For the input parameter named "csv_of_severities" specify the following word or words, each surrounded by double quotes, with only the first letter in uppercase and, if more than one, separated by commas: +``` + + +--- + +# Parameters for the Logic App that invoke the Posture Analysis promptbook and sends the results by email +(Logic App template: https://github.com/Azure/Security-Copilot/tree/main/Logic%20Apps/CfS-SendPromptbookResultsByEmail) + + +## Paramters related to the responses of the promptbook + + +### Value for the Logic App Paramter 'SkipPromptsInOutput' +Indexes of the prompts whose responses should not be included in the delivered email. +(Leave empty) +``` +[] +``` + +### Value for the Logic App Paramter 'ReplacePromptsInOutput' +Text to be used for replacing the prompts in the delivered email. +NOTE: It may include words and numbers that should be consistent with the values specified for the input parameters of the promptbook. +``` +["List the global threats that should be prioritized based on their exposure score and the vulnerabilities present in my environment.","List the recommendations for better coverage in our SIEM (Microsoft Sentinel) against the most impactful threats. Also, show the recommendations for better utilization of our collected logs.","List the top 10 high and medium severity recommendations based on the number of impacted resources in my cloud PaaS environments in Azure, AWS, and GCP."] +``` + + +## Paramters related to the HTML formatting of the delivered email + + +### Value for the Logic App Paramter 'HtmlBodyHeader' +(Shades of **green**) +``` +

 

+``` + + +### Value for the Logic App Paramter 'HtmlBodyRow' +(same as in the Logic App template) +``` +
***CONTENT1***
***CONTENT2***
+``` diff --git a/Logic Apps/ciso-reporting/readme.md b/Logic Apps/ciso-reporting/readme.md new file mode 100644 index 00000000..a3151bf0 --- /dev/null +++ b/Logic Apps/ciso-reporting/readme.md @@ -0,0 +1,3 @@ +Please refer to: +* [Description](https://www.linkedin.com/pulse/periodic-reporting-security-managers-cisos-using-stefano-pescosolido-fm80f/) +* [Install Guide](https://github.com/Azure/Security-Copilot/blob/main/Logic%20Apps/ciso-reporting/install-guide.md) diff --git a/Monitoring/Adjust CFS Capacity with Azure Monitor/readme.md b/Monitoring/Adjust CFS Capacity with Azure Monitor/readme.md deleted file mode 100644 index a7007624..00000000 --- a/Monitoring/Adjust CFS Capacity with Azure Monitor/readme.md +++ /dev/null @@ -1,68 +0,0 @@ -## Using Azure Automation to raise and lower the amount of SCUs programmatically - -The first thing we need to do is create an Azure Automation Account. -
If you’ve never used an Automation Account before, you may need to follow [these instructions] (https://learn.microsoft.com/en-us/azure/automation/quickstarts/create-azure-automation-account-portal) to enable automation in Azure. -
Once, you have that enabled find the Automation Accounts in Azure and select +Create to create a new one. -
- -![Image1](https://github.com/anfisher1967/Copilot-For-Security/blob/main/Images/Azure%20Automation%20Images/_AA.png) -
- -After hitting next, you’ll fill out the Basics information including the Subscription, Resource group and Region. -
You’ll also need to give the Automation Account a name. -
- -![Image2](https://github.com/anfisher1967/Copilot-For-Security/blob/main/Images/Azure%20Automation%20Images/_CreateAA.png) -
- -Select Next and confirm that the System assigned check box is checked. -
If you want to change the Networking settings (I change mine to Private access) or add Tags, do so. And then press Review + Create. -
-![Image3](https://github.com/anfisher1967/Copilot-For-Security/blob/main/Images/Azure%20Automation%20Images/_system.png) -
- -
Now that the account is created, we need to give it permissions to make the SCU change. -
In another browser tab, locate your Security Copilot Resource and select Access Control (IAM). -
Select the +Add button and choose Add role assignment. -
-![Image4](https://github.com/anfisher1967/Copilot-For-Security/blob/main/Images/Azure%20Automation%20Images/_IAM.png) -
- -
Select the Privileged administrator roles tab and select Contributor. -
-![Image5](https://github.com/anfisher1967/Copilot-For-Security/blob/main/Images/Azure%20Automation%20Images/_IAM2.png) -
- -
You may be thinking - “Whoa! I’m giving Contributor rights to this account.” -
But managed identities are the preferred method for runbooks because they allow your runbook to easily access other Microsoft Entra protected resources. -
The identity is managed by the Azure platform and doesn't require you to provision or rotate any secrets. If you need more information, find it here. - -
Now let’s pop back to our other tab and create the Runbook. -
Find Runbooks in the Process Automation section. Select +Create a runbook. -
-![Image5](https://github.com/anfisher1967/Copilot-For-Security/blob/main/Images/Azure%20Automation%20Images/_runbook.png) -
- -
Give it a name, select PowerShell as the Runbook type and 7.2 as the Runtime version. Then press Review + Create. -
-![Image6](https://github.com/anfisher1967/Copilot-For-Security/blob/main/Images/Azure%20Automation%20Images/_runbook2.png) -
- -You should now be at the Edit PowerShell Runbook page. Paste this code in the window. This code will change the number of SCUs from your current setting to one SCU. -``` -Connect-AzAccount -Identity -Set-AzContext -Subscription "your subscription" -$Resource = Get-AzResource -ResourceGroupName YourResourceGroup -ResourceName TheNameofYourCoilotResource -ResourceType microsoft.securitycopilot/capacities -$Resource.Properties.numberOfUnits = 1 -$Resource | Set-AzResource -Force -``` -You can test it if you like in the Test pane. This will show you if there are any errors. Save the runbook. - -Now, we’re going to set it so that this runbook kicks off every night at 10 p.m. Make your way back to the Azure Automation page and select Schedules in the Shared Resources section. Select +Add a schedule. Give the schedule a Name and select the date you want it to start. Select Recurring in the Recurrence section. And set Recur every to 1 Day. Set the expiration if you want to. Now select Create. -
-![Image7](https://github.com/anfisher1967/Copilot-For-Security/blob/main/Images/Azure%20Automation%20Images/_schedule.png) -
- -Now you’re all finished! You can duplicate these steps to create another Automation that increases the SCUs at 7 am or whatever time your team starts their day. - -And remember, if you are working an incident at 2 a.m. and need more SCUs, you can manually change this in the Azure portal at any time. diff --git a/Monitoring/SCU Capacity Optimizer/customrole/azuredeploy.json b/Monitoring/SCU Capacity Optimizer/customrole/azuredeploy.json deleted file mode 100644 index e577b313..00000000 --- a/Monitoring/SCU Capacity Optimizer/customrole/azuredeploy.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/subscriptiondeploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "SecurityCopilotCreatorRoleName": { - "defaultValue": "Security Copilot Compute Creator", - "type": "string" - } - }, - "variables": { - "rbacGuid": "[guid(deployment().name)]" - }, - "resources": [ - { - "type": "Microsoft.Authorization/roleDefinitions", - "apiVersion": "2017-05-01", - "name": "[variables('rbacGuid')]", - "properties": { - "roleName": "[parameters('SecurityCopilotCreatorRoleName')]", - "IsCustom": true, - "Description": "This role is designed to allow the creation of Security Copilot Compute Capacities, including the scaling SCU count.", - "permissions": [ - { - "Actions": [ - "Microsoft.SecurityCopilot/capacities/read", - "Microsoft.SecurityCopilot/capacities/write" - ], - "NotActions": [] - } - ], - "AssignableScopes": [ - "[subscription().id]" - ] - } - } - ], - "outputs": { - "assignableScope": { - "type": "string", - "value": "[subscription().id]" - } - } -} \ No newline at end of file diff --git a/Monitoring/SCU Capacity Optimizer/images/SCU_Capacity_Optimizer_Edit_Dark.png b/Monitoring/SCU Capacity Optimizer/images/SCU_Capacity_Optimizer_Edit_Dark.png deleted file mode 100644 index c42b4468..00000000 Binary files a/Monitoring/SCU Capacity Optimizer/images/SCU_Capacity_Optimizer_Edit_Dark.png and /dev/null differ diff --git a/Monitoring/SCU Capacity Optimizer/images/SCU_Capacity_Optimizer_Edit_Light.png b/Monitoring/SCU Capacity Optimizer/images/SCU_Capacity_Optimizer_Edit_Light.png deleted file mode 100644 index 51b089c2..00000000 Binary files a/Monitoring/SCU Capacity Optimizer/images/SCU_Capacity_Optimizer_Edit_Light.png and /dev/null differ diff --git a/Monitoring/SCU Capacity Optimizer/images/SCU_Capacity_Optimizer_Scaler_Logicapp_Dark.png b/Monitoring/SCU Capacity Optimizer/images/SCU_Capacity_Optimizer_Scaler_Logicapp_Dark.png deleted file mode 100644 index cc54ef01..00000000 Binary files a/Monitoring/SCU Capacity Optimizer/images/SCU_Capacity_Optimizer_Scaler_Logicapp_Dark.png and /dev/null differ diff --git a/Monitoring/SCU Capacity Optimizer/images/SCU_Capacity_Optimizer_Scaler_Logicapp_Light.png b/Monitoring/SCU Capacity Optimizer/images/SCU_Capacity_Optimizer_Scaler_Logicapp_Light.png deleted file mode 100644 index fd80ff71..00000000 Binary files a/Monitoring/SCU Capacity Optimizer/images/SCU_Capacity_Optimizer_Scaler_Logicapp_Light.png and /dev/null differ diff --git a/Monitoring/SCU Capacity Optimizer/images/SCU_Capacity_Optimizer_View_Dark.png b/Monitoring/SCU Capacity Optimizer/images/SCU_Capacity_Optimizer_View_Dark.png deleted file mode 100644 index a5eb4b7e..00000000 Binary files a/Monitoring/SCU Capacity Optimizer/images/SCU_Capacity_Optimizer_View_Dark.png and /dev/null differ diff --git a/Monitoring/SCU Capacity Optimizer/images/SCU_Capacity_Optimizer_View_Light.png b/Monitoring/SCU Capacity Optimizer/images/SCU_Capacity_Optimizer_View_Light.png deleted file mode 100644 index a0bbe643..00000000 Binary files a/Monitoring/SCU Capacity Optimizer/images/SCU_Capacity_Optimizer_View_Light.png and /dev/null differ diff --git a/Monitoring/SCU Capacity Optimizer/images/SCU_Capacity_Optimizer_solution-architecture.png b/Monitoring/SCU Capacity Optimizer/images/SCU_Capacity_Optimizer_solution-architecture.png deleted file mode 100644 index 71ee74c5..00000000 Binary files a/Monitoring/SCU Capacity Optimizer/images/SCU_Capacity_Optimizer_solution-architecture.png and /dev/null differ diff --git a/Monitoring/SCU Capacity Optimizer/readme.md b/Monitoring/SCU Capacity Optimizer/readme.md deleted file mode 100644 index 3df07fe9..00000000 --- a/Monitoring/SCU Capacity Optimizer/readme.md +++ /dev/null @@ -1,116 +0,0 @@ -# SCU Capacity Optimizer -Author: Lachlan Watson - -This solution will allow you to view and control the Security Copilot Compute Capacity SCUs based on a schedule.
-The solution is created using 2 core components: -- Sentinel Workbook - Security Copilot Optimizer -- Playbook (Logic App) - Security Copilot Scaler - -In the event of a SOC operating in a more limited capacity than 24x7 such as 8x5 this can mean significant cost savings by allowing the playbook to scale the compute capacity according to the schedule that has been applied while still taking advantage of the robust capabilities from Security Copilot.
- -![Solution Architecture](./images/SCU_Capacity_Optimizer_solution-architecture.png) - -The Security Copilot Optimizer Sentinel solution offers 3 core features:
- -1. View - Security teams can view existing scheduling (or lack there of) on a Compute Capacity, and displays predicted costs of the solution - -2. Modify - Security architects can add/modify existing schedules, view cost calculations based on the new schedule, and compare it with the existing config. This is achieved by applying a Resource Tag called: **SecurityCopilotConfig**. - -3. Apply - Finally, the Playbook/LogicApp runs hourly (5 minutes after the hour, to avoid conflict with other automations), to find resources with the SecurityCopilotConfig tag applied, then for each compute capacity evaluates high usage schedule configuration to determine the correct SCU count, then applies if any changes need to be made. - -The SecurityCopilotConfig tag holds the information required by the Playbook to apply separate configs across multiple compute capacities. - -Here is the structure of the Resource Tag: - -```json - { - "SCULimits": { - "Min": 1, "Max": 3 - }, - "Schedule": { - "ActiveDays": [ - 1, 2, 3, 4 - ], - "ActiveStartHour": 8, - "ActiveEndHour": 18, - "TimeOffset": -6 - } - } -``` -Property definitions: -- SCULimits - - Min: SCU count applied for Low Usage days/times - - Max: SCU count applied for High Usage days/times -- Schedule - - ActiveDays: integer representations of days of the week: - - 0 = Sunday - - 1 = Monday - - 2 = Tuesday - - 3 = Wednesday - - 4 = Thursday - - 5 = Friday - - 6 = Saturday - - ActiveStartHour: for high usage days, time when Maximum SCU count should be applied - - ActiveEndHour: for high usage days, time when Minimum SCU count should be applied - - TimeOffset: Hour offset from UTC. This is leveraged to ensure CfS is operating in the desired local time, and is paired with the workbook to validate offset to local comparison. - -## Prerequisites - -### Azure Resource Requirements -- Security Copilot Compute Capacity - So we have something to which we can view/apply a schedule -- Log Analytics Workspace (ideally Sentinel enabled) for operation of the Workbook - -### Azure RBAC Requirements -Least-privilege resource deployment requires: -- Workbook - Monitoring Contributor -- Playbook - Logic App Contributor - -Post-config requirements: -- Subscription Owner is required to create and apply a least-privilege custom role for the Playbook. You may choose to assign the Contributor role if you do not want to use a custom role.
- -Usage requirements: -- Contributor access of some kind is required to the Security Copilot Compute Capacity for any users who will be modifying the schedule since it works by applying Resource Tags to the Compute Capacity. - -## Deployment -**Deploy Solution**
-[![Deploy to Azure](https://aka.ms/deploytoazurebutton)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2FAzure%2FSecurity-Copilot%2Fmain%2FMonitoring%2FSCU%20Capacity%20Optimizer%2Fsolution%2Fazuredeploy.json)
- -Note: During deployment, the selected Playbook Resource Group must be different than the Sentinel Resource Group. - - -## Post-deployment -1. (Optional) Deploy the Security Copilot Capacity Creator role to each subscription where Security Copilot Compute Capacities are present for a least-privilege approach
-[![Deploy to Azure](https://aka.ms/deploytoazurebutton)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2FAzure%2FSecurity-Copilot%2Fmain%2FMonitoring%2FSCU%20Capacity%20Optimizer%2Fcustomrole%2Fazuredeploy.json) - -2. Assign the Security Copilot Capacity Creator custom role, or Contributor built-in role to the Playbook's managed identity - https://docs.microsoft.com/azure/logic-apps/create-managed-service-identity?tabs=consumption#assign-managed-identity-role-based-access-in-the-azure-portal - -3. Open the playbook in the Logic App Designer and ensure connections are displaying correctly

- -## Usage -**Basic usage steps:** - -1. The Security Copilot SCU Optimizer workbook should now be present in your Sentinel workspace. If you don't have Sentinel access, or don't have Sentinel enabled, then you can find the Azure Workbook resource in your Sentinel resource group. - -2. Select a Security Copilot Compute Capacity. You should see if there is or is not a schedule applied to that Compute Capacity. - -3. Select the Edit tab, and select the desired schedule to review the cost changes based on your team's standard work hours. - -4. When ready, select the **Apply Configuration** button to deploy the Resource Tags. - -5. When the next hour rolls around, the Playbook/Logic app will trigger, evaluate the Resource Tag, and apply the appropriate SCU limit to the Compute Capacity. - - If you'd like the changes to apply immediately you can manually trigger the Playbook. - -[Learn more about Security Copilot](https://learn.microsoft.com/en-us/copilot/security/microsoft-security-copilot)
-[Learn more about Playbooks (Logic Apps)](https://learn.microsoft.com/en-us/azure/logic-apps/logic-apps-overview)
-[Learn more about Workbooks](https://learn.microsoft.com/en-us/azure/azure-monitor/visualize/workbooks-overview)
- -## Screenshots -**Workbook - View No Schedule**
-![Workbook View - Dark](./images/SCU_Capacity_Optimizer_View_Dark.png) -![Workbook View - Light](./images/SCU_Capacity_Optimizer_View_Light.png)

-**Workbook - Apply/Modify Schedule**
-![Workbook Edit - Dark](./images/SCU_Capacity_Optimizer_Edit_Dark.png) -![Workbook Edit - Light](./images/SCU_Capacity_Optimizer_Edit_Light.png)

-**Playbook**
-![Playbook Overview - Dark](./images/SCU_Capacity_Optimizer_Scaler_Logicapp_Dark.png) -![Playbook Ovewview - Light](./images/SCU_Capacity_Optimizer_Scaler_Logicapp_Light.png) \ No newline at end of file diff --git a/Monitoring/SCU Capacity Optimizer/solution/azuredeploy.json b/Monitoring/SCU Capacity Optimizer/solution/azuredeploy.json deleted file mode 100644 index e2e857de..00000000 --- a/Monitoring/SCU Capacity Optimizer/solution/azuredeploy.json +++ /dev/null @@ -1,635 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "title": "", - "description": "", - "prerequisites": "", - "postDeployment": [], - "prerequisitesDeployTemplateFile": "", - "lastUpdateTime": "", - "entities": [], - "tags": [], - "support": { - "tier": "community", - "armtemplate": "Generated from https://github.com/Azure/Azure-Sentinel/tree/master/Tools/Playbook-ARM-Template-Generator" - }, - "author": { - "name": "" - } - }, - "parameters": { - "resourceLocation": { - "type": "string", - "allowedValues": [ - "australiaeast", - "australiasoutheast", - "brazilsouth", - "brazilsoutheast", - "canadacentral", - "canadaeast", - "centralindia", - "centralus", - "eastasia", - "eastus", - "eastus2", - "francecentral", - "francesouth", - "germanynorth", - "germanywestcentral", - "israelcentral", - "italynorth", - "japaneast", - "japanwest", - "jioindiawest", - "koreacentral", - "koreasouth", - "northcentralus", - "northeurope", - "norwayeast", - "norwaywest", - "polandcentral", - "qatarcentral", - "southafricanorth", - "southafricawest", - "southcentralus", - "southeastasia", - "southindia", - "swedencentral", - "switzerlandnorth", - "switzerlandwest", - "uaecentral", - "uaenorth", - "uksouth", - "ukwest", - "westcentralus", - "westeurope", - "westindia", - "westus", - "westus2", - "westus3" - ], - "defaultValue": "eastus", - "metadata": { - "description": "The location of the resources to be deployed." - } - }, - "playbookResourceGroup": { - "defaultValue": "", - "type": "string", - "metadata": { - "description": "The name of the Resource Group to store Security Copilot playbooks. This must be different than the Sentinel Resource Group." - } - }, - "playbookName": { - "defaultValue": "lga-securitycopilot-scu-scaler", - "type": "string", - "metadata": { - "description": "The name of the playbook associated with the workbook that applies the schedule hourly to the Security Copilot Compute Capacities." - } - }, - "sentinelResourceGroup": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Sentinel Workbooks are required be installed in the Sentinel Resource Group." - } - }, - "sentinelWorkspaceName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The name of Sentinel instance to which the workbook will be associated" - } - }, - "sentinelWorkbookDisplayName": { - "type": "string", - "defaultValue": "Security Copilot SCU Capacity Optimizer", - "metadata": { - "description": "The friendly name for the workbook that is used in the Gallery or Saved List. This name must be unique within a resource group." - } - }, - "sentinelWorkbookId": { - "type": "string", - "defaultValue": "[newGuid()]", - "metadata": { - "description": "The unique guid for this workbook instance. If new, leave as default" - } - } - }, - "variables": { - "ArmConnectionName": "[concat('mgid-', parameters('PlaybookName'))]" - }, - "resources": [ - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2021-04-01", - "name": "nestedTemplate", - "resourceGroup": "[parameters('playbookResourceGroup')]", - "subscriptionId": "[subscription().subscriptionId]", - "properties": { - "mode": "Incremental", - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": {}, - "variables": {}, - "resources": [ - { - "type": "Microsoft.Web/connections", - "apiVersion": "2016-06-01", - "name": "[variables('ArmConnectionName')]", - "location": "[parameters('resourceLocation')]", - "kind": "V1", - "properties": { - "displayName": "[variables('ArmConnectionName')]", - "customParameterValues": {}, - "parameterValueType": "Alternative", - "api": { - "id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', parameters('resourceLocation'), '/managedApis/arm')]" - } - } - }, - { - "name": "[parameters('PlaybookName')]", - "type": "Microsoft.Logic/workflows", - "location": "[parameters('resourceLocation')]", - "tags": {}, - "identity": { - "type": "SystemAssigned" - }, - "apiVersion": "2017-07-01", - "dependsOn": [ - "[resourceId(subscription().subscriptionId,parameters('playbookResourceGroup'),'Microsoft.Web/connections', variables('ArmConnectionName'))]" - ], - "properties": { - "provisioningState": "Succeeded", - "state": "Enabled", - "definition": { - "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "$connections": { - "defaultValue": {}, - "type": "Object" - } - }, - "triggers": { - "Recurrence_-_Every_Hour": { - "recurrence": { - "interval": 1, - "frequency": "Hour", - "timeZone": "Eastern Standard Time", - "startTime": "2024-05-23T17:05:00Z" - }, - "evaluatedRecurrence": { - "interval": 1, - "frequency": "Hour", - "timeZone": "Eastern Standard Time", - "startTime": "2024-05-23T17:05:00Z" - }, - "type": "Recurrence" - } - }, - "actions": { - "List_subscriptions": { - "runAfter": {}, - "type": "ApiConnection", - "inputs": { - "host": { - "connection": { - "name": "@parameters('$connections')['arm']['connectionId']" - } - }, - "method": "get", - "path": "/subscriptions", - "queries": { - "x-ms-api-version": "2016-06-01" - } - } - }, - "For_Each_-_Subscription": { - "foreach": "@body('List_subscriptions')?['value']", - "actions": { - "List_resources_by_subscription": { - "type": "ApiConnection", - "inputs": { - "host": { - "connection": { - "name": "@parameters('$connections')['arm']['connectionId']" - } - }, - "method": "get", - "path": "/subscriptions/@{encodeURIComponent(items('For_Each_-_Subscription')?['subscriptionId'])}/resources", - "queries": { - "x-ms-api-version": "2016-06-01", - "$filter": "tagName eq 'SecurityCopilotConfig'" - } - } - }, - "For_Each_-_Resource": { - "foreach": "@body('List_resources_by_subscription')?['value']", - "actions": { - "Parse_SecurityCopilot_Tag_JSON": { - "runAfter": { - "Read_a_resource": [ - "Succeeded" - ] - }, - "type": "ParseJson", - "inputs": { - "content": "@replace(item()?['tags']['SecurityCopilotConfig'],'\\', '')", - "schema": { - "type": "object", - "properties": { - "SCULimits": { - "type": "object", - "properties": { - "Min": { - "type": "integer" - }, - "Max": { - "type": "integer" - } - } - }, - "Schedule": { - "type": "object", - "properties": { - "ActiveDays": { - "type": "array", - "items": { - "type": "integer" - } - }, - "ActiveStartHour": { - "type": "integer" - }, - "ActiveEndHour": { - "type": "integer" - }, - "TimeOffset": { - "type": "integer" - } - } - } - } - } - } - }, - "Condition_-_Is_Active_SCU_Day": { - "actions": { - "Condition_-_Is_Active_Time": { - "actions": { - "Condition_-_Is_Current_SCU_not_equal_to_Max_SCU": { - "actions": { - "Create_or_update_a_resource_-_High_Usage_Day_-_High_Usage_Hours": { - "type": "ApiConnection", - "inputs": { - "host": { - "connection": { - "name": "@parameters('$connections')['arm']['connectionId']" - } - }, - "method": "put", - "body": { - "location": "@item()?['location']", - "tags": "@body('Read_a_resource')?['tags']", - "properties": { - "numberOfUnits": "@body('Parse_SecurityCopilot_Tag_JSON')?['SCULimits']?['Max']", - "crossGeoCompute": "@{body('Read_a_resource')?['properties']['crossGeoCompute']}", - "geo": "@{body('Read_a_resource')?['properties']['geo']}" - } - }, - "path": "/subscriptions/@{encodeURIComponent(split(item()?['id'], '/')[2])}/resourcegroups/@{encodeURIComponent(split(item()?['id'], '/')[4])}/providers/@{encodeURIComponent('Microsoft.SecurityCopilot')}/@{encodeURIComponent('capacities/',split(item()?['id'], '/')[8])}", - "queries": { - "x-ms-api-version": "2023-12-01-preview" - } - } - } - }, - "else": { - "actions": {} - }, - "expression": { - "and": [ - { - "not": { - "equals": [ - "@body('Read_a_resource')?['properties']['numberOfUnits']", - "@body('Parse_SecurityCopilot_Tag_JSON')?['SCULimits']?['Max']" - ] - } - } - ] - }, - "type": "If" - } - }, - "else": { - "actions": { - "Condition_-_Is_Current_SCU_not_equal_to_Min_SCU": { - "actions": { - "Create_or_update_a_resource_-_Active_Day_-_Inactive_Hours": { - "type": "ApiConnection", - "inputs": { - "host": { - "connection": { - "name": "@parameters('$connections')['arm']['connectionId']" - } - }, - "method": "put", - "body": { - "location": "@item()?['location']", - "tags": "@body('Read_a_resource')?['tags']", - "properties": { - "numberOfUnits": "@body('Parse_SecurityCopilot_Tag_JSON')?['SCULimits']?['Min']", - "crossGeoCompute": "@{body('Read_a_resource')?['properties']['crossGeoCompute']}", - "geo": "@{body('Read_a_resource')?['properties']['geo']}" - } - }, - "path": "/subscriptions/@{encodeURIComponent(split(item()?['id'], '/')[2])}/resourcegroups/@{encodeURIComponent(split(item()?['id'], '/')[4])}/providers/@{encodeURIComponent('Microsoft.SecurityCopilot')}/@{encodeURIComponent('capacities/',split(item()?['id'], '/')[8])}", - "queries": { - "x-ms-api-version": "2023-12-01-preview" - } - } - } - }, - "else": { - "actions": {} - }, - "expression": { - "and": [ - { - "not": { - "equals": [ - "@body('Read_a_resource')?['properties']['numberOfUnits']", - "@body('Parse_SecurityCopilot_Tag_JSON')?['SCULimits']?['Min']" - ] - } - } - ] - }, - "type": "If" - } - } - }, - "expression": { - "and": [ - { - "greaterOrEquals": [ - "@int(addHours(utcNow(), body('Parse_SecurityCopilot_Tag_JSON')?['Schedule']?['TimeOffset'],'HH'))", - "@body('Parse_SecurityCopilot_Tag_JSON')?['Schedule']?['ActiveStartHour']" - ] - }, - { - "less": [ - "@int(addHours(utcNow(), body('Parse_SecurityCopilot_Tag_JSON')?['Schedule']?['TimeOffset'],'HH'))", - "@body('Parse_SecurityCopilot_Tag_JSON')?['Schedule']?['ActiveEndHour']" - ] - } - ] - }, - "type": "If" - } - }, - "runAfter": { - "Set_variable_-_varActiveDays": [ - "Succeeded" - ] - }, - "else": { - "actions": { - "Condition_-_Is_Current_SCU_not_equal_to_Min_SCU_2": { - "actions": { - "Create_or_update_a_resource_-_Inactive_Day": { - "type": "ApiConnection", - "inputs": { - "host": { - "connection": { - "name": "@parameters('$connections')['arm']['connectionId']" - } - }, - "method": "put", - "body": { - "location": "@item()?['location']", - "tags": "@body('Read_a_resource')?['tags']", - "properties": { - "numberOfUnits": "@body('Parse_SecurityCopilot_Tag_JSON')?['SCULimits']?['Min']", - "crossGeoCompute": "@{body('Read_a_resource')?['properties']['crossGeoCompute']}", - "geo": "@{body('Read_a_resource')?['properties']['geo']}" - } - }, - "path": "/subscriptions/@{encodeURIComponent(split(item()?['id'], '/')[2])}/resourcegroups/@{encodeURIComponent(split(item()?['id'], '/')[4])}/providers/@{encodeURIComponent('Microsoft.SecurityCopilot')}/@{encodeURIComponent('capacities/',split(item()?['id'], '/')[8])}", - "queries": { - "x-ms-api-version": "2023-12-01-preview" - } - } - } - }, - "else": { - "actions": {} - }, - "expression": { - "and": [ - { - "not": { - "equals": [ - "@body('Read_a_resource')?['properties']['numberOfUnits']", - "@body('Parse_SecurityCopilot_Tag_JSON')?['SCULimits']?['Min']" - ] - } - } - ] - }, - "type": "If" - } - } - }, - "expression": { - "and": [ - { - "contains": [ - "@variables('varActiveDays')", - "@variables('varDayOfWeek')" - ] - } - ] - }, - "type": "If" - }, - "Read_a_resource": { - "type": "ApiConnection", - "inputs": { - "host": { - "connection": { - "name": "@parameters('$connections')['arm']['connectionId']" - } - }, - "method": "get", - "path": "/subscriptions/@{encodeURIComponent(split(item()?['id'], '/')[2])}/resourcegroups/@{encodeURIComponent(split(item()?['id'], '/')[4])}/providers/@{encodeURIComponent('Microsoft.SecurityCopilot')}/@{encodeURIComponent(concat('capacities/', split(item()?['id'], '/')[8]))}", - "queries": { - "x-ms-api-version": "2023-12-01-preview" - } - } - }, - "Set_variable_-_varActiveDays": { - "runAfter": { - "Set_variable_-_varDayOfWeek": [ - "Succeeded" - ] - }, - "type": "SetVariable", - "inputs": { - "name": "varActiveDays", - "value": "@body('Parse_SecurityCopilot_Tag_JSON')?['Schedule']?['ActiveDays']" - } - }, - "Set_variable_-_varDayOfWeek": { - "runAfter": { - "Parse_SecurityCopilot_Tag_JSON": [ - "Succeeded" - ] - }, - "type": "SetVariable", - "inputs": { - "name": "varDayOfWeek", - "value": "@int(dayOfWeek(addHours(utcNow(), body('Parse_SecurityCopilot_Tag_JSON')?['Schedule']?['TimeOffset'])))" - } - } - }, - "runAfter": { - "List_resources_by_subscription": [ - "Succeeded" - ] - }, - "type": "Foreach", - "runtimeConfiguration": { - "concurrency": { - "repetitions": 1 - } - } - } - }, - "runAfter": { - "Initialize_varCleanTags": [ - "Succeeded" - ] - }, - "type": "Foreach", - "runtimeConfiguration": { - "concurrency": { - "repetitions": 1 - } - } - }, - "Initialize_variable_-_varDayOfWeek": { - "runAfter": { - "List_subscriptions": [ - "Succeeded" - ] - }, - "type": "InitializeVariable", - "inputs": { - "variables": [ - { - "name": "varDayOfWeek", - "type": "integer" - } - ] - } - }, - "Initialize_varCleanTags": { - "runAfter": { - "Initialize_variable_-_varActiveDays": [ - "Succeeded" - ] - }, - "type": "InitializeVariable", - "inputs": { - "variables": [ - { - "name": "varCleanTags", - "type": "object" - } - ] - } - }, - "Initialize_variable_-_varActiveDays": { - "runAfter": { - "Initialize_variable_-_varDayOfWeek": [ - "Succeeded" - ] - }, - "type": "InitializeVariable", - "inputs": { - "variables": [ - { - "name": "varActiveDays", - "type": "array" - } - ] - } - } - }, - "outputs": {} - }, - "parameters": { - "$connections": { - "value": { - "arm": { - "connectionId": "[resourceId(subscription().subscriptionId,parameters('playbookResourceGroup'),'Microsoft.Web/connections', variables('ArmConnectionName'))]", - "connectionName": "[variables('ArmConnectionName')]", - "id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', parameters('resourceLocation'), '/managedApis/arm')]", - "connectionProperties": { - "authentication": { - "type": "ManagedServiceIdentity" - } - } - } - } - } - } - } - } - ] - }, - "parameters": {} - } - }, - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2021-04-01", - "name": "nestedTemplate", - "resourceGroup": "[parameters('sentinelResourceGroup')]", - "subscriptionId": "[subscription().subscriptionId]", - "properties": { - "mode": "Incremental", - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": {}, - "variables": {}, - "resources": [ - { - "name": "[parameters('sentinelWorkbookId')]", - "type": "microsoft.insights/workbooks", - "location": "[parameters('resourceLocation')]", - "apiVersion": "2022-04-01", - "dependsOn": [], - "kind": "shared", - "properties": { - "displayName": "[parameters('sentinelWorkbookDisplayName')]", - "serializedData": "{\"version\":\"Notebook/1.0\",\"items\":[{\"type\":9,\"content\":{\"version\":\"KqlParameterItem/1.0\",\"parameters\":[{\"id\":\"f368d9e2-9d09-4574-a430-f502386bbdd0\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"Subscription\",\"type\":6,\"isRequired\":true,\"multiSelect\":true,\"quote\":\"'\",\"delimiter\":\",\",\"query\":\"resourcecontainers\\r\\n| where type == \\\"microsoft.resources/subscriptions\\\"\",\"crossComponentResources\":[\"value::all\"],\"typeSettings\":{\"additionalResourceOptions\":[\"value::all\"],\"showDefault\":false},\"timeContext\":{\"durationMs\":86400000},\"queryType\":1,\"resourceType\":\"microsoft.resourcegraph/resources\",\"value\":[\"value::all\"]},{\"id\":\"90fe1617-a8fd-46b3-aa4e-cd86cc1ab277\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"LogAnalyticsWorkspace\",\"label\":\"Log Analytics Workspace\",\"type\":5,\"isRequired\":true,\"query\":\"resources\\r\\n| where type == \\\"microsoft.operationalinsights/workspaces\\\"\",\"typeSettings\":{\"resourceTypeFilter\":{\"microsoft.operationalinsights/workspaces\":true},\"additionalResourceOptions\":[],\"showDefault\":false},\"queryType\":1,\"resourceType\":\"microsoft.resourcegraph/resources\"},{\"id\":\"8d055c65-cd00-4640-af41-693e9d444a73\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"SCUPrice\",\"label\":\"Price per SCU\",\"type\":1,\"description\":\"This is used to calculate the estimated costs below. It is not necessary to specify a type of currency, though estimate labels show \\\"$\\\"\",\"isRequired\":true,\"query\":\"print \\\"4\\\"\",\"crossComponentResources\":[\"{LogAnalyticsWorkspace}\"],\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},{\"id\":\"b26debb3-5d28-4ec2-bf0c-bb3304f0aad1\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"SCUDropdownLimit\",\"label\":\"SCU Dropdown Limit\",\"type\":2,\"description\":\"Modifies the limit available in the Minimum SCU and Maximum SCU dropdowns under the \\\"Add/Edit Schedule\\\" tab\",\"isRequired\":true,\"query\":\"range SCU from 20 to 100 step 10\\r\\n| project value = SCU, label = strcat(SCU, iff(SCU>1,\\\" SCUs\\\", \\\" SCU\\\")), selected = iff(SCU == toint(20), true, false)\\r\\n\",\"typeSettings\":{\"additionalResourceOptions\":[],\"showDefault\":false},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},{\"id\":\"ef17d7a7-8d54-41cd-91d6-b1ec220276a0\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"📖Help\",\"type\":10,\"description\":\"This displays getting started text to help you validate your solution, and work through visualizing and applying schedules to your Security Copilot Compute Capacities.\",\"isRequired\":true,\"typeSettings\":{\"additionalResourceOptions\":[],\"showDefault\":false},\"jsonData\":\"[\\r\\n { \\\"value\\\": \\\"Yes\\\", \\\"label\\\": \\\"Yes\\\"},\\r\\n { \\\"value\\\": \\\"No\\\", \\\"label\\\": \\\"No\\\", \\\"selected\\\":true },\\r\\n { \\\"value\\\": \\\"Change Log\\\", \\\"label\\\": \\\"Change Log\\\"}\\r\\n]\",\"timeContext\":{\"durationMs\":86400000},\"value\":\"Yes\"},{\"id\":\"83e8f941-0eeb-4614-b553-ae94d8dd5e1e\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"ResourceId\",\"type\":1,\"query\":\"print ResourceId = \\\"Blank\\\"\",\"crossComponentResources\":[\"{LogAnalyticsWorkspace}\"],\"isHiddenWhenLocked\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},{\"id\":\"3c398c1c-654f-4792-8c16-ac9232983454\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"ResourceName\",\"type\":1,\"isHiddenWhenLocked\":true},{\"id\":\"1704ba63-2cf5-44b6-8762-30b24e505223\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"CurrentSCU\",\"type\":1,\"isHiddenWhenLocked\":true},{\"id\":\"4238ba76-54a2-4d73-a1fb-6429780df635\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"MinSCU\",\"label\":\"MinSCU Default\",\"type\":1,\"query\":\"print MinSCU = 0\",\"crossComponentResources\":[\"{LogAnalyticsWorkspace}\"],\"isHiddenWhenLocked\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},{\"id\":\"a3a2daa5-d024-4412-bfee-e37e25342d2d\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"MaxSCU\",\"label\":\"MaxSCU Default\",\"type\":1,\"query\":\"print MaxSCU = 0\",\"crossComponentResources\":[\"{LogAnalyticsWorkspace}\"],\"isHiddenWhenLocked\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},{\"id\":\"faa23c27-33f9-4587-801c-4e3a53565eec\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"ActiveDays\",\"label\":\"High Usage Days Default\",\"type\":2,\"multiSelect\":true,\"quote\":\"\",\"delimiter\":\",\",\"query\":\"let NewActiveDays = datatable(value:int, label:string, selected:boolean) [\\r\\n0, \\\"Sunday\\\", false,\\r\\n1, \\\"Monday\\\", true,\\r\\n2, \\\"Tuesday\\\", true,\\r\\n3, \\\"Wednesday\\\", true,\\r\\n4, \\\"Thursday\\\", true,\\r\\n5, \\\"Friday\\\", true,\\r\\n6, \\\"Saturday\\\",false\\r\\n];\\r\\nNewActiveDays\",\"crossComponentResources\":[\"{LogAnalyticsWorkspace}\"],\"isHiddenWhenLocked\":true,\"typeSettings\":{\"additionalResourceOptions\":[],\"showDefault\":false},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},{\"id\":\"247ee385-d118-4647-9539-3aa0bf4a6a8f\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"ActiveStartHour\",\"label\":\"High Usage Start Time Default\",\"type\":1,\"isHiddenWhenLocked\":true,\"value\":\"9\"},{\"id\":\"0d08608a-793c-4e02-a5ef-1de3d490a64d\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"ActiveEndHour\",\"label\":\"High Usage End Time Default\",\"type\":1,\"isHiddenWhenLocked\":true,\"value\":\"17\"},{\"id\":\"f6a6332c-62f5-4469-b318-276c6207b0bd\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"TimeOffset\",\"label\":\"Time Offset from UTC Default\",\"type\":1,\"isHiddenWhenLocked\":true,\"value\":\"0\"}],\"style\":\"pills\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},\"name\":\"parameters - 2\"},{\"type\":1,\"content\":{\"json\":\"# Change Log:\\r\\n\\r\\n### 12/Nov/2024\\r\\n - Added Change Log\\r\\n - Added SCU Dropdown Limit parameter to allow modification of Min and Max SCU Dropdown limits\\r\\n - Adjusted Add/Modify graph to display Time Offset based on Dropdown\\r\\n \\t- Previously this was displaying Time Offset from Resource Tags, so it was unset when deploying a new Schedule\\r\\n - Added more details to Deploy Schedule UI to provide instructions for manual deployment\\r\\n - Added Help text to explain Schedule cost calculations\\r\\n - Changed label from \\\"Copilot for Security\\\" to \\\"Security Copilot\\\"\",\"style\":\"info\"},\"conditionalVisibility\":{\"parameterName\":\"📖Help\",\"comparison\":\"isEqualTo\",\"value\":\"Change Log\"},\"name\":\"ChangeLog - 1\"},{\"type\":1,\"content\":{\"json\":\"# Welcome to the Security Copilot SCU Capacity Optimizer\\r\\n\\r\\nAuthor: Lachlan Watson\\r\\n\\r\\nThis solution will allow you to view and control the Security Copilot Compute Capacity SCUs based on a schedule.
\\r\\nThe solution is created using 2 core components:\\r\\n- Sentinel Workbook - Security Copilot SCU Capacity Optimizer\\r\\n- Playbook (Logic App) - Security Copilot SCU Scaler\\r\\n\\r\\nIn the event of a SOC operating in a more limited capacity than 24x7 such as 8x5 this can mean significant cost savings by allowing the playbook to scale the compute capacity according to the schedule that has been applied while still taking advantage of the robust capabilities from Security Copilot.
\\r\\n\\r\\nIf you do not already have the Playbook deployed, refer to the solution documentation for complete install instructions:\\r\\n[Solution Documentation](https://aka.ms/scuoptimizer)\\r\\n\\r\\n![Solution Architecture](https://raw.githubusercontent.com/LSLWatson/CopilotforSecurity/c6fc9484ab610fd6db68c1a41d3d5cb88d7c38ff/SCU_Capacity_Optimizer/images/SCU_Capacity_Optimizer_solution-architecture.png)\\r\\n\\r\\nThe Security Copilot SCU Capacity Optimizer solution offers 3 core features:
\\r\\n\\r\\n1. View - Security teams can view existing scheduling (or lack there of) on a Compute Capacity, and displays predicted costs of the solution\\r\\n\\r\\n2. Modify - Security architects can add/modify existing schedules, view cost calculations based on the new schedule, and compare it with the existing config. This is achieved by applying a Resource Tag called: **SecurityCopilotConfig**.\\r\\n\\r\\n3. Apply - Finally, the Playbook/LogicApp runs hourly (5 minutes after the hour, to avoid conflict with other automations), to find resources with the SecurityCopilotConfig tag applied, then for each compute capacity evaluates high usage schedule configuration to determine the correct SCU count, then applies if any changes need to be made.\\r\\n\\r\\nThe SecurityCopilotConfig tag holds the information required by the Playbook to apply separate configs across multiple compute capacities.\\r\\n\\r\\n## Usage\\r\\n**Requirements:**\\r\\n- View - Reader access is required to see the Security Copilot Compute Capacity\\r\\n\\r\\n- Apply/Edit - Contributor access of some kind is required to the Security Copilot Compute Capacity for any users who will be modifying the schedule since it works by applying Resource Tags.\\r\\n\\r\\n**Steps**\\r\\n\\r\\n1. Select a Security Copilot Compute Capacity. In the View tab you will see if there is or is not a schedule applied to that Compute Capacity, and a visualization of the deployed SCU capacities over time.\\r\\n2. Select the Add/Edit Schedule tab, enter the desired schedule to review the cost changes based on your team's standard work hours.\\r\\n3. When ready, select the **Apply Configuration** button to deploy the Resource Tags.\\r\\n4. When the next hour rolls around, the Playbook/Logic app will trigger, evaluate the Resource Tag, and apply the appropriate SCU limit to the Compute Capacity.\\r\\n - This can also be manually triggered if you'd like to enforce the change immediately.\",\"style\":\"info\"},\"conditionalVisibility\":{\"parameterName\":\"📖Help\",\"comparison\":\"isEqualTo\",\"value\":\"Yes\"},\"name\":\"help - 1\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"resources\\r\\n| where type =~ 'microsoft.securitycopilot/capacities'\\r\\n| project name, id, location, resourceGroup,subscriptionId,['Current SCU'] = tostring(properties.numberOfUnits),['Cross-Geo Compute'] = tostring(properties.crossGeoCompute), ResourceTags = todynamic(tags)\\r\\n| extend CFSConfig = tostring(ResourceTags.SecurityCopilotConfig)\\r\\n| extend HasConfig = iff (isempty(CFSConfig), False, True)\\r\\n| extend SCULimits = parse_json(CFSConfig).SCULimits, Schedule = parse_json(CFSConfig).Schedule\\r\\n| extend MinSCU = coalesce(toint(SCULimits.Min),toint(['Current SCU'])),\\r\\n MaxSCU = coalesce(toint(SCULimits.Max),toint(['Current SCU'])),\\r\\n ActiveDays = iff(isnotempty(Schedule.ActiveDays), todynamic(Schedule.ActiveDays),dynamic[{ActiveDays}])),\\r\\n ActiveStartHour = coalesce(toint(Schedule.ActiveStartHour),{ActiveStartHour}),\\r\\n ActiveEndHour = coalesce(toint(Schedule.ActiveEndHour),{ActiveEndHour}),\\r\\n TimeOffset = coalesce(toint(Schedule.TimeOffset),{TimeOffset})\\r\\n//| project ActiveEndHour\\r\\n| extend CostPerSCU = {SCUPrice}\\r\\n| extend HighUsageHours = iff(HasConfig==True, (ActiveEndHour - ActiveStartHour)* array_length(ActiveDays), 0)\\r\\n| extend LowUsageHours = ((24*7) - HighUsageHours), HighSCU = HighUsageHours * MaxSCU\\r\\n| extend LowSCU = LowUsageHours * MinSCU\\r\\n| extend TotalSCU = LowSCU + HighSCU\\r\\n| extend WeeklySCUCost = TotalSCU * CostPerSCU\\r\\n| extend MonthlySCUCost = (WeeklySCUCost/7) * 30\\r\\n| extend ConfigStatus = case(\\r\\n isempty(CFSConfig), \\\"❌ Config Missing\\\",\\r\\n \\\"✅ Config Present\\\")\\r\\n\\r\\n\\r\\n\",\"size\":4,\"title\":\"Select a Security Copilot Compute Capacity:\",\"exportedParameters\":[{\"fieldName\":\"id\",\"parameterName\":\"ResourceId\",\"parameterType\":5},{\"fieldName\":\"name\",\"parameterName\":\"ResourceName\",\"parameterType\":1},{\"fieldName\":\"Current SCU\",\"parameterName\":\"CurrentSCU\",\"parameterType\":1},{\"fieldName\":\"MinSCU\",\"parameterName\":\"MinSCU\",\"parameterType\":1},{\"fieldName\":\"MaxSCU\",\"parameterName\":\"MaxSCU\",\"parameterType\":1},{\"fieldName\":\"ActiveDays\",\"parameterName\":\"ActiveDays\",\"parameterType\":1},{\"fieldName\":\"ActiveStartHour\",\"parameterName\":\"ActiveStartHour\",\"parameterType\":1},{\"fieldName\":\"ActiveEndHour\",\"parameterName\":\"ActiveEndHour\",\"parameterType\":1},{\"fieldName\":\"CostPerSCU\",\"parameterName\":\"CostPerSCU\",\"parameterType\":1},{\"fieldName\":\"HighUsageHours\",\"parameterName\":\"HighUsageHours\",\"parameterType\":1},{\"fieldName\":\"LowUsageHours\",\"parameterName\":\"LowUsageHours\",\"parameterType\":1},{\"fieldName\":\"TotalSCU\",\"parameterName\":\"TotalSCU\",\"parameterType\":1},{\"fieldName\":\"WeeklySCUCost\",\"parameterName\":\"WeeklySCUCost\",\"parameterType\":1},{\"fieldName\":\"ConfigStatus\",\"parameterName\":\"ConfigStatus\",\"parameterType\":1},{\"fieldName\":\"TimeOffset\",\"parameterName\":\"TimeOffset\",\"parameterType\":1},{\"fieldName\":\"ResourceTags\",\"parameterName\":\"ResourceTags\",\"parameterType\":1},{\"fieldName\":\"MonthlySCUCost\",\"parameterName\":\"MonthlySCUCost\",\"parameterType\":1},{\"fieldName\":\"HasConfig\",\"parameterName\":\"HasConfig\",\"parameterType\":1}],\"queryType\":1,\"resourceType\":\"microsoft.resourcegraph/resources\",\"crossComponentResources\":[\"{Subscription}\"],\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"name\",\"formatter\":5},{\"columnMatch\":\"ResourceTags\",\"formatter\":5},{\"columnMatch\":\"CFSConfig\",\"formatter\":5},{\"columnMatch\":\"HasConfig\",\"formatter\":5},{\"columnMatch\":\"SCULimits\",\"formatter\":5},{\"columnMatch\":\"Schedule\",\"formatter\":5},{\"columnMatch\":\"MinSCU\",\"formatter\":5},{\"columnMatch\":\"MaxSCU\",\"formatter\":5},{\"columnMatch\":\"ActiveDays\",\"formatter\":5},{\"columnMatch\":\"ActiveStartHour\",\"formatter\":5},{\"columnMatch\":\"ActiveEndHour\",\"formatter\":5},{\"columnMatch\":\"TimeOffset\",\"formatter\":5},{\"columnMatch\":\"CostPerSCU\",\"formatter\":5},{\"columnMatch\":\"HighUsageHours\",\"formatter\":5},{\"columnMatch\":\"LowUsageHours\",\"formatter\":5},{\"columnMatch\":\"HighSCU\",\"formatter\":5},{\"columnMatch\":\"LowSCU\",\"formatter\":5},{\"columnMatch\":\"TotalSCU\",\"formatter\":5},{\"columnMatch\":\"WeeklySCUCost\",\"formatter\":5},{\"columnMatch\":\"MonthlySCUCost\",\"formatter\":5}],\"rowLimit\":10},\"sortBy\":[]},\"name\":\"query - 1\"},{\"type\":9,\"content\":{\"version\":\"KqlParameterItem/1.0\",\"crossComponentResources\":[\"{LogAnalyticsWorkspace}\"],\"parameters\":[{\"id\":\"0ac976dd-c7bf-4d11-a68b-5956248370ce\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"ActiveDays\",\"type\":2,\"multiSelect\":true,\"quote\":\" \",\"delimiter\":\",\",\"query\":\"let ActiveDays = datatable(value:int, label:string) [\\r\\n0, \\\"Sunday\\\",\\r\\n1, \\\"Monday\\\",\\r\\n2, \\\"Tuesday\\\",\\r\\n3, \\\"Wednesday\\\",\\r\\n4, \\\"Thursday\\\",\\r\\n5, \\\"Friday\\\",\\r\\n6, \\\"Saturday\\\"\\r\\n];\\r\\nActiveDays\\r\\n| project value, label, selected = iff(value in ({ActiveDays}), true, false)\",\"crossComponentResources\":[\"{LogAnalyticsWorkspace}\"],\"isHiddenWhenLocked\":true,\"typeSettings\":{\"additionalResourceOptions\":[],\"showDefault\":false},\"timeContext\":{\"durationMs\":86400000},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"}],\"style\":\"pills\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},\"name\":\"parameters - 6\"},{\"type\":11,\"content\":{\"version\":\"LinkItem/1.0\",\"style\":\"tabs\",\"links\":[{\"id\":\"ba7a9242-cae1-49b8-8807-a35b81f10e6f\",\"cellValue\":\"Nav\",\"linkTarget\":\"parameter\",\"linkLabel\":\"View Config\",\"subTarget\":\"View\",\"preText\":\"View Config\",\"style\":\"link\"},{\"id\":\"70318539-7291-4d95-85f9-30cd7ccfa38c\",\"cellValue\":\"Nav\",\"linkTarget\":\"parameter\",\"linkLabel\":\"Add/Edit Schedule\",\"subTarget\":\"Edit\",\"preText\":\"Edit Config\",\"style\":\"link\"}]},\"customWidth\":\"50\",\"conditionalVisibility\":{\"parameterName\":\"ResourceId\",\"comparison\":\"isNotEqualTo\",\"value\":\"Blank\"},\"name\":\"links - 4\",\"styleSettings\":{\"maxWidth\":\"100\"}},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"loadType\":\"always\",\"items\":[{\"type\":9,\"content\":{\"version\":\"KqlParameterItem/1.0\",\"crossComponentResources\":[\"{LogAnalyticsWorkspace}\"],\"parameters\":[{\"id\":\"d6cb9b36-2e00-4ab0-ac93-4f0d54ba1073\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"NewMinSCU\",\"label\":\"Minimum SCU\",\"type\":2,\"query\":\"range SCU from 1 to {SCUDropdownLimit} step 1\\r\\n| project value = SCU, label = strcat(SCU, iff(SCU>1,\\\" SCUs\\\", \\\" SCU\\\")), selected = iff(SCU == toint({MinSCU}), true, false)\\r\\n\",\"crossComponentResources\":[\"{LogAnalyticsWorkspace}\"],\"typeSettings\":{\"additionalResourceOptions\":[],\"showDefault\":false},\"timeContext\":{\"durationMs\":86400000},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},{\"id\":\"994f321e-5368-4269-a564-04208f7b5888\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"NewMaxSCU\",\"label\":\"Maximum SCU\",\"type\":2,\"query\":\"range SCU from 1 to {SCUDropdownLimit} step 1\\r\\n| project value = SCU, label = strcat(SCU, iff(SCU>1,\\\" SCUs\\\", \\\" SCU\\\")), selected = iff(SCU == toint({MaxSCU}), true, false)\\r\\n\",\"crossComponentResources\":[\"{LogAnalyticsWorkspace}\"],\"typeSettings\":{\"additionalResourceOptions\":[],\"showDefault\":false},\"timeContext\":{\"durationMs\":86400000},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},{\"id\":\"5d386c57-3b13-43e9-a365-4999c0a6092f\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"NewActiveDays\",\"label\":\"High Usage Days\",\"type\":2,\"multiSelect\":true,\"quote\":\"\",\"delimiter\":\",\",\"query\":\"let NewActiveDays = datatable(value:int, label:string) [\\r\\n0, \\\"Sunday\\\",\\r\\n1, \\\"Monday\\\",\\r\\n2, \\\"Tuesday\\\",\\r\\n3, \\\"Wednesday\\\",\\r\\n4, \\\"Thursday\\\",\\r\\n5, \\\"Friday\\\",\\r\\n6, \\\"Saturday\\\"\\r\\n];\\r\\nNewActiveDays\\r\\n| project value, label, selected = iff(value in ({ActiveDays}), true, false)\",\"crossComponentResources\":[\"{LogAnalyticsWorkspace}\"],\"typeSettings\":{\"additionalResourceOptions\":[],\"showDefault\":false},\"timeContext\":{\"durationMs\":86400000},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},{\"id\":\"4dd28d31-e64c-4233-acad-d78049f9c639\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"NewActiveStartHour\",\"label\":\"High Usage Start Hour\",\"type\":2,\"query\":\"let NewActiveStartHour = datatable(value:int, label:string)[\\r\\n 0,\\\"12 AM (Midnight)\\\",\\r\\n 1,\\\"1 AM\\\",\\r\\n 2,\\\"2 AM\\\" ,\\r\\n 3,\\\"3 AM\\\" ,\\r\\n 4,\\\"4 AM\\\" ,\\r\\n 5,\\\"5 AM\\\" ,\\r\\n 6,\\\"6 AM\\\" ,\\r\\n 7,\\\"7 AM\\\" ,\\r\\n 8,\\\"8 AM\\\" ,\\r\\n 9,\\\"9 AM\\\" ,\\r\\n 10,\\\"10 AM\\\" ,\\r\\n 11,\\\"11 AM\\\" ,\\r\\n 12,\\\"12 PM (Noon)\\\" ,\\r\\n 13,\\\"1 PM\\\" ,\\r\\n 14,\\\"2 PM\\\" ,\\r\\n 15,\\\"3 PM\\\" ,\\r\\n 16,\\\"4 PM\\\" ,\\r\\n 17,\\\"5 PM\\\" ,\\r\\n 18,\\\"6 PM\\\" ,\\r\\n 19,\\\"7 PM\\\" ,\\r\\n 20,\\\"8 PM\\\" ,\\r\\n 21,\\\"9 PM\\\" ,\\r\\n 22,\\\"10 PM\\\" ,\\r\\n 23,\\\"11 PM\\\" \\r\\n];\\r\\nNewActiveStartHour\\r\\n|project value, label, selected = iff(value == {ActiveStartHour}, true, false)\\r\\n\",\"crossComponentResources\":[\"{LogAnalyticsWorkspace}\"],\"typeSettings\":{\"additionalResourceOptions\":[],\"showDefault\":false},\"timeContext\":{\"durationMs\":86400000},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},{\"id\":\"320bfeaa-ee23-4fbf-9e9d-067e5ed915f8\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"NewActiveEndHour\",\"label\":\"High Usage End Hour\",\"type\":2,\"query\":\"let NewActiveEndHour = datatable(value:int, label:string)[\\r\\n 0,\\\"12 AM (Midnight)\\\",\\r\\n 1,\\\"1 AM\\\",\\r\\n 2,\\\"2 AM\\\" ,\\r\\n 3,\\\"3 AM\\\" ,\\r\\n 4,\\\"4 AM\\\" ,\\r\\n 5,\\\"5 AM\\\" ,\\r\\n 6,\\\"6 AM\\\" ,\\r\\n 7,\\\"7 AM\\\" ,\\r\\n 8,\\\"8 AM\\\" ,\\r\\n 9,\\\"9 AM\\\" ,\\r\\n 10,\\\"10 AM\\\" ,\\r\\n 11,\\\"11 AM\\\" ,\\r\\n 12,\\\"12 PM (Noon)\\\" ,\\r\\n 13,\\\"1 PM\\\" ,\\r\\n 14,\\\"2 PM\\\" ,\\r\\n 15,\\\"3 PM\\\" ,\\r\\n 16,\\\"4 PM\\\" ,\\r\\n 17,\\\"5 PM\\\" ,\\r\\n 18,\\\"6 PM\\\" ,\\r\\n 19,\\\"7 PM\\\" ,\\r\\n 20,\\\"8 PM\\\" ,\\r\\n 21,\\\"9 PM\\\" ,\\r\\n 22,\\\"10 PM\\\" ,\\r\\n 23,\\\"11 PM\\\" \\r\\n];\\r\\nNewActiveEndHour\\r\\n|project value, label, selected = iff(value == {ActiveEndHour}, true, false)\\r\\n\",\"crossComponentResources\":[\"{LogAnalyticsWorkspace}\"],\"typeSettings\":{\"additionalResourceOptions\":[],\"showDefault\":false},\"timeContext\":{\"durationMs\":86400000},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},{\"id\":\"e72ccd8d-43cd-4d3d-986e-3dd65151809f\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"NewTimeOffset\",\"label\":\"UTC Time Offset\",\"type\":2,\"query\":\"let NewActiveStartHour = datatable(value:int, label:string)[\\r\\n -12,\\\"-12 Hours\\\",\\r\\n -11,\\\"-11 Hours\\\",\\r\\n -10,\\\"-10 Hours\\\" ,\\r\\n -9,\\\"-9 Hours\\\" ,\\r\\n -8,\\\"-8 Hours\\\" ,\\r\\n -7,\\\"-7 Hours\\\" ,\\r\\n -6,\\\"-6 Hours\\\" ,\\r\\n -5,\\\"-5 Hours\\\" ,\\r\\n -4,\\\"-4 Hours\\\" ,\\r\\n -3,\\\"-3 Hours\\\" ,\\r\\n -2,\\\"-2 Hours\\\" ,\\r\\n -1,\\\"-1 Hour\\\" ,\\r\\n 0,\\\"0 Hours\\\" ,\\r\\n 1,\\\"+1 Hour\\\" ,\\r\\n 2,\\\"+2 Hours\\\" ,\\r\\n 3,\\\"+3 Hours\\\" ,\\r\\n 4,\\\"+4 Hours\\\" ,\\r\\n 5,\\\"+5 Hours\\\" ,\\r\\n 6,\\\"+6 Hours\\\" ,\\r\\n 7,\\\"+7 Hours\\\" ,\\r\\n 8,\\\"+8 Hours\\\" ,\\r\\n 9,\\\"+9 Hours\\\" ,\\r\\n 10,\\\"+10 Hours\\\" ,\\r\\n 11,\\\"+11 Hours\\\" ,\\r\\n 12,\\\"+12 Hours\\\" \\r\\n];\\r\\nNewActiveStartHour\\r\\n|project value, label, selected = iff(value == {TimeOffset}, true, false)\\r\\n\",\"crossComponentResources\":[\"{LogAnalyticsWorkspace}\"],\"typeSettings\":{\"additionalResourceOptions\":[],\"showDefault\":false},\"timeContext\":{\"durationMs\":86400000},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},{\"id\":\"61c24dd2-b52e-4002-ac46-045e2f07e355\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"MinOverMax\",\"type\":1,\"query\":\"print MinOverMax = iff({NewMinSCU} > {NewMaxSCU}, true, false)\",\"crossComponentResources\":[\"{LogAnalyticsWorkspace}\"],\"isHiddenWhenLocked\":true,\"timeContext\":{\"durationMs\":86400000},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"}],\"style\":\"above\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},\"name\":\"parameters - 5\"},{\"type\":9,\"content\":{\"version\":\"KqlParameterItem/1.0\",\"crossComponentResources\":[\"{LogAnalyticsWorkspace}\"],\"parameters\":[{\"id\":\"1ffcd520-6411-48c3-a8ed-50c353b3efa5\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"NewHighUsageHours\",\"label\":\"High Usage Hours\",\"type\":1,\"query\":\"let NewActiveDays = dynamic([{NewActiveDays}]);\\r\\nlet NewHighHours = ({NewActiveEndHour} - {NewActiveStartHour})* array_length(NewActiveDays);\\r\\nprint NewHighHours = tostring(NewHighHours)\",\"crossComponentResources\":[\"{LogAnalyticsWorkspace}\"],\"isHiddenWhenLocked\":true,\"timeContext\":{\"durationMs\":86400000},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},{\"id\":\"2f383967-6870-447d-8370-205672262861\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"NewLowUsageHours\",\"label\":\"Low Usage Hours\",\"type\":1,\"query\":\"let NewLowUsageHours = (24*7) - tolong({NewHighUsageHours});\\r\\nprint NewLowUsageHours = NewLowUsageHours\",\"crossComponentResources\":[\"{LogAnalyticsWorkspace}\"],\"isHiddenWhenLocked\":true,\"timeContext\":{\"durationMs\":86400000},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},{\"id\":\"319da61d-d14d-4588-982e-99d108ae7d26\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"NewLowSCU\",\"label\":\"Low Total SCU\",\"type\":1,\"query\":\"let LowTotalSCU = {NewLowUsageHours} * {NewMinSCU};\\r\\nprint LowTotalSCU = LowTotalSCU\",\"crossComponentResources\":[\"{LogAnalyticsWorkspace}\"],\"isHiddenWhenLocked\":true,\"timeContext\":{\"durationMs\":86400000},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},{\"id\":\"de38bd75-34ae-442e-b1fd-f5522346b9d1\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"NewHighSCU\",\"label\":\"High Total SCU\",\"type\":1,\"query\":\"let HighUsageSCU = tolong({NewHighUsageHours}) * tolong({NewMaxSCU});\\r\\nprint HighUsageSCU = tostring(HighUsageSCU)\",\"crossComponentResources\":[\"{LogAnalyticsWorkspace}\"],\"isHiddenWhenLocked\":true,\"timeContext\":{\"durationMs\":86400000},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},{\"id\":\"81ae5141-7c19-4d72-8d83-0251d5f002f6\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"NewTotalSCU\",\"label\":\"Total Weekly SCU\",\"type\":1,\"query\":\"let NewTotalSCU = tolong({NewLowSCU}) + tolong({NewHighSCU});\\r\\nprint NewTotalSCU = NewTotalSCU\",\"crossComponentResources\":[\"{LogAnalyticsWorkspace}\"],\"isHiddenWhenLocked\":true,\"timeContext\":{\"durationMs\":86400000},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},{\"id\":\"5a91a140-2b88-4e8f-b810-e5533ffbfe8f\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"NewWeeklySCUCost\",\"label\":\"Total Weekly SCU Cost\",\"type\":1,\"query\":\"let NewWeeklySCUCost = tolong({NewTotalSCU}) * tolong({CostPerSCU});\\r\\nprint NewWeeklySCUCost = tostring(NewWeeklySCUCost)\",\"crossComponentResources\":[\"{LogAnalyticsWorkspace}\"],\"isHiddenWhenLocked\":true,\"timeContext\":{\"durationMs\":86400000},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},{\"id\":\"b78a669f-30c0-4720-b94c-ec6d49966feb\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"NewMonthlySCUCost\",\"label\":\"Total Monthly SCU Cost\",\"type\":1,\"query\":\"let NewMonthlySCUCost = (({NewWeeklySCUCost})/7) * 30;\\r\\nprint NewMonthlySCUCost = NewMonthlySCUCost\",\"crossComponentResources\":[\"{LogAnalyticsWorkspace}\"],\"isHiddenWhenLocked\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},{\"id\":\"aac7ddd9-5c17-40ab-9262-954d0ef51a28\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"WeeklyDiff\",\"label\":\"Estimated Weekly Change\",\"type\":1,\"query\":\"let WeeklySCUCost = tolong({TotalSCU}) * tolong({CostPerSCU});\\r\\nlet NewWeeklySCUCost = tolong({NewTotalSCU}) * tolong({CostPerSCU});\\r\\nprint WeeklyDifference = NewWeeklySCUCost - WeeklySCUCost\",\"crossComponentResources\":[\"{LogAnalyticsWorkspace}\"],\"isHiddenWhenLocked\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},{\"id\":\"9687f227-0cb5-49b7-a636-05cb76a4df1c\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"MonthlyDiff\",\"label\":\"Estimated Monthly Change\",\"type\":1,\"query\":\"let WeeklySCUCost = {TotalSCU} * {CostPerSCU};\\r\\nlet NewWeeklySCUCost = {NewTotalSCU} * {CostPerSCU};\\r\\nlet MonthlySCUCost = round(({WeeklySCUCost}/7)*30,0);\\r\\nlet NewMonthlySCUCost = round(({NewWeeklySCUCost}/7)*30,0);\\r\\nprint MonthlyDifference = NewMonthlySCUCost - MonthlySCUCost\",\"crossComponentResources\":[\"{LogAnalyticsWorkspace}\"],\"isHiddenWhenLocked\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},{\"id\":\"a5eef48e-d661-44e6-b81f-86363a6f773e\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"LocalTime\",\"type\":1,\"query\":\"let LocalDate = datetime_add('hour', {NewTimeOffset}, now());\\r\\nprint LocalTime = format_datetime(LocalDate, 'h:mm tt')\",\"crossComponentResources\":[\"{LogAnalyticsWorkspace}\"],\"isHiddenWhenLocked\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},{\"id\":\"9739a6a5-6b5c-4290-85db-28cfe659828a\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"NewActiveDaysLabel\",\"type\":1,\"query\":\"let ActiveDays= \\\"{NewActiveDays}\\\";\\r\\nprint ActiveDayLabel = ActiveDays\\r\\n| extend ActiveDayLabel = replace_strings(\\r\\n ActiveDays,\\r\\n dynamic(['0', '1', '2', '3', '4', '5', '6']),\\r\\n dynamic([' Sunday', ' Monday', ' Tuesday', ' Wednesday', ' Thursday', ' Friday', 'Saturday']))\",\"crossComponentResources\":[\"{LogAnalyticsWorkspace}\"],\"isHiddenWhenLocked\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"}],\"style\":\"pills\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},\"name\":\"parameters - 6\"},{\"type\":1,\"content\":{\"json\":\"**Note**\\r\\n - The Minimum SCU value must be less than, or equal to Maximum SCU\",\"style\":\"error\"},\"conditionalVisibility\":{\"parameterName\":\"MinOverMax\",\"comparison\":\"isEqualTo\",\"value\":\"true\"},\"name\":\"text - 3\"},{\"type\":1,\"content\":{\"json\":\"### Validate UTC Offset with Local time:\\r\\n\\r\\n#### Local Time as of last refresh: {LocalTime}\"},\"name\":\"text - 6\"},{\"type\":1,\"content\":{\"json\":\"**Note**\\r\\n - Make sure you have configured the **UTC Time Offset** value\",\"style\":\"warning\"},\"customWidth\":\"30\",\"conditionalVisibility\":{\"parameterName\":\"NewTimeOffset\",\"comparison\":\"isEqualTo\",\"value\":\"0\"},\"name\":\"TimeOffsetZero\",\"styleSettings\":{\"maxWidth\":\"30\"}},{\"type\":11,\"content\":{\"version\":\"LinkItem/1.0\",\"style\":\"paragraph\",\"links\":[{\"id\":\"1092f1f7-166e-4745-9483-e2adb861f262\",\"linkTarget\":\"ArmAction\",\"linkLabel\":\"Apply Configuration\",\"preText\":\"\",\"style\":\"primary\",\"linkIsContextBlade\":true,\"armActionContext\":{\"path\":\"{ResourceId}/providers/Microsoft.Resources/tags/default?api-version=2022-09-01\",\"headers\":[],\"params\":[],\"body\":\"{\\r\\n \\\"operation\\\": \\\"Merge\\\",\\r\\n \\\"properties\\\": {\\r\\n \\\"tags\\\": {\\r\\n \\\"SecurityCopilotConfig\\\": \\\"{ \\\\\\\"SCULimits\\\\\\\": { \\\\\\\"Min\\\\\\\": {NewMinSCU}, \\\\\\\"Max\\\\\\\": {NewMaxSCU} }, \\\\\\\"Schedule\\\\\\\": { \\\\\\\"ActiveDays\\\\\\\": [{NewActiveDays}], \\\\\\\"ActiveStartHour\\\\\\\": {NewActiveStartHour}, \\\\\\\"ActiveEndHour\\\\\\\": {NewActiveEndHour}, \\\\\\\"TimeOffset\\\\\\\": {NewTimeOffset}}}\\\"\\r\\n }\\r\\n }\\r\\n }\",\"httpMethod\":\"PATCH\",\"title\":\"Add/Modify Security Copilot Resource Tag\",\"description\":\"# This action adds/modifies the Security Copilot resource tags\\n## This change, when paired with the Security Copilot Scaler logic app can impact the performance and costs associated with Security Copilot Compute Capacities\\n\\n## The schedule will be applied the next time the Scaler logic app executes. This can be force by manually running the logic app.\\n\\n## Tag changes will not immediately be reflected in this workbook, and may require 30 seconds before manually refreshing\",\"runLabel\":\"Continue Deploying Schedule\"}}]},\"conditionalVisibility\":{\"parameterName\":\"MinOverMax\",\"comparison\":\"isNotEqualTo\",\"value\":\"true\"},\"name\":\"links - 2\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let StartDate = startofweek(datetime_add(\\\"hour\\\", {NewTimeOffset}, now()));\\r\\nlet EndDate = endofweek(datetime_add(\\\"hour\\\", {NewTimeOffset}, now()));\\r\\nlet ActiveDays=dynamic([{NewActiveDays}]);\\r\\nlet ResourceName = \\\"{ResourceName}\\\";\\r\\nlet MinSCU = toint({NewMinSCU});\\r\\nlet MaxSCU = toint({NewMaxSCU});\\r\\nlet ActiveStartHour = {NewActiveStartHour};\\r\\nlet ActiveEndHour = {NewActiveEndHour};\\r\\nrange TimeDate from StartDate to EndDate step 1h\\r\\n| extend DayofWeek = toint(dayofweek(TimeDate)/1d), HourofDay = hourofday(datetime_add(\\\"hour\\\", {NewTimeOffset}, TimeDate))\\r\\n| extend SCU = iff(DayofWeek in (ActiveDays) and (HourofDay >= ActiveStartHour and HourofDay < ActiveEndHour), MaxSCU, MinSCU)\\r\\n| project-away DayofWeek,HourofDay\",\"size\":1,\"title\":\"Projected weekly schedule for {ResourceName}\",\"color\":\"green\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticsWorkspace}\"],\"visualization\":\"barchart\",\"chartSettings\":{\"seriesLabelSettings\":[{\"seriesName\":\"SCU\",\"color\":\"green\"},{\"seriesName\":\"$_thresholdLine\",\"color\":\"greenDarkDark\"}],\"customThresholdLine\":\"{NewMinSCU}\",\"customThresholdLineStyle\":0,\"ySettings\":{\"numberFormatSettings\":{\"unit\":17,\"options\":{\"style\":\"decimal\",\"useGrouping\":true}},\"min\":0}}},\"name\":\"query - 2 - Copy\"},{\"type\":1,\"content\":{\"json\":\"- **Weekly Estimated Cost** calculations are based on the projected schedule\\r\\n\\r\\n- **Monthly Estimated Cost** calculations are based on (Weekly Estimated Cost / 7 days) * 30 days\",\"style\":\"info\"},\"conditionalVisibility\":{\"parameterName\":\"📖Help\",\"comparison\":\"isEqualTo\",\"value\":\"Yes\"},\"name\":\"help - 2\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"loadType\":\"always\",\"items\":[{\"type\":1,\"content\":{\"json\":\"### Scheduled Capacity\"},\"customWidth\":\"50\",\"name\":\"text - 6\",\"styleSettings\":{\"maxWidth\":\"50%\"}},{\"type\":1,\"content\":{\"json\":\"### High Usage Times\"},\"customWidth\":\"50\",\"name\":\"text - 7\",\"styleSettings\":{\"maxWidth\":\"50%\"}},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let MinMax = datatable(label:string, label2: string, value:string, )[\\r\\n \\\"Low Usage Capacity\\\",\\\"SCU(s)\\\", \\\"{NewMinSCU}\\\",\\r\\n \\\"High Usage Capacity\\\", \\\"SCU(s)\\\", \\\"{NewMaxSCU}\\\"\\r\\n ];\\r\\nMinMax\",\"size\":4,\"timeContext\":{\"durationMs\":86400000},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticsWorkspace}\"],\"visualization\":\"tiles\",\"tileSettings\":{\"titleContent\":{\"columnMatch\":\"label\",\"formatter\":1},\"leftContent\":{\"columnMatch\":\"value\",\"formatter\":12,\"formatOptions\":{\"min\":0,\"palette\":\"blue\"},\"numberFormat\":{\"unit\":17,\"options\":{\"style\":\"decimal\"}}},\"rightContent\":{\"columnMatch\":\"label2\",\"formatter\":1,\"formatOptions\":{\"compositeBarSettings\":{\"labelText\":\"\",\"columnSettings\":[]}}},\"showBorder\":false}},\"customWidth\":\"50\",\"name\":\"query - 3\",\"styleSettings\":{\"maxWidth\":\"50%\"}},{\"type\":1,\"content\":{\"json\":\"\\r\\n> ## {NewActiveDaysLabel}\\r\\n\"},\"customWidth\":\"50\",\"name\":\"text - 4\",\"styleSettings\":{\"maxWidth\":\"50%\"}},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let Summaries = datatable(label:string,value:string, )[\\r\\n \\\"Weekly High Usage SCUs\\\", \\\"{NewHighUsageHours}\\\",\\r\\n \\\"Weekly Low Usage SCUs\\\", \\\"{NewLowUsageHours}\\\",\\r\\n \\\"Weekly Total SCUs\\\", \\\"{NewTotalSCU}\\\"\\r\\n ];\\r\\nSummaries\",\"size\":4,\"timeContext\":{\"durationMs\":86400000},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"tiles\",\"tileSettings\":{\"titleContent\":{\"columnMatch\":\"label\"},\"leftContent\":{\"columnMatch\":\"value\",\"formatter\":12,\"formatOptions\":{\"min\":0,\"palette\":\"purple\"},\"numberFormat\":{\"unit\":17,\"options\":{\"style\":\"decimal\"}}},\"showBorder\":false}},\"customWidth\":\"50\",\"name\":\"query - 5\",\"styleSettings\":{\"maxWidth\":\"50%\"}},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let ActiveHours = datatable(label:string, label2: string, value:string, )[\\r\\n \\\"Start Time\\\",\\\"hours\\\", \\\"{NewActiveStartHour}\\\",\\r\\n \\\"End Time\\\",\\\"hours\\\", \\\"{NewActiveEndHour}\\\",\\r\\n \\\"Time Offset from UTC\\\", \\\"hours\\\", \\\"{NewTimeOffset}\\\",\\r\\n ];\\r\\nActiveHours\",\"size\":4,\"timeContext\":{\"durationMs\":86400000},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticsWorkspace}\"],\"visualization\":\"tiles\",\"tileSettings\":{\"titleContent\":{\"columnMatch\":\"label\",\"formatter\":1},\"leftContent\":{\"columnMatch\":\"value\",\"formatter\":12,\"formatOptions\":{\"min\":0,\"palette\":\"green\"},\"numberFormat\":{\"unit\":26,\"options\":{\"style\":\"decimal\"}}},\"showBorder\":false}},\"customWidth\":\"50\",\"name\":\"query - 4\",\"styleSettings\":{\"maxWidth\":\"50%\"}},{\"type\":1,\"content\":{\"json\":\"### Cost Estimates\"},\"customWidth\":\"50\",\"name\":\"text - 8\",\"styleSettings\":{\"maxWidth\":\"50%\"}},{\"type\":1,\"content\":{\"json\":\"### Change from Current Schedule\"},\"customWidth\":\"50\",\"name\":\"text - 9\",\"styleSettings\":{\"maxWidth\":\"50%\"}},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let Summaries = datatable(label:string,value:string, )[\\r\\n \\\"Cost Per SCU\\\", \\\"{SCUPrice}\\\",\\r\\n \\\"Weekly Estimated Cost\\\", \\\"{NewWeeklySCUCost}\\\",\\r\\n \\\"Monthly Estimated Cost\\\", \\\"{NewMonthlySCUCost}\\\"\\r\\n ];\\r\\nSummaries\",\"size\":4,\"timeContext\":{\"durationMs\":86400000},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticsWorkspace}\"],\"visualization\":\"tiles\",\"tileSettings\":{\"titleContent\":{\"columnMatch\":\"label\"},\"leftContent\":{\"columnMatch\":\"value\",\"formatter\":12,\"formatOptions\":{\"palette\":\"turquoise\"},\"numberFormat\":{\"unit\":0,\"options\":{\"currency\":\"USD\",\"style\":\"currency\"}}},\"showBorder\":false}},\"customWidth\":\"50\",\"name\":\"query - 6\",\"styleSettings\":{\"maxWidth\":\"50%\"}},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let Summaries = datatable(label:string,value:string, )[\\r\\n \\\"Weekly Change\\\", \\\"{WeeklyDiff}\\\",\\r\\n \\\"Monthly Change\\\", \\\"{MonthlyDiff}\\\"\\r\\n ];\\r\\nSummaries\",\"size\":4,\"timeContext\":{\"durationMs\":86400000},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticsWorkspace}\"],\"visualization\":\"tiles\",\"tileSettings\":{\"titleContent\":{\"columnMatch\":\"label\"},\"leftContent\":{\"columnMatch\":\"value\",\"formatter\":12,\"formatOptions\":{\"min\":-1000,\"max\":1000,\"palette\":\"purpleBlueGreen\"},\"numberFormat\":{\"unit\":0,\"options\":{\"currency\":\"USD\",\"style\":\"currency\"}}},\"showBorder\":false}},\"customWidth\":\"50\",\"name\":\"query - 5\",\"styleSettings\":{\"maxWidth\":\"50%\"}}]},\"customWidth\":\"100\",\"name\":\"StatGroup - Copy\"}]},\"conditionalVisibilities\":[{\"parameterName\":\"Nav\",\"comparison\":\"isEqualTo\",\"value\":\"Edit\"},{\"parameterName\":\"ResourceId\",\"comparison\":\"isNotEqualTo\",\"value\":\"Blank\"}],\"name\":\"EditConfig\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"items\":[{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"\\r\\nlet StartDate = startofweek(datetime_add(\\\"hour\\\", {TimeOffset}, now()));\\r\\nlet EndDate = endofweek(datetime_add(\\\"hour\\\", {TimeOffset}, now()));\\r\\nlet ActiveDays=dynamic([{ActiveDays}]);\\r\\nlet ResourceName = \\\"{ResourceName}\\\";\\r\\nlet MinSCU = {MinSCU};\\r\\nlet MaxSCU = {MaxSCU};\\r\\nlet CurrentSCU = {CurrentSCU};\\r\\nlet ActiveStartHour = {ActiveStartHour};\\r\\nlet ActiveEndHour = {ActiveEndHour};\\r\\nrange TimeDate from StartDate to EndDate step 1h\\r\\n| extend DayofWeek = toint(dayofweek(TimeDate)/1d), HourofDay = hourofday(datetime_add(\\\"hour\\\", {TimeOffset}, TimeDate))\\r\\n| extend SCU = iff(isnotempty(ActiveDays),iff(DayofWeek in (ActiveDays) and (HourofDay >= ActiveStartHour and HourofDay < ActiveEndHour), MaxSCU, MinSCU),CurrentSCU)\\r\\n| project-away DayofWeek,HourofDay\",\"size\":1,\"timeContext\":{\"durationMs\":86400000},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticsWorkspace}\"],\"visualization\":\"barchart\",\"chartSettings\":{\"seriesLabelSettings\":[{\"seriesName\":\"SCU\",\"color\":\"blue\"},{\"seriesName\":\"$_thresholdLine\",\"label\":\"Minimum SCU\",\"color\":\"blueDark\"}],\"customThresholdLine\":\"{MinSCU}\",\"customThresholdLineStyle\":0,\"xSettings\":{\"numberFormatSettings\":{\"unit\":17,\"options\":{\"style\":\"decimal\",\"useGrouping\":true}}},\"ySettings\":{\"numberFormatSettings\":{\"unit\":0,\"options\":{\"style\":\"decimal\",\"useGrouping\":true}}}}},\"name\":\"query - 6\"},{\"type\":9,\"content\":{\"version\":\"KqlParameterItem/1.0\",\"crossComponentResources\":[\"{LogAnalyticsWorkspace}\"],\"parameters\":[{\"id\":\"180d4c94-2bd1-4f45-ad4a-066074a286fc\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"ActiveDaysLabel\",\"type\":1,\"query\":\"let ActiveDays= \\\"{ActiveDays}\\\";\\r\\nprint ActiveDayLabel = ActiveDays\\r\\n| extend ActiveDayLabel = replace_strings(\\r\\n ActiveDays,\\r\\n dynamic(['0', '1', '2', '3', '4', '5', '6']),\\r\\n dynamic([' Sunday', ' Monday', ' Tuesday', ' Wednesday', ' Thursday', ' Friday', 'Saturday']))\",\"crossComponentResources\":[\"{LogAnalyticsWorkspace}\"],\"isHiddenWhenLocked\":true,\"timeContext\":{\"durationMs\":86400000},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"}],\"style\":\"pills\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},\"name\":\"parameters - 2\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"loadType\":\"always\",\"items\":[{\"type\":1,\"content\":{\"json\":\"### Scheduled Capacity\"},\"customWidth\":\"50\",\"name\":\"text - 6\",\"styleSettings\":{\"maxWidth\":\"50%\"}},{\"type\":1,\"content\":{\"json\":\"### High Usage Times\"},\"customWidth\":\"50\",\"name\":\"text - 7\",\"styleSettings\":{\"maxWidth\":\"50%\"}},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let MinMax = datatable(label:string, label2: string, value:string, )[\\r\\n \\\"Low Usage Capacity\\\",\\\"SCU(s)\\\", \\\"{MinSCU}\\\",\\r\\n \\\"High Usage Capacity\\\", \\\"SCU(s)\\\", \\\"{MaxSCU}\\\"\\r\\n ];\\r\\nMinMax\",\"size\":4,\"timeContext\":{\"durationMs\":86400000},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticsWorkspace}\"],\"visualization\":\"tiles\",\"tileSettings\":{\"titleContent\":{\"columnMatch\":\"label\",\"formatter\":1},\"leftContent\":{\"columnMatch\":\"value\",\"formatter\":12,\"formatOptions\":{\"min\":0,\"palette\":\"blue\"},\"numberFormat\":{\"unit\":17,\"options\":{\"style\":\"decimal\"}}},\"rightContent\":{\"columnMatch\":\"label2\",\"formatter\":1,\"formatOptions\":{\"compositeBarSettings\":{\"labelText\":\"\",\"columnSettings\":[]}}},\"showBorder\":false}},\"customWidth\":\"50\",\"name\":\"query - 3\",\"styleSettings\":{\"maxWidth\":\"50%\"}},{\"type\":1,\"content\":{\"json\":\"\\r\\n> ## {ActiveDaysLabel}\\r\\n\"},\"customWidth\":\"50\",\"name\":\"text - 4\",\"styleSettings\":{\"maxWidth\":\"50%\"}},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let Summaries = datatable(label:string,value:string, )[\\r\\n \\\"Weekly High Usage SCUs\\\", \\\"{HighUsageHours}\\\",\\r\\n \\\"Weekly Low Usage SCUs\\\", \\\"{LowUsageHours}\\\",\\r\\n \\\"Weekly Total SCUs\\\", \\\"{TotalSCU}\\\"\\r\\n ];\\r\\nSummaries\",\"size\":4,\"timeContext\":{\"durationMs\":86400000},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticsWorkspace}\"],\"visualization\":\"tiles\",\"tileSettings\":{\"titleContent\":{\"columnMatch\":\"label\"},\"leftContent\":{\"columnMatch\":\"value\",\"formatter\":12,\"formatOptions\":{\"min\":0,\"palette\":\"purple\"},\"numberFormat\":{\"unit\":17,\"options\":{\"style\":\"decimal\"}}},\"showBorder\":false}},\"customWidth\":\"50\",\"name\":\"query - 5\",\"styleSettings\":{\"maxWidth\":\"50%\"}},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let ActiveHours = datatable(label:string, label2: string, value:string, )[\\r\\n \\\"Start Time\\\",\\\"hours\\\", \\\"{ActiveStartHour}\\\",\\r\\n \\\"End Time\\\",\\\"hours\\\", \\\"{ActiveEndHour}\\\",\\r\\n \\\"Time Offset from UTC\\\", \\\"hours\\\", \\\"{TimeOffset}\\\",\\r\\n ];\\r\\nActiveHours\",\"size\":4,\"timeContext\":{\"durationMs\":86400000},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticsWorkspace}\"],\"visualization\":\"tiles\",\"tileSettings\":{\"titleContent\":{\"columnMatch\":\"label\",\"formatter\":1},\"leftContent\":{\"columnMatch\":\"value\",\"formatter\":12,\"formatOptions\":{\"min\":0,\"palette\":\"green\"},\"numberFormat\":{\"unit\":26,\"options\":{\"style\":\"decimal\"}}},\"showBorder\":false}},\"customWidth\":\"50\",\"name\":\"query - 4\",\"styleSettings\":{\"maxWidth\":\"50%\"}},{\"type\":1,\"content\":{\"json\":\"### Cost Estimates\"},\"customWidth\":\"100\",\"name\":\"text - 8\",\"styleSettings\":{\"maxWidth\":\"100%\"}},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let Summaries = datatable(label:string,value:string, )[\\r\\n \\\"Cost Per SCU\\\", \\\"{SCUPrice}\\\",\\r\\n \\\"Weekly Estimated Cost\\\", \\\"{WeeklySCUCost}\\\",\\r\\n \\\"Monthly Estimated Cost\\\", \\\"{MonthlySCUCost}\\\"\\r\\n ];\\r\\nSummaries\",\"size\":4,\"timeContext\":{\"durationMs\":86400000},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticsWorkspace}\"],\"visualization\":\"tiles\",\"tileSettings\":{\"titleContent\":{\"columnMatch\":\"label\"},\"leftContent\":{\"columnMatch\":\"value\",\"formatter\":12,\"formatOptions\":{\"palette\":\"turquoise\"},\"numberFormat\":{\"unit\":0,\"options\":{\"currency\":\"USD\",\"style\":\"currency\"}}},\"showBorder\":false}},\"customWidth\":\"50\",\"name\":\"query - 6\",\"styleSettings\":{\"maxWidth\":\"50%\"}}]},\"customWidth\":\"100\",\"name\":\"StatGroup - ViewGroup\"}],\"exportParameters\":true},\"conditionalVisibilities\":[{\"parameterName\":\"Nav\",\"comparison\":\"isEqualTo\",\"value\":\"View\"},{\"parameterName\":\"ResourceId\",\"comparison\":\"isNotEqualTo\",\"value\":\"Blank\"},{\"parameterName\":\"HasConfig\",\"comparison\":\"isEqualTo\",\"value\":\"1\"}],\"name\":\"ViewScheduleGroup\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"items\":[{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"\\r\\nlet StartDate = startofweek(datetime_add(\\\"hour\\\", {TimeOffset}, now()));\\r\\nlet EndDate = endofweek(datetime_add(\\\"hour\\\", {TimeOffset}, now()));\\r\\nlet ActiveDays=dynamic([{ActiveDays}]);\\r\\nlet ResourceName = \\\"{ResourceName}\\\";\\r\\nlet MinSCU = {MinSCU};\\r\\nlet MaxSCU = {MaxSCU};\\r\\nlet CurrentSCU = {CurrentSCU};\\r\\nlet ActiveStartHour = {ActiveStartHour};\\r\\nlet ActiveEndHour = {ActiveEndHour};\\r\\nrange TimeDate from StartDate to EndDate step 1h\\r\\n| extend DayofWeek = toint(dayofweek(TimeDate)/1d), HourofDay = hourofday(datetime_add(\\\"hour\\\", {TimeOffset}, TimeDate))\\r\\n| extend SCU = iff(isnotempty(ActiveDays),iff(DayofWeek in (ActiveDays) and (HourofDay >= ActiveStartHour and HourofDay < ActiveEndHour), MaxSCU, MinSCU),CurrentSCU)\\r\\n| project-away DayofWeek,HourofDay\",\"size\":1,\"timeContext\":{\"durationMs\":86400000},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"barchart\",\"chartSettings\":{\"seriesLabelSettings\":[{\"seriesName\":\"SCU\",\"color\":\"blue\"},{\"seriesName\":\"$_thresholdLine\",\"label\":\"Minimum SCU\",\"color\":\"blueDark\"}],\"customThresholdLine\":\"{MinSCU}\",\"customThresholdLineStyle\":0,\"xSettings\":{\"numberFormatSettings\":{\"unit\":17,\"options\":{\"style\":\"decimal\",\"useGrouping\":true}}},\"ySettings\":{\"numberFormatSettings\":{\"unit\":0,\"options\":{\"style\":\"decimal\",\"useGrouping\":true}}}}},\"name\":\"query - 6\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"loadType\":\"always\",\"items\":[{\"type\":1,\"content\":{\"json\":\"### Configured Capacity\"},\"customWidth\":\"50\",\"name\":\"text - 6\",\"styleSettings\":{\"maxWidth\":\"50%\"}},{\"type\":1,\"content\":{\"json\":\"### Cost Estimates\"},\"customWidth\":\"50\",\"name\":\"text - 8\",\"styleSettings\":{\"maxWidth\":\"50%\"}},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let SCUCapacity = datatable(label:string, label2: string, value:string, )[\\r\\n \\\"Hourly Capacity\\\",\\\"SCU(s)\\\", \\\"{CurrentSCU}\\\",\\r\\n \\\"Weekly Total SCUs\\\",\\\"SCU(s)\\\", \\\"{TotalSCU}\\\"\\r\\n ];\\r\\nSCUCapacity\",\"size\":4,\"timeContext\":{\"durationMs\":86400000},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"tiles\",\"tileSettings\":{\"titleContent\":{\"columnMatch\":\"label\",\"formatter\":1},\"leftContent\":{\"columnMatch\":\"value\",\"formatter\":12,\"formatOptions\":{\"min\":0,\"palette\":\"blue\"},\"numberFormat\":{\"unit\":17,\"options\":{\"style\":\"decimal\"}}},\"rightContent\":{\"columnMatch\":\"label2\",\"formatter\":1,\"formatOptions\":{\"compositeBarSettings\":{\"labelText\":\"\",\"columnSettings\":[]}}},\"showBorder\":false}},\"customWidth\":\"50\",\"name\":\"query - 3\",\"styleSettings\":{\"maxWidth\":\"50%\"}},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let Summaries = datatable(label:string,value:string, )[\\r\\n \\\"Cost Per SCU\\\", \\\"{SCUPrice}\\\",\\r\\n \\\"Weekly Estimated Cost\\\", \\\"{WeeklySCUCost}\\\",\\r\\n \\\"Monthly Estimated Cost\\\", \\\"{MonthlySCUCost}\\\"\\r\\n ];\\r\\nSummaries\",\"size\":4,\"timeContext\":{\"durationMs\":86400000},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"tiles\",\"tileSettings\":{\"titleContent\":{\"columnMatch\":\"label\"},\"leftContent\":{\"columnMatch\":\"value\",\"formatter\":12,\"formatOptions\":{\"palette\":\"turquoise\"},\"numberFormat\":{\"unit\":0,\"options\":{\"currency\":\"USD\",\"style\":\"currency\"}}},\"showBorder\":false}},\"customWidth\":\"50\",\"name\":\"query - 6\",\"styleSettings\":{\"maxWidth\":\"50%\"}}]},\"customWidth\":\"100\",\"name\":\"StatGroup - ViewGroup\"}],\"exportParameters\":true},\"conditionalVisibilities\":[{\"parameterName\":\"Nav\",\"comparison\":\"isEqualTo\",\"value\":\"View\"},{\"parameterName\":\"ResourceId\",\"comparison\":\"isNotEqualTo\",\"value\":\"Blank\"},{\"parameterName\":\"HasConfig\",\"comparison\":\"isNotEqualTo\",\"value\":\"1\"}],\"name\":\"ViewNoScheduleGroup\"}],\"isLocked\":false,\"fromTemplateId\":\"sentinel-UserWorkbook\"}", - "version": "1.0", - "sourceId": "[concat('/subscriptions/',subscription().subscriptionId,'/resourcegroups/',parameters('sentinelResourceGroup'),'/providers/Microsoft.OperationalInsights/workspaces/',parameters('sentinelWorkspaceName'))]", - "category": "sentinel" - } - } - ] - }, - "parameters": {} - } - } - ] -} diff --git a/Plugins/Community Based Plugins/Copilot Logins/KQL_Plugin_CfSLogins.yaml b/Plugins/Community Based Plugins/Copilot Logins/KQL_Plugin_CfSLogins.yaml deleted file mode 100644 index 8c5cc390..00000000 --- a/Plugins/Community Based Plugins/Copilot Logins/KQL_Plugin_CfSLogins.yaml +++ /dev/null @@ -1,19 +0,0 @@ -Descriptor: - Name: CfSLogins - DisplayName: Copilot for Security Portal Logins - Description: Identifies Copilot for Security Portal logins in the last 3 days - -SkillGroups: - - Format: KQL - Skills: - - Name: CfSLogins - DisplayName: Copilot for Security Portal Logins - Description: Identifies Copilot for Security Portal logins in the last 3 days - Settings: - Target: Sentinel - TenantId: - SubscriptionId: - ResourceGroupName: - WorkspaceName: - Template: |- - SigninLogs | where TimeGenerated >= ago(3d) | where AppDisplayName == "Medeina Portal" | project TimeGenerated, Identity, UserPrincipalName, AppDisplayName, OperationName diff --git a/Plugins/Community Based Plugins/Copilot Logins/Manifest_SecurityCopilotLogins.yaml b/Plugins/Community Based Plugins/Copilot Logins/Manifest_SecurityCopilotLogins.yaml new file mode 100644 index 00000000..905c01b0 --- /dev/null +++ b/Plugins/Community Based Plugins/Copilot Logins/Manifest_SecurityCopilotLogins.yaml @@ -0,0 +1,24 @@ +Descriptor: + Name: SecurityCopilotLoginsStandalone + DisplayName: Security Copilot Portal Logins + Description: Identifies Security Copilot Portal logins in the last 3 days + +SkillGroups: + - Format: KQL + Skills: + - Name: SecurityCopilotLogins + DisplayName: Security Copilot Portal Logins + Description: Fetches Security Copilot Standalone Portal logins in the last 3 days - for each identity, the count of logins performed is returned + Settings: + Target: Sentinel + TenantId: + SubscriptionId: + ResourceGroupName: + WorkspaceName: + Template: |- + SigninLogs + | where TimeGenerated >= ago(3d) + | where AppDisplayName == "Security Copilot" + | project TimeGenerated, Identity, UserPrincipalName, OperationName + | summarize count() by Identity + | order by count_ diff --git a/Plugins/Community Based Plugins/Copilot Logins/Readme.md b/Plugins/Community Based Plugins/Copilot Logins/Readme.md index d104dbc8..41e50f5d 100644 --- a/Plugins/Community Based Plugins/Copilot Logins/Readme.md +++ b/Plugins/Community Based Plugins/Copilot Logins/Readme.md @@ -1,20 +1,49 @@ -# Security Copilot Plugin: Security Copilot Portal Logins +# Security Copilot Login Activities -### **This plugin enables you to track how many logins there have been to the Security Copilot standalone experience and who did it.** +## DESCRIPTION +This plugin enables you to track how many logins there have been to the Security Copilot standalone experience and who did it. For each identity, the count of logins performed is returned. -### Pre-requisites +--- -- [Security Copilot Enabled](https://learn.microsoft.com/en-us/security-copilot/get-started-security-copilot#onboarding-to-microsoft-security-copilot) -- [Access to upload custom plugins](https://learn.microsoft.com/en-us/security-copilot/manage-plugins?tabs=securitycopilotplugin#managing-custom-plugins) -- [Microsoft Sentinel Workspace](https://learn.microsoft.com/en-us/azure/sentinel/quickstart-onboard) created. -- Parameters for KQL Plugin - Microsoft Sentinel Workspace Name, Subscription ID, Resource Group Name and Entra Tenant ID +## TYPE AND REQUIREMENTS +**TYPE**: KQL (Sentinel)
+**SOURCE**: _Signinlogs_ table
+**REQUIREMENTS**: Log Analytics Workspace with Sentinel enabled -### Instructions +--- -#### Upload the Custom Plugin +## SKILLS + + + + + + + + + + + + + + +
SkillNameDescriptionParameters
SecurityCopilotLoginsFetches Security Copilot Standalone Portal logins in the last 3 days +
    +
+
+ + +--- + +## SAMPLE PROMPTS + +- `« Fetches Security Copilot Standalone Portal logins in the last 3 days and detect if there are anomalies in user accesses »` +--- + +## SCREENSHOTS +
+ +
-1. Obtain the file KQL_Plugin_CfSLogins.yaml from this directory. -2. Modify the yaml file to specify your specific Entra TentantId, SubscriptionId, ResourceGroupName and WorkspaceName for your Sentinel instance. -3. Upload the custom plugin For more information, see: [Security Copilot Plugin: Security Copilot Portal Logins](https://rodtrent.substack.com/p/copilot-for-security-plugin-copilot) diff --git a/Plugins/Community Based Plugins/IP Geolocation/Readme.md b/Plugins/Community Based Plugins/IP Geolocation/Readme.md deleted file mode 100644 index bbea30fa..00000000 --- a/Plugins/Community Based Plugins/IP Geolocation/Readme.md +++ /dev/null @@ -1,20 +0,0 @@ -# Security Copilot Plugin: IP Geolocation - -### **This plugin will match IP Addresses to their geolocation based on the Sentinel SecurityEvents table and a publicly available database.** - -### Pre-requisites - -- [Security Copilot Enabled](https://learn.microsoft.com/en-us/security-copilot/get-started-security-copilot#onboarding-to-microsoft-security-copilot) -- [Access to upload custom plugins](https://learn.microsoft.com/en-us/security-copilot/manage-plugins?tabs=securitycopilotplugin#managing-custom-plugins) -- [Microsoft Sentinel Workspace](https://learn.microsoft.com/en-us/azure/sentinel/quickstart-onboard) created. -- Parameters for KQL Plugin - Microsoft Sentinel Workspace Name, Subscription ID, Resource Group Name and Entra Tenant ID - -### Instructions - -#### Upload the Custom Plugin - -1. Obtain the file KQL_Plugin_IP4toGEO.yaml from this directory. -2. Modify the yaml file to specify your specific Entra TentantId, SubscriptionId, ResourceGroupName and WorkspaceName for your Sentinel instance. -3. Upload the custom plugin - -For more information, see: [Security Copilot Plugin: IP Geolocation](https://rodtrent.substack.com/p/copilot-for-security-plugin-ip-geolocation) diff --git a/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/DefenderIncidentInvestigation/Device-Info.yml b/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/DefenderIncidentInvestigation/Device-Info.yml new file mode 100644 index 00000000..9ee526b1 --- /dev/null +++ b/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/DefenderIncidentInvestigation/Device-Info.yml @@ -0,0 +1,130 @@ +Descriptor: + Name: DeviceInformation + DisplayName: Custom Plugin Defender Device Info + Description: Device information, including OS, IPs, users, and more, powered by Defender XDR, developed by Bi Yue Xu. + +SkillGroups: + - Format: KQL + Skills: + + - Name: DeviceOSInfo + DisplayName: Device OS Information + Description: Latest device OS information with the device name as the input. + Inputs: + - Name: devicename + Description: Please provide the device name, e.g. vnevado-win10b, to retrieve OS information. + Required: true + Settings: + Target: Defender + Template: |- + DeviceInfo + | where DeviceName contains '{{devicename}}' + | where Timestamp > ago(30d) + | summarize arg_max(Timestamp,*) by DeviceName + | project Timestamp, DeviceName, OSPlatform, OSVersionInfo, OSBuild, DeviceType, Vendor, JoinType + + - Name: DeviceIPInfo + DisplayName: Device Current and Past IPs + Description: The current and past IPs assigned to this device in the last 10 days. + Inputs: + - Name: devicename + Description: Please provide the device name, e.g. vnevado-win10b, to retrieve current and past IP addresses. + Required: true + Settings: + Target: Defender + Template: |- + DeviceNetworkInfo + | where DeviceName contains '{{devicename}}' + | where Timestamp > ago(10d) + | mv-expand parsejson(IPAddresses) + | extend DeviceIPAddress = tostring(IPAddresses.IPAddress) + | summarize arg_max(Timestamp, DeviceIPAddress) by DeviceId, DeviceName, MacAddress + | project Timestamp, DeviceName, MacAddress, DeviceIPAddress + | sort by Timestamp + + - Name: DeviceUserInfo + DisplayName: Device Users and Login Counts + Description: List users logged onto this device and the number of times, within the last 30 days. + Inputs: + - Name: devicename + Description: Please provide the device name, e.g. vnevado-win10b, to get logged-in users information. + Required: true + Settings: + Target: Defender + Template: |- + DeviceInfo + | where DeviceName contains '{{devicename}}' + | where Timestamp > ago(30d) + | mv-expand parsejson(LoggedOnUsers) + | extend UserName = tostring(LoggedOnUsers.UserName) + | extend Domain = tostring(LoggedOnUsers.DomainName) + | extend Sid = tostring(LoggedOnUsers.Sid) + | summarize LoginCount = count() by DeviceName, UserName, Domain, Sid + | project DeviceName, UserName, LoginCount, Domain, Sid + | sort by LoginCount + + - Name: DeviceAlertInfo + DisplayName: Device Alert Information + Description: Alerts observed on this device in the last 30 days. + Inputs: + - Name: devicename + Description: Please provide the device name, e.g. vnevado-win10b, to get its alert information. + Required: true + Settings: + Target: Defender + Template: |- + AlertInfo + | where Timestamp > ago(30d) + | join AlertEvidence on AlertId + | where DeviceName contains '{{devicename}}' + | extend Date = format_datetime(Timestamp, "yyyy-MM-dd") + | summarize by Date, Title, Severity, Categories, DetectionSource + | sort by Date desc, Categories + + - Name: DeviceAppInfo + DisplayName: Device Installed Applications + Description: Currently installed applications on this device. + Inputs: + - Name: devicename + Description: Please provide the device name, e.g. vnevado-win10b, to view its installed applications. + Required: true + Settings: + Target: Defender + Template: |- + DeviceTvmSoftwareInventory + | where DeviceName contains '{{devicename}}' + | summarize by DeviceName, SoftwareName, SoftwareVersion, SoftwareVendor, EndOfSupportDate + | sort by SoftwareName asc + + - Name: DeviceVulnerabilityInfo + DisplayName: Device Vulnerability Information + Description: Vulnerabilities identified on this device. + Inputs: + - Name: devicename + Description: Please provide the device name, e.g. vnevado-win10b, to view its vulnerabilities. + Required: true + Settings: + Target: Defender + Template: |- + DeviceTvmSoftwareVulnerabilities + | where DeviceName contains '{{devicename}}' + | join DeviceTvmSoftwareVulnerabilitiesKB on CveId + | project SoftwareName, SoftwareVersion, CveId, VulnerabilitySeverityLevel, CvssScore, IsExploitAvailable + | sort by SoftwareName asc, CveId desc + + - Name: DeviceCriticalVulnerabilities + DisplayName: Device Critical Vulnerabilities + Description: Vulnerability with CVSS score 7 or higher, or exploit code for vulnerability is publicly available. + Inputs: + - Name: devicename + Description: Please provide the device name, e.g. vnevado-win10b, to view its critical vulnerabilities. + Required: true + Settings: + Target: Defender + Template: |- + DeviceTvmSoftwareVulnerabilities + | where DeviceName contains '{{devicename}}' + | join kind=inner(DeviceTvmSoftwareVulnerabilitiesKB) on CveId + | where IsExploitAvailable == 1 or CvssScore >= 7 + | project SoftwareName, SoftwareVersion, CveId, VulnerabilitySeverityLevel, CvssScore + | sort by SoftwareName asc, CveId desc diff --git a/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/DefenderIncidentInvestigation/Playbook-Device.yml b/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/DefenderIncidentInvestigation/Playbook-Device.yml new file mode 100644 index 00000000..3c82a2d6 --- /dev/null +++ b/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/DefenderIncidentInvestigation/Playbook-Device.yml @@ -0,0 +1,503 @@ +Descriptor: + Name: Copilot_IR_Playbook-Device + DisplayName: Custom Plugin Defender Device Investigation + Description: Incident Response playbook for suspicious Windows device, powered by Defender XDR, developed by Bi Yue Xu. + +SkillGroups: + - Format: KQL + Skills: + - Name: ListPast15DayFilesDownloaded + DisplayName: File - Last 15 Days Files Downloaded + Description: Lists files downloaded to this device in the last 15 days. + Inputs: + - Name: devicename + Description: Provide the device name, e.g. vnevado-win10b, to view file download activities. + Required: true + Settings: + Target: Defender + Template: |- + DeviceFileEvents + | where DeviceName contains '{{devicename}}' + | where Timestamp > ago(15d) + | where isnotempty(FileOriginUrl) and ActionType == "FileCreated" + | project Timestamp, DeviceName, ActionType, FileName, FolderPath, FileOriginUrl, InitiatingProcessFileName, InitiatingProcessAccountName, InitiatingProcessCommandLine + | sort by Timestamp desc + + - Name: ListFilesDownloaded + DisplayName: File - Files Downloaded + Description: Lists files downloaded to this device in specific timeframe, in past 30 days. + Inputs: + - Name: devicename + Description: Provide the device name, e.g. vnevado-win10b, to view file download activities. + Required: true + - Name: starttime + Description: Provide a start time e.g. Oct 3, 2024 18:00 or 2024-10-03 18:00 + Required: true + - Name: endtime + Description: Provide an end time e.g. Oct 3, 2024 18:00 or 2024-10-03 18:00 + Required: true + Settings: + Target: Defender + Template: |- + DeviceFileEvents + | extend StartTime = datetime('{{starttime}}') + | extend EndTime = datetime('{{endtime}}') + | where Timestamp between (StartTime .. EndTime) + | where DeviceName contains '{{devicename}}' + | where isnotempty(FileOriginUrl) and ActionType == "FileCreated" + | project Timestamp, DeviceName, ActionType, FileName, FolderPath, FileOriginUrl, InitiatingProcessFileName, InitiatingProcessAccountName, InitiatingProcessCommandLine + | sort by Timestamp desc + + - Name: FileInDeviceEvents + DisplayName: File - Any Device Events Related To This File + Description: Display device events that include the filename, in specific timeframe. + Inputs: + - Name: filename + Description: Provide the file name you want to search, e.g. DomainDominance198 + Required: true + - Name: starttime + Description: Provide a start time in UTC e.g. Oct 3, 2024 18:00 or 2024-10-03 18:00 + Required: true + - Name: endtime + Description: Provide an end time in UTC e.g. Oct 3, 2024 18:00 or 2024-10-03 18:00 + Required: true + Settings: + Target: Defender + Template: |- + union DeviceProcessEvents, DeviceFileEvents, DeviceNetworkEvents, DeviceRegistryEvents, DeviceEvents, DeviceLogonEvents, DeviceImageLoadEvents + | extend StartTime = datetime('{{starttime}}') + | extend EndTime = datetime('{{endtime}}') + | where Timestamp between (StartTime .. EndTime) + | search '{{filename}}' + + - Name: SensitiveFilesEvents + DisplayName: File - Sensitive Files Events + Description: Lists sensitive files events on this device in the last 10 days. + Inputs: + - Name: devicename + Description: Provide the device name, e.g. vnevado-win10b, to view sensitive file activities. + Required: true + Settings: + Target: Defender + Template: |- + DeviceFileEvents + | where DeviceName contains '{{devicename}}' + | where Timestamp > ago(10d) + | where IsAzureInfoProtectionApplied == "1" or isnotempty(SensitivityLabel) + | project Timestamp, ActionType, FileName, FolderPath, InitiatingProcessFileName, InitiatingProcessCommandLine, InitiatingProcessAccountName, SHA256, IsAzureInfoProtectionApplied, SensitivityLabel, SensitivitySubLabel + | sort by Timestamp desc + + - Name: FileOrigin + DisplayName: File - File Origin + Description: Display the origin or source of the file, in past 30 days. + Inputs: + - Name: filename + Description: Provide the file name, e.g. DomainDominance198.ps1 + Required: true + Settings: + Target: Defender + Template: |- + DeviceFileEvents + | where FileName contains '{{filename}}' + | where Timestamp > ago(30d) + | project Timestamp, DeviceName, FileName, FileOriginUrl, FileOriginReferrerUrl + | sort by Timestamp desc + + - Name: ProcessExecutionSummary + DisplayName: Process - Process Executions Summary + Description: Summary of process executions on this device in specific timeframe, in past 30 days. + Inputs: + - Name: devicename + Description: Provide the device name, e.g. vnevado-win10b, to view process execution activities. + Required: true + - Name: starttime + Description: Provide a start time e.g. Oct 3, 2024 18:00 or 2024-10-03 18:00 + Required: true + - Name: endtime + Description: Provide an end time e.g. Oct 3, 2024 18:00 or 2024-10-03 18:00 + Required: true + Settings: + Target: Defender + Template: |- + DeviceProcessEvents + | extend StartTime = datetime('{{starttime}}') + | extend EndTime = datetime('{{endtime}}') + | where Timestamp between (StartTime .. EndTime) + | where DeviceName contains '{{devicename}}' + | extend ProcessName = FileName, ProcessPath = FolderPath + | summarize Count = count() by ProcessName, ProcessPath, AccountName + | sort by ProcessName asc + + - Name: ProcessExecutionDetails + DisplayName: Process - Detailed Process Executions + Description: Detailed all process execution events on device within a brief period, e.g. an hour. + Inputs: + - Name: devicename + Description: Provide the device name, e.g. vnevado-win10b, to view process execution activities. + Required: true + - Name: starttime + Description: Provide a start time e.g. Oct 3, 2024 18:00 or 2024-10-03 18:00 + Required: true + - Name: endtime + Description: Provide an end time e.g. Oct 3, 2024 18:00 or 2024-10-03 18:00 + Required: true + Settings: + Target: Defender + Template: |- + DeviceProcessEvents + | extend StartTime = datetime('{{starttime}}') + | extend EndTime = datetime('{{endtime}}') + | where Timestamp between (StartTime .. EndTime) + | where DeviceName contains '{{devicename}}' + | extend ProcessName = FileName, ProcessPath = FolderPath + | project Timestamp, DeviceName, ProcessName, ProcessPath, ProcessCommandLine, InitiatingProcessFileName, InitiatingProcessCommandLine, InitiatingProcessAccountName + | sort by Timestamp desc + + - Name: ProcessDetailedExecution + DisplayName: Process - Detailed Process Events + Description: Detailed specific process execution events on device within a defined time frame. + Inputs: + - Name: processname + Description: Provide the process name e.g. powershell + Required: true + - Name: devicename + Description: Provide the device name, e.g. vnevado-win10b + Required: true + - Name: starttime + Description: Provide a start time in UTC e.g. Oct 3, 2024 18:00 or 2024-10-03 18:00 + Required: true + - Name: endtime + Description: Provide an end time in UTC e.g. Oct 3, 2024 18:00 or 2024-10-03 18:00 + Required: true + Settings: + Target: Defender + Template: |- + DeviceProcessEvents + | extend StartTime = datetime('{{starttime}}') + | extend EndTime = datetime('{{endtime}}') + | where Timestamp between (StartTime .. EndTime) + | where DeviceName contains '{{devicename}}' + | where FileName contains '{{processname}}' or InitiatingProcessFileName contains '{{processname}}' + | project Timestamp, DeviceName, AccountName, ActionType, ProcessName = FileName, ProcessCommandLine, InitiatingProcessFileName, InitiatingProcessCommandLine, InitiatingProcessAccountName + | sort by Timestamp asc + + - Name: RDPConnectionToThisDevice + DisplayName: Lateral Movement - RDP To Device + Description: Inbound RDP connection to this device in a specific timeframe. + Inputs: + - Name: devicename + Description: Please provide the device name, e.g. vnevado-win10b, to view RDP events. + Required: true + - Name: starttime + Description: Provide a start time e.g. Oct 3, 2024 18:00 or 2024-10-03 18:00 + Required: true + - Name: endtime + Description: Provide an end time e.g. Oct 3, 2024 18:00 or 2024-10-03 18:00 + Required: true + Settings: + Target: Defender + Template: |- + DeviceEvents + | extend StartTime = datetime('{{starttime}}') + | extend EndTime = datetime('{{endtime}}') + | where Timestamp between (StartTime .. EndTime) + | where ActionType == "RemoteDesktopConnection" + | where DeviceName contains '{{devicename}}' + | extend SourceIP = LocalIP + | extend InternalPrivateIP = ipv4_is_private(SourceIP) + | project Timestamp, ActionType, DeviceName, SourceIP, InternalPrivateIP + | sort by Timestamp desc + + - Name: RDPConnectionToThisDevice-Last10Days + DisplayName: Lateral Movement - Last 10 Days RDP To Device + Description: Inbound RDP connection to this device in past 10 days. + Inputs: + - Name: devicename + Description: Please provide the device name, e.g. vnevado-win10b, to view RDP events. + Required: true + Settings: + Target: Defender + Template: |- + DeviceEvents + | where ActionType == "RemoteDesktopConnection" + | where Timestamp > ago(10d) + | where DeviceName contains '{{devicename}}' + | extend SourceIP = LocalIP + | extend InternalPrivateIP = ipv4_is_private(SourceIP) + | project Timestamp, ActionType, DeviceName, SourceIP, InternalPrivateIP + | sort by Timestamp desc + + - Name: LogonToDevice + DisplayName: Lateral Movement - Logons To Device In Last 10 Days + Description: Logon events from other devices to this device in the last 10 days. + Inputs: + - Name: devicename + Description: Please provide the device name, e.g. vnevado-win10b, to view logon events to this device. + Required: true + Settings: + Target: Defender + Template: |- + DeviceLogonEvents + | where Timestamp > ago(10d) + | where DeviceName contains '{{devicename}}' + | extend DestinationDevice = DeviceName, SourceDevice = RemoteDeviceName, SourceIP = RemoteIP, SourceIPType = RemoteIPType + | project Timestamp, SourceDevice, SourceIP, SourceIPType, DestinationDevice, ActionType, LogonType, AccountName, Protocol, IsLocalAdmin + | sort by Timestamp desc + + - Name: LogonToDeviceWithTimeRange + DisplayName: Lateral Movement - Logon To Device + Description: Logon events from other devices to this device in a specific timeframe, in the past 30 days. + Inputs: + - Name: devicename + Description: Provide the device name, e.g. vnevado-win10b, to view logon events to this device. + Required: true + - Name: starttime + Description: Provide a start time e.g. Oct 3, 2024 18:00 or 2024-10-03 18:00 + Required: true + - Name: endtime + Description: Provide an end time e.g. Oct 3, 2024 18:00 or 2024-10-03 18:00 + Required: true + Settings: + Target: Defender + Template: |- + DeviceLogonEvents + | extend StartTime = datetime('{{starttime}}') + | extend EndTime = datetime('{{endtime}}') + | where Timestamp between (StartTime .. EndTime) + | where DeviceName contains '{{devicename}}' + | extend DestinationDevice = DeviceName, SourceDevice = RemoteDeviceName, SourceIP = RemoteIP, SourceIPType = RemoteIPType + | project Timestamp, SourceDevice, SourceIP, SourceIPType, DestinationDevice, ActionType, LogonType, AccountName, Protocol, IsLocalAdmin + | sort by Timestamp desc + + - Name: DeviceOutboundNetworkEvents + DisplayName: Network - Outbound Network Events + Description: Device outbound network events, including attempts and failed connections, in a specific timeframe, in the past 30 days. + Inputs: + - Name: devicename + Description: Provide the device name, e.g. vnevado-win10b, to view outbound network events. + Required: true + - Name: starttime + Description: Provide a start time e.g. Oct 3, 2024 18:00 or 2024-10-03 18:00 + Required: true + - Name: endtime + Description: Provide an end time e.g. Oct 3, 2024 18:00 or 2024-10-03 18:00 + Required: true + Settings: + Target: Defender + Template: |- + DeviceNetworkEvents + | extend StartTime = datetime('{{starttime}}') + | extend EndTime = datetime('{{endtime}}') + | where Timestamp between (StartTime .. EndTime) + | where DeviceName contains '{{devicename}}' + | where ActionType has_any ("ConnectionSuccess", "ConnectionAttempt", "ConnectionFailed", "ConnectionRequest") + | summarize NetworkConnectionCount = count() by InitiatingProcessFileName, InitiatingProcessFolderPath, RemoteIP, RemoteUrl, RemotePort, Protocol + + - Name: DeviceInboundNetworkEvents + DisplayName: Network - Inbound Network Events + Description: Device inbound network events and attempts in a specific timeframe, in the past 30 days. + Inputs: + - Name: devicename + Description: Provide the device name, e.g. vnevado-win10b, to view inbound network events. + Required: true + - Name: starttime + Description: Provide a start time e.g. Oct 3, 2024 18:00 or 2024-10-03 18:00 + Required: true + - Name: endtime + Description: Provide an end time e.g. Oct 3, 2024 18:00 or 2024-10-03 18:00 + Required: true + Settings: + Target: Defender + Template: |- + DeviceNetworkEvents + | extend StartTime = datetime('{{starttime}}') + | extend EndTime = datetime('{{endtime}}') + | where Timestamp between (StartTime .. EndTime) + | where DeviceName contains '{{devicename}}' + | where ActionType has_any ("InboundConnectionAccepted", "InboundConnectionAcknowledged", "InboundConnectionAttempt") + | summarize count() by ActionType, RemoteIP, RemotePort, LocalIP, LocalPort, Protocol + + - Name: DevicelisteningPorts + DisplayName: Network - Device Listening Ports + Description: Displays device listening ports in specific timeframe, in the past 30 days. + Inputs: + - Name: devicename + Description: Provide the device name, e.g. vnevado-win10b. + Required: true + - Name: starttime + Description: Provide a start time e.g. Oct 3, 2024 18:00 or 2024-10-03 18:00 + Required: true + - Name: endtime + Description: Provide an end time e.g. Oct 3, 2024 18:00 or 2024-10-03 18:00 + Required: true + Settings: + Target: Defender + Template: |- + DeviceNetworkEvents + | extend StartTime = datetime('{{starttime}}') + | extend EndTime = datetime('{{endtime}}') + | where Timestamp between (StartTime .. EndTime) + | where DeviceName contains '{{devicename}}' + | where ActionType == "ListeningConnectionCreated" + | where LocalPort < 49151 + | project Timestamp, InitiatingProcessFileName, InitiatingProcessCommandLine, LocalPort + | sort by LocalPort asc + + - Name: DeviceScheduledTask + DisplayName: Device Events - Scheduled Task Events + Description: Scheduled task events seen on a device in a specific timeframe, in the last 30 days. + Inputs: + - Name: devicename + Description: Provide the device name, e.g. vnevado-win10b, to view scheduled tasks events. + Required: true + - Name: starttime + Description: Provide a start time e.g. Oct 3, 2024 18:00 or 2024-10-03 18:00 + Required: true + - Name: endtime + Description: Provide an end time e.g. Oct 3, 2024 18:00 or 2024-10-03 18:00 + Required: true + Settings: + Target: Defender + Template: |- + DeviceEvents + | extend StartTime = datetime('{{starttime}}') + | extend EndTime = datetime('{{endtime}}') + | where Timestamp between (StartTime .. EndTime) + | where DeviceName contains '{{devicename}}' + | where ActionType has_any ("ScheduledTaskCreated", "ScheduledTaskDeleted", "ScheduledTaskUpdated") + | project Timestamp, ActionType, InitiatingProcessAccountName, AdditionalFields, IsInitiatingProcessRemoteSession, IsProcessRemoteSession + | sort by Timestamp desc + + - Name: DeviceUserAccount + DisplayName: Device Events - User Account Events + Description: User account events seen on a device in a specific timeframe, in the last 30 days. + Inputs: + - Name: devicename + Description: Provide the device name, e.g. vnevado-win10b, to view user account events. + Required: true + - Name: starttime + Description: Provide a start time e.g. Oct 3, 2024 18:00 or 2024-10-03 18:00 + Required: true + - Name: endtime + Description: Provide an end time e.g. Oct 3, 2024 18:00 or 2024-10-03 18:00 + Required: true + Settings: + Target: Defender + Template: |- + DeviceEvents + | extend StartTime = datetime('{{starttime}}') + | extend EndTime = datetime('{{endtime}}') + | where Timestamp between (StartTime .. EndTime) + | where DeviceName contains '{{devicename}}' + | where ActionType has_any ("UserAccountCreated", "UserAccountDeleted", "UserAccountModified") + | project Timestamp, ActionType, AccountName, AccountDomain, InitiatingProcessAccountName, AdditionalFields, IsInitiatingProcessRemoteSession, IsProcessRemoteSession + | sort by Timestamp desc + + - Name: DeviceUserAccountAddedOrRemovedFromLocalGroup + DisplayName: Device Events - User Account Added Or Removed From Local Group + Description: User account added or removed from local group on a device in a specific timeframe, in the last 30 days. + Inputs: + - Name: devicename + Description: Provide the device name, e.g. vnevado-win10b, to view user account events. + Required: true + - Name: starttime + Description: Provide a start time e.g. Oct 3, 2024 18:00 or 2024-10-03 18:00 + Required: true + - Name: endtime + Description: Provide an end time e.g. Oct 3, 2024 18:00 or 2024-10-03 18:00 + Required: true + Settings: + Target: Defender + Template: |- + DeviceEvents + | extend StartTime = datetime('{{starttime}}') + | extend EndTime = datetime('{{endtime}}') + | where Timestamp between (StartTime .. EndTime) + | where DeviceName contains '{{devicename}}' + | where ActionType has_any ("UserAccountAddedToLocalGroup", "UserAccountRemovedFromLocalGroup") + | extend GroupInfo = parse_json(AdditionalFields) + | extend GroupName = GroupInfo.GroupName, GroupDomainName= GroupInfo.GroupDomainName + | project Timestamp, ActionType, AccountSid, GroupName, GroupDomainName, InitiatingProcessAccountName, InitiatingProcessAccountDomain + | sort by Timestamp desc + + - Name: DeviceASRRules + DisplayName: Suspicious Activities - ASR Rules Triggered + Description: ASR rules that were triggered on this device in the past 7 days. + Inputs: + - Name: devicename + Description: Please provide the device name, e.g. vnevado-win10b, to view ASR rules. + Required: true + Settings: + Target: Defender + Template: |- + DeviceEvents + | where Timestamp > ago(7d) + | where ActionType startswith "Asr" + | where DeviceName contains '{{devicename}}' + | summarize ASR_Rule_Count = count() by ActionType + + - Name: DeviceAMSIEvents + DisplayName: Suspicious Activities - ASMSI Script Detection + Description: Script detection events from the Windows Antimalware Scan Interface (AMSI) in past 7 days. + Inputs: + - Name: devicename + Description: Please provide the device name, e.g. vnevado-win10b, to view AMSI events. + Required: true + Settings: + Target: Defender + Template: |- + DeviceEvents + | where ActionType == "AmsiScriptDetection" + | where Timestamp > ago(7d) + | where DeviceName contains '{{devicename}}' + | project Timestamp, InitiatingProcessFileName, SHA256, InitiatingProcessCommandLine, InitiatingProcessFolderPath, InitiatingProcessAccountName + | sort by Timestamp desc + + - Name: DeviceExploitGuard + DisplayName: Suspicious Activities - Exploit Guard Events + Description: Exploit Guard events detected on this device in past 7 days. + Inputs: + - Name: devicename + Description: Please provide the device name, e.g. vnevado-win10b, to view Exploit Guard events. + Required: true + Settings: + Target: Defender + Template: |- + DeviceEvents + | where ActionType startswith 'ExploitGuard' and ActionType !contains 'NetworkProtection' + | where Timestamp > ago(7d) + | where DeviceName contains '{{devicename}}' + | project Timestamp, ActionType, InitiatingProcessFileName, InitiatingProcessSHA256, InitiatingProcessCommandLine, InitiatingProcessFolderPath, InitiatingProcessAccountName + | sort by Timestamp desc + + - Name: DeviceNetworkProtection + DisplayName: Suspicious Activities - Network Protection Events + Description: Network Protection events triggered on this device in the past 7 days. + Inputs: + - Name: devicename + Description: Please provide the device name, e.g. vnevado-win10b, to view Network Protection events. + Required: true + Settings: + Target: Defender + Template: |- + DeviceEvents + | where Timestamp > ago(7d) + | where ActionType contains "ExploitGuardNetworkProtection" + | where DeviceName contains '{{devicename}}' + | project Timestamp, ActionType, InitiatingProcessFileName, InitiatingProcessCommandLine, InitiatingProcessAccountName, RemoteUrl + + - Name: DeviceTamperingAttempt + DisplayName: Suspicious Activities - Device Tampering Attempts + Description: Possible tampering attempts on this device in the past 7 days. + Inputs: + - Name: devicename + Description: Please provide the device name, e.g. vnevado-win10b, to view possible tampering attempt events. + Required: true + Settings: + Target: Defender + Template: |- + DeviceEvents + | where Timestamp > ago(7d) + | where DeviceName contains '{{devicename}}' + | where ActionType == "TamperingAttempt" + | sort by Timestamp desc \ No newline at end of file diff --git a/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/DefenderIncidentInvestigation/readme.md b/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/DefenderIncidentInvestigation/readme.md new file mode 100644 index 00000000..3f3b8c9f --- /dev/null +++ b/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/DefenderIncidentInvestigation/readme.md @@ -0,0 +1,139 @@ +# Defender Incident Investigation + +
+ +## DESCRIPTION + +The two custom plugins, "Custom Plugin Defender Device Investigation" and "Custom Plugin Defender Device Info," provide a range of Security Copilot skills to support device investigation and hunting within Microsoft Defender XDR. + +
+ +## TYPE AND REQUIREMENTS + +- **TYPE**: KQL (Defender) +- **SOURCE**: Defender for Endpoint schema tables +- **REQUIREMENTS**: Access to Defender XDR + +
+ +## The “Custom Plugin Defender Device Investigation” provides the following skills: + +Title: **File - Files Downloaded** + +- Description: Lists files downloaded to this device in specific timeframe within past 30 days. + +Title: **File - Last 15 Days Files Downloaded** + +- Description: Lists files downloaded to this device in the last 15 days. + +Title: **File - Any Device Events Related To This File** + +- Description: Display device events that include the filename, in specific timeframe. + +Title: **File -** **Sensitive Files Events** + +- Description: Lists sensitive files events on this device in the last 10 days. + +Title: **File - File Origin** + +- Description: Display the origin or source of the file, in past 30 days. + +Title: **Process -** **Process Executions Summary** + +- Description: Summary of process executions on this device in specific timeframe. + +Title: **Process - Detailed Process Executions** + +- Description: Detailed all process execution events on device within a brief period, e.g. an hour. + +Title: **Process - Detailed Process Events** + +- Description: Detailed specific process execution events on device within a defined time frame. + +Title: **Lateral Movement - RDP To Device** + +- Description: Inbound RDP connection to this device in a specific timeframe. + +Title: **Lateral Movement - Logon To Device** + +- Description: Logon events from other devices to this device in a specific timeframe. + +Title: **Lateral Movement - Logons To Device In Last 10 Days** + +- Description: Logon events from other devices to this device in the last 10 days. + +Title: **Network - Outbound Network Events** + +- Description: Device outbound network events, including attempts and failed connections. + +Title: **Network - Inbound Network Events** + +- Description: Device inbound network events and attempts in a specific timeframe. + +Title: **Network - Device Listening Ports** + +- Description: Displays device listening ports in specific timeframe. + +Title: **Device Events - Scheduled Task Events** + +- Description: Scheduled task events seen on a device in a specific timeframe. + +Title: **Device Events - User Account Events** + +- Description: User account events seen on a device in a specific timeframe. + +Title: **Device Events - User Account Added Or Removed From Local Group** + +- Description: User account added or removed from local group in a specific timeframe. + +Title: **Suspicious Activities - ASR Rules Triggered** + +- Description: ASR rules that were triggered on this device in the past 7 days. + +Title: **Suspicious Activities - ASMSI Script Detection** + +- Description: Script detection from Windows Antimalware Scan Interface (AMSI) in past 7 days. + +Title: **Suspicious Activities - Exploit Guard Events** + +- Description: Exploit Guard events detected on this device in past 7 days. + +Title: **Suspicious Activities - Network Protection Events** + +- Description: Network Protection events triggered on this device in the past 7 days. + +Title: **Suspicious Activities - Device Tampering Attempts** + +- Description: Possible tampering attempts on this device in the past 7 days. + +
+ +## The “Custom Plugin Defender Device Info” offers the following skills: + +Title: **Device OS Information** + +- Description: Latest device OS information with the device name as the input. + +Title: **Device Current and Past IPs** + +- Description: The current and past IPs assigned to this device in the last 10 days. + +Title: **Device Users and Login Counts** + +- Description: List users logged onto this device and the number of times, within the last 10 days. + +Title: **Device Alert Information** + +- Description: Alerts observed on this device in the last 30 days. + +Title: **Device Installed Applications** + +- Description: Currently installed applications on this device. + +Title: **Device Vulnerability Information** + +- Description: Vulnerabilities identified on this device. + +Title: **Device Critical Vulnerabilities** + +- Description: Vulnerability with CVSS score 7 or higher, or exploit is publicly available. \ No newline at end of file diff --git a/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/DEVICEENRICHMENT.md b/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/DEVICEENRICHMENT.md index 9d4af180..6d097328 100644 --- a/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/DEVICEENRICHMENT.md +++ b/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/DEVICEENRICHMENT.md @@ -28,3 +28,68 @@ Example Prompt: List CVEs for some-host-name with severity level High. Retrieve a list of devices affected by a specified CVE identifier. Provides insights into the impact of the vulnerability on each device, highlights critical risks, and assesses the overall security posture to support remediation efforts across the network. Example Prompt: Find all devices affected by CVE-2023-12345. + +### DeviceDefenderSensorHealthReport +Analyzes Microsoft Defender sensor health and configuration status across Windows, macOS, and Linux devices. Evaluates critical security features, including tamper protection, real-time protection, cloud protection, and behavior monitoring. Assesses Defender operational states (Active, Passive, EDR Blocked) and compliance status. + +Example Prompt: Retrieve Defender sensor health status for device01. + +### DefenderAvSignatureHealthReport +Evaluates Microsoft Defender antivirus signature currency and real-time protection status across Windows, macOS, and Linux devices. Identifies outdated signature versions, monitors the latest available signatures, and detects devices with inactive real-time protection. + +Example Prompt: Get Defender AV signature health report for all devices. + +### WindowsCriticalVulnerabilitiesReport +Assesses critical vulnerabilities on Windows 10 and 11 devices. Identifies high-risk devices with the most critical vulnerabilities, tracks exploitable CVEs, and evaluates zero-day vulnerabilities. Helps prioritize remediation efforts based on severity and exploitability. + +Example Prompt: List top 10 Windows devices with critical vulnerabilities. + +### ASRRuleTriggerCount +Analyzes the frequency of Attack Surface Reduction (ASR) rule triggers across devices. Identifies high-frequency rules and helps security teams assess potential disruption before enabling ASR rules in enforcement mode. + +Example Prompt: Show the most frequently triggered ASR rules in our environment. + +### ASRBusinessHoursImpact +Analyzes when ASR rules are triggered to assess business operational impact. Compares working hours vs. non-working hours trigger patterns and identifies rules with the highest impact during business hours. + +Example Prompt: Analyze ASR rule impact during business hours. + +### ASRImplementationPlan +Creates a phased implementation plan for ASR rules based on impact assessment. Calculates combined impact scores and categorizes rules into low, medium, and high impact phases for a staged approach to enforcement. + +Example Prompt: Create a phased implementation plan for ASR rules. + +### ASRPlatformCompatibility +Analyzes ASR rule compatibility with different Windows platforms. Creates a platform compatibility matrix showing which rules are supported on which platforms to prevent attempted enforcement of unsupported rules. + +Example Prompt: Show ASR rule compatibility across our Windows platforms. + +### ASRImpactByDevice +Analyzes ASR rule triggers per device to determine operational impact. Identifies endpoints at risk of breaking functionality when ASR rules are enforced. + +Example Prompt: Identify devices most impacted by ASR rules. + +### ASRRuleTrends +Tracks how ASR rules are triggered over time. Monitors daily ASR rule trigger counts and helps assess whether rules are becoming more or less impactful over time. + +Example Prompt: Show ASR rule triggering trends over the past month. + +### ASRRuleApplicationMapping +Maps ASR rules to specific applications to understand business impact. Identifies which applications trigger specific ASR rules and helps develop targeted exclusion strategies for business-critical applications. + +Example Prompt: Map ASR rules to the applications they impact. + +### ASRUserImpactAnalysis +Analyzes ASR rule triggers by user to determine operational impact. Identifies departments at risk of workflow disruption and helps plan targeted user communication before enforcement. + +Example Prompt: Analyze which users are most affected by ASR rules. + +### ASRExclusionCandidates +Identifies potential candidates for ASR rule exclusions based on trigger frequency. Creates ready-to-use exclusion path formats to minimize disruption when moving to enforcement mode. + +Example Prompt: Find candidates for ASR rule exclusions. + +### ASRProcessAnalysis +Analyzes which specific processes are triggering ASR rules. Provides detailed information on process paths, file names, and command lines to distinguish between legitimate software needing exclusions and potentially malicious activity. + +Example Prompt: Analyze which processes are triggering ASR rules. \ No newline at end of file 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 e5ae85e0..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 @@ -15,10 +15,17 @@ Descriptor: Settings: - - Name: SubscriptionName - Label: SubscriptionName - Description: This is the subscription name that security copilot will use for sentinel. - HintText: yoursubscriptionname + - Name: TenantId + Label: TenantId + Description: Azure TenantId. + HintText: TenantId + SettingType: String + Required: true + + - Name: SubscriptionId + Label: SubscriptionId + Description: This is the subscription ID that security copilot will use for sentinel. + HintText: SubscriptionId SettingType: String Required: true @@ -122,7 +129,8 @@ SkillGroups: Required: false Settings: Target: Sentinel - SubscriptionName: "{{SubscriptionName}}" + TenantId: "{{TenantId}}" + SubscriptionId: "{{SubscriptionId}}" ResourceGroupName: "{{ResourceGroupName}}" WorkspaceName: "{{WorkspaceName}}" Template: |- @@ -168,7 +176,7 @@ SkillGroups: - Name: DeviceCVESearch DisplayName: Device CVE Search DescriptionForModel: |- - Performs a KQL query to identify software vulnerabilities (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. This skill provides actionable security insights, enabling security analysts to prioritize remediation and address vulnerabilities effectively. @@ -187,11 +195,19 @@ SkillGroups: DeviceTvmSoftwareVulnerabilities | where DeviceName == "{{device_name}}" | where VulnerabilitySeverityLevel contains "{{severity_level}}" - // For each vulnerability: - // 1. Analyze the software vendors to determine what type of software they produce and explain why the vulnerability could be problematic. - // 2. Summarize the device's security state based on the number of CVEs, their severity levels, and any other relevant information. - // Use this information to provide security insights and prioritize remediation efforts. | project DeviceName, SoftwareVendor, SoftwareName, CveId + | 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 @@ -216,3 +232,918 @@ SkillGroups: // 1. Analyze the impact of the vulnerability on the device, considering the type of software and why the vulnerability could be problematic. // 2. Summarize the device's security posture based on the number of devices impacted and their importance. // Use this information to provide security insights and prioritize remediation efforts. + + - Name: DeviceDefenderSensorHealthReport + DisplayName: Microsoft Defender Sensor Health Report + DescriptionForModel: |- + Executes a KQL query against DeviceTvmSecureConfigurationAssessment to evaluate Microsoft Defender sensor health status and configurations across Windows, macOS, and Linux devices. This skill: + 1. **Evaluates Defender Components**: Assesses critical security features including real-time protection, tamper protection, cloud protection, PUA protection, and behavior monitoring. + 2. **Tracks Protection Status**: Monitors Defender operational states (Active/Passive/EDR Blocked) and compliance status (GOOD/BAD). + 3. **Monitors Version Currency**: Tracks antivirus signature versions, product versions, and engine versions to ensure up-to-date protection. + This skill enables security teams to identify misconfigurations, outdated signatures, and disabled protections that could impact endpoint security. + Description: |- + Analyzes Microsoft Defender health and configuration status using DeviceTvmSecureConfigurationAssessment data. Evaluates multiple security components: + + - Protection Features: Real-time protection, cloud protection, email scanning, PUA protection, and behavior monitoring + - Operational Status: + • GOOD: Configuration meets security requirements + • BAD: Configuration fails compliance checks, requires attention + • Active: Defender is primary antivirus with full protection + • Passive: Defender is in passive mode (another antivirus is primary) + • EDR Blocked: Defender is blocked by endpoint detection and response policy + • Unknown: Status cannot be determined + + Tracks version information for signatures, product, and engine to ensure current protection levels. Results help prioritize security gaps and maintain strong endpoint protection across Windows, macOS, and Linux systems. + Inputs: + - Name: device_name + Description: Name of the device to evaluate (e.g., "Device01") + Required: true + Settings: + Target: Defender + Template: |- + DeviceTvmSecureConfigurationAssessment + | where isnotempty('{{device_name}}') and DeviceName =~ '{{device_name}}' // Only filter if device_name is provided + or isempty('{{device_name}}') // Otherwise return all devices + | where ConfigurationId in ('scid-90', 'scid-91', 'scid-2003', 'scid-2010', 'scid-2011', 'scid-2012', 'scid-2013', 'scid-2016', 'scid-5095', 'scid-6095', + 'scid-5090', 'scid-6090', 'scid-5091', 'scid-6091', 'scid-5094', 'scid-6094') + | extend Test = case( + ConfigurationId == 'scid-2003' and OSPlatform startswith 'Windows', 'TamperProtection', + ConfigurationId == 'scid-2010' and OSPlatform startswith 'Windows', 'DefenderMode', + ConfigurationId == 'scid-90' and OSPlatform startswith 'Windows', 'EmailScanning', + ConfigurationId == 'scid-2011' and OSPlatform startswith 'Windows', 'AntivirusSignatureVersion', + ConfigurationId == 'scid-5095' and OSPlatform == 'macOS', 'AntivirusSignatureVersion', + ConfigurationId == 'scid-6095' and OSPlatform == 'Linux', 'AntivirusSignatureVersion', + ConfigurationId == 'scid-2012' and OSPlatform startswith 'Windows','RealtimeProtection', + ConfigurationId == 'scid-5090' and OSPlatform == 'macOS', 'RealtimeProtection', + ConfigurationId == 'scid-6090' and OSPlatform == 'Linux', 'RealtimeProtection', + ConfigurationId == 'scid-2013' and OSPlatform startswith 'Windows', 'PUAProtection', + ConfigurationId == 'scid-5091' and OSPlatform == 'macOS', 'PUAProtection', + ConfigurationId == 'scid-6091' and OSPlatform == 'Linux', 'PUAProtection', + ConfigurationId == 'scid-2016' and OSPlatform startswith 'Windows', 'CloudProtection', + ConfigurationId == 'scid-5094' and OSPlatform == 'macOS', 'CloudProtection', + ConfigurationId == 'scid-6094' and OSPlatform == 'Linux', 'CloudProtection', + ConfigurationId == 'scid-91' and OSPlatform startswith 'Windows', 'BehaviourMonitoring', + 'NA') + | extend Result = case( + ConfigurationId == 'scid-2010', case( + parse_json(Context)[0][0] == '0', 'Active', + parse_json(Context)[0][0] == '1', 'Passive', + parse_json(Context)[0][0] == '4', 'EDR Blocked', + 'Unknown' + ), + IsCompliant == 1, 'GOOD', + 'BAD' + ) + | extend SignVer = case( + ConfigurationId == 'scid-2011' and OSPlatform startswith 'Windows', parse_json(Context)[0], + ConfigurationId == 'scid-5095' and OSPlatform == 'macOS', parse_json(Context)[0], + ConfigurationId == 'scid-6095' and OSPlatform == 'Linux', parse_json(Context)[0], + '') + | extend DeviceName = toupper(tostring(split(DeviceName, '.')[0])) + | extend packed = pack(Test, Result) + | summarize Tests = make_bag(packed), + SignatureData = max(SignVer), + DeviceName = any(DeviceName), + OSPlatform = any(OSPlatform) + by DeviceId + // Create a default record if no data exists + | union (print + DeviceId = "", + Tests = pack( + "TamperProtection", "N/A", + "DefenderMode", "N/A", + "EmailScanning", "N/A", + "AntivirusSignatureVersion", "N/A", + "RealtimeProtection", "N/A", + "PUAProtection", "N/A", + "CloudProtection", "N/A", + "BehaviourMonitoring", "N/A" + ), + SignatureData = "", + DeviceName = "", + OSPlatform = "" + ) + | evaluate bag_unpack(Tests) + | extend AVSignatureVersion = tostring(parse_json(SignatureData)[0]), + Date = todatetime(parse_json(SignatureData)[2]), + ProductVersion = tostring(parse_json(SignatureData)[3]), + EngineVersion = tostring(parse_json(SignatureData)[1]) + | join kind=leftouter (DeviceInfo + | distinct DeviceId, MachineGroup, OnboardingStatus) + on DeviceId + | where OnboardingStatus == "Onboarded" or isempty(DeviceId) + | project DeviceName, OSPlatform, MachineGroup, + TamperProtection, DefenderMode, EmailScanning, + AntivirusSignatureVersion, RealtimeProtection, + PUAProtection, CloudProtection, BehaviourMonitoring, + AVSignatureVersion, Date, ProductVersion, EngineVersion + + - Name: DefenderAvSignatureHealthReport + DisplayName: Microsoft Defender AV Signature Health Report + DescriptionForModel: |- + Executes a KQL query against DeviceTvmSecureConfigurationAssessment to evaluate Microsoft Defender antivirus signature status and real-time protection health across Windows, macOS, and Linux devices. This skill: + 1. **Tracks Signature Currency**: Compares current device signatures against latest available signatures per platform + 2. **Monitors Protection Status**: Identifies devices with inactive real-time protection + 3. **Cross-Platform Coverage**: Evaluates protection across Windows, macOS, and Linux endpoints + This skill enables security teams to quickly identify devices with outdated signatures or disabled real-time protection that require immediate attention. + Description: |- + Analyzes Microsoft Defender antivirus signature status and real-time protection health using DeviceTvmSecureConfigurationAssessment data. Evaluates critical security components: + + - Signature Status: + • Current device signature version + • Latest available signature version for the platform + • Last update timestamp + - Protection Status: + • GOOD: Real-time protection is active and functioning + • BAD: Real-time protection is inactive or malfunctioning + + Results are filtered to show only devices requiring attention (outdated signatures or inactive protection), helping teams prioritize updates and fixes across Windows, macOS, and Linux systems. + Settings: + Target: Defender + Template: |- + // Find the latest signature version for each OS platform + let LatestSignatures = + DeviceTvmSecureConfigurationAssessment + | where ConfigurationId in ('scid-2011', 'scid-5095', 'scid-6095') // AV signature configs for Windows, macOS, Linux + | where IsCompliant == 1 // Only look at devices with good signatures + | extend OSPlatform = case( + OSPlatform startswith 'Windows', 'Windows', + OSPlatform == 'macOS', 'macOS', + OSPlatform == 'Linux', 'Linux', + 'Other' + ) + | extend SignVer = parse_json(Context)[0] + | extend SignatureVersion = tostring(SignVer) + | summarize arg_max(todatetime(parse_json(Context)[2]), SignatureVersion) by OSPlatform; + // Main query for devices with AV issues + DeviceTvmSecureConfigurationAssessment + | where ConfigurationId in ('scid-90', 'scid-91', 'scid-2003', 'scid-2010', 'scid-2011', 'scid-2012', 'scid-2013', 'scid-2016', 'scid-5095', 'scid-6095', + 'scid-5090', 'scid-6090', 'scid-5091', 'scid-6091', 'scid-5094', 'scid-6094') + | extend Test = case( + ConfigurationId == 'scid-2011' and OSPlatform startswith 'Windows', 'AntivirusSignatureVersion', + ConfigurationId == 'scid-5095' and OSPlatform == 'macOS', 'AntivirusSignatureVersion', + ConfigurationId == 'scid-6095' and OSPlatform == 'Linux', 'AntivirusSignatureVersion', + ConfigurationId == 'scid-2012' and OSPlatform startswith 'Windows','RealtimeProtection', + ConfigurationId == 'scid-5090' and OSPlatform == 'macOS', 'RealtimeProtection', + ConfigurationId == 'scid-6090' and OSPlatform == 'Linux', 'RealtimeProtection', + 'NA') + | where Test in ('AntivirusSignatureVersion', 'RealtimeProtection') + | extend Result = case( + IsCompliant == 1, 'GOOD', + 'BAD' + ) + | extend SignVer = case( + ConfigurationId in ('scid-2011', 'scid-5095', 'scid-6095'), parse_json(Context)[0], + '') + | extend DeviceName = toupper(tostring(split(DeviceName, '.')[0])) + | extend OSType = case( + OSPlatform startswith 'Windows', 'Windows', + OSPlatform == 'macOS', 'macOS', + OSPlatform == 'Linux', 'Linux', + 'Other' + ) + | extend packed = pack(Test, Result) + | summarize Tests = make_bag(packed), + SignatureData = max(SignVer), + DeviceName = any(DeviceName), + OSPlatform = any(OSPlatform), + OSType = any(OSType) + by DeviceId + | evaluate bag_unpack(Tests) + | extend AVSignatureVersion = tostring(parse_json(SignatureData)[0]), + Date = todatetime(parse_json(SignatureData)[2]) + | join kind=leftouter (DeviceInfo + | distinct DeviceId, MachineGroup, OnboardingStatus) + on DeviceId + | join kind=leftouter LatestSignatures on $left.OSType == $right.OSPlatform + | where OnboardingStatus == "Onboarded" + // Filter to show only devices with AV issues + | where AntivirusSignatureVersion == "BAD" or RealtimeProtection == "BAD" + | project + DeviceName, + OSPlatform, + MachineGroup, + ['AV Status'] = RealtimeProtection, + ['Current Signature'] = AVSignatureVersion, + ['Latest Available Signature'] = SignatureVersion, + ['Last Update'] = Date + | sort by DeviceName asc + | take 15 + + - Name: WindowsCriticalVulnerabilitiesReport + DisplayName: Windows Critical Vulnerabilities Health Report + DescriptionForModel: |- + Executes a KQL query against DeviceTvmSoftwareVulnerabilities and DeviceTvmSoftwareVulnerabilitiesKB to evaluate critical vulnerabilities across Windows 10 and 11 devices. This skill: + 1. **Identifies High-Risk Devices**: Finds top 10 devices with most critical vulnerabilities + 2. **Tracks Exploit Status**: Monitors exploitable vulnerabilities and CVSS scores + 3. **Evaluates Zero-Day Impact**: Identifies devices affected by zero-day vulnerabilities + This skill enables security teams to quickly identify and prioritize the most vulnerable Windows devices requiring immediate attention. + Description: |- + Analyzes critical vulnerabilities on Windows 10 and 11 devices using DeviceTvmSoftwareVulnerabilities and DeviceTvmSoftwareVulnerabilitiesKB data. Evaluates critical security metrics: + + - Vulnerability Status: + • Total critical vulnerabilities per device + • Zero-day vulnerability count + • Number of exploitable vulnerabilities + - Risk Assessment: + • Maximum CVSS score + • Maximum EPSS score + • CVSS vector patterns + - System Details: + • OS Platform and Version + • Architecture + • CVE count + + Results are ordered by critical vulnerability count and CVSS scores, helping teams prioritize remediation efforts across Windows devices. + Settings: + Target: Defender + Template: |- + let TopDevices = + DeviceTvmSoftwareVulnerabilities + | where OSPlatform in ("Windows10", "Windows11") + | where VulnerabilitySeverityLevel == "Critical" + | summarize VulnCount=count() by DeviceId, DeviceName + | top 10 by VulnCount desc; + let DeviceVulns = DeviceTvmSoftwareVulnerabilities + | where OSPlatform in ("Windows10", "Windows11") + | where VulnerabilitySeverityLevel == "Critical" + | where DeviceId in (TopDevices); + DeviceVulns + | summarize + ['Critical Vulnerabilities'] = count(), + ['Zero Days'] = countif(CveTags has "ZeroDay") + by DeviceId, DeviceName, OSPlatform, OSVersion, OSArchitecture, CveId + | lookup DeviceTvmSoftwareVulnerabilitiesKB on CveId + | summarize + ['Critical Vulnerabilities'] = max(['Critical Vulnerabilities']), + ['Zero Days'] = max(['Zero Days']), + ['Max CVSS Score'] = max(CvssScore), + ['Exploitable CVEs'] = countif(IsExploitAvailable == true), + ['Max EPSS Score'] = max(EpssScore), + ['CVSS Vectors'] = make_set(CvssVector), + ['CVE Count'] = dcount(CveId) + by DeviceId, DeviceName, OSPlatform, OSVersion, OSArchitecture + | project + DeviceName, + OSPlatform, + OSVersion, + OSArchitecture, + ['Critical Vulnerabilities'], + ['Zero Days'], + ['Exploitable CVEs'], + ['Max CVSS Score'], + ['Max EPSS Score'], + ['CVE Count'], + ['CVSS Vectors'] + | order by ['Critical Vulnerabilities'] desc, ['Max CVSS Score'] desc + + - Name: ASRRuleTriggerCount + DisplayName: ASR Rule Trigger Frequency + DescriptionForModel: |- + Executes a KQL query against DeviceEvents to determine how often each Attack Surface Reduction (ASR) rule has been triggered. This skill: + 1. **Identifies High-Frequency Rules**: Counts occurrences of each ASR rule + 2. **Prioritizes Impact**: Ranks rules based on trigger frequency + 3. **Prepares for Enforcement**: Helps security teams assess the potential disruption before enabling ASR rules + This skill is useful for determining which ASR rules are triggering frequently and require careful evaluation before enforcement. + Description: |- + Analyzes the frequency of ASR rule triggers across devices using DeviceEvents data. Evaluates key security insights: + + - **ASR Rule Analysis**: + • Count of ASR rule triggers + • ASR rule names + • Ordered by most frequently triggered rules + + - **Risk & Enforcement Considerations**: + • Identifies high-frequency ASR rules that may impact business processes + • Helps prioritize which ASR rules should be tested first + + Results provide security teams with data to understand potential operational impact before enforcing ASR rules in block mode. + Settings: + Target: Defender + Template: |- + DeviceEvents + | where tolower(ActionType) startswith "asr" + | where TimeGenerated > ago(30d) + | extend RuleName = case( + ActionType == "AsrRuleBlockOfficeChildProcessCreation", "Block all Office applications from creating child processes", + ActionType == "AsrRuleBlockOfficeExeContentExecution", "Block Office applications from creating executable content", + ActionType == "AsrRuleBlockOfficeMacroChildProcesses", "Block Office applications from injecting code into other processes", + ActionType == "AsrRuleBlockPersistenceThroughWmiEventSubscription", "Block persistence through WMI event subscription", + ActionType == "AsrRuleBlockAdobeReaderChildProcess", "Block Adobe Reader from creating child processes", + ActionType == "AsrRuleBlockObfuscatedScripts", "Block execution of potentially obfuscated scripts", + ActionType == "AsrRuleBlockOfficeCommsChildProcess", "Block Office communication application from creating child processes", + ActionType == "AsrRuleBlockWin32ApiCallsFromOfficeMacros", "Block Win32 API calls from Office macros", + ActionType == "AsrRuleBlockCredentialStealingFromWindowsLocalSecurityAuthoritySubsystem", "Block credential stealing from the Windows local security authority subsystem (lsass.exe)", + ActionType == "AsrRuleBlockUntrustedUnsignedProcessThatRunFromUsb", "Block untrusted and unsigned processes that run from USB", + ActionType == "AsrRuleBlockExecutableContentFromEmailClientAndWebmail", "Block executable content from email client and webmail", + ActionType == "AsrRuleBlockExecutableFilesRunningUnlessMeetCriteria", "Block executable files from running unless they meet a prevalence, age, or trusted list criterion", + ActionType == "AsrRuleBlockJavaScriptOrVBScriptFromLaunchingDownloadedExecutableContent", "Block JavaScript or VBScript from launching downloaded executable content", + ActionType == "AsrRuleBlockExecutionOfPotentiallyObfuscatedScripts", "Block execution of potentially obfuscated scripts", + ActionType == "AsrRuleUseAdvancedProtectionAgainstRansomware", "Use advanced protection against ransomware", + ActionType == "AsrRuleBlockProcessCreationsFromPsexecAndWmiCommands", "Block process creations originating from PSExec and WMI commands", + ActionType == "AsrRuleBlockRebootingMachineInSafeMode", "Block rebooting machine in Safe Mode (preview)", + ActionType == "AsrRuleBlockAbuseOfExploitedVulnerableSignedDrivers", "Block abuse of exploited vulnerable signed drivers", + ActionType == "AsrRuleBlockUseOfCopiedOrImpersonatedSystemTools", "Block use of copied or impersonated system tools (preview)", + ActionType == "AsrRuleBlockWebshellCreationForServers", "Block Webshell creation for Servers", + ActionType + ) + | summarize TriggerCount = count() by RuleName, ActionType, FileName, FolderPath + | where TriggerCount > 10 + | extend ExclusionPath = iff(isnotempty(FolderPath) and isnotempty(FileName), strcat(FolderPath, "\\", FileName), "") + | order by TriggerCount desc + | project RuleName, ActionType, FileName, FolderPath, ExclusionPath, TriggerCount + + - Name: ASRBusinessHoursImpact + DisplayName: ASR Rule Business Hours Impact Analysis + DescriptionForModel: |- + Executes a KQL query against DeviceEvents to analyze ASR rule impact during business hours vs. non-business hours. This skill: + 1. **Compares Working vs. Non-Working Hours**: Analyzes when ASR rules are triggered + 2. **Identifies Business-Critical Rules**: Highlights rules with high business hours impact + 3. **Supports Risk Assessment**: Helps evaluate potential productivity impact + This skill helps security teams understand the potential impact on business operations when enforcing ASR rules. + Description: |- + Analyzes when ASR rules are triggered to assess business operational impact. Key insights include: + + - **Temporal Impact Analysis**: + • Business hours vs. non-business hours trigger comparison + • Business impact ratio calculation + + - **Operational Risk Assessment**: + • Identifies rules with highest impact during working hours + • Helps prioritize rules based on potential productivity disruption + + Results help security teams understand when ASR rules are most likely to impact business operations. + Settings: + Target: Defender + Template: |- + DeviceEvents + | where tolower(ActionType) startswith "asr" + | where TimeGenerated > ago(30d) + | extend RuleName = case( + ActionType == "AsrRuleBlockOfficeChildProcessCreation", "Block all Office applications from creating child processes", + ActionType == "AsrRuleBlockOfficeExeContentExecution", "Block Office applications from creating executable content", + ActionType == "AsrRuleBlockOfficeMacroChildProcesses", "Block Office applications from injecting code into other processes", + ActionType == "AsrRuleBlockPersistenceThroughWmiEventSubscription", "Block persistence through WMI event subscription", + ActionType == "AsrRuleBlockAdobeReaderChildProcess", "Block Adobe Reader from creating child processes", + ActionType == "AsrRuleBlockObfuscatedScripts", "Block execution of potentially obfuscated scripts", + ActionType == "AsrRuleBlockOfficeCommsChildProcess", "Block Office communication application from creating child processes", + ActionType == "AsrRuleBlockWin32ApiCallsFromOfficeMacros", "Block Win32 API calls from Office macros", + ActionType == "AsrRuleBlockCredentialStealingFromWindowsLocalSecurityAuthoritySubsystem", "Block credential stealing from the Windows local security authority subsystem (lsass.exe)", + ActionType == "AsrRuleBlockUntrustedUnsignedProcessThatRunFromUsb", "Block untrusted and unsigned processes that run from USB", + ActionType == "AsrRuleBlockExecutableContentFromEmailClientAndWebmail", "Block executable content from email client and webmail", + ActionType == "AsrRuleBlockExecutableFilesRunningUnlessMeetCriteria", "Block executable files from running unless they meet a prevalence, age, or trusted list criterion", + ActionType == "AsrRuleBlockJavaScriptOrVBScriptFromLaunchingDownloadedExecutableContent", "Block JavaScript or VBScript from launching downloaded executable content", + ActionType == "AsrRuleBlockExecutionOfPotentiallyObfuscatedScripts", "Block execution of potentially obfuscated scripts", + ActionType == "AsrRuleUseAdvancedProtectionAgainstRansomware", "Use advanced protection against ransomware", + ActionType == "AsrRuleBlockProcessCreationsFromPsexecAndWmiCommands", "Block process creations originating from PSExec and WMI commands", + ActionType == "AsrRuleBlockRebootingMachineInSafeMode", "Block rebooting machine in Safe Mode (preview)", + ActionType == "AsrRuleBlockAbuseOfExploitedVulnerableSignedDrivers", "Block abuse of exploited vulnerable signed drivers", + ActionType == "AsrRuleBlockUseOfCopiedOrImpersonatedSystemTools", "Block use of copied or impersonated system tools (preview)", + ActionType == "AsrRuleBlockWebshellCreationForServers", "Block Webshell creation for Servers", + ActionType + ) + | extend HourOfDay = toint(datetime_part("hour", TimeGenerated)) + | extend DayOfWeek = toint(dayofweek(TimeGenerated)) + | extend IsBusinessHours = HourOfDay >= 9 and HourOfDay <= 17 and DayOfWeek >= 1 and DayOfWeek <= 5 + | summarize BusinessHoursTriggers = countif(IsBusinessHours == true), + NonBusinessHoursTriggers = countif(IsBusinessHours == false) + by RuleName, ActionType + | extend TotalTriggers = BusinessHoursTriggers + NonBusinessHoursTriggers + | where TotalTriggers > 0 + | extend BusinessImpactRatio = round((1.0 * BusinessHoursTriggers) / TotalTriggers, 2) + | order by BusinessImpactRatio desc, BusinessHoursTriggers desc + + - Name: ASRImplementationPlan + DisplayName: ASR Implementation Phasing Plan + DescriptionForModel: |- + Executes a KQL query against DeviceEvents to create a phased implementation plan for ASR rules. This skill: + 1. **Calculates Impact Scores**: Combines trigger count, device count, and user count + 2. **Creates Phased Approach**: Groups rules into low, medium, and high impact phases + 3. **Supports Gradual Rollout**: Helps security teams implement ASR rules in a controlled manner + This skill is essential for planning a staged approach to ASR rule enforcement. + Description: |- + Creates a phased implementation plan for ASR rules based on impact assessment. Key insights include: + + - **Impact Assessment**: + • Combined impact score calculation + • Rule categorization by impact level + + - **Implementation Strategy**: + • Three-phase implementation approach + • Start with low-impact rules and gradually enforce higher-impact rules + + Results provide security teams with a structured approach to implementing ASR rules in enforcement mode. + Settings: + Target: Defender + Template: |- + // Get rule trigger counts + let ruleTriggers = DeviceEvents + | where tolower(ActionType) startswith "asr" + | where TimeGenerated > ago(30d) + | extend RuleName = case( + ActionType == "AsrRuleBlockOfficeChildProcessCreation", "Block all Office applications from creating child processes", + ActionType == "AsrRuleBlockOfficeExeContentExecution", "Block Office applications from creating executable content", + ActionType == "AsrRuleBlockOfficeMacroChildProcesses", "Block Office applications from injecting code into other processes", + ActionType == "AsrRuleBlockPersistenceThroughWmiEventSubscription", "Block persistence through WMI event subscription", + ActionType == "AsrRuleBlockAdobeReaderChildProcess", "Block Adobe Reader from creating child processes", + ActionType == "AsrRuleBlockObfuscatedScripts", "Block execution of potentially obfuscated scripts", + ActionType == "AsrRuleBlockOfficeCommsChildProcess", "Block Office communication application from creating child processes", + ActionType == "AsrRuleBlockWin32ApiCallsFromOfficeMacros", "Block Win32 API calls from Office macros", + ActionType == "AsrRuleBlockCredentialStealingFromWindowsLocalSecurityAuthoritySubsystem", "Block credential stealing from the Windows local security authority subsystem (lsass.exe)", + ActionType == "AsrRuleBlockUntrustedUnsignedProcessThatRunFromUsb", "Block untrusted and unsigned processes that run from USB", + ActionType == "AsrRuleBlockExecutableContentFromEmailClientAndWebmail", "Block executable content from email client and webmail", + ActionType == "AsrRuleBlockExecutableFilesRunningUnlessMeetCriteria", "Block executable files from running unless they meet a prevalence, age, or trusted list criterion", + ActionType == "AsrRuleBlockJavaScriptOrVBScriptFromLaunchingDownloadedExecutableContent", "Block JavaScript or VBScript from launching downloaded executable content", + ActionType == "AsrRuleBlockExecutionOfPotentiallyObfuscatedScripts", "Block execution of potentially obfuscated scripts", + ActionType == "AsrRuleUseAdvancedProtectionAgainstRansomware", "Use advanced protection against ransomware", + ActionType == "AsrRuleBlockProcessCreationsFromPsexecAndWmiCommands", "Block process creations originating from PSExec and WMI commands", + ActionType == "AsrRuleBlockRebootingMachineInSafeMode", "Block rebooting machine in Safe Mode (preview)", + ActionType == "AsrRuleBlockAbuseOfExploitedVulnerableSignedDrivers", "Block abuse of exploited vulnerable signed drivers", + ActionType == "AsrRuleBlockUseOfCopiedOrImpersonatedSystemTools", "Block use of copied or impersonated system tools (preview)", + ActionType == "AsrRuleBlockWebshellCreationForServers", "Block Webshell creation for Servers", + ActionType + ) + | summarize TriggerCount = count() by RuleName, ActionType; + // Get device counts per rule + let deviceCounts = DeviceEvents + | where tolower(ActionType) startswith "asr" + | where TimeGenerated > ago(30d) + | summarize DeviceCount = dcount(DeviceName) by ActionType; + // Get user counts per rule + let userCounts = DeviceEvents + | where tolower(ActionType) startswith "asr" + | where TimeGenerated > ago(30d) + | summarize UserCount = dcount(AccountName) by ActionType; + // Get max values for normalization + let maxTriggerCount = toscalar(ruleTriggers | summarize max(TriggerCount)); + let maxDeviceCount = toscalar(deviceCounts | summarize max(DeviceCount)); + let maxUserCount = toscalar(userCounts | summarize max(UserCount)); + let ruleCount = toscalar(ruleTriggers | summarize count()); + // Join the data and calculate normalized impact scores + ruleTriggers + | join kind=leftouter deviceCounts on ActionType + | join kind=leftouter userCounts on ActionType + // Normalize each metric to a 0-1 scale + | extend NormalizedTriggers = 1.0 * TriggerCount / maxTriggerCount + | extend NormalizedDevices = 1.0 * DeviceCount / maxDeviceCount + | extend NormalizedUsers = 1.0 * UserCount / maxUserCount + // Apply weights to each factor (can be adjusted based on organizational priorities) + | extend NormalizedImpactScore = (0.4 * NormalizedTriggers) + (0.3 * NormalizedDevices) + (0.3 * NormalizedUsers) + | sort by NormalizedImpactScore asc + | extend RowNum = row_number() + | extend ImplementationPhase = case( + RowNum <= (ruleCount / 3), "Phase 1 (Low Impact)", + RowNum <= (ruleCount * 2 / 3), "Phase 2 (Medium Impact)", + "Phase 3 (High Impact)" + ) + | project RuleName, ActionType, TriggerCount, DeviceCount, UserCount, NormalizedImpactScore, ImplementationPhase + | sort by NormalizedImpactScore asc + + - Name: ASRPlatformCompatibility + DisplayName: ASR Rule Platform Compatibility Analysis + DescriptionForModel: |- + Executes a KQL query against DeviceEvents to analyze ASR rule compatibility with different Windows platforms. This skill: + 1. **Maps Platform Compatibility**: Shows which ASR rules are supported on which platforms + 2. **Identifies Platform-Specific Issues**: Highlights rules that may not be compatible with certain platforms + 3. **Enables Targeted Deployment**: Helps plan ASR rule enforcement based on platform distribution + This skill is valuable for organizations with mixed Windows environments to understand compatibility constraints. + Description: |- + Analyzes ASR rule compatibility with different Windows platforms. Key insights include: + + - **Platform Compatibility Matrix**: + • Shows which ASR rules are supported on which platforms + • Identifies platform-specific compatibility issues + + - **Deployment Planning**: + • Helps create platform-specific ASR policies + • Prevents attempted enforcement of unsupported rules + + Results enable security teams to plan appropriate ASR policies for their specific Windows environment mix. + Settings: + Target: Defender + Template: |- + let actionTypeToRuleName = datatable(ActionTypePattern:string, RuleName:string) [ + // Standard rule names + "AsrRuleBlockOfficeChildProcessCreation", "Block all Office applications from creating child processes", + "AsrRuleBlockOfficeExeContentExecution", "Block Office applications from creating executable content", + "AsrRuleBlockOfficeMacroChildProcesses", "Block Office applications from injecting code into other processes", + "AsrRuleBlockPersistenceThroughWmiEventSubscription", "Block persistence through WMI event subscription", + "AsrRuleBlockAdobeReaderChildProcess", "Block Adobe Reader from creating child processes", + "AsrRuleBlockObfuscatedScripts", "Block execution of potentially obfuscated scripts", + "AsrRuleBlockOfficeCommsChildProcess", "Block Office communication application from creating child processes", + "AsrRuleBlockWin32ApiCallsFromOfficeMacros", "Block Win32 API calls from Office macros", + "AsrRuleBlockCredentialStealingFromWindowsLocalSecurityAuthoritySubsystem", "Block credential stealing from the Windows local security authority subsystem (lsass.exe)", + "AsrRuleBlockUntrustedUnsignedProcessThatRunFromUsb", "Block untrusted and unsigned processes that run from USB", + "AsrRuleBlockExecutableContentFromEmailClientAndWebmail", "Block executable content from email client and webmail", + "AsrRuleBlockExecutableFilesRunningUnlessMeetCriteria", "Block executable files from running unless they meet a prevalence, age, or trusted list criterion", + "AsrRuleBlockJavaScriptOrVBScriptFromLaunchingDownloadedExecutableContent", "Block JavaScript or VBScript from launching downloaded executable content", + "AsrRuleBlockExecutionOfPotentiallyObfuscatedScripts", "Block execution of potentially obfuscated scripts", + "AsrRuleUseAdvancedProtectionAgainstRansomware", "Use advanced protection against ransomware", + "AsrRuleBlockProcessCreationsFromPsexecAndWmiCommands", "Block process creations originating from PSExec and WMI commands", + "AsrRuleBlockRebootingMachineInSafeMode", "Block rebooting machine in Safe Mode (preview)", + "AsrRuleBlockAbuseOfExploitedVulnerableSignedDrivers", "Block abuse of exploited vulnerable signed drivers", + "AsrRuleBlockUseOfCopiedOrImpersonatedSystemTools", "Block use of copied or impersonated system tools (preview)", + "AsrRuleBlockWebshellCreationForServers", "Block Webshell creation for Servers", + // Additional patterns for actual event log formats + "AsrPsexecWmiChildProcessBlocked", "Block process creations originating from PSExec and WMI commands", + "AsrPsexecWmiChildProcessAudited", "Block process creations originating from PSExec and WMI commands", + "AsrLsassCredentialTheftBlocked", "Block credential stealing from the Windows local security authority subsystem (lsass.exe)", + "AsrLsassCredentialTheftAudited", "Block credential stealing from the Windows local security authority subsystem (lsass.exe)", + "AsrOfficeChildProcessBlocked", "Block all Office applications from creating child processes", + "AsrOfficeChildProcessAudited", "Block all Office applications from creating child processes", + "AsrOfficeProcessInjectionBlocked", "Block Office applications from injecting code into other processes", + "AsrOfficeProcessInjectionAudited", "Block Office applications from injecting code into other processes", + "AsrOfficeMacroWin32ApiCallsBlocked", "Block Win32 API calls from Office macros", + "AsrOfficeMacroWin32ApiCallsAudited", "Block Win32 API calls from Office macros", + "AsrScriptObfuscatedMshtmlContentBlocked", "Block execution of potentially obfuscated scripts", + "AsrScriptObfuscatedMshtmlContentAudited", "Block execution of potentially obfuscated scripts", + "AsrOfficeExecutableContentCreationBlocked", "Block Office applications from creating executable content", + "AsrOfficeExecutableContentCreationAudited", "Block Office applications from creating executable content", + "AsrPersistenceThroughWmiEventSubscriptionAudited", "Block persistence through WMI event subscription", + "AsrPersistenceThroughWmiEventSubscriptionBlocked", "Block persistence through WMI event subscription", + "AsrOfficeCommunicationChildProcessBlocked", "Block Office communication application from creating child processes", + "AsrOfficeCommunicationChildProcessAudited", "Block Office communication application from creating child processes", + "AsrAdobeReaderChildProcessBlocked", "Block Adobe Reader from creating child processes", + "AsrAdobeReaderChildProcessAudited", "Block Adobe Reader from creating child processes", + "AsrRansomwareBlocked", "Use advanced protection against ransomware", + "AsrRansomwareAudited", "Use advanced protection against ransomware", + "AsrUntrustedExecutableBlocked", "Block untrusted and unsigned processes that run from USB", + "AsrUntrustedExecutableAudited", "Block untrusted and unsigned processes that run from USB", + "AsrJsVbsLaunchExecutableContentBlocked", "Block JavaScript or VBScript from launching downloaded executable content", + "AsrJsVbsLaunchExecutableContentAudited", "Block JavaScript or VBScript from launching downloaded executable content", + "AsrEmailContentExecutionBlocked", "Block executable content from email client and webmail", + "AsrEmailContentExecutionAudited", "Block executable content from email client and webmail", + "AsrExecutableFilesDroppedFromEmailBlocked", "Block executable files from running unless they meet a prevalence, age, or trusted list criterion", + "AsrExecutableFilesDroppedFromEmailAudited", "Block executable files from running unless they meet a prevalence, age, or trusted list criterion", + "AsrVulnerableSignedDriverBlocked", "Block abuse of exploited vulnerable signed drivers", + "AsrVulnerableSignedDriverAudited", "Block abuse of exploited vulnerable signed drivers", + "AsrImitationSystemToolsBlocked", "Block use of copied or impersonated system tools (preview)", + "AsrImitationSystemToolsAudited", "Block use of copied or impersonated system tools (preview)", + "AsrWebShellCreationBlocked", "Block Webshell creation for Servers", + "AsrWebShellCreationAudited", "Block Webshell creation for Servers", + "AsrSafeModeRebootBlocked", "Block rebooting machine in Safe Mode (preview)", + "AsrSafeModeRebootAudited", "Block rebooting machine in Safe Mode (preview)" + ]; + // Create the platform compatibility reference table + let rulePlatformCompatibility = datatable(RuleName:string, Win10_11:bool, ServerPost1803:bool, Server2016:bool) [ + "Block abuse of exploited vulnerable signed drivers", true, true, true, + "Block Adobe Reader from creating child processes", true, true, true, + "Block all Office applications from creating child processes", true, true, true, + "Block credential stealing from the Windows local security authority subsystem (lsass.exe)", true, true, true, + "Block executable content from email client and webmail", true, true, true, + "Block executable files from running unless they meet a prevalence, age, or trusted list criterion", true, true, true, + "Block execution of potentially obfuscated scripts", true, true, true, + "Block JavaScript or VBScript from launching downloaded executable content", true, true, false, + "Block Office applications from creating executable content", true, true, true, + "Block Office applications from injecting code into other processes", true, true, true, + "Block Office communication application from creating child processes", true, true, true, + "Block persistence through WMI event subscription", true, true, false, + "Block process creations originating from PSExec and WMI commands", true, true, true, + "Block rebooting machine in Safe Mode (preview)", true, true, true, + "Block untrusted and unsigned processes that run from USB", true, true, true, + "Block use of copied or impersonated system tools (preview)", true, true, true, + "Block Webshell creation for Servers", false, true, true, + "Block Win32 API calls from Office macros", true, false, false, + "Use advanced protection against ransomware", true, true, true + ]; + // Get rule trigger counts with improved pattern matching + let deviceTriggers = DeviceEvents + | where tolower(ActionType) startswith "asr" + | where TimeGenerated > ago(30d) + | extend MatchedPattern = ActionType + | join kind=leftouter actionTypeToRuleName on $left.ActionType == $right.ActionTypePattern + | extend RuleName = iif(isnotempty(RuleName), RuleName, strcat("Unknown Rule: ", ActionType)) + | summarize TriggerCount = count() by RuleName, MatchedPattern; + // Join rule triggers with platform compatibility information + deviceTriggers + | join kind=leftouter rulePlatformCompatibility on RuleName + | project + RuleName, + MatchedPattern, + TriggerCount, + Windows10_11_Compatible = iff(isnotempty(Win10_11) and Win10_11, "Supported", "Not Supported"), + ServerPost1803_Compatible = iff(isnotempty(ServerPost1803) and ServerPost1803, "Supported", "Not Supported"), + Server2016_Compatible = iff(isnotempty(Server2016) and Server2016, "Supported", "Not Supported") + | order by RuleName asc + + - Name: ASRImpactByDevice + DisplayName: ASR Rule Impact Analysis by Device + DescriptionForModel: |- + Executes a KQL query against DeviceEvents to analyze ASR rule impact by device. This skill: + 1. **Identifies Affected Devices**: Counts ASR rule triggers per device + 2. **Highlights High-Impact Endpoints**: Finds devices triggering ASR rules frequently + 3. **Enables Targeted Testing**: Helps security teams isolate test groups before full ASR enforcement + This skill is useful for prioritizing devices that need evaluation before ASR rules are enforced. + Description: |- + Analyzes ASR rule triggers per device to determine operational impact. Key insights include: + + - **Device-Level ASR Analysis**: + • Number of ASR rule triggers per device + • Most frequently triggered ASR rules + + - **Risk & Readiness Considerations**: + • Identifies endpoints at risk of breaking functionality + • Helps security teams plan phased ASR rule enforcement + + Results allow teams to pinpoint which devices should be monitored closely when enabling ASR rules. + Settings: + Target: Defender + Template: |- + DeviceEvents + | where tolower(ActionType) startswith "asr" + | where TimeGenerated > ago(30d) + | extend RuleName = case( + ActionType == "AsrRuleBlockOfficeChildProcessCreation", "Block all Office applications from creating child processes", + ActionType == "AsrRuleBlockOfficeExeContentExecution", "Block Office applications from creating executable content", + ActionType == "AsrRuleBlockOfficeMacroChildProcesses", "Block Office applications from injecting code into other processes", + ActionType == "AsrRuleBlockPersistenceThroughWmiEventSubscription", "Block persistence through WMI event subscription", + ActionType == "AsrRuleBlockAdobeReaderChildProcess", "Block Adobe Reader from creating child processes", + ActionType == "AsrRuleBlockObfuscatedScripts", "Block execution of potentially obfuscated scripts", + ActionType == "AsrRuleBlockOfficeCommsChildProcess", "Block Office communication application from creating child processes", + ActionType == "AsrRuleBlockWin32ApiCallsFromOfficeMacros", "Block Win32 API calls from Office macros", + ActionType == "AsrRuleBlockCredentialStealingFromWindowsLocalSecurityAuthoritySubsystem", "Block credential stealing from the Windows local security authority subsystem (lsass.exe)", + ActionType == "AsrRuleBlockUntrustedUnsignedProcessThatRunFromUsb", "Block untrusted and unsigned processes that run from USB", + ActionType == "AsrRuleBlockExecutableContentFromEmailClientAndWebmail", "Block executable content from email client and webmail", + ActionType == "AsrRuleBlockExecutableFilesRunningUnlessMeetCriteria", "Block executable files from running unless they meet a prevalence, age, or trusted list criterion", + ActionType == "AsrRuleBlockJavaScriptOrVBScriptFromLaunchingDownloadedExecutableContent", "Block JavaScript or VBScript from launching downloaded executable content", + ActionType == "AsrRuleBlockExecutionOfPotentiallyObfuscatedScripts", "Block execution of potentially obfuscated scripts", + ActionType == "AsrRuleUseAdvancedProtectionAgainstRansomware", "Use advanced protection against ransomware", + ActionType == "AsrRuleBlockProcessCreationsFromPsexecAndWmiCommands", "Block process creations originating from PSExec and WMI commands", + ActionType == "AsrRuleBlockRebootingMachineInSafeMode", "Block rebooting machine in Safe Mode (preview)", + ActionType == "AsrRuleBlockAbuseOfExploitedVulnerableSignedDrivers", "Block abuse of exploited vulnerable signed drivers", + ActionType == "AsrRuleBlockUseOfCopiedOrImpersonatedSystemTools", "Block use of copied or impersonated system tools (preview)", + ActionType == "AsrRuleBlockWebshellCreationForServers", "Block Webshell creation for Servers", + ActionType + ) + | summarize TriggerCount = count() by DeviceName, RuleName, ActionType + | order by TriggerCount desc + + + - Name: ASRRuleTrends + DisplayName: ASR Rule Trends Over Time + DescriptionForModel: |- + Executes a KQL query against DeviceEvents to track ASR rule triggers over time. This skill: + 1. **Monitors ASR Rule Trends**: Aggregates ASR rule triggers per day + 2. **Identifies Changes in Attack Patterns**: Helps detect increasing or decreasing ASR activity + 3. **Supports Security Policy Decisions**: Provides data to guide ASR rule enforcement strategies + This skill helps security teams analyze ASR rule activity over time to fine-tune security policies. + Description: |- + Tracks how ASR rules are triggered over time. Evaluates: + + - **Trend Analysis**: + • Daily ASR rule trigger counts + • Breakdown by ASR rule type + + - **Security Policy Insights**: + • Helps assess whether ASR rules are becoming more or less impactful + • Supports data-driven enforcement decisions + + Results enable security teams to monitor and adjust ASR rule implementations based on observed trends. + Settings: + Target: Defender + Template: |- + DeviceEvents + | where tolower(ActionType) startswith "asr" + | extend RuleName = case( + ActionType == "AsrRuleBlockOfficeChildProcessCreation", "Block all Office applications from creating child processes", + ActionType == "AsrRuleBlockOfficeExeContentExecution", "Block Office applications from creating executable content", + ActionType == "AsrRuleBlockOfficeMacroChildProcesses", "Block Office applications from injecting code into other processes", + ActionType == "AsrRuleBlockPersistenceThroughWmiEventSubscription", "Block persistence through WMI event subscription", + ActionType == "AsrRuleBlockAdobeReaderChildProcess", "Block Adobe Reader from creating child processes", + ActionType == "AsrRuleBlockObfuscatedScripts", "Block execution of potentially obfuscated scripts", + ActionType == "AsrRuleBlockOfficeCommsChildProcess", "Block Office communication application from creating child processes", + ActionType == "AsrRuleBlockWin32ApiCallsFromOfficeMacros", "Block Win32 API calls from Office macros", + ActionType == "AsrRuleBlockCredentialStealingFromWindowsLocalSecurityAuthoritySubsystem", "Block credential stealing from the Windows local security authority subsystem (lsass.exe)", + ActionType == "AsrRuleBlockUntrustedUnsignedProcessThatRunFromUsb", "Block untrusted and unsigned processes that run from USB", + ActionType == "AsrRuleBlockExecutableContentFromEmailClientAndWebmail", "Block executable content from email client and webmail", + ActionType == "AsrRuleBlockExecutableFilesRunningUnlessMeetCriteria", "Block executable files from running unless they meet a prevalence, age, or trusted list criterion", + ActionType == "AsrRuleBlockJavaScriptOrVBScriptFromLaunchingDownloadedExecutableContent", "Block JavaScript or VBScript from launching downloaded executable content", + ActionType == "AsrRuleBlockExecutionOfPotentiallyObfuscatedScripts", "Block execution of potentially obfuscated scripts", + ActionType == "AsrRuleUseAdvancedProtectionAgainstRansomware", "Use advanced protection against ransomware", + ActionType == "AsrRuleBlockProcessCreationsFromPsexecAndWmiCommands", "Block process creations originating from PSExec and WMI commands", + ActionType == "AsrRuleBlockRebootingMachineInSafeMode", "Block rebooting machine in Safe Mode (preview)", + ActionType == "AsrRuleBlockAbuseOfExploitedVulnerableSignedDrivers", "Block abuse of exploited vulnerable signed drivers", + ActionType == "AsrRuleBlockUseOfCopiedOrImpersonatedSystemTools", "Block use of copied or impersonated system tools (preview)", + ActionType == "AsrRuleBlockWebshellCreationForServers", "Block Webshell creation for Servers", + ActionType + ) + | summarize TriggerCount = count() by bin(TimeGenerated, 1d), RuleName + | order by TimeGenerated desc + + - Name: ASRRuleApplicationMapping + DisplayName: ASR Rule to Application Mapping + DescriptionForModel: |- + Executes a KQL query against DeviceEvents to map ASR rules to specific applications. This skill: + 1. **Maps Rules to Applications**: Identifies which applications trigger specific ASR rules + 2. **Assesses Business Impact**: Helps identify critical business applications affected by ASR rules + 3. **Supports Exclusion Planning**: Provides data for creating targeted exclusions + This skill is essential for understanding which business applications might be affected when enforcing ASR rules. + Description: |- + Maps ASR rules to specific applications to understand business impact. Key insights include: + + - **Application Impact Analysis**: + • Which applications trigger specific ASR rules + • Frequency of triggers per application + + - **Business Continuity Planning**: + • Identifies critical applications that may require exclusions + • Helps prioritize testing before enforcement + + Results allow security teams to develop targeted exclusion strategies for business-critical applications. + Settings: + Target: Defender + Template: |- + DeviceEvents + | where tolower(ActionType) startswith "asr" + | where TimeGenerated > ago(30d) + | extend RuleName = case( + ActionType == "AsrRuleBlockOfficeChildProcessCreation", "Block all Office applications from creating child processes", + ActionType == "AsrRuleBlockOfficeExeContentExecution", "Block Office applications from creating executable content", + ActionType == "AsrRuleBlockOfficeMacroChildProcesses", "Block Office applications from injecting code into other processes", + ActionType == "AsrRuleBlockPersistenceThroughWmiEventSubscription", "Block persistence through WMI event subscription", + ActionType == "AsrRuleBlockAdobeReaderChildProcess", "Block Adobe Reader from creating child processes", + ActionType == "AsrRuleBlockObfuscatedScripts", "Block execution of potentially obfuscated scripts", + ActionType == "AsrRuleBlockOfficeCommsChildProcess", "Block Office communication application from creating child processes", + ActionType == "AsrRuleBlockWin32ApiCallsFromOfficeMacros", "Block Win32 API calls from Office macros", + ActionType == "AsrRuleBlockCredentialStealingFromWindowsLocalSecurityAuthoritySubsystem", "Block credential stealing from the Windows local security authority subsystem (lsass.exe)", + ActionType == "AsrRuleBlockUntrustedUnsignedProcessThatRunFromUsb", "Block untrusted and unsigned processes that run from USB", + ActionType == "AsrRuleBlockExecutableContentFromEmailClientAndWebmail", "Block executable content from email client and webmail", + ActionType == "AsrRuleBlockExecutableFilesRunningUnlessMeetCriteria", "Block executable files from running unless they meet a prevalence, age, or trusted list criterion", + ActionType == "AsrRuleBlockJavaScriptOrVBScriptFromLaunchingDownloadedExecutableContent", "Block JavaScript or VBScript from launching downloaded executable content", + ActionType == "AsrRuleBlockExecutionOfPotentiallyObfuscatedScripts", "Block execution of potentially obfuscated scripts", + ActionType == "AsrRuleUseAdvancedProtectionAgainstRansomware", "Use advanced protection against ransomware", + ActionType == "AsrRuleBlockProcessCreationsFromPsexecAndWmiCommands", "Block process creations originating from PSExec and WMI commands", + ActionType == "AsrRuleBlockRebootingMachineInSafeMode", "Block rebooting machine in Safe Mode (preview)", + ActionType == "AsrRuleBlockAbuseOfExploitedVulnerableSignedDrivers", "Block abuse of exploited vulnerable signed drivers", + ActionType == "AsrRuleBlockUseOfCopiedOrImpersonatedSystemTools", "Block use of copied or impersonated system tools (preview)", + ActionType == "AsrRuleBlockWebshellCreationForServers", "Block Webshell creation for Servers", + ActionType + ) + | summarize TriggerCount = count() by RuleName, InitiatingProcessFileName, InitiatingProcessFolderPath + | order by RuleName, TriggerCount desc + + - Name: ASRUserImpactAnalysis + DisplayName: ASR Rule Impact Analysis by User + DescriptionForModel: |- + Executes a KQL query against DeviceEvents to analyze ASR rule impact by user. This skill: + 1. **Identifies Affected Users**: Counts ASR rule triggers per user account + 2. **Highlights High-Impact Users/Departments**: Finds users triggering ASR rules frequently + 3. **Enables User-Centric Planning**: Helps security teams plan user communication and training + This skill is useful for identifying which users or departments will be most affected by ASR enforcement. + Description: |- + Analyzes ASR rule triggers by user to determine operational impact. Key insights include: + + - **User-Level ASR Analysis**: + • Number of ASR rule triggers per user + • User distribution across rules + + - **Organizational Impact Considerations**: + • Identifies departments at risk of workflow disruption + • Helps plan targeted user communication before enforcement + + Results allow teams to identify key stakeholders who should be involved in ASR testing and rollout. + Settings: + Target: Defender + Template: |- + DeviceEvents + | where tolower(ActionType) startswith "asr" + | where TimeGenerated > ago(30d) + | extend RuleName = case( + ActionType == "AsrRuleBlockOfficeChildProcessCreation", "Block all Office applications from creating child processes", + ActionType == "AsrRuleBlockOfficeExeContentExecution", "Block Office applications from creating executable content", + ActionType == "AsrRuleBlockOfficeMacroChildProcesses", "Block Office applications from injecting code into other processes", + ActionType == "AsrRuleBlockPersistenceThroughWmiEventSubscription", "Block persistence through WMI event subscription", + ActionType == "AsrRuleBlockAdobeReaderChildProcess", "Block Adobe Reader from creating child processes", + ActionType == "AsrRuleBlockObfuscatedScripts", "Block execution of potentially obfuscated scripts", + ActionType == "AsrRuleBlockOfficeCommsChildProcess", "Block Office communication application from creating child processes", + ActionType == "AsrRuleBlockWin32ApiCallsFromOfficeMacros", "Block Win32 API calls from Office macros", + ActionType == "AsrRuleBlockCredentialStealingFromWindowsLocalSecurityAuthoritySubsystem", "Block credential stealing from the Windows local security authority subsystem (lsass.exe)", + ActionType == "AsrRuleBlockUntrustedUnsignedProcessThatRunFromUsb", "Block untrusted and unsigned processes that run from USB", + ActionType == "AsrRuleBlockExecutableContentFromEmailClientAndWebmail", "Block executable content from email client and webmail", + ActionType == "AsrRuleBlockExecutableFilesRunningUnlessMeetCriteria", "Block executable files from running unless they meet a prevalence, age, or trusted list criterion", + ActionType == "AsrRuleBlockJavaScriptOrVBScriptFromLaunchingDownloadedExecutableContent", "Block JavaScript or VBScript from launching downloaded executable content", + ActionType == "AsrRuleBlockExecutionOfPotentiallyObfuscatedScripts", "Block execution of potentially obfuscated scripts", + ActionType == "AsrRuleUseAdvancedProtectionAgainstRansomware", "Use advanced protection against ransomware", + ActionType == "AsrRuleBlockProcessCreationsFromPsexecAndWmiCommands", "Block process creations originating from PSExec and WMI commands", + ActionType == "AsrRuleBlockRebootingMachineInSafeMode", "Block rebooting machine in Safe Mode (preview)", + ActionType == "AsrRuleBlockAbuseOfExploitedVulnerableSignedDrivers", "Block abuse of exploited vulnerable signed drivers", + ActionType == "AsrRuleBlockUseOfCopiedOrImpersonatedSystemTools", "Block use of copied or impersonated system tools (preview)", + ActionType == "AsrRuleBlockWebshellCreationForServers", "Block Webshell creation for Servers", + ActionType + ) + | summarize TriggerCount = count() by AccountName, AccountDomain, RuleName + | order by TriggerCount desc + + - Name: ASRExclusionCandidates + DisplayName: ASR Rule Exclusion Candidates + DescriptionForModel: |- + Executes a KQL query against DeviceEvents to identify potential candidates for ASR rule exclusions. This skill: + 1. **Identifies Common Triggers**: Finds files and paths frequently triggering ASR rules + 2. **Formulates Exclusion Paths**: Creates ready-to-use exclusion path formats + 3. **Prioritizes Exclusions**: Ranks exclusion candidates by frequency + This skill helps security teams create targeted exclusions to minimize business disruption when enforcing ASR rules. + Description: |- + Identifies potential candidates for ASR rule exclusions based on trigger frequency. Insights include: + + - **Exclusion Planning**: + • Files and paths frequently triggering ASR rules + • Ready-to-use exclusion path formats + + - **Business Continuity Support**: + • Helps create targeted exclusions for legitimate business applications + • Minimizes disruption when moving to enforcement mode + + Results allow security teams to proactively plan exclusions before enforcing ASR rules. + Settings: + Target: Defender + Template: |- + DeviceEvents + | where tolower(ActionType) startswith "asr" + | where TimeGenerated > ago(30d) + | extend RuleName = case( + ActionType == "AsrRuleBlockOfficeChildProcessCreation", "Block all Office applications from creating child processes", + ActionType == "AsrRuleBlockOfficeExeContentExecution", "Block Office applications from creating executable content", + ActionType == "AsrRuleBlockOfficeMacroChildProcesses", "Block Office applications from injecting code into other processes", + ActionType == "AsrRuleBlockPersistenceThroughWmiEventSubscription", "Block persistence through WMI event subscription", + ActionType == "AsrRuleBlockAdobeReaderChildProcess", "Block Adobe Reader from creating child processes", + ActionType == "AsrRuleBlockObfuscatedScripts", "Block execution of potentially obfuscated scripts", + ActionType == "AsrRuleBlockOfficeCommsChildProcess", "Block Office communication application from creating child processes", + ActionType == "AsrRuleBlockWin32ApiCallsFromOfficeMacros", "Block Win32 API calls from Office macros", + ActionType == "AsrRuleBlockCredentialStealingFromWindowsLocalSecurityAuthoritySubsystem", "Block credential stealing from the Windows local security authority subsystem (lsass.exe)", + ActionType == "AsrRuleBlockUntrustedUnsignedProcessThatRunFromUsb", "Block untrusted and unsigned processes that run from USB", + ActionType == "AsrRuleBlockExecutableContentFromEmailClientAndWebmail", "Block executable content from email client and webmail", + ActionType == "AsrRuleBlockExecutableFilesRunningUnlessMeetCriteria", "Block executable files from running unless they meet a prevalence, age, or trusted list criterion", + ActionType == "AsrRuleBlockJavaScriptOrVBScriptFromLaunchingDownloadedExecutableContent", "Block JavaScript or VBScript from launching downloaded executable content", + ActionType == "AsrRuleBlockExecutionOfPotentiallyObfuscatedScripts", "Block execution of potentially obfuscated scripts", + ActionType == "AsrRuleUseAdvancedProtectionAgainstRansomware", "Use advanced protection against ransomware", + ActionType == "AsrRuleBlockProcessCreationsFromPsexecAndWmiCommands", "Block process creations originating from PSExec and WMI commands", + ActionType == "AsrRuleBlockRebootingMachineInSafeMode", "Block rebooting machine in Safe Mode (preview)", + ActionType == "AsrRuleBlockAbuseOfExploitedVulnerableSignedDrivers", "Block abuse of exploited vulnerable signed drivers", + ActionType == "AsrRuleBlockUseOfCopiedOrImpersonatedSystemTools", "Block use of copied or impersonated system tools (preview)", + ActionType == "AsrRuleBlockWebshellCreationForServers", "Block Webshell creation for Servers", + ActionType + ) + | summarize TriggerCount = count() by RuleName, ActionType, FileName, FolderPath + | where TriggerCount > 10 + | extend ExclusionPath = iff(isnotempty(FolderPath) and isnotempty(FileName), strcat(FolderPath, "\\", FileName), "") + | order by TriggerCount desc + + - Name: ASRProcessAnalysis + DisplayName: ASR Rule Process Trigger Analysis + DescriptionForModel: |- + Executes a KQL query against DeviceEvents to analyze which processes are triggering ASR rules. This skill: + 1. **Identifies Specific Processes**: Examines initiating process details for each ASR rule + 2. **Quantifies Process Impact**: Shows how many devices and users are affected by each process + 3. **Tracks Timing Patterns**: Monitors when processes first began triggering rules and their recurrence + This skill helps security teams determine if ASR triggers are from legitimate or suspicious processes. + Description: |- + Analyzes which specific processes are triggering ASR rules to determine if exclusions are needed. Key insights include: + + - **Process-Level Analysis**: + • Detailed information on process paths, file names, and command lines + • Frequency analysis of process triggers per ASR rule + + - **Context for Decision-Making**: + • Timeline data showing first and last occurrences + • Device and user scope for each process trigger pattern + + Results help security teams distinguish between legitimate software needing exclusions and potentially malicious activity. + Settings: + Target: Defender + Template: |- + DeviceEvents + | where tolower(ActionType) startswith "asr" + | where TimeGenerated > ago(30d) + | extend RuleName = case( + ActionType == "AsrRuleBlockOfficeChildProcessCreation", "Block all Office applications from creating child processes", + ActionType == "AsrRuleBlockOfficeExeContentExecution", "Block Office applications from creating executable content", + ActionType == "AsrRuleBlockOfficeMacroChildProcesses", "Block Office applications from injecting code into other processes", + ActionType == "AsrRuleBlockPersistenceThroughWmiEventSubscription", "Block persistence through WMI event subscription", + ActionType == "AsrRuleBlockAdobeReaderChildProcess", "Block Adobe Reader from creating child processes", + ActionType == "AsrRuleBlockObfuscatedScripts", "Block execution of potentially obfuscated scripts", + ActionType == "AsrRuleBlockOfficeCommsChildProcess", "Block Office communication application from creating child processes", + ActionType == "AsrRuleBlockWin32ApiCallsFromOfficeMacros", "Block Win32 API calls from Office macros", + ActionType == "AsrRuleBlockCredentialStealingFromWindowsLocalSecurityAuthoritySubsystem", "Block credential stealing from the Windows local security authority subsystem (lsass.exe)", + ActionType == "AsrRuleBlockUntrustedUnsignedProcessThatRunFromUsb", "Block untrusted and unsigned processes that run from USB", + ActionType == "AsrRuleBlockExecutableContentFromEmailClientAndWebmail", "Block executable content from email client and webmail", + ActionType == "AsrRuleBlockExecutableFilesRunningUnlessMeetCriteria", "Block executable files from running unless they meet a prevalence, age, or trusted list criterion", + ActionType == "AsrRuleBlockJavaScriptOrVBScriptFromLaunchingDownloadedExecutableContent", "Block JavaScript or VBScript from launching downloaded executable content", + ActionType == "AsrRuleBlockExecutionOfPotentiallyObfuscatedScripts", "Block execution of potentially obfuscated scripts", + ActionType == "AsrRuleUseAdvancedProtectionAgainstRansomware", "Use advanced protection against ransomware", + ActionType == "AsrRuleBlockProcessCreationsFromPsexecAndWmiCommands", "Block process creations originating from PSExec and WMI commands", + ActionType == "AsrRuleBlockRebootingMachineInSafeMode", "Block rebooting machine in Safe Mode (preview)", + ActionType == "AsrRuleBlockAbuseOfExploitedVulnerableSignedDrivers", "Block abuse of exploited vulnerable signed drivers", + ActionType == "AsrRuleBlockUseOfCopiedOrImpersonatedSystemTools", "Block use of copied or impersonated system tools (preview)", + ActionType == "AsrRuleBlockWebshellCreationForServers", "Block Webshell creation for Servers", + ActionType + ) + | summarize + TriggerCount = count(), + FirstSeen = min(TimeGenerated), + LastSeen = max(TimeGenerated), + DeviceCount = dcount(DeviceName), + UserCount = dcount(AccountName), + DeviceList = make_set(DeviceName, 5), + UserList = make_set(AccountName, 5) + by RuleName, ActionType, InitiatingProcessFileName, InitiatingProcessFolderPath, InitiatingProcessCommandLine + | order by RuleName asc, TriggerCount desc \ No newline at end of file diff --git a/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/EMAILENRICHMENT.md b/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/EMAILENRICHMENT.md index 0dd56a9b..06f42cfe 100644 --- a/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/EMAILENRICHMENT.md +++ b/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/EMAILENRICHMENT.md @@ -18,3 +18,14 @@ Example Prompt: Find all emails sent to user@example.com in the past 7 days. Search for emails in the `EmailEvents` table sent from a specific sender. Provides key details, including the time, sender, recipient, subject, body, and attachment count, to assist in analyzing sender activity. Example Prompt: Retrieve all emails sent from admin@example.com in the past week. + +### NetworkMessageIdEmailSearch +Search for emails in the `EmailEvents` table by a specified `NetworkMessageId`. Provides key email details, including time, sender, recipient, and subject. This skill helps security analysts pinpoint specific email messages in investigations, enabling focused threat analysis and remediation. + +Example Prompt: Retrieve email details for NetworkMessageId "1d6ce5463-10b7-4c1e-e0a9-08dd529394bc". + +### QuarantineRequestInsights +Analyze email quarantine requests and their approvals to understand user actions and email security patterns. Retrieves key details for thorough investigation. + +Example Prompt: Analyze quarantine requests and approvals for email security insights. + diff --git a/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/EmailEnrichment.yaml b/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/EmailEnrichment.yaml index d2c51b2c..a71379e6 100644 --- a/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/EmailEnrichment.yaml +++ b/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/EmailEnrichment.yaml @@ -99,3 +99,62 @@ SkillGroups: | where SenderFromAddress == "{{SenderAddress}}" | where TimeGenerated >= ago(7d) | project TimeGenerated, SenderFromAddress, RecipientEmailAddress, Subject, NetworkMessageId, AttachmentCount + + - Name: NetworkMessageIdEmailSearch + DisplayName: Search Emails by NetworkMessageId + DescriptionForModel: |- + Performs a KQL query on the `EmailEvents` table to locate emails by their `NetworkMessageId`. Returns detailed information for each email, including: + - `TimeGenerated`: Timestamp of when the email was sent or received. + - `Sender`: Email address of the sender. + - `Recipient`: Email address of the recipient. + - `Subject`: Subject line of the email. + - `AttachmentCount`: Number of attachments in the email. + This skill helps security analysts pinpoint specific email messages in investigations, enabling focused threat analysis and remediation. + Description: Search for emails in the `EmailEvents` table by a specified `NetworkMessageId`. Provides key email details, including time, sender, recipient, and subject. + Inputs: + - Name: NetworkMessageId + Description: The unique `NetworkMessageId` to search for in the `EmailEvents` table. + Required: true + Settings: + Target: Defender + Template: |- + EmailEvents + | where NetworkMessageId == "{{NetworkMessageId}}" + | join kind=leftouter ( + EmailAttachmentInfo + | project NetworkMessageId, FileName + ) on NetworkMessageId + | project-rename AttachmentName=FileName + | project TimeGenerated, SenderFromAddress, RecipientEmailAddress, Subject, NetworkMessageId, AttachmentName + + - Name: QuarantineRequestInsights + DisplayName: Quarantine Request Insights + DescriptionForModel: |- + Performs a KQL query to correlate quarantine request actions with their approvals. Provides details such as request and action times, email sender information, and user activity to facilitate email security investigations. + Description: Analyze email quarantine requests and their approvals to understand user actions and email security patterns. Retrieves key details for thorough investigation. + Settings: + Target: Defender + Template: |- + let DeniedLookupTime = ago(30d); + let RequestedLookupTime = ago(30d); + let EmailEventLookupTime = ago(30d); + CloudAppEvents + | where Timestamp >= DeniedLookupTime + | where ActionType == "QuarantineDenyReleaseMessage" + | extend NetworkMessageId = tostring(parse_json(RawEventData)["NetworkMessageId"]) + | project-rename DeniedTime=Timestamp + | join (CloudAppEvents + | where Timestamp >= RequestedLookupTime + | where ActionType == "QuarantineRequestReleaseMessage" + | extend NetworkMessageId = tostring(parse_json(RawEventData)["NetworkMessageId"]) + | extend RecipientEmailAddress = tostring(parse_json(RawEventData)["UserId"]) + | project-rename RequestTime=Timestamp) on NetworkMessageId + | project AccountDisplayName, NetworkMessageId, RequestTime, RecipientEmailAddress, DeniedTime + | join EmailEvents on NetworkMessageId + | where Timestamp >= EmailEventLookupTime + | where RecipientEmailAddress == RecipientEmailAddress1 + | project-rename EmailTime=Timestamp + | project-rename RequestedBy=RecipientEmailAddress + | project-rename DeniedBy=AccountDisplayName + | project EmailTime, SenderFromAddress, SenderDisplayName, Subject, RequestedBy, RecipientEmailAddress1, DeniedBy, DeniedTime, RequestTime,NetworkMessageId,NetworkMessageId1 + | sort by DeniedTime \ No newline at end of file diff --git a/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/IPEnrichment.md b/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/IPEnrichment.md new file mode 100644 index 00000000..e198c79a --- /dev/null +++ b/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/IPEnrichment.md @@ -0,0 +1,11 @@ +# Enrichment Plugin Suite +**Author: Craig Freyman** + +## IPEnrichment Plugin +This suite of IP enrichment skills is designed to enhance security investigations by providing insights into IP-related activity. These tools help identify suspicious login attempts, detect brute force attacks, and analyze login anomalies associated with specific IP addresses. + +### IdentifyFailedLoginIPs +Identify IP addresses with repeated failed login attempts across multiple accounts. Highlights IPs with suspicious activity patterns, such as brute force attacks or password spray attempts, and provides key metrics like the number of failed attempts and distinct accounts targeted. + +Example Prompt: Identify IPs with more than 10 failed login attempts in the past 24 hours. + diff --git a/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/IPEnrichment.yaml b/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/IPEnrichment.yaml new file mode 100644 index 00000000..8ef11001 --- /dev/null +++ b/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/IPEnrichment.yaml @@ -0,0 +1,36 @@ +Descriptor: + Name: IP Enrichment Skills + DisplayName: IP Enrichment Skills + DescriptionForModel: | + A set of skills designed to analyze and enrich IP-related data to identify suspicious activities, such as failed login attempts, brute force attacks, and password spray attempts. These skills help in detecting and responding to potential threats originating from specific IP addresses. + Description: | + IP Enrichment Skills provide tools to analyze IP activity, focusing on identifying potential malicious behavior such as repeated failed login attempts across multiple accounts. This is useful for detecting brute force attacks, password spray attempts, or logins targeting disabled accounts. It enhances investigation efficiency by pinpointing problematic IP addresses. + SupportedAuthTypes: + - None + +SkillGroups: + - Format: KQL + Skills: + - Name: IdentifyFailedLoginIPs + DisplayName: Identify IPs with Suspicious Failed Login Activity + DescriptionForModel: |- + This skill identifies IP addresses with repeated failed login attempts across multiple accounts. It highlights IPs with suspicious activity patterns, such as brute force or password spray attacks, and provides key metrics like the number of failed attempts and distinct accounts targeted. + Description: |- + Use this query to identify IP addresses exhibiting suspicious behavior, such as multiple failed logins across different accounts. The skill helps detect brute force attacks, password spray attempts, and other login anomalies, making it easier to respond to potential security threats. + Settings: + Target: Defender + Template: |- + let FailedLoginThreshold = 10; // Define the threshold for failed logins + let TimeRange = 1d; // Specify the time range for analysis + // Find failed logins + SigninLogs + | where TimeGenerated >= ago(TimeRange) + // Focus on failed login activities + | where ResultType in ("50074", "50053", "50126", "120014", "500571") // Adjust these ResultType codes for failed logins + | extend FailureReason = Status.failureReason, UserPrincipalName, IPAddress, TimeGenerated + // Group failed login attempts by IP and user + | summarize FailedAttempts = count(), DistinctAccounts = dcount(UserPrincipalName), FailedUsers = make_set(UserPrincipalName) by IPAddress, bin(TimeGenerated, 1h) + // Filter for significant activity + | where FailedAttempts >= FailedLoginThreshold + | project TimeGenerated, IPAddress, FailedAttempts, DistinctAccounts, FailedUsers + | order by FailedAttempts desc diff --git a/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/IncidentEnrichment.yaml b/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/IncidentEnrichment.yaml index 5b5ca5e6..d5c79564 100644 --- a/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/IncidentEnrichment.yaml +++ b/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/IncidentEnrichment.yaml @@ -13,23 +13,30 @@ Descriptor: - None Settings: + - Name: TenantId + Label: TenantId + Description: Your Azure TenantId + HintText: TenantId + SettingType: String + Required: true + - Name: SubscriptionName Label: SubscriptionName - Description: This is the subscription name that security copilot will use for sentinel. + Description: This is the subscription name that security copilot will use for Sentinel. HintText: yoursubscriptionname SettingType: String Required: true - Name: WorkspaceName Label: WorkspaceName - Description: This is the workspace name that security copilot will use for sentinel. + Description: This is the workspace name that security copilot will use for Sentinel. HintText: yourworkspace SettingType: String Required: true - Name: ResourceGroupName Label: ResourceGroupName - Description: This is the resource group name that security copilot will use for sentinel. + Description: This is the resource group name that security copilot will use for Sentinel. HintText: yourresourcegroup SettingType: String Required: true @@ -56,6 +63,7 @@ SkillGroups: Required: false Settings: Target: Sentinel + TenantId: "{{TenantId}}" SubscriptionName: "{{SubscriptionName}}" ResourceGroupName: "{{ResourceGroupName}}" WorkspaceName: "{{WorkspaceName}}" @@ -95,6 +103,7 @@ SkillGroups: Required: true Settings: Target: Sentinel + TenantId: "{{TenantId}}" SubscriptionName: "{{SubscriptionName}}" ResourceGroupName: "{{ResourceGroupName}}" WorkspaceName: "{{WorkspaceName}}" diff --git a/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/PURVIEWENRICHMENT.md b/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/PURVIEWENRICHMENT.md new file mode 100644 index 00000000..1632d660 --- /dev/null +++ b/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/PURVIEWENRICHMENT.md @@ -0,0 +1,36 @@ +# Enrichment Plugin Suite +**Author: Craig Freyman** + +## PurviewEnrichment Plugin +This suite of Purview enrichment skills is designed to enhance security investigations by querying DLP events and file activities for users and devices. These tools help security analysts correlate data loss prevention (DLP) alerts with other event types, aiding in investigation and response efforts. + +### DLPGetlatestDlpalertperuser +Fetches the latest DLP alerts generated by a specific user. Provides insights into policy violations, sensitive information types, and remediation actions. + +Example Prompt: Retrieve the latest DLP alerts for user "john.doe@example.com". + +### DLPgetAllDlpalerts +Fetches all DLP alerts for analysis across workloads. This allows security teams to identify trends, detect potential threats, and assess policy effectiveness. + +Example Prompt: Retrieve all DLP alerts for the past 30 days. + +### DLPgetalertbyPolicy +Fetches all DLP alerts associated with a specified policy. Helps security analysts evaluate policy impact and fine-tune configurations. + +Example Prompt: Retrieve DLP alerts for policy "Confidential Data Policy". + +### DLPgetFileactivity +Retrieves file activity details related to DLP violations, including labels, sensitive information, and associated user actions. + +Example Prompt: Get file activity for "Confidential_Report.docx". + +### DLPgetEndpointactivity +Retrieves sensitive file activity occurring on a specified device. Useful for identifying unauthorized data access or exfiltration attempts. + +Example Prompt: Get endpoint activity for device "workstation-01". + +### DLPgetSiteCollectionActivity +Fetches SharePoint site collection or library activity related to DLP alerts. Helps monitor sensitive file movements and unauthorized access attempts. + +Example Prompt: Retrieve SharePoint activity for "HR-Confidential" site collection. + diff --git a/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/PurviewEnrichment.yaml b/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/PurviewEnrichment.yaml new file mode 100644 index 00000000..9c67015d --- /dev/null +++ b/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/PurviewEnrichment.yaml @@ -0,0 +1,2164 @@ +Descriptor: + Name: DataSecurityAnalyst + DisplayName: Purview - Triage, investigation, analysis, tuning + Description: Data Security skills to query DLP events and file activities for Users and devices and correlate with other event types + +SkillGroups: + - Format: KQL + Skills: + - Name: DLPGetlatestDlpalertperuser + DisplayName: Get Latest DLP alerts by user + Description: Fetches the latest dlp events, alerts generated by the user + Inputs: + - Name: User + Description: The user associated with the alert + Required: true + Settings: + Target: Defender + Template: |- + let user = '{{User}}'; + CloudAppEvents + | where ActionType has "DLPRuleMatch" or ActionType has "DLPRuleUndo" or RawEventData.PolicyMatchInfo.PolicyId <> "" + | where ActionType !in ('FileRead','FileArchived','FileModified','FileRenamed') + | where parse_json(tostring(parse_json(tostring(parse_json(tostring(RawEventData.PolicyDetails))[0].Rules))[0].Actions))[0] !in ('Encrypt','ExApplyBrandingTemplate') + | where tolower(RawEventData.UserId) has tolower(user) + | extend PolicyName_ = tostring(parse_json(tostring(RawEventData.PolicyDetails))[0].PolicyName) + | extend SensitiveInformationTypeName_ = tostring(parse_json(tostring(parse_json(tostring(parse_json(tostring(parse_json(tostring(RawEventData.PolicyDetails))[0].Rules))[0].ConditionsMatched)).SensitiveInformation))[0].SensitiveInformationTypeName) + | extend FileName_ = tostring(parse_json(tostring(RawEventData.SharePointMetaData)).FileName) + | extend RuleName_ = tostring(parse_json(tostring(parse_json(tostring(RawEventData.PolicyDetails))[0].Rules))[0].RuleName) + | extend Severity_ = tostring(parse_json(tostring(parse_json(tostring(RawEventData.PolicyDetails))[0].Rules))[0].Severity) + | extend Attachments_ = tostring(parse_json(tostring(RawEventData.Item)).Attachments) + | extend Recipients = strcat("To:",RawEventData.ExchangeMetaData.To, " CC:",RawEventData.ExchangeMetaData.CC," BCC:",RawEventData.ExchangeMetaData.BCC) + | extend SharePointMetaData_SiteCollectionUrl_s = tostring(parse_json(tostring(RawEventData.SharePointMetaData)).SiteCollectionUrl) + | extend Documents = iff(isempty(FileName_), Attachments_, FileName_) + | join kind = leftouter + ( + CloudAppEvents + | where RawEventData.Operation == "SecureLinkUsed" or RawEventData.Operation == "AddedToSecureLink" + | extend SourceFileName = tostring(RawEventData.SourceFileName) + | extend SharePointMetaData_SiteCollectionUrl_s = tostring(RawEventData.SiteUrl) + | extend UserId = tostring(RawEventData.UserId) + | extend TargetUserOrGroupName = tostring(RawEventData.TargetUserOrGroupName) + | summarize by SharePointMetaData_SiteCollectionUrl_s,SourceFileName,TargetUserOrGroupName + | summarize TargetUserOrGroupName = make_list(strcat(TargetUserOrGroupName)) by SharePointMetaData_SiteCollectionUrl_s,SourceFileName + | summarize take_any(TargetUserOrGroupName) by SourceFileName,SharePointMetaData_SiteCollectionUrl_s + |project SourceFileName, SharePointMetaData_SiteCollectionUrl_s, TargetUserOrGroupName + ) + on SharePointMetaData_SiteCollectionUrl_s + | extend TargetUsers = iff(isnotempty(tostring(TargetUserOrGroupName)), tostring(TargetUserOrGroupName), Recipients) + | extend Subject = tostring(parse_json(tostring(RawEventData.ExchangeMetaData)).Subject) + | extend PolicyName_EndPoint = tostring(parse_json(tostring(RawEventData.PolicyMatchInfo)).PolicyName) + | extend PolicyName = iff(isempty(PolicyName_), PolicyName_EndPoint, PolicyName_) + | extend Workload = tostring(RawEventData.Workload) + | extend Object = tostring(RawEventData.ObjectId) + | join kind=leftouter + ( + CloudAppEvents + | where RawEventData.Workload has "Endpoint" + | extend SensitiveInfoTypeName_ = tostring(parse_json(tostring(RawEventData.SensitiveInfoTypeData))[0].SensitiveInfoTypeName) + | where SensitiveInfoTypeName_ <> "" + | extend EndPointFile = tostring(RawEventData.ObjectId) + | extend PolicyName = tostring(parse_json(tostring(RawEventData.PolicyMatchInfo)).PolicyName) + | project EndPointFile,SensitiveInfoTypeName_,PolicyName + ) + on $left.Object == $right.EndPointFile + | extend Documents = iff(isempty(Documents), EndPointFile, Documents) + | extend SensitiveInformationType = iff(isempty(SensitiveInformationTypeName_), SensitiveInfoTypeName_, SensitiveInformationTypeName_) + | extend EndpointOperation_ = tostring(parse_json(tostring(RawEventData.EndpointMetaData)).EndpointOperation) + | extend TargetDomain = tostring(parse_json(tostring(RawEventData.EndpointMetaData)).TargetDomain) + | extend Target = iff(isempty(tostring(TargetDomain)), TargetUsers, tostring(TargetDomain)) + | project Timestamp,ActionType,Workload,PolicyName,SensitiveInformationType,Documents,Target,Subject,Severity_ + | summarize arg_max(Timestamp, *) by Workload,PolicyName,SensitiveInformationType,Documents,Target + | sort by Timestamp + + - Name: DLPgetAllDlpalerts + DisplayName: Get all dlp alerts so that they can be analyzed + Description: Fetches all dlp alerts so that they can be analyzed + Inputs: + - Name: workload + Description: Specify the workload for which the alerts are to be fetched + Required: false + Settings: + Target: Defender + Template: |- + let workload = '{{workload}}'; + let EndPointAction = datatable(ActionName: string, Action: int) [ + 'None', '0', + 'Audit', '1', + 'Warn', '2', + 'WarnAndBypass', '3', + 'Block', '4', + 'Allow', '5' + ]; + CloudAppEvents + //| search incident + | where ActionType has 'DLPRuleMatch' or ActionType has "DLPRuleUndo" or RawEventData.PolicyMatchInfo.PolicyId <> "" + | where ActionType !in ('FileRead','FileArchived','FileModified','FileRenamed') + | where RawEventData.Workload has workload + | extend CreationTime = tostring(RawEventData.CreationTime) + | extend User = tolower(RawEventData.UserId) + //| project RawEventData,RawEventData2,User,RawEventData2.ObjectId,RawEventData.Workload,Timestamp + | where parse_json(tostring(parse_json(tostring(parse_json(tostring(RawEventData.PolicyDetails))[0].Rules))[0].Actions))[0] !in ('Encrypt','ExApplyBrandingTemplate') + | extend PolicyName_ = tostring(parse_json(tostring(RawEventData.PolicyDetails))[0].PolicyName) + | extend Action = tostring(parse_json(tostring(parse_json(tostring(parse_json(tostring(RawEventData.PolicyDetails))[0].Rules))[0].Actions))) + | extend RuleName_ = tostring(parse_json(tostring(parse_json(tostring(RawEventData.PolicyDetails))[0].Rules))[0].RuleName) + | extend classifiermatch = tostring(parse_json(tostring(parse_json(tostring(parse_json(tostring(parse_json(tostring(RawEventData.PolicyDetails))[0].Rules))[0].ConditionsMatched)).SensitiveInformation))) + | extend PolicyName_ = tostring(parse_json(tostring(RawEventData.PolicyDetails))[0].PolicyName) + | extend SensitiveInformationTypeName_ = tostring(parse_json(tostring(parse_json(tostring(parse_json(tostring(parse_json(tostring(RawEventData.PolicyDetails))[0].Rules))[0].ConditionsMatched)).SensitiveInformation))[0].SensitiveInformationTypeName) + | extend FileName_ = tostring(parse_json(tostring(RawEventData.SharePointMetaData)).FileName) + | extend RuleName_ = tostring(parse_json(tostring(parse_json(tostring(RawEventData.PolicyDetails))[0].Rules))[0].RuleName) + | extend Severity_ = tostring(parse_json(tostring(parse_json(tostring(RawEventData.PolicyDetails))[0].Rules))[0].Severity) + | extend Attachments_ = tostring(parse_json(tostring(RawEventData.Item)).Attachments) + | extend Recipients = strcat('To:',RawEventData.ExchangeMetaData.To, ' CC:',RawEventData.ExchangeMetaData.CC,' BCC:',RawEventData.ExchangeMetaData.BCC) + | extend SharePointMetaData_SiteCollectionUrl_s = tostring(parse_json(tostring(RawEventData.SharePointMetaData)).SiteCollectionUrl) + | extend Documents = iff(isempty(FileName_), Attachments_, FileName_) + | extend endpointdata = (parse_json(RawEventData.EndpointMetaData)) + | extend EndPointAction = toint(RawEventData.EnforcementMode) + | extend EndPointFile = tostring(RawEventData.ObjectId) + | join kind= leftouter + ( + EndPointAction + ) + on $left.EndPointAction==$right.Action + | extend Action = iff(isempty(Action), tolower(ActionName), Action) + | extend endpointclassifiermatch = endpointdata.SensitiveInfoTypeData + | extend Documents = iff(isempty(Documents), EndPointFile, Documents) + | mv-expand RawEventData.SensitiveInfoTypeData + | extend SensitiveInfoTypeName_ = tostring(RawEventData_SensitiveInfoTypeData.SensitiveInfoTypeName) + | extend SensitiveInformationType = iff(isempty(SensitiveInformationTypeName_), SensitiveInfoTypeName_, SensitiveInformationTypeName_) + | extend EndpointOperation_ = ActionType + | extend TargetDomain = tostring(parse_json(tostring(RawEventData.EndpointMetaData)).TargetDomain) + | extend Subject = tostring(parse_json(tostring(RawEventData.ExchangeMetaData)).Subject) + | project Workload = tostring(RawEventData.Workload),Action,User,SensitiveInformationType,EndpointOperation_,TargetDomain,Subject,Documents,Recipients,Timestamp + | summarize arg_max(Timestamp, *) by User,SensitiveInformationType,Subject,EndpointOperation_ + | sort by Timestamp + + - Name: DLPgetalertbyPolicy + DisplayName: Get the DLP alerts by policy + Description: Fetches all dlp alerts associated with a specific policy + Inputs: + - Name: policy + Description: Specify the policy for which the alerts are to be fetched + Required: false + Settings: + Target: Defender + Template: |- + let policyvar = '{{policy}}'; + let workload = ''; + let EndPointAction = datatable(ActionName: string, Action: int) [ + 'None', '0', + 'Audit', '1', + 'Warn', '2', + 'WarnAndBypass', '3', + 'Block', '4', + 'Allow', '5' + ]; + CloudAppEvents + //| search incident + | where ActionType has 'DLPRuleMatch' or ActionType has "DLPRuleUndo" or RawEventData.PolicyMatchInfo.PolicyId <> "" + | where ActionType !in ('FileRead','FileArchived','FileModified','FileRenamed') + | where RawEventData.Workload has workload + | extend CreationTime = tostring(RawEventData.CreationTime) + | extend User = tolower(RawEventData.UserId) + | where parse_json(tostring(parse_json(tostring(parse_json(tostring(RawEventData.PolicyDetails))[0].Rules))[0].Actions))[0] !in ('Encrypt','ExApplyBrandingTemplate') + | extend Action = tostring(parse_json(tostring(parse_json(tostring(parse_json(tostring(RawEventData.PolicyDetails))[0].Rules))[0].Actions))) + | extend PolicyName_ = tostring(parse_json(tostring(RawEventData.PolicyDetails))[0].PolicyName) + | extend PolicyNameEP = tostring(parse_json(tostring(RawEventData.MatchedPolicies))[0].PolicyName) + | extend PolicyName = iff(isempty(PolicyName_),PolicyNameEP,PolicyName_) + | where PolicyName has policyvar + | extend RuleName_ = tostring(parse_json(tostring(parse_json(tostring(RawEventData.PolicyDetails))[0].Rules))[0].RuleName) + | extend classifiermatch = tostring(parse_json(tostring(parse_json(tostring(parse_json(tostring(parse_json(tostring(RawEventData.PolicyDetails))[0].Rules))[0].ConditionsMatched)).SensitiveInformation))) + | extend SensitiveInformationTypeName_ = tostring(parse_json(tostring(parse_json(tostring(parse_json(tostring(parse_json(tostring(RawEventData.PolicyDetails))[0].Rules))[0].ConditionsMatched)).SensitiveInformation))[0].SensitiveInformationTypeName) + | extend FileName_ = tostring(parse_json(tostring(RawEventData.SharePointMetaData)).FileName) + | extend RuleName_ = tostring(parse_json(tostring(parse_json(tostring(RawEventData.PolicyDetails))[0].Rules))[0].RuleName) + | extend Severity_ = tostring(parse_json(tostring(parse_json(tostring(RawEventData.PolicyDetails))[0].Rules))[0].Severity) + | extend Attachments_ = tostring(parse_json(tostring(RawEventData.Item)).Attachments) + | extend Recipients = strcat('To:',RawEventData.ExchangeMetaData.To, ' CC:',RawEventData.ExchangeMetaData.CC,' BCC:',RawEventData.ExchangeMetaData.BCC) + | extend SharePointMetaData_SiteCollectionUrl_s = tostring(parse_json(tostring(RawEventData.SharePointMetaData)).SiteCollectionUrl) + | extend Documents = iff(isempty(FileName_), Attachments_, FileName_) + | extend endpointdata = (parse_json(RawEventData.EndpointMetaData)) + | extend EndPointAction = toint(RawEventData.EnforcementMode) + | extend EndPointFile = tostring(RawEventData.ObjectId) + | join kind= leftouter + ( + EndPointAction + ) + on $left.EndPointAction==$right.Action + | extend Action = iff(isempty(Action), tolower(ActionName), Action) + | extend endpointclassifiermatch = endpointdata.SensitiveInfoTypeData + | extend Documents = iff(isempty(Documents), EndPointFile, Documents) + | mv-expand RawEventData.SensitiveInfoTypeData + | extend SensitiveInfoTypeName_ = tostring(RawEventData_SensitiveInfoTypeData.SensitiveInfoTypeName) + | extend SensitiveInformationType = iff(isempty(SensitiveInformationTypeName_), SensitiveInfoTypeName_, SensitiveInformationTypeName_) + | extend EndpointOperation_ = ActionType + | extend TargetDomain = tostring(parse_json(tostring(RawEventData.EndpointMetaData)).TargetDomain) + | extend Subject = tostring(parse_json(tostring(RawEventData.ExchangeMetaData)).Subject) + | project Workload = tostring(RawEventData.Workload),Action,User,SensitiveInformationType,EndpointOperation_,TargetDomain,Subject,Documents,Recipients,PolicyName,Timestamp + | summarize arg_max(Timestamp, *) by User,SensitiveInformationType,Subject,PolicyName + | sort by Timestamp + + - Name: DLPgetFileactivity + DisplayName: Get the file activity of the specified file + Description: Fetches the file activity of the latest file including labels and sensitive information + Inputs: + - Name: document + Description: The file activity associated with the specific file + Required: true + Settings: + Target: Defender + Template: |- + let document = '{{document}}'; + CloudAppEvents + | extend sit = parse_json(tostring(parse_json(tostring(parse_json(tostring(parse_json(tostring(RawEventData.PolicyDetails))[0].Rules))[0].ConditionsMatched)).SensitiveInformation)) + | mv-expand sit + | where sit.Location has document or tostring(parse_json(tostring(RawEventData.Item)).Attachments) has document or url_decode(ObjectName) has document or RawEventData.ObjectId has document or RawEventData.ItemName has document or RawEventData.DestinationFileName has document + | extend User = tostring(RawEventData.UserId) + | extend LabelGUID1 = parse_json(tostring(RawEventData.SensitivityLabelEventData)).SensitivityLabelId + | extend LabelGUID2 = iff(isempty(tostring(RawEventData.LabelId)), LabelGUID1, tostring(RawEventData.LabelId)) + | extend LabelGUID3 = iff(isempty(tostring(RawEventData.SensitivityLabelId)), LabelGUID2, tostring(RawEventData.SensitivityLabelId)) + | extend OldSensitivityLabelId = tostring(parse_json(tostring(RawEventData.SensitivityLabelEventData)).OldSensitivityLabelId) + | extend LabelGUID = iff(isempty(tostring(LabelGUID3)),OldSensitivityLabelId, tostring(LabelGUID3)) + | mv-expand PolicyDetails = RawEventData.PolicyDetails + | mv-expand Rules = PolicyDetails.Rules + | mv-expand Sensitive = Rules.ConditionsMatched.SensitiveInformation + | extend sit = parse_json(tostring(RawEventData.SensitiveInfoTypeData)) + | mv-expand sit + | extend sit = iff(isempty(sit.SensitiveInfoTypeName),Sensitive.SensitiveInformationTypeName,sit.SensitiveInfoTypeName) + | extend SensitiveInfoTypeName = tostring(sit) + | project Timestamp,Activity=ActionType,User,CountryCode,LabelGUID,SensitiveInfoTypeName + | sort by Timestamp + + - Name: DLPgetEndpointactivity + DisplayName: Get the sensitive file activity of the specified Device + Description: Fetches the sensitive file activity happening on the Device + Inputs: + - Name: device + Description: The file activity associated with the specific device + Required: true + Settings: + Target: Defender + Template: |- + let devicevar = '{{device}}'; + let AppEvents = (CloudAppEvents + | where parse_json(tostring(RawEventData.SensitiveInfoTypeData)) != "" + | where parse_json(tostring(RawEventData.SensitiveInfoTypeData)) != "[]" + | extend sit = parse_json(tostring(RawEventData.SensitiveInfoTypeData)) + | mv-expand sit + | extend UserId = tolower(RawEventData.UserId) + | extend SensitiveInfoTypeName = tostring(sit.SensitiveInfoTypeName) + | extend document = tostring(RawEventData.ObjectId) + ); + AppEvents + | where tolower(RawEventData.DeviceName) == tolower(devicevar) + | extend ObjectId = tostring(RawEventData.ObjectId) + | extend DeviceName = tostring(RawEventData.DeviceName) + | extend Application = tostring(RawEventData.Application) + | summarize count = count() by document,Application,SensitiveInfoTypeName,ActionType,DeviceName,CountryCode + |order by ActionType + + - Name: DLPgetSiteCollectionActivity + DisplayName: Get the SharePoint site collection or library activity of the specified collection + Description: Fetches the SharePoint site collection or library activity of the selected collection + Inputs: + - Name: sitecollection + Description: The site or library activity associated with the specified collection + Required: true + Settings: + Target: Defender + Template: |- + let sitecollection = '{{sitecollection}}'; + CloudAppEvents + | where RawEventData.Workload == "OneDrive" or RawEventData.Workload == "SharePoint" + | where RawEventData.ObjectId has sitecollection + | extend Operation = tostring(RawEventData.Operation) + //Filter to a smaller set of operations, add additional operations as needed + | where Operation in ('FileAccessed','FilePreviewed','FileModifiedExtended','FileSensitivityLabelApplied','FileModified','FileUploaded','FileDownloaded','FileRenamed','AddedToSecureLink','SharingSet','SecureLinkCreated','SharingInheritanceBroken','FileSensitivityLabelChanged','CompanyLinkUsed','CompanyLinkCreated','SharingRevoked','FileSyncDownloadedFull','AddedToGroup','FileSensitivityLabelRemoved') + | extend Account = tostring(RawEventData.UserId) + | extend SharePointMetaData_SiteCollectionUrl_s = tostring((RawEventData.SharePointMetaData.SiteCollectionUrl)) + | extend FilePathUrl_ = url_decode(tostring(parse_json(tostring(RawEventData.SharePointMetaData)).FilePathUrl)) + | extend SourceRelativeUrl = tostring(RawEventData.SourceRelativeUrl) + | extend SourceFileName_ = tostring(RawEventData.SourceFileName) + | extend TargetUserOrGroupName = tostring(RawEventData.TargetUserOrGroupName) + | summarize + by + Timestamp, + Operation, + SourceFileName_, + Account, + SourceRelativeUrl, + TargetUserOrGroupName + | sort by Timestamp + + - Name: DLPTeamsDetectionsUser + DisplayName: Get all the meetings, chats and channel messages that triggered an alert for a user or recipient + Description: Fetches all the meetings, chats and channel messages that triggered an alert for a user or recipient + Inputs: + - Name: Recipient + Description: The recipient associated with the alert + Required: false + - Name: User + Description: the user associated with the information + Required: false + Settings: + Target: Defender + Template: |- + let Recipient = '{{Recipient}}'; + let user = '{{User}}'; + let TeamsMeeting = ( + CloudAppEvents + | where Application == 'Microsoft Teams' + | where ActionType == 'DLPRuleMatch' + | extend UserId_ = tostring(RawEventData.UserId) + | extend Workload_ = tostring(RawEventData.Workload) + | extend ObjectId_ = tostring(RawEventData.ObjectId) + | extend MessageId_ = ObjectId_ + | join kind=leftouter + ( CloudAppEvents + | where RawEventData.Name == 'policyViolation' + | extend MessageId_ = tostring(RawEventData.MessageId) + | extend MeetingDetailId_ = tostring(RawEventData.MeetingDetailId) + ) + on MessageId_ + | extend ChatThreadId_ = tostring(RawEventData1.ItemName) + | join kind=inner + ( CloudAppEvents + | where RawEventData.Operation == 'MeetingDetail' + | extend ChatThreadId_ = tostring(RawEventData.ChatThreadId) + | extend meetid = tostring(RawEventData.Id) + ) on ChatThreadId_ + | extend Meeting = tostring(parse_json(tostring(parse_json(tostring(RawEventData.ExchangeMetaData)).To))[0]) + | extend PolicyName = tostring(parse_json(tostring(RawEventData.PolicyDetails))[0].PolicyName) + | extend RuleName = tostring(parse_json(tostring(parse_json(tostring(RawEventData.PolicyDetails))[0].Rules))[0].RuleName) + | extend SensitiveInformationTypeName = tostring(parse_json(tostring(parse_json(tostring(parse_json(tostring(parse_json(tostring(RawEventData.PolicyDetails))[0].Rules))[0].ConditionsMatched)).SensitiveInformation))[0].SensitiveInformationTypeName) + | project meetid,Meeting,PolicyName,RuleName,SensitiveInformationTypeName,UserId_ + | join kind=inner + ( + CloudAppEvents + | where RawEventData.Operation == 'MeetingParticipantDetail' + | extend meetid = tostring(RawEventData.MeetingDetailId) + ) + on meetid + | extend UPN = tostring(parse_json(tostring(RawEventData.Attendees))[0].UPN) + | extend RecipientType = tostring(parse_json(tostring(RawEventData.Attendees))[0].RecipientType) + //| where RecipientType startswith 'Guest' + | extend Type = "TeamsMeeting" + | project Type,UPN,RecipientType,Meeting,PolicyName,RuleName,SensitiveInformationTypeName,UserId_); + let channel =( + CloudAppEvents + | where Application == 'Microsoft Teams' + | where ActionType == 'DLPRuleMatch' + | extend UserId_ = tostring(RawEventData.UserId) + | extend TeamName = tolower(parse_json(tostring(parse_json(tostring(RawEventData.ExchangeMetaData)).To))[0]) + | extend PolicyName = tostring(parse_json(tostring(RawEventData.PolicyDetails))[0].PolicyName) + | extend RuleName = tostring(parse_json(tostring(parse_json(tostring(RawEventData.PolicyDetails))[0].Rules))[0].RuleName) + | extend SensitiveInformationTypeName = tostring(parse_json(tostring(parse_json(tostring(parse_json(tostring(parse_json(tostring(RawEventData.PolicyDetails))[0].Rules))[0].ConditionsMatched)).SensitiveInformation))[0].SensitiveInformationTypeName) + | join kind=inner + ( + CloudAppEvents + | where Application == 'Microsoft Teams' + | extend TeamName = tolower(RawEventData.TeamName) + | extend UPN = tostring(parse_json(tostring(RawEventData.Members))[0].UPN) + | extend GroupId = tostring(RawEventData.AADGroupId) + | extend HasGuestUsers = tostring(parse_json(tostring(RawEventData.ParticipantInfo)).HasGuestUsers) + | extend HasUnauthenticatedUsers = tostring(parse_json(tostring(RawEventData.ParticipantInfo)).HasUnauthenticatedUsers) + ) on TeamName + | where HasGuestUsers == 'true' or HasUnauthenticatedUsers == 'true' + | project TeamName,ActionType,HasGuestUsers,HasUnauthenticatedUsers,UPN,PolicyName,RuleName,SensitiveInformationTypeName,UserId_ + | extend Type = "TeamChannel"); + let individual = ( + CloudAppEvents + | where Application == 'Microsoft Teams' + | where ActionType has 'DLPRuleMatch' + | extend UPN = tostring(parse_json(tostring(parse_json(tostring(RawEventData.ExchangeMetaData)).To))[0]) + | where UPN matches regex '(\\S*@\\S*.\\S*)' + | extend UserId_ = tostring(RawEventData.UserId) + | extend TeamName = tolower(parse_json(tostring(parse_json(tostring(RawEventData.ExchangeMetaData)).To))[0]) + | extend PolicyName = tostring(parse_json(tostring(RawEventData.PolicyDetails))[0].PolicyName) + | extend RuleName = tostring(parse_json(tostring(parse_json(tostring(RawEventData.PolicyDetails))[0].Rules))[0].RuleName) + | extend SensitiveInformationTypeName = tostring(parse_json(tostring(parse_json(tostring(parse_json(tostring(parse_json(tostring(RawEventData.PolicyDetails))[0].Rules))[0].ConditionsMatched)).SensitiveInformation))[0].SensitiveInformationTypeName) + | extend Type = "Individual"); + union channel,TeamsMeeting,individual + | where UserId_ has user + | where UPN has Recipient + | extend Name = iff(isempty(TeamName),Meeting,TeamName) + | summarize by Type,User=UserId_,Name,Target=UPN,PolicyName,SensitiveInformationTypeName + + - Name: DLPFilesSharedwithUser + DisplayName: Get the file and shared user information + Description: Fetches all information associated with Shared information and user + Inputs: + - Name: document + Description: the file associated with the user activity + Required: true + - Name: User + Description: the user associated with the information + Required: false + Settings: + Target: Defender + Template: |- + let document = '{{document}}'; + let user = '{{User}}'; + let DLP = (CloudAppEvents + | where RawEventData.Workload in ('SharePoint', 'OneDrive') + | where ActionType == "DLPRuleMatch" + | extend FilePathUrl_ = url_decode(tostring(RawEventData.SharePointMetaData.FilePathUrl)) + | extend path = parse_path(FilePathUrl_) + | where path.Filename has document + | extend DirectoryPath = tostring(path.DirectoryPath) + | summarize by DirectoryPath ); + let officedata = ( + CloudAppEvents + | where RawEventData.Operation == "SecureLinkUsed" or RawEventData.Operation == "AddedToSecureLink" + | extend ObjectId_ = tostring(RawEventData.ObjectId) + | where ObjectId_ has_any (DLP) + | extend SharePointMetaData_SiteCollectionUrl = tostring(RawEventData.SiteUrl) + | extend SharePointMetaData_FileName = tostring((RawEventData.SourceFileName)) + | extend Account = tostring(RawEventData.UserId) + | extend Targetsplit = split(Account, "#") + | extend TargetUserOrGroupName = iff(isempty(RawEventData.TargetUserOrGroupName), Targetsplit[1], RawEventData.TargetUserOrGroupName) + //Exclude internal domains if required + //| where TargetUserOrGroupName !has "mydom1.com" + | extend TargetUserOrGroupName = tolower(TargetUserOrGroupName) + | summarize + by + SharePointMetaData_FileName, + SharePointMetaData_SiteCollectionUrl, + TargetUserOrGroupName, + ObjectId_, + Account); + let dlpmain = ( + CloudAppEvents + | where ActionType == "DLPRuleMatch" + | where RawEventData.Workload == "OneDrive" or RawEventData.Workload == "SharePoint" + | extend FilePathUrl_ = url_decode(tostring(RawEventData.SharePointMetaData.FilePathUrl)) + | extend path = parse_path(FilePathUrl_) + | where path.Filename has document + | extend Account = tostring(RawEventData.UserId) + | where Account has user + | extend ObjectId_ = url_decode(tostring(ObjectName)) + | extend SharePointMetaData_SiteCollectionUrl = tostring((RawEventData.SharePointMetaData.SiteCollectionUrl)) + | where tolower(RawEventData.UserId) == tolower(Account) + | where RawEventData.Workload in ('SharePoint', 'OneDrive') + | extend SourceFileName = tostring(RawEventData.SourceFileName) + ); + let folder = (dlpmain + | join kind = leftouter + (officedata + | summarize + by + SharePointMetaData_SiteCollectionUrl, + SharePointMetaData_FileName, + TargetUserOrGroupName, + ObjectId_ + | summarize TargetUserOrGroupName = make_list(strcat(TargetUserOrGroupName)) + by + ObjectId_, + SharePointMetaData_SiteCollectionUrl, + SharePointMetaData_FileName + | summarize take_any(TargetUserOrGroupName) + by + ObjectId_, + SharePointMetaData_SiteCollectionUrl, + SharePointMetaData_FileName + | join kind = leftouter + ( + officedata + | summarize + by + SharePointMetaData_SiteCollectionUrl, + SharePointMetaData_FileName, + TargetUserOrGroupName, + ObjectId_ + | extend Domsplit = split(TargetUserOrGroupName, "@") + | extend domain = Domsplit[1] + | summarize TargetDomain = make_list(strcat(domain)) by SharePointMetaData_FileName, ObjectId_ + | summarize take_any(TargetDomain) by SharePointMetaData_FileName, ObjectId_ + ) + on ObjectId_ + ) + on ObjectId_ + ); + let files = (folder + //| where TargetUserOrGroupName == "" + | join kind = leftouter + (officedata + | summarize TargetUserOrGroupName = make_list(strcat(TargetUserOrGroupName)) by SharePointMetaData_FileName, SharePointMetaData_SiteCollectionUrl + | summarize take_any(TargetUserOrGroupName) by SharePointMetaData_FileName, SharePointMetaData_SiteCollectionUrl + | join kind = leftouter + ( + officedata + | summarize + by + SharePointMetaData_SiteCollectionUrl, + SharePointMetaData_FileName, + TargetUserOrGroupName + | extend Domsplit = split(TargetUserOrGroupName, "@") + | extend domain = Domsplit[1] + | summarize TargetDomain = make_list(strcat(domain)) by SharePointMetaData_FileName + | summarize take_any(TargetDomain) by SharePointMetaData_FileName + ) + on SharePointMetaData_FileName + ) + on SharePointMetaData_FileName, SharePointMetaData_SiteCollectionUrl + | extend TargetUserOrGroupName = TargetUserOrGroupName1 + | extend TargetDomain = TargetDomain1 + | where TargetUserOrGroupName != "" + ); + union folder, files + | extend FileName = tostring(parse_json(tostring(RawEventData.SharePointMetaData)).FileName) + | summarize arg_max(Timestamp, *) by ObjectId_, Account, tostring(RawEventData.Id) + | extend FileNameEncoded = url_encode(FileName) + | extend MDALink = (strcat('https://security.microsoft.com/cloudapps/files?filename=eq(',FileNameEncoded,')')) //Use this link to view older operations directly from Microsoft Defender for Cloud Apps + | project Timestamp,FileName,TargetUser= TargetUserOrGroupName,SiteCollection = ObjectId_ + | sort by Timestamp + + - Name: DLPAnomalies + DisplayName: Get DLP anomalies or outliers from the log stream + Description: Fetches anomalies or outliers from the log stream + Settings: + Target: Defender + Template: |- + CloudAppEvents + | where ActionType == "DlpRuleMatch" + | extend UserId = tostring(RawEventData.UserId) + | summarize EventCount=count() by UserId, bin(Timestamp,30m) + | order by Timestamp + | summarize EventCount=make_list(EventCount),TimeGenerated=make_list(Timestamp) by UserId + | extend outliers=series_decompose_anomalies(EventCount) + | mv-expand TimeGenerated, EventCount, outliers + | where outliers == 1 + | summarize aggregate=sum(todecimal(EventCount)) by UserId + + - Name: DLPExchangeTuning + DisplayName: Receive DLP tuning recommendations for Exchange + Description: This skill provides tuning and optimization recommendations for DLP policies in Exchange + Settings: + Target: Defender + Template: |- + let Exchange = ( + CloudAppEvents + | where ActionType has 'DLPRuleMatch' + | where RawEventData.Workload has 'Exchange' + | extend To = todynamic(parse_json(tostring(parse_json(RawEventData.ExchangeMetaData).To))) + | extend PolicyName = tostring(parse_json(tostring(RawEventData.PolicyDetails))[0].PolicyName) + | extend SensitiveInformationTypeName = tostring(parse_json(tostring(parse_json(tostring(parse_json(tostring(parse_json(tostring(RawEventData.PolicyDetails))[0].Rules))[0].ConditionsMatched)).SensitiveInformation))[0].SensitiveInformationTypeName)); + let To = ( + Exchange + | extend Type = 'To' + | extend To = todynamic(parse_json(tostring(parse_json(RawEventData.ExchangeMetaData).To))) + | mv-expand To + | summarize count() by tostring(To),PolicyName,tostring(RawEventData.Workload),Type,SensitiveInformationTypeName + | take 25); + let From = ( + Exchange + | extend Type = 'From' + | extend From = tostring(parse_json(tostring(RawEventData.ExchangeMetaData)).From) + | summarize count() by From,PolicyName,tostring(RawEventData.Workload),Type,SensitiveInformationTypeName + | take 25 ); + let Location = ( + Exchange + | extend Type = 'MatchLocation' + | extend Location = tostring(parse_json(tostring(parse_json(tostring(parse_json(tostring(parse_json(tostring(RawEventData.PolicyDetails))[0].Rules))[0].ConditionsMatched)).SensitiveInformation))[0].Location) + | extend PolicyName = tostring(parse_json(tostring(RawEventData.PolicyDetails))[0].PolicyName) + | summarize count() by Location,PolicyName,SensitiveInformationTypeName,tostring(RawEventData.Workload),Type + |take 25 ); + let Sensitive = ( + Exchange + | extend Type = 'SensitiveInformationType' + | extend PolicyName = tostring(parse_json(tostring(RawEventData.PolicyDetails))[0].PolicyName) + | summarize count() by SensitiveInformationTypeName,PolicyName,tostring(RawEventData.Workload),Type + |take 25 ); + let domains = ( + Exchange + | extend Type = 'Targetdomains' + | extend To = todynamic(parse_json(tostring(parse_json(RawEventData.ExchangeMetaData).To))) + | mv-expand To + | extend user = split(To,'@') + | summarize count() by Targetdomain=tostring(user[1]),PolicyName,tostring(RawEventData.Workload),Type,SensitiveInformationTypeName + | take 25 ); + let subject = ( + Exchange + | extend Type = 'subject' + | extend Subject = tostring(parse_json(tostring(RawEventData.ExchangeMetaData)).Subject) + | summarize count() by Subject,PolicyName,tostring(RawEventData.Workload),Type,SensitiveInformationTypeName + | take 25); + union To, From, Location, Sensitive,domains,subject + + - Name: DLPSharePointTuning + DisplayName: Receive DLP tuning recommendations for SharePoint and OneDrive + Description: This skill provides tuning recommendations for DLP policies in SharePoint and OneDrive + Settings: + Target: Defender + Template: |- + let SPO = ( + CloudAppEvents + | where ActionType == 'DLPRuleMatch' + | where RawEventData.Workload == 'OneDrive' or RawEventData.Workload == 'SharePoint' + | extend Account = tostring(RawEventData.UserId) + | extend SharePointMetaData_SiteCollectionUrl_s = tostring((RawEventData.SharePointMetaData.SiteCollectionUrl)) + | extend PolicyName = tostring(parse_json(tostring(RawEventData.PolicyDetails))[0].PolicyName) + | extend FileName = tostring(parse_json(tostring(RawEventData.SharePointMetaData)).FileName) + | extend SensitiveInformationTypeName = tostring(parse_json(tostring(parse_json(tostring(parse_json(tostring(parse_json(tostring(RawEventData.PolicyDetails))[0].Rules))[0].ConditionsMatched)).SensitiveInformation))[0].SensitiveInformationTypeName) + | join kind = inner + ( + CloudAppEvents + | where RawEventData.Operation == 'SecureLinkUsed' or RawEventData.Operation == 'AddedToSecureLink' + | extend SourceFileName = tostring(RawEventData.SourceFileName) + | extend SharePointMetaData_SiteCollectionUrl_s = tostring(RawEventData.SiteUrl) + | extend UserId = tostring(RawEventData.UserId) + | extend TargetUserOrGroupName = tostring(RawEventData.TargetUserOrGroupName) + | extend Sharedwith= iff(isempty(TargetUserOrGroupName), UserId, TargetUserOrGroupName) + |project + SourceFileName, + SharePointMetaData_SiteCollectionUrl_s, + TargetUserOrGroupName, + Sharedwith + ) + on SharePointMetaData_SiteCollectionUrl_s + | project Timestamp,Account,SharePointMetaData_SiteCollectionUrl_s, SourceFileName, Sharedwith,RawEventData,PolicyName,FileName,SensitiveInformationTypeName + | sort by Timestamp); + let Account = ( + SPO + | summarize count() by Account + | extend Type = 'Account' + | take 25); + let Target = ( + SPO + | extend Type = 'Target' + | summarize count() by Targetuser=Sharedwith,Type + | take 25); + let TargetDom = ( + SPO + | extend Type = 'domains' + | extend TargetDom = split(Sharedwith,'@') + | summarize count() by targetDomain=tostring(TargetDom[1]),Type + | take 25); + let TopSites = ( + SPO + | extend Type = 'TopSites' + | summarize count() by SharePointMetaData_SiteCollectionUrl_s,Type,SensitiveInformationTypeName + | take 25); + let TopDocs = ( + SPO + | extend Type = 'TopDocs' + | summarize count() by FileName,Type,SensitiveInformationTypeName + | take 25); + let TopSIT = ( + SPO + | extend Type = 'TopSIT' + | summarize count() by Type,SensitiveInformationTypeName + | take 25); + union TargetDom,Account,Target,TopSites,TopDocs,TopSIT + + - Name: DLPTeamsTuning + DisplayName: Receive DLP tuning recommendations for Teams + Description: This skill provides tuning recommendations for DLP policies in Teams + Settings: + Target: Defender + Template: |- + let TeamsMeeting = ( + CloudAppEvents + | where Application == 'Microsoft Teams' + | where ActionType == 'DLPRuleMatch' + | extend UserId_ = tostring(RawEventData.UserId) + | extend Workload_ = tostring(RawEventData.Workload) + | extend ObjectId_ = tostring(RawEventData.ObjectId) + | extend MessageId_ = ObjectId_ + | join kind=inner + ( CloudAppEvents + | where RawEventData.Name == 'policyViolation' + | extend MessageId_ = tostring(RawEventData.MessageId) + | extend MeetingDetailId_ = tostring(RawEventData.MeetingDetailId) + ) + on MessageId_ + | extend ChatThreadId_ = tostring(RawEventData1.ItemName) + | join kind=inner + ( CloudAppEvents + | where RawEventData.Operation == 'MeetingDetail' + | extend ChatThreadId_ = tostring(RawEventData.ChatThreadId) + | extend meetid = tostring(RawEventData.Id) + ) on ChatThreadId_ + | extend Meeting = tostring(parse_json(tostring(parse_json(tostring(RawEventData.ExchangeMetaData)).To))[0]) + | extend PolicyName = tostring(parse_json(tostring(RawEventData.PolicyDetails))[0].PolicyName) + | extend RuleName = tostring(parse_json(tostring(parse_json(tostring(RawEventData.PolicyDetails))[0].Rules))[0].RuleName) + | extend SensitiveInformationTypeName = tostring(parse_json(tostring(parse_json(tostring(parse_json(tostring(parse_json(tostring(RawEventData.PolicyDetails))[0].Rules))[0].ConditionsMatched)).SensitiveInformation))[0].SensitiveInformationTypeName) + | project meetid,Meeting,PolicyName,RuleName,SensitiveInformationTypeName,UserId_ + | join kind=inner + ( + CloudAppEvents + | where RawEventData.Operation == 'MeetingParticipantDetail' + | extend meetid = tostring(RawEventData.MeetingDetailId) + ) + on meetid + | extend UPN = tostring(parse_json(tostring(RawEventData.Attendees))[0].UPN) + | extend RecipientType = tostring(parse_json(tostring(RawEventData.Attendees))[0].RecipientType) + | where RecipientType startswith 'Guest' + | project UPN,RecipientType,Meeting,PolicyName,RuleName,SensitiveInformationTypeName,UserId_); + let channel =( + CloudAppEvents + | where Application == 'Microsoft Teams' + | where ActionType == 'DLPRuleMatch' + | extend UserId_ = tostring(RawEventData.UserId) + | extend TeamName = tolower(parse_json(tostring(parse_json(tostring(RawEventData.ExchangeMetaData)).To))[0]) + | extend PolicyName = tostring(parse_json(tostring(RawEventData.PolicyDetails))[0].PolicyName) + | extend RuleName = tostring(parse_json(tostring(parse_json(tostring(RawEventData.PolicyDetails))[0].Rules))[0].RuleName) + | extend SensitiveInformationTypeName = tostring(parse_json(tostring(parse_json(tostring(parse_json(tostring(parse_json(tostring(RawEventData.PolicyDetails))[0].Rules))[0].ConditionsMatched)).SensitiveInformation))[0].SensitiveInformationTypeName) + | join kind=inner + ( + CloudAppEvents + | where Application == 'Microsoft Teams' + | extend TeamName = tolower(RawEventData.TeamName) + | extend UPN = tostring(parse_json(tostring(RawEventData.Members))[0].UPN) + | extend GroupId = tostring(RawEventData.AADGroupId) + | extend HasGuestUsers = tostring(parse_json(tostring(RawEventData.ParticipantInfo)).HasGuestUsers) + | extend HasUnauthenticatedUsers = tostring(parse_json(tostring(RawEventData.ParticipantInfo)).HasUnauthenticatedUsers) + ) on TeamName + | where HasGuestUsers == 'true' or HasUnauthenticatedUsers == 'true' + | project TeamName,ActionType,HasGuestUsers,HasUnauthenticatedUsers,UPN,PolicyName,RuleName,SensitiveInformationTypeName,UserId_); + let individual = ( + CloudAppEvents + | where Application == 'Microsoft Teams' + | where ActionType has 'DLPRuleMatch' + | extend UPN = tostring(parse_json(tostring(parse_json(tostring(RawEventData.ExchangeMetaData)).To))[0]) + | where UPN matches regex '(\\S*@\\S*.\\S*)' + | extend UserId_ = tostring(RawEventData.UserId) + | extend TeamName = tolower(parse_json(tostring(parse_json(tostring(RawEventData.ExchangeMetaData)).To))[0]) + | extend PolicyName = tostring(parse_json(tostring(RawEventData.PolicyDetails))[0].PolicyName) + | extend RuleName = tostring(parse_json(tostring(parse_json(tostring(RawEventData.PolicyDetails))[0].Rules))[0].RuleName) + | extend SensitiveInformationTypeName = tostring(parse_json(tostring(parse_json(tostring(parse_json(tostring(parse_json(tostring(RawEventData.PolicyDetails))[0].Rules))[0].ConditionsMatched)).SensitiveInformation))[0].SensitiveInformationTypeName)); + let Account = ( + union channel,TeamsMeeting,individual + | summarize count() by UserId_,SensitiveInformationTypeName + | extend Type = 'Account' + | take 25); + let Target = ( + union channel,TeamsMeeting,individual + | extend Type = 'Target' + | where UPN <> '' + | summarize count() by UPN,Type,SensitiveInformationTypeName + | take 25); + let TopChannel = ( + union channel,TeamsMeeting + | extend Type = 'TopChannel' + | where HasGuestUsers <> '' + | summarize count() by TeamName,Type,SensitiveInformationTypeName + | take 25); + let TopMeeting = ( + union channel,TeamsMeeting + | extend Type = 'TopMeeting' + | where RecipientType startswith 'Guest' + | summarize count() by Meeting,Type,SensitiveInformationTypeName + | take 25); + union Account,Target,TopChannel,TopMeeting + + - Name: DLPEndPointTuning + DisplayName: Receive DLP tuning recommendations for Endpoints + Description: This skill provides tuning recommendations for Endpoint DLP policies + Settings: + Target: Defender + Template: |- + let endpoint =( + CloudAppEvents + | where RawEventData.Workload has 'EndPoint' + | where RawEventData.PolicyMatchInfo <> '' + | extend RuleName = tostring(parse_json(tostring(RawEventData.PolicyMatchInfo)).RuleName) + | extend PolicyName = tostring(parse_json(tostring(RawEventData.PolicyMatchInfo)).PolicyName) + | extend SensitiveInfoTypeName = tostring(parse_json(tostring(RawEventData.SensitiveInfoTypeData))[0].SensitiveInfoTypeName) + | extend TargetDomain = tostring(RawEventData.TargetDomain) + | extend EnforcementMode_ = tostring(parse_json(tostring(RawEventData.EndpointMetaData)).EnforcementMode) + | extend EndpointOperation_ = tostring(parse_json(tostring(RawEventData.EndpointMetaData)).EndpointOperation) + | extend document = tostring(RawEventData.ObjectId) + | extend UserId = tostring(RawEventData.UserId) + ); + let Account = ( + endpoint + | summarize count() by UserId + | extend Type = 'Account' + | take 25); + let Target = ( + endpoint + | extend TargetDomain = tolower(RawEventData.TargetDomain) + | where TargetDomain <> '' + | extend Type = 'Target' + | summarize count() by TargetDomain,Type,SensitiveInfoTypeName + | take 25); + let TargetPrinter = ( + endpoint + | extend TargetPrinterName = tostring(RawEventData.TargetPrinterName) + | where TargetPrinterName <> '' + | extend Type = 'TargetPrinter' + | summarize count() by TargetPrinterName,Type,SensitiveInfoTypeName + | take 25); + let TargetFilePath = ( + endpoint + | extend TargetFilePath = tostring(RawEventData.TargetFilePath) + | extend Type = 'TargetFilePath' + | where TargetFilePath <> '' + | summarize count() by TargetFilePath,Type,SensitiveInfoTypeName + | take 25); + let Device = ( + endpoint + | extend DeviceName_ = tostring(RawEventData.DeviceName) + | extend Type = 'TopDevice' + | where DeviceName_ <> '' + | summarize count() by DeviceName_,Type,SensitiveInfoTypeName + | take 25); + let Topdocument = ( + endpoint + | extend Type = 'TopDoc' + | extend docname = parse_path(document).Filename + | where docname <> '' + | summarize count() by tostring(docname),Type,SensitiveInfoTypeName + | take 25); + let TopSIT = ( + endpoint + | extend Type = 'TopSIT' + | summarize count() by SensitiveInfoTypeName,Type + | take 25); + union Account,Topdocument,TopSIT,Target,TargetFilePath,TargetPrinter,Device + + - Name: DLPOCRTuning + DisplayName: Receive DLP tuning recommendations for OCR + Description: This skill provides tuning recommendations for OCR enabled DLP policies + Settings: + Target: Defender + Template: |- + let endpoint =( + CloudAppEvents + | where RawEventData.Workload has 'EndPoint' + | where RawEventData.PolicyMatchInfo.PolicyName <> '' + | mv-expand Sensitive = RawEventData.SensitiveInfoTypeData + | extend TargetDomain = tostring(RawEventData.TargetDomain) + | extend EnforcementMode_ = tostring(parse_json(tostring(RawEventData.EndpointMetaData)).EnforcementMode) + | extend EndpointOperation_ = tostring(parse_json(tostring(RawEventData.EndpointMetaData)).EndpointOperation) + | extend Location = tostring(parse_path(tostring(RawEventData.ObjectId)).FileType) + | where Location has_any ('JPEG', 'JPG', 'PNG', 'BMP', 'TIFF','PDF')); + let Exchange = ( + CloudAppEvents + | where ActionType has 'DLPRuleMatch' + | extend Type = 'To' + | where RawEventData.Workload has 'Exchange' + | mv-expand PolicyDetails = RawEventData.PolicyDetails + | mv-expand Rules = PolicyDetails.Rules + | mv-expand Sensitive = Rules.ConditionsMatched.SensitiveInformation + | extend Location = tolower(Sensitive.Location) + | where Location has_any ('JPEG', 'JPG', 'PNG', 'BMP', 'TIFF','PDF')); + let Teams = ( + CloudAppEvents + | where ActionType has 'DLPRuleMatch' + | extend Type = 'To' + | where RawEventData.Workload has 'MicrosoftTeams' + | mv-expand PolicyDetails = RawEventData.PolicyDetails + | mv-expand Rules = PolicyDetails.Rules + | mv-expand Sensitive = Rules.ConditionsMatched.SensitiveInformation + | extend FileSize_ = tostring(parse_json(tostring(RawEventData.ExchangeMetaData)).FileSize) + | where toint(FileSize_) > 50000); + let spo = ( + CloudAppEvents + | where ActionType has 'DLPRuleMatch' + | where RawEventData.Workload has 'SharePoint' or RawEventData.Workload has 'OneDrive' + | extend Location = tostring((parse_path(ObjectName)).Filename) + | mv-expand PolicyDetails = RawEventData.PolicyDetails + | mv-expand Rules = PolicyDetails.Rules + | mv-expand Sensitive = Rules.ConditionsMatched.SensitiveInformation + | where Sensitive != '' + | where Location has_any ('JPEG', 'JPG', 'PNG', 'BMP', 'TIFF','PDF')); + union Exchange,Teams,spo,endpoint + | extend sit = (Sensitive.SensitiveInformationTypeName) + | extend sit = iff(isempty(sit),Sensitive.SensitiveInfoTypeName,Sensitive.SensitiveInformationTypeName) + | extend PolicyName = PolicyDetails.PolicyName + | extend PolicyName = iff(isempty(PolicyName),RawEventData.PolicyMatchInfo.PolicyName,Sensitive.SensitiveInformationTypeName) + | summarize UserId = make_set(tostring(RawEventData.UserId)), Detections = make_list(tostring(sit)),count(), arg_max(Timestamp, *) by Timestamp, tostring(RawEventData.UserId),tostring(Sensitive.SensitiveInformationTypeName),Location + | extend To = todynamic(parse_json(tostring(parse_json(tostring(RawEventData.ExchangeMetaData)).To))[0]) + | mv-expand To + | extend user = split(To,'@') + | project Timestamp, tostring(RawEventData.UserId),tostring(sit),Location,Subject=ObjectName,tostring(RawEventData.Workload),tostring(RawEventData.UserId),count_,user[1],PolicyName + + - Name: SensitiveSharePointOverSharing + DisplayName: Oversharing events in SharePoint and OneDrive + Description: This skill provides details about oversharing events in SharePoint and OneDrive + Inputs: + - Name: department + Description: Specify the department for which we should return the oversharing events + Required: false + - Name: sitecollection + Description: Specify the site collection for which we should return the oversharing events + Required: false + Settings: + Target: Defender + Template: |- + let department = '{{department}}'; + let sitecollection = '{{sitecollection}}'; + let sharinglink = ( + CloudAppEvents + | where RawEventData.Workload == 'OneDrive' or RawEventData.Workload == 'SharePoint' + | where RawEventData.ObjectId has sitecollection + | extend Operation = tostring(RawEventData.Operation) + //Filter to a smaller set of operations, add additional operations as needed + | where Operation == 'AddedToSecureLink' + or Operation == 'SecureLinkCreated' + or Operation == 'SecureLinkUsed' + or Operation == 'SharingSet' + or ActionType == 'RemoveProtection' + | extend Sharinglink = tostring(RawEventData.TargetUserOrGroupName) + | where Sharinglink matches regex 'SharingLinks.[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}.Organization\\w*.[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}' + | join kind= leftouter + (CloudAppEvents + | where ActionType == 'GroupRemoved' + | extend TargetUserOrGroupName_ = tostring(RawEventData.TargetUserOrGroupName) + | extend Sharinglink = tostring(parse_json(tostring(RawEventData.ModifiedProperties))[0].NewValue) + ) + on Sharinglink + |extend Type = 'SharingLink' + ); + let group = ( + CloudAppEvents + | where RawEventData.Workload == 'OneDrive' or RawEventData.Workload == 'SharePoint' + | where RawEventData.ObjectId has sitecollection + | extend Operation = tostring(RawEventData.Operation) + //Filter to a smaller set of operations, add additional operations as needed + | where Operation == 'AddedToSecureLink' + or Operation == 'SecureLinkCreated' + or Operation == 'SecureLinkUsed' + or Operation == 'SharingSet' + or ActionType == 'RemoveProtection' + | extend Sharinglink = tostring(RawEventData.TargetUserOrGroupName) + | where Sharinglink has 'Everyone' + | join kind= leftouter + (CloudAppEvents + | where ActionType == 'CompanyLinkRemoved' + | extend TargetUserOrGroupName_ = tostring(RawEventData.TargetUserOrGroupName) + ) + on ObjectId + | extend Type = 'Group' + ); + union sharinglink,group + | extend UnshareOp = tostring(RawEventData1.Operation) + | extend ShareTime_ = tostring(RawEventData.CreationTime) + | extend UnshareTime = tostring(RawEventData1.CreationTime) + | project Type,ObjectName,Sharinglink,ShareTime_,UnshareOp,UnshareTime,tostring(RawEventData.UserId) + | join kind=leftouter + ( + IdentityInfo + | where Timestamp > ago(7d) + | extend AccountUpn = tolower(AccountUpn) + ) + on $left.RawEventData_UserId == $right.AccountUpn + | where Department has department + | summarize arg_max(Timestamp, *) by Type,ObjectName,Sharinglink,ShareTime_,UnshareOp,UnshareTime,RawEventData_UserId,Department,Country,City + + - Name: SensitiveGetExternalUploadsfromclients + DisplayName: Get uploads to external websites from clients containing sensitive information + Description: Fetches uploads to external websites from clients containing sensitive information + Inputs: + - Name: site + Description: Specify the site for which the uploads are to be fetched + Required: false + - Name: user + Description: Specify the user for which the uploads are to be fetched + Required: false + - Name: sitname + Description: Specify the sensitive information type for which the uploads are to be fetched + Required: false + Settings: + Target: Defender + Template: |- + let site = '{{site}}'; + let user = '{{user}}'; + let sitvar = '{{sitname}}'; + CloudAppEvents + | where RawEventData.Workload has 'EndPoint' + | where RawEventData.TargetDomain has site + | where RawEventData.UserId has user + | where ActionType has 'FileUploadedToCloud' + | mv-expand Sensitive = RawEventData.SensitiveInfoTypeData + | where Sensitive.SensitiveInfoTypeName has sitvar + | extend TargetDomain = tostring(RawEventData.TargetDomain) + | extend EnforcementMode_ = tostring(parse_json(tostring(RawEventData.EndpointMetaData)).EnforcementMode) + | extend EndpointOperation_ = tostring(parse_json(tostring(RawEventData.EndpointMetaData)).EndpointOperation) + | extend FileName = tostring(parse_path(tostring(RawEventData.ObjectId)).Filename) + | summarize count() by TargetDomain, Datatype=tostring(Sensitive.SensitiveInfoTypeName) + + - Name: SensitiveGetInformationaboutfilesuploadedtospecificsite + DisplayName: Get the actual file uploads to specific external websites from clients containing sensitive information + Description: Fetches the actual files uploads to specific external websites from clients + Inputs: + - Name: site + Description: Specify the site for which the uploads are to be fetched + Required: true + - Name: sitname + Description: Specify the sensitive information type for which the uploads are to be fetched + Required: false + - Name: document + Description: Specify the document for which the uploads are to be fetched + Required: false + Settings: + Target: Defender + Template: |- + let site = '{{site}}'; + let sitvar = '{{sitname}}'; + let document = '{{document}}'; + CloudAppEvents + | where RawEventData.Workload has 'EndPoint' + | where RawEventData.TargetDomain has site + | where ActionType has 'FileUploadedToCloud' + | mv-expand Sensitive = RawEventData.SensitiveInfoTypeData + | where Sensitive.SensitiveInfoTypeName has sitvar + | extend TargetDomain = tostring(RawEventData.TargetDomain) + | extend EnforcementMode_ = tostring(parse_json(tostring(RawEventData.EndpointMetaData)).EnforcementMode) + | extend EndpointOperation_ = tostring(parse_json(tostring(RawEventData.EndpointMetaData)).EndpointOperation) + | extend FileName = tostring(parse_path(tostring(RawEventData.ObjectId)).Filename) + | where FileName has document + | summarize count() by TargetDomain, Datatype=tostring(Sensitive.SensitiveInfoTypeName),FileName + + - Name: SensitiveFilesUserAccessed + DisplayName: Get information about sensitive files a user has been accessing + Description: Fetches information about sensitive files being accessed by users + Inputs: + - Name: user + Description: Specify the user for which you want to get file access + Required: true + - Name: ISP + Description: Filter the information for a specific ISP + Required: false + - Name: IPTag + Description: Filter the information for a specific IPTag + Required: false + Settings: + Target: Defender + Template: |- + let user = '{{user}}'; + let ispvar = '{{ISP}}'; + let iptagvar = '{{IPTag}}'; + let endpoint = ( + CloudAppEvents + | where ISP has ispvar + | extend IPTags = tostring(IPTags) + | where IPTags has iptagvar + | where RawEventData.UserId has user + | where parse_json(tostring(RawEventData.SensitiveInfoTypeData)) != '' + | where parse_json(tostring(RawEventData.SensitiveInfoTypeData)) != '[]' + | extend sit = parse_json(tostring(RawEventData.SensitiveInfoTypeData)) + | mv-expand sit + | extend UserId = tolower(RawEventData.UserId) + | extend TargetDomain = tostring(RawEventData.TargetDomain) + | extend ObjectName = iff(isempty(ObjectName),tostring(RawEventData.ObjectId),ObjectName) + | summarize count() by DataType=tostring(sit.SensitiveInfoTypeName),CountryCode,City,UserId,Document=ObjectName,Workload=tostring(RawEventData.Workload),TargetDomain,ISP,tostring(IPTags)); + let office = ( + CloudAppEvents + | where ActionType has 'DLPRuleMatch' + | where RawEventData.UserId has user + | extend sit = parse_json(tostring(parse_json(tostring(parse_json(tostring(parse_json(tostring(RawEventData.PolicyDetails))[0].Rules))[0].ConditionsMatched)).SensitiveInformation)) + | where sit != '' + | mv-expand sit + | extend UserId = tolower(RawEventData.UserId) + | extend sit_SensitiveInfoTypeName = tostring(sit.SensitiveInformationTypeName) + | join kind=inner + (CloudAppEvents + | where ActionType !has 'DLPRuleMatch' + | where ISP has ispvar + | extend IPTags = tostring(IPTags) + | where IPTags has iptagvar + | project ISP, ObjectId,IPTags,ActionType + ) on ObjectId + | summarize count() by DataType=sit_SensitiveInfoTypeName,UserId,Document=ObjectName,Workload=tostring(RawEventData.Workload),ISP=ISP1,IPTags=tostring(IPTags1)); + union endpoint,office + | project DataType,CountryCode,City,UserId,Document,Workload,TargetDomain,Count=count_,ISP,tostring(IPTags) + + - Name: SensitivefilesaccessedbyCompromisedusers + DisplayName: Get sensitive documents that are accessed by compromised users + Description: Fetches sensitive documents that are accessed by compromised users + Inputs: + - Name: user + Description: Specify the user for which the sensitive content is to be fetched + Required: false + - Name: document + Description: Specify the document to identify to map to possible compromised users + Required: false + - Name: ISP + Description: Filter the information for a specific ISP + Required: false + - Name: IPTag + Description: Filter the information for a specific IPTag + Required: false + Settings: + Target: Defender + Template: |- + let ispvar = '{{ISP}}'; + let iptagvar = '{{IPTag}}'; + let user = '{{user}}'; + let document = '{{document}}'; + let AlertEvidence = (AlertEvidence + | where Timestamp > ago(31d) + //| where ProcessCommandLine =='' + | extend upnsuffix = todynamic (AdditionalFields).UPNSuffix + | extend upn1 = iff(isempty(todynamic (AdditionalFields).Recipient),todynamic (AdditionalFields).Upn,todynamic (AdditionalFields).Recipient) + | extend user = parse_json(tostring(todynamic(AdditionalFields).Account)) + | extend upn2 = strcat(user.Name,'@',user.UPNSuffix) + | extend upn3 = iff(isempty(user.Name),upn1,upn2) + | extend upn4 = strcat(AccountName,'@',AccountDomain) + | extend upn5 = strcat(AccountName,'@',upnsuffix) + | extend upn6 = iff(isempty(upnsuffix),upn4,upn5) + | extend upn = iff(isempty(upn3),upn6,upn3) + | extend upn = tolower(upn) + | distinct upn,ThreatFamily,Categories + | extend d = bag_pack("ThreatFamily", ThreatFamily, "Categories", Categories) + | summarize AttackDetail = make_list(strcat(d)) by upn); + let endpoint = ( + AlertEvidence + | where upn has tolower(user) + | join kind=inner + ( + CloudAppEvents + | where ISP has ispvar + | extend IPTags = tostring(IPTags) + | where IPTags has iptagvar + | where parse_json(tostring(RawEventData.SensitiveInfoTypeData)) != '' + | where parse_json(tostring(RawEventData.SensitiveInfoTypeData)) != '[]' + | extend sit = parse_json(tostring(RawEventData.SensitiveInfoTypeData)) + | mv-expand sit + | extend UserId = tolower(RawEventData.UserId) + | extend TargetDomain = tostring(RawEventData.TargetDomain) + | extend ObjectName = iff(isempty(ObjectName),tostring(RawEventData.ObjectId),ObjectName) + | where ObjectName has document + | summarize count() by tostring(sit.SensitiveInfoTypeName),CountryCode,City,UserId,ObjectName,tostring(RawEventData.Workload),TargetDomain,ISP,tostring(IPTags) + ) + on $left.upn == $right.UserId); + let office = ( AlertEvidence + | where upn has tolower(user) + | join kind=inner + ( + CloudAppEvents + | where ActionType has 'DLPRuleMatch' + | extend sit = parse_json(tostring(parse_json(tostring(parse_json(tostring(parse_json(tostring(RawEventData.PolicyDetails))[0].Rules))[0].ConditionsMatched)).SensitiveInformation)) + | where sit != '' + | mv-expand sit + | extend UserId = tolower(RawEventData.UserId) + | extend sit_SensitiveInfoTypeName = tostring(sit.SensitiveInformationTypeName) + | where url_decode(ObjectName) has document + | join kind=inner + (CloudAppEvents + | where ActionType !has 'DLPRuleMatch' + | where ISP has ispvar + | extend IPTags = tostring(IPTags) + | where IPTags has iptagvar + | project ISP, ObjectId,IPTags,ActionType + ) on ObjectId + | summarize count() by DataType=sit_SensitiveInfoTypeName,UserId,Document=ObjectName,Workload=tostring(RawEventData.Workload),ISP=ISP1,IPTags=tostring(IPTags1)) + on $left.upn == $right.UserId); + union endpoint,office + | join kind=leftouter + ( + IdentityInfo + | where Timestamp > ago(7d) + | extend AccountUpn = tolower(AccountUpn) + ) + on $left.upn == $right.AccountUpn + | summarize arg_max(Timestamp, *) by sit_SensitiveInfoTypeName,CountryCode,City,UserId,ObjectName,RawEventData_Workload,TargetDomain,tostring(AttackDetail) + | summarize sum(count_) by DataType=sit_SensitiveInfoTypeName,CountryCode,City,UserId,upn,Department,Document=ObjectName,Workload=RawEventData_Workload,TargetDomain,AttackDetail=tostring(AttackDetail),ISP,tostring(IPTags) + + - Name: SensitiveEmailAccessactivitybyCVEorVendororNetwork + DisplayName: Get the sensitive file activity based on CVE,Vendor,ISP or IPTags for Exchange, email content. + Description: Fetches the sensitive email activity correlated with CVE or Vendor + Inputs: + - Name: CVE + Description: The CVE to investigate across the file activity + Required: false + - Name: Vendor + Description: The vendor to investigate across the file activity + Required: false + - Name: ISP + Description: Filter the information for a specific ISP + Required: false + - Name: IPTag + Description: Filter the information for a specific IPTag + Required: false + - Name: sit + Description: Filter the information for a specific Sensitivity Information Type + Required: false + Settings: + Target: Defender + Template: |- + let cveid1 = '{{CVE}}'; + let vendor = '{{Vendor}}'; + let ispvar = '{{ISP}}'; + let iptagvar = '{{IPTag}}'; + let sitvar = '{{sit}}'; + CloudAppEvents + | where Timestamp > ago(30d) + | where tostring(RawEventData.Workload) == "Exchange" + | where ActionType == "DlpRuleMatch" + | extend sit = parse_json(tostring(parse_json(tostring(parse_json(tostring(parse_json(tostring(RawEventData.PolicyDetails))[0].Rules))[0].ConditionsMatched)).SensitiveInformation)) + | where sit != '' + | mv-expand sit + | where tostring(sit.SensitiveInformationTypeName) has sitvar + | project InternetMessageId=ObjectId,sit,Subject = ObjectName + | join kind=inner + ( + CloudAppEvents + | where tostring(RawEventData.Operation) == "MailItemsAccessed" + | where tolower(ISP) has tolower(ispvar) + | extend IPtagsv = iff(isnull(IPTags),'NotSet',IPTags) + | extend IPTags = tostring(IPTags) + | where IPtagsv has iptagvar + | mv-expand RawEventData.Folders + | mv-expand FolderItem = RawEventData_Folders.FolderItems + | extend InternetMessageId = tostring(FolderItem.InternetMessageId) + | extend SensitivityLabel = tostring(FolderItem.Sensitivity) + ) + on $left.InternetMessageId == $right.InternetMessageId + | join kind=leftouter + ( + DeviceInfo + | summarize by DeviceId,PublicIP,DeviceName + ) + on $left.IPAddress == $right.PublicIP + | summarize arg_max(Timestamp, *) by UserId = tolower(RawEventData.UserId),InternetMessageId,ISP,tostring(IPTags),DeviceId + | join kind = leftouter + ( + DeviceTvmSoftwareVulnerabilities + | extend cveid1 = iff(isempty(cveid1),'NotSet',cveid1) + | extend vendor = iff(isempty(vendor),'NotSet',vendor) + | where CveId has cveid1 or SoftwareVendor has vendor + ) + on DeviceId + | join kind=leftouter + ( + IdentityInfo + | where Timestamp > ago(7d) + | extend AccountUpn = tolower(AccountUpn) + ) + on $left.UserId == $right.AccountUpn + | summarize count = count() by DataType=tostring(sit.SensitiveInformationTypeName),SensitivityLabel,DeviceName,Department,SoftwareName,SoftwareVersion,ISP,tostring(IPTags),CveId + + + - Name: SensitiveEmailsaccessedbyusers + DisplayName: Who accessed the emails containing sensitive information and from where + Description: Fetches the sensitive information type,label user and the email accessed as well as ISP, Device information + Inputs: + - Name: user + Description: Specify the user id for which the email access is to be fetched + Required: false + - Name: subject + Description: Specify the subject for which the email access is to be fetched + Required: false + - Name: InternetMessageId + Description: Specify the InternetMessageId for which the email access is to be fetched + Required: false + Settings: + Target: Defender + Template: |- + let user = '{{user}}'; + let subject = '{{subject}}'; + let InternetMessageId = '{{InternetMessageId}}'; + CloudAppEvents + | where tostring(RawEventData.Workload) == "Exchange" + | where ActionType == "DlpRuleMatch" + | where tolower(RawEventData.UserId) has tolower(user) + | where ObjectId has InternetMessageId + | where tolower(ObjectName) has tolower(subject) + | extend sit = parse_json(tostring(parse_json(tostring(parse_json(tostring(parse_json(tostring(RawEventData.PolicyDetails))[0].Rules))[0].ConditionsMatched)).SensitiveInformation)) + | where sit != '' + | mv-expand sit + | project InternetMessageId=ObjectId,sit,Subject = ObjectName + | join kind=inner + ( + CloudAppEvents + | where tostring(RawEventData.Operation) == "MailItemsAccessed" + | mv-expand RawEventData.Folders + | mv-expand FolderItem = RawEventData_Folders.FolderItems + | extend InternetMessageId = tostring(FolderItem.InternetMessageId) + | extend SensitivityLabel = tostring(FolderItem.Sensitivity) + ) + on $left.InternetMessageId == $right.InternetMessageId + | join kind=leftouter + ( + DeviceInfo + | summarize by DeviceId,PublicIP,DeviceName + ) + on $left.IPAddress == $right.PublicIP + | summarize arg_max(Timestamp, *) by UserId = tostring(RawEventData.UserId),InternetMessageId,ISP,tostring(IPTags),DeviceName + | project AccountType,Subject,DataType=sit.SensitiveInformationTypeName,SensitivityLabel,MailboxOwnerUPN = tostring(RawEventData.MailboxOwnerUPN),UserId = tostring(RawEventData.UserId),ISP,IPTags,CountryCode,UserAgent,DeviceName + + - Name: DLPInformationaboutforwarding + DisplayName: Was the email forwarded from the user's mailbox + Description: Fetches the full information about a message including if the message was forwarded. + Inputs: + - Name: user + Description: Specify the user for which the email forwarding information is to be fetched + Required: false + - Name: subject + Description: Specify the subject for which the email forwarding information is to be fetched + Required: false + - Name: InternetMessageId + Description: Specify the InternetMessageId for which the email forwarding information is to be fetched + Required: false + Settings: + Target: Defender + Template: |- + let user = '{{user}}'; + let subject = '{{subject}}'; + let InternetMessageId = '{{InternetMessageId}}'; + CloudAppEvents + | where RawEventData.Workload has 'Exchange' + | where tolower(RawEventData.UserId) has tolower(user) + | where ActionType has "DLPRuleMatch" + | where ObjectId has InternetMessageId + | join kind=inner + ( + EmailEvents + | where Subject has subject + | where InternetMessageId has InternetMessageId + | extend ObjectId = InternetMessageId + ) + on ObjectId + | summarize by Subject,RecipientEmailAddress,InternetMessageId + + - Name: SensitiveUnlabelledDocumentOperations + DisplayName: Show unlabelled document operations across departments in contrast to labelled + Description: Fetches the unlabelled document operations across departments in contrast to labelled + Inputs: + - Name: department + Description: Specify the department for which the unlabelled document operations are to be fetched + Required: false + Settings: + Target: Defender + Template: |- + let department = '{{department}}'; + let content = ( + CloudAppEvents + | where Timestamp > ago(30d) + | where ActionType in ('FileCopiedToClipboard', 'FileCopiedToNetworkShare', 'FileCopiedToRemoteDesktopSession', 'FileCopiedToRemovableMedia', 'FilePrinted', 'FileSensitivityLabelApplied', 'FileUploadedToCloud','FileUploaded','SharingSet', 'FileRead','FileRenamed', 'MipLabel', 'SensitivityLabelApplied', 'SensitivityLabeledFileOpened', 'SensitivityLabelRemoved', 'SensitivityLabelUpdated', 'ArchiveCreated', 'FileAccessedByUnallowedApp', 'FileArchived','FileAccessed', 'FileCreatedOnNetworkShare', 'FileCreatedOnRemovableMedia', 'FileModified', 'RemoveProtection','FileTransferredByBluetooth', 'WebpagePrinted', 'WebpageCopiedToClipboard', 'WebpageSavedToLocal', 'PastedToBrowser') + | where Application <> 'Microsoft Exchange Online' + | extend ObjectId_ = tostring(RawEventData.ObjectId) + | where ObjectId_ !has 'AppData' + | where ActivityType <> 'Send' + | extend LabelGUID1 = parse_json(tostring(RawEventData.SensitivityLabelEventData)).SensitivityLabelId + | extend LabelGUID2 = iff(isempty(tostring(RawEventData.LabelId)), LabelGUID1, tostring(RawEventData.LabelId)) + | extend LabelGUID3 = iff(isempty(tostring(RawEventData.SensitivityLabelId)), LabelGUID2, tostring(RawEventData.SensitivityLabelId)) + | extend OldSensitivityLabelId = tostring(parse_json(tostring(RawEventData.SensitivityLabelEventData)).OldSensitivityLabelId) + | extend LabelGUID = iff(isempty(tostring(LabelGUID3)),OldSensitivityLabelId, tostring(LabelGUID3)) + | extend userid= tolower(RawEventData.UserId) + ); + let unlabelledcontent = ( + content + | where LabelGUID =='' + |summarize count() by userid + | extend Type = 'UnLabelledOps' + ); + let labelledcontent=( + content + | where LabelGUID !='' + | summarize count() by userid + | extend Type = 'LabelledOps'); + union unlabelledcontent,labelledcontent + | join kind=leftouter + ( + IdentityInfo + | where Timestamp > ago(7d) + | extend AccountUpn = tolower(AccountUpn) + ) + on $left.userid == $right.AccountUpn + | where Department has department + | summarize arg_max(Timestamp, *) by userid,Type + | summarize sum(count_) by Department,Type + + - Name: SensitiveInformationOperationsbyDepartment + DisplayName: Show Sensitive document operations across based on Sensitive Information Type, department and location + Description: Fetches the sensitive document operations across based on Sensitive Information Type, department and location + Inputs: + - Name: department + Description: Specify the department for which the sensitive document operations are to be fetched + Required: false + - Name: sitname + Description: Specify the sensitive information type for which the sensitive document operations are to be fetched + Required: false + Settings: + Target: Defender + Template: |- + let department = '{{department}}'; + let sitvar = '{{sitname}}'; + let endpoint = ( + CloudAppEvents + | where parse_json(tostring(RawEventData.SensitiveInfoTypeData)) != '' + | where parse_json(tostring(RawEventData.SensitiveInfoTypeData)) != '[]' + | extend sit = parse_json(tostring(RawEventData.SensitiveInfoTypeData)) + | mv-expand sit + | where sit.SensitiveInfoTypeName has sitvar + | extend UserId = tolower(RawEventData.UserId) + | extend TargetDomain = tostring(RawEventData.TargetDomain) + | extend ObjectName = iff(isempty(ObjectName),tostring(RawEventData.ObjectId),ObjectName) + | summarize count() by Datatype=tostring(sit.SensitiveInfoTypeName),CountryCode,City,UserId,ObjectName,tostring(RawEventData.Workload),TargetDomain); + let office = ( + CloudAppEvents + | where ActionType has 'DLPRuleMatch' + | extend sit = parse_json(tostring(parse_json(tostring(parse_json(tostring(parse_json(tostring(RawEventData.PolicyDetails))[0].Rules))[0].ConditionsMatched)).SensitiveInformation)) + | where sit != '' + | mv-expand sit + | extend UserId = tolower(RawEventData.UserId) + | extend sit_SensitiveInfoTypeName = tostring(sit.SensitiveInformationTypeName) + | join kind=inner + (CloudAppEvents + | where ActionType !has 'DLPRuleMatch' + | project ISP, ObjectId,IPTags,ActionType,CountryCode,City + | where CountryCode != '' + ) on ObjectId + | extend CountryCode = CountryCode1 + | extend City = City1 + | summarize count() by Datatype=sit_SensitiveInfoTypeName,UserId,ObjectName,tostring(RawEventData.Workload),CountryCode,City); + union endpoint,office + | join kind=inner + ( + IdentityInfo + | where Timestamp > ago(7d) + | extend AccountUpn = tolower(AccountUpn) + ) + on $left.UserId == $right.AccountUpn + | where Department has department + | summarize arg_max(Timestamp, *) by Datatype,CountryCode,City + | summarize sum(count_) by Datatype,CountryCode,City,Department + + - Name: SensitiveLabelledActivitybyCVEorVendor + DisplayName: Get the sensitivity label file activity based on CVE or Vendor + Description: Fetches the sensitivity label file activity happening on the Device associated with the CVE or Vendor + Inputs: + - Name: CVE + Description: The CVE to investigate across the file activity + Required: false + - Name: Vendor + Description: The vendor to investigate across the file activity + Required: false + - Name: ISP + Description: Filter the information for a specific ISP + Required: false + - Name: IPTag + Description: Filter the information for a specific IPTag + Required: false + Settings: + Target: Defender + Template: |- + let cveid1 = '{{CVE}}'; + let vendor = '{{Vendor}}'; + let ispvar = '{{ISP}}'; + let iptagvar = '{{IPTag}}'; + CloudAppEvents + | where RawEventData.MDATPDeviceId != '' + | where ISP has ispvar + | extend IPTags = tostring(IPTags) + | where IPTags has iptagvar + | where Timestamp > ago(31d) + | where ActionType in ('FileCopiedToClipboard', 'FileCopiedToNetworkShare', 'FileCopiedToRemoteDesktopSession', 'FileCopiedToRemovableMedia', 'FilePrinted', 'FileSensitivityLabelApplied', 'FileUploadedToCloud','FileUploaded','SharingSet', 'FileRead','FileRenamed', 'MipLabel', 'SensitivityLabelApplied', 'SensitivityLabeledFileOpened', 'SensitivityLabelRemoved', 'SensitivityLabelUpdated', 'ArchiveCreated', 'FileAccessedByUnallowedApp', 'FileArchived','FileAccessed', 'FileCreatedOnNetworkShare', 'FileCreatedOnRemovableMedia', 'FileModified', 'RemoveProtection','FileTransferredByBluetooth', 'WebpagePrinted', 'WebpageCopiedToClipboard', 'WebpageSavedToLocal', 'PastedToBrowser','FileSensitivityLabelChanged','FileDownloaded','SensitivityLabelPolicyMatched','SharingLinkCreated','SharingLinkUsed') + | where Application <> 'Microsoft Exchange Online' + | extend ObjectId_ = tostring(RawEventData.ObjectId) + | where ObjectId_ !has 'AppData' + | where ActivityType <> 'Send' + | extend LabelGUID1 = parse_json(tostring(RawEventData.SensitivityLabelEventData)).SensitivityLabelId + | extend LabelGUID2 = iff(isempty(tostring(RawEventData.LabelId)), LabelGUID1, tostring(RawEventData.LabelId)) + | extend LabelGUID3 = iff(isempty(tostring(RawEventData.SensitivityLabelId)), LabelGUID2, tostring(RawEventData.SensitivityLabelId)) + | extend OldSensitivityLabelId = tostring(parse_json(tostring(RawEventData.SensitivityLabelEventData)).OldSensitivityLabelId) + | extend LabelGUID = iff(isempty(tostring(LabelGUID3)),OldSensitivityLabelId, tostring(LabelGUID3)) + | where LabelGUID !='' + | extend userid = tolower(RawEventData.UserId) + | extend DeviceId = tostring(RawEventData.MDATPDeviceId) + | extend AppUsed = tostring(RawEventData.Application) + | join kind = inner + ( + DeviceTvmSoftwareVulnerabilities + | extend cveid1 = iff(isempty(cveid1),'NotSet',cveid1) + | extend vendor = iff(isempty(vendor),'NotSet',vendor) + | where CveId has cveid1 or SoftwareVendor has vendor + ) + on DeviceId + | summarize count() by LabelGUID,City,userid,CountryCode,ISP,tostring(IPTags),DeviceName,AppUsed + | join kind=leftouter + ( + IdentityInfo + | where Timestamp > ago(7d) + | extend AccountUpn = tolower(AccountUpn) + ) + on $left.userid == $right.AccountUpn + | summarize arg_max(Timestamp, *) by LabelGUID,Department,Country,userid,City,DeviceName,ISP,AppUsed,tostring(IPTags) + | summarize sum(count_) by LabelGUID,Department,City,CountryCode,userid,ISP,tostring(IPTags),DeviceName,AppUsed + + - Name: SensitiveLabelledActivityoutliers + DisplayName: Get the sensitivity label file activity that are outliers or anomalies + Description: Fetches the sensitivity label file activity happening and identifies the outliers and anomalies + Inputs: + - Name: ISP + Description: Filter the information for a specific ISP + Required: false + - Name: IPTag + Description: Filter the information for a specific IPTag + Required: false + Settings: + Target: Defender + Template: |- + let ispvar = '{{ISP}}'; + let iptagvar = '{{IPTag}}'; + CloudAppEvents + | where Timestamp > ago(14d) + | where RawEventData.MDATPDeviceId != '' + | where ISP has ispvar + | extend IPTags = tostring(IPTags) + | where IPTags has iptagvar + | where ActionType in ('FileCopiedToClipboard', 'FileCopiedToNetworkShare', 'FileCopiedToRemoteDesktopSession', 'FileCopiedToRemovableMedia', 'FilePrinted', 'FileSensitivityLabelApplied', 'FileUploadedToCloud','FileUploaded','SharingSet', 'FileRead','FileRenamed', 'MipLabel', 'SensitivityLabelApplied', 'SensitivityLabeledFileOpened', 'SensitivityLabelRemoved', 'SensitivityLabelUpdated', 'ArchiveCreated', 'FileAccessedByUnallowedApp', 'FileArchived','FileAccessed', 'FileCreatedOnNetworkShare', 'FileCreatedOnRemovableMedia', 'FileModified', 'RemoveProtection','FileTransferredByBluetooth', 'WebpagePrinted', 'WebpageCopiedToClipboard', 'WebpageSavedToLocal', 'PastedToBrowser','FileSensitivityLabelChanged','FileDownloaded','SensitivityLabelPolicyMatched','SharingLinkCreated','SharingLinkUsed') + | where Application <> 'Microsoft Exchange Online' + | extend ObjectId_ = tostring(RawEventData.ObjectId) + | where ObjectId_ !has 'AppData' + | where ActivityType <> 'Send' + | extend LabelGUID1 = parse_json(tostring(RawEventData.SensitivityLabelEventData)).SensitivityLabelId + | extend LabelGUID2 = iff(isempty(tostring(RawEventData.LabelId)), LabelGUID1, tostring(RawEventData.LabelId)) + | extend LabelGUID3 = iff(isempty(tostring(RawEventData.SensitivityLabelId)), LabelGUID2, tostring(RawEventData.SensitivityLabelId)) + | extend OldSensitivityLabelId = tostring(parse_json(tostring(RawEventData.SensitivityLabelEventData)).OldSensitivityLabelId) + | extend LabelGUID = iff(isempty(tostring(LabelGUID3)),OldSensitivityLabelId, tostring(LabelGUID3)) + | where LabelGUID !='' + | extend userid = tolower(RawEventData.UserId) + | extend DeviceId = tostring(RawEventData.MDATPDeviceId) + | extend AppUsed = tostring(RawEventData.Application) + | summarize EventCount=count() by userid,LabelGUID,ISP,tostring(IPTags),AppUsed, bin(Timestamp,30m) + | order by Timestamp + | summarize EventCount=make_list(EventCount),TimeGenerated=make_list(Timestamp) by userid,ISP,tostring(IPTags),AppUsed + | extend outliers=series_decompose_anomalies(EventCount) + | mv-expand TimeGenerated, EventCount, outliers + | where outliers == 1 + | summarize aggregate=sum(todecimal(EventCount)) by userid,ISP,tostring(IPTags),AppUsed + + - Name: SensitiveEndpointactivitybyCVEorVendor + DisplayName: Get the sensitive file activity based on CVE or Vendor + Description: Fetches the sensitive file activity happening on the Device associated with the CVE or Vendor + Inputs: + - Name: CVE + Description: The CVE to investigate across the file activity + Required: false + - Name: Vendor + Description: The vendor to investigate across the file activity + Required: false + - Name: ISP + Description: Filter the information for a specific ISP + Required: false + - Name: IPTag + Description: Filter the information for a specific IPTag + Required: false + - Name: sit + Description: Filter the information for a specific Sensitivity Information Type + Required: false + Settings: + Target: Defender + Template: |- + let cveid = '{{CVE}}'; + let vendor = '{{Vendor}}'; + let ispvar = '{{ISP}}'; + let iptagvar = '{{IPTag}}'; + let sitvar = '{{sit}}'; + DeviceTvmSoftwareVulnerabilities + | where CveId has cveid + | where SoftwareVendor has vendor + | join kind = inner + ( + CloudAppEvents + | where ISP has ispvar + | extend IPTags = tostring(IPTags) + | where IPTags has iptagvar + | where parse_json(tostring(RawEventData.SensitiveInfoTypeData)) != "" + | where parse_json(tostring(RawEventData.SensitiveInfoTypeData)) != "[]" + | extend sit = parse_json(tostring(RawEventData.SensitiveInfoTypeData)) + | mv-expand sit + | extend UserId = tolower(RawEventData.UserId) + | extend SensitiveInfoTypeName = tostring(sit.SensitiveInfoTypeName) + | where SensitiveInfoTypeName has sitvar + | extend document = tostring(RawEventData.ObjectId) + | extend DeviceId = tostring(RawEventData.MDATPDeviceId) + | extend AppUsed = tostring(RawEventData.Application) + ) on DeviceId + | summarize count = count() by SensitiveInfoTypeName,DeviceName,CountryCode,SoftwareName,SoftwareVersion,AppUsed,ISP,tostring(IPTags) + + + - Name: SensitiveEndpointactivitybyNetwork + DisplayName: Get the sensitive file activity based on network ISP or IPTags + Description: Fetches the sensitive file activity happening on the Device associated with the network ISP or IPTags + Inputs: + - Name: Vendor + Description: The vendor to investigate across the file activity + Required: false + - Name: ISP + Description: Filter the information for a specific ISP + Required: false + - Name: IPTag + Description: Filter the information for a specific IPTag + Required: false + - Name: sit + Description: Filter the information for a specific Sensitivity Information Type + Required: false + Settings: + Target: Defender + Template: |- + let vendor = '{{Vendor}}'; + let ispvar = '{{ISP}}'; + let iptagvar = '{{IPTag}}'; + let sitvar = '{{sit}}'; + CloudAppEvents + | where ISP has ispvar + | extend IPTags = tostring(IPTags) + | where IPTags has iptagvar + | where parse_json(tostring(RawEventData.SensitiveInfoTypeData)) != "" + | where parse_json(tostring(RawEventData.SensitiveInfoTypeData)) != "[]" + | extend sit = parse_json(tostring(RawEventData.SensitiveInfoTypeData)) + | mv-expand sit + | extend UserId = tolower(RawEventData.UserId) + | extend SensitiveInfoTypeName = tostring(sit.SensitiveInfoTypeName) + | where SensitiveInfoTypeName has sitvar + | extend document = tostring(RawEventData.ObjectId) + | extend DeviceId = tostring(RawEventData.MDATPDeviceId) + | extend AppUsed = tostring(RawEventData.Application) + | summarize count = count() by SensitiveInfoTypeName,CountryCode,AppUsed,ISP,tostring(IPTags),DeviceId + + - Name: SensitiveContentAnomalies + DisplayName: Get Sensitive content anomalies from the log stream + Description: Fetches the Sensitive content anomalies from the log stream + Inputs: + - Name: ISP + Description: Filter the information for a specific ISP + Required: false + - Name: IPTag + Description: Filter the information for a specific IPTag + Required: false + Settings: + Target: Defender + Template: |- + let ispvar = '{{ISP}}'; + let iptagvar = '{{IPTag}}'; + let min_peak_t=now(-30d); + let max_peak_t=now(-14d); + let min_baseline_t=now(-13d); + let max_baseline_t=now(-1d); + let splitime=(max_baseline_t+min_peak_t)/2.0; + CloudAppEvents + | where ISP has ispvar + | extend IPTags = tostring(IPTags) + | where IPTags has iptagvar + | where parse_json(tostring(RawEventData.SensitiveInfoTypeData)) != "" + | where parse_json(tostring(RawEventData.SensitiveInfoTypeData)) != "[]" + | extend sit = parse_json(tostring(RawEventData.SensitiveInfoTypeData)) + | mv-expand sit + | extend SensitiveInfoTypeName = tostring(sit.SensitiveInfoTypeName) + | extend UserId = tostring(RawEventData.UserId) + | extend document = tostring(RawEventData.ObjectId) + | project Timestamp,document,UserId,SensitiveInfoTypeName,DeviceType,ISP,IPTags + | where (Timestamp between(min_baseline_t..max_baseline_t)) or + (Timestamp between(min_peak_t..max_peak_t)) + | extend AB=iff(Timestamp > splitime, 'Anomaly', 'Baseline') + | evaluate diffpatterns(AB, 'Anomaly', 'Baseline') + + + - Name: SensitiveEndpointinternetfacing + DisplayName: Get the sensitive file activity for devices that are internet facing + Description: Fetches the sensitive file activity happening on the Devices that are internet facing + Inputs: + - Name: sit + Description: Filter the information for a specific Sensitivity Information Type + Required: false + Settings: + Target: Defender + Template: |- + let sitvar = '{{sit}}'; + DeviceInfo + | where Timestamp > ago(7d) + | where IsInternetFacing + | extend InternetFacingInfo = AdditionalFields + | extend InternetFacingReason = extractjson("$.InternetFacingReason", InternetFacingInfo, typeof(string)), InternetFacingLocalPort = extractjson("$.InternetFacingLocalPort", InternetFacingInfo, typeof(int)), InternetFacingScannedPublicPort = extractjson("$.InternetFacingPublicScannedPort", InternetFacingInfo, typeof(int)), InternetFacingScannedPublicIp = extractjson("$.InternetFacingPublicScannedIp", InternetFacingInfo, typeof(string)), InternetFacingLocalIp = extractjson("$.InternetFacingLocalIp", InternetFacingInfo, typeof(string)), InternetFacingTransportProtocol=extractjson("$.InternetFacingTransportProtocol", InternetFacingInfo, typeof(string)), InternetFacingLastSeen = extractjson("$.InternetFacingLastSeen", InternetFacingInfo, typeof(datetime)) + | summarize arg_max(Timestamp, *) by DeviceId + | project DeviceId,InternetFacingInfo,DeviceName + | join kind = inner + ( + CloudAppEvents + | where Timestamp > ago(31d) + | where parse_json(tostring(RawEventData.SensitiveInfoTypeData)) != "" + | where parse_json(tostring(RawEventData.SensitiveInfoTypeData)) != "[]" + | extend sit = parse_json(tostring(RawEventData.SensitiveInfoTypeData)) + | mv-expand sit + | extend UserId = tolower(RawEventData.UserId) + | extend SensitiveInfoTypeName = tostring(sit.SensitiveInfoTypeName) + | where SensitiveInfoTypeName has sitvar + | extend document = tostring(RawEventData.ObjectId) + | extend DeviceId = tostring(RawEventData.MDATPDeviceId) + | extend AppUsed = tostring(RawEventData.Application) + ) on DeviceId + | summarize count = count() by SensitiveInfoTypeName,DeviceName,CountryCode,InternetFacingInfo,AppUsed + + - Name: SensitiveContentSummaryofApplications + DisplayName: Get Applications that are accessing sensitive content + Description: Fetches the Applications that are accessing sensitive content + Settings: + Target: Defender + Template: |- + CloudAppEvents + | where parse_json(tostring(RawEventData.SensitiveInfoTypeData)) != "" + | where parse_json(tostring(RawEventData.SensitiveInfoTypeData)) != "[]" + | extend sit = parse_json(tostring(RawEventData.SensitiveInfoTypeData)) + | mv-expand sit + | extend UserId = tolower(RawEventData.UserId) + | extend SensitiveInfoTypeName = tostring(sit.SensitiveInfoTypeName) + | extend document = tostring(RawEventData.ObjectId) + | extend DeviceId = tostring(RawEventData.MDATPDeviceId) + | extend AppUsed = tolower(RawEventData.Application) + | where AppUsed !="" + | summarize by AppUsed,DeviceId,SensitiveInfoTypeName + | join kind=inner + ( + DeviceProcessEvents + | where Timestamp > ago(7d) + | extend AppUsed = tolower(FileName) + | summarize by AppUsed,SHA256,ProcessVersionInfoCompanyName,ProcessVersionInfoProductName,DeviceId + ) + on AppUsed,DeviceId + | summarize count() by AppUsed,SHA256,ProcessVersionInfoCompanyName,ProcessVersionInfoProductName + | sort by count_ + + - Name: SensitiveContentApplicationdetail + DisplayName: Get the Sensitive data types that an Application is accessing + Description: Fetches the Sensitive data types that and Application is accessing + Inputs: + - Name: ApplicationName + Description: Filter the information for a specific Application + Required: true + - Name: SensitiveInfoTypeName + Description: Filter the information for a specific Sensitive Information Type + Required: false + Settings: + Target: Defender + Template: |- + let sitvar = '{{SensitiveInfoTypeName}}'; + let app = '{{ApplicationName}}'; + CloudAppEvents + | where parse_json(tostring(RawEventData.SensitiveInfoTypeData)) != "" + | where parse_json(tostring(RawEventData.SensitiveInfoTypeData)) != "[]" + | extend sit = parse_json(tostring(RawEventData.SensitiveInfoTypeData)) + | mv-expand sit + | extend UserId = tolower(RawEventData.UserId) + | extend SensitiveInfoTypeName = tostring(sit.SensitiveInfoTypeName) + | where SensitiveInfoTypeName has sitvar + | extend document = tostring(RawEventData.ObjectId) + | extend DeviceId = tostring(RawEventData.MDATPDeviceId) + | extend AppUsed = tolower(RawEventData.Application) + | where AppUsed !="" + | where AppUsed has tolower(app) + | summarize by AppUsed,DeviceId,SensitiveInfoTypeName + | join kind=inner + ( + DeviceProcessEvents + | where Timestamp > ago(7d) + | extend AppUsed = tolower(FileName) + | summarize by AppUsed,SHA256,ProcessVersionInfoCompanyName,ProcessVersionInfoProductName,DeviceId + ) + on AppUsed,DeviceId + | summarize count() by AppUsed,SHA256,ProcessVersionInfoCompanyName,ProcessVersionInfoProductName,SensitiveInfoTypeName + | sort by count_ + + - Name: SensitiveContentAppNetworkActivity + DisplayName: Get Application network activity for apps accessing sensitive content + Description: Fetches the Application network activity for apps accessing sensitive content + Inputs: + - Name: ApplicationName + Description: Filter the information for a specific Application + Required: true + - Name: SensitiveInfoTypeName + Description: Filter the information for a specific Sensitive Information Type + Required: false + Settings: + Target: Defender + Template: |- + let sitvar = '{{SensitiveInfoTypeName}}'; + let app = '{{ApplicationName}}'; + let corequery = ( + CloudAppEvents + | where parse_json(tostring(RawEventData.SensitiveInfoTypeData)) != "" + | where parse_json(tostring(RawEventData.SensitiveInfoTypeData)) != "[]" + | extend sit = parse_json(tostring(RawEventData.SensitiveInfoTypeData)) + | mv-expand sit + | extend UserId = tolower(RawEventData.UserId) + | extend SensitiveInfoTypeName = tostring(sit.SensitiveInfoTypeName) + | where SensitiveInfoTypeName has sitvar + | extend document = tostring(RawEventData.ObjectId) + | extend DeviceId = tostring(RawEventData.MDATPDeviceId) + | extend AppUsed = tolower(RawEventData.Application) + | where AppUsed == tolower(app) + | where AppUsed !="" + | summarize count() by AppUsed,SensitiveInfoTypeName,DeviceId + | join kind=inner ( + DeviceNetworkEvents + | extend AppUsed = tolower(InitiatingProcessFileName)) + on AppUsed,DeviceId); + corequery + | summarize count() by DeviceId,AppUsed,SensitiveInfoTypeName,RemotePort,RemoteUrl,RemoteIP,LocalIPType,RemoteIPType,DeviceName,InitiatingProcessSHA256 + + - Name: Getemailbyrecipient + DisplayName: Get email messages by recipient + Description: Fetches the email messages by recipient + Inputs: + - Name: Recipient + Description: Filter the information for a specific Recipient + Required: true + Settings: + Target: Defender + Template: |- + let Recipient = '{{Recipient}}'; + EmailEvents + | where RecipientEmailAddress has Recipient + | project SenderMailFromAddress,Subject,RecipientEmailAddress,AttachmentCount,UrlCount,InternetMessageId + + - Name: MicrosoftCopilotActivityRiskyUser + DisplayName: Get the Microsoft Copilot activity labelled and unlabelled as well as data type activity for Risky Users + Description: Fetches the Microsoft Copilot activity labelled and unlabelled as well as data type activity for Risky Users + Settings: + Target: Defender + Template: |- + CloudAppEvents + | where Timestamp > ago(30d) + | where ActionType has 'CopilotInteraction' + | extend AccessedResources = iif(array_length(RawEventData.CopilotEventData.AccessedResources)==0, todynamic('Empty:Field') , RawEventData.CopilotEventData.AccessedResources) + | mv-expand AccessedResources + | extend Content = url_decode(tostring(AccessedResources.SiteUrl)) + | extend ObjectName = tolower(replace_string(Content,'?web=1','')) + | extend UserId = tostring(RawEventData.UserId) + | extend LabelGUID = tostring(AccessedResources.SensitivityLabelId) + | summarize arg_max(Timestamp, *) by UserId,ObjectName + | join kind=leftouter + (CloudAppEvents + | where ActionType has 'DLPRuleMatch' + | extend sit = parse_json(tostring(parse_json(tostring(parse_json(tostring(parse_json(tostring(RawEventData.PolicyDetails))[0].Rules))[0].ConditionsMatched)).SensitiveInformation)) + | where sit != '' + | mv-expand sit + | extend UserId = tolower(RawEventData.UserId) + | extend ObjectName = tolower(url_decode(tostring(ObjectName))) + | where ObjectName != '' + | extend sit_SensitiveInfoTypeName = tostring(sit.SensitiveInformationTypeName) + | summarize by sit_SensitiveInfoTypeName,ObjectName + | summarize SensitiveInformationTypes = make_list(strcat(sit_SensitiveInfoTypeName)) by ObjectName) + on ObjectName + | extend AISystemPlugin_ = tostring(parse_json(tostring(parse_json(tostring(RawEventData.CopilotEventData)).AISystemPlugin))) + | extend ModelName_ = tostring(parse_json(tostring(parse_json(tostring(RawEventData.CopilotEventData)).ModelTransparencyDetails))[0].ModelName) + | extend Id_ = tostring(RawEventData.Id) + | summarize AccessedSite = make_list(strcat(ObjectName)) by UserId,Id_,LabelGUID,tostring(SensitiveInformationTypes),City,CountryCode + | extend upn = tolower(UserId) + | join kind=inner + ( AlertEvidence + | where Timestamp > ago(31d) + | where ProcessCommandLine =='' + | extend upnsuffix = todynamic (AdditionalFields).UPNSuffix + | extend upn1 = iff(isempty(todynamic (AdditionalFields).Recipient),todynamic (AdditionalFields).Upn,todynamic (AdditionalFields).Recipient) + | extend user = parse_json(tostring(todynamic(AdditionalFields).Account)) + | extend upn2 = strcat(user.Name,'@',user.UPNSuffix) + | extend upn3 = iff(isempty(user.Name),upn1,upn2) + | extend upn4 = strcat(AccountName,'@',AccountDomain) + | extend upn5 = strcat(AccountName,'@',upnsuffix) + | extend upn6 = iff(isempty(upnsuffix),upn4,upn5) + | extend upn = iff(isempty(upn3),upn6,upn3) + | extend upn = tolower(upn) + | extend Type = 'Compromised' + | summarize by upn,AttackTitle=Title + ) on upn + | join kind=leftouter + ( + IdentityInfo + | where Timestamp > ago(7d) + | extend AccountUpn = tolower(AccountUpn) + | summarize arg_max(Timestamp, *) by AccountUpn + ) + on $left.UserId == $right.AccountUpn + | summarize by tostring(AccessedSite),LabelGUID,UserId,AttackTitle,DataType=tostring(SensitiveInformationTypes),Department,Manager + + + - Name: MicrosoftCopilotActivityperlocation + DisplayName: Get the Microsoft Copilot activity labelled and unlabelled as well as data type based on user location and department + Description: Fetches the Microsoft Copilot activity labelled and unlabelled as well as data type based on user location and department + Inputs: + - Name: Department + Description: Filter the information for a specific Department + Required: false + - Name: LabelGUID + Description: Filter the information for a specific LabelGUID + Required: false + Settings: + Target: Defender + Template: |- + let departmentvar = '{{Department}}'; + let LabelGUIDvar = '{{LabelGUID}}'; + CloudAppEvents + | where Timestamp > ago(30d) + | where ActionType has 'CopilotInteraction' + | extend AccessedResources = iif(array_length(RawEventData.CopilotEventData.AccessedResources)==0, todynamic('Empty:Field') , RawEventData.CopilotEventData.AccessedResources) + | mv-expand AccessedResources + | extend Content = url_decode(tostring(AccessedResources.SiteUrl)) + | extend ObjectName = tolower(replace_string(Content,'?web=1','')) + | extend UserId = tostring(RawEventData.UserId) + | extend LabelGUID = tostring(AccessedResources.SensitivityLabelId) + | where LabelGUID == LabelGUIDvar + | extend App = tostring(RawEventData.CopilotEventData.AppHost) + | summarize arg_max(Timestamp, *) by UserId,ObjectName,App + | join kind=leftouter + (CloudAppEvents + | where ActionType has 'DLPRuleMatch' + | extend sit = parse_json(tostring(parse_json(tostring(parse_json(tostring(parse_json(tostring(RawEventData.PolicyDetails))[0].Rules))[0].ConditionsMatched)).SensitiveInformation)) + | where sit != '' + | mv-expand sit + | extend UserId = tolower(RawEventData.UserId) + | extend ObjectName = tolower(url_decode(tostring(ObjectName))) + | where ObjectName != '' + | extend sit_SensitiveInfoTypeName = tostring(sit.SensitiveInformationTypeName) + | summarize by sit_SensitiveInfoTypeName,ObjectName + | summarize SensitiveInformationTypes = make_list(strcat(sit_SensitiveInfoTypeName)) by ObjectName) + on ObjectName + | extend AISystemPlugin_ = tostring(parse_json(tostring(parse_json(tostring(RawEventData.CopilotEventData)).AISystemPlugin))) + | extend ModelName_ = tostring(parse_json(tostring(parse_json(tostring(RawEventData.CopilotEventData)).ModelTransparencyDetails))[0].ModelName) + | extend Id_ = tostring(RawEventData.Id) + | summarize AccessedSite = make_list(strcat(ObjectName)) by UserId,Id_,LabelGUID,tostring(SensitiveInformationTypes),City,CountryCode,App + | join kind=leftouter + ( + IdentityInfo + | where Timestamp > ago(7d) + | extend AccountUpn = tolower(AccountUpn) + | summarize arg_max(Timestamp, *) by AccountUpn + ) + on $left.UserId == $right.AccountUpn + | where Department has departmentvar + | summarize by AccessedSites=tostring(AccessedSite),RequestId=Id_,App,LabelGUID,tostring(SensitiveInformationTypes),City,CountryCode,Department,JobTitle,Manager,BlastRadius,RiskLevel,RiskLevelDetails + + - Name: TopUsersByDLPEvidence + DisplayName: Get Top Users with Data Loss Prevention Alerts + Description: Identifies the top users based on the number of Microsoft Data Loss Prevention alerts and provides a breakdown of alert severity (high, medium, or low) for the last 7 days. + Settings: + Target: Defender + Template: |- + let timeframe = 7d; + AlertEvidence + | where TimeGenerated > ago(timeframe) + | where DetectionSource == "Microsoft Data Loss Prevention" + | extend AF = todynamic(AdditionalFields) + // Don't parse Tags yet, just get basic user info first + | extend User = case( + isnotempty(AccountUpn), AccountUpn, + isnotempty(AF.Name) and AF.Type =~ "account", tostring(AF.Name), + "" + ) + | where isnotempty(User) + | summarize TotalAlerts = count() by User + | top 5 by TotalAlerts + | join kind=inner ( + AlertEvidence + | where TimeGenerated > ago(timeframe) + | where DetectionSource == "Microsoft Data Loss Prevention" + | extend AF = todynamic(AdditionalFields) + | extend TagInfo = AF.Tags + | mv-expand TagInfo + | extend User = case( + isnotempty(AccountUpn), AccountUpn, + isnotempty(AF.Name) and AF.Type =~ "account", tostring(AF.Name), + "" + ) + | extend TagName = tostring(TagInfo.TagName) + | extend Severity = case( + TagName == "external_user_risk_tag", "High", + "Medium" + ) + | summarize + HighSeverity = countif(Severity == "High"), + MediumSeverity = countif(Severity == "Medium"), + LowSeverity = countif(Severity == "Low") + by User + ) on User + | project + User, + TotalAlerts, + HighSeverity, + MediumSeverity, + LowSeverity + | sort by TotalAlerts desc + + - Name: DLPAlertsBySeverityAndUser + DisplayName: Get DLP Alerts by Severity for a Specific User + Description: Retrieves detailed Data Loss Prevention (DLP) alerts for a specific user, filtered by severity. The query provides insights into matched policies, sensitive information types, associated files, confidence levels, enforced actions, and sender/recipient details. Designed to help analysts identify patterns, consolidate alerts, and prioritize incidents for efficient resolution. + Inputs: + - Name: User + Description: The specific user ID to query DLP alerts for (e.g., user@example.com). + Required: true + - Name: Severity + Description: The severity level to filter the alerts (e.g., High, Medium, Low). + Required: true + Settings: + Target: Defender + Template: |- + let user = '{{User}}'; + let severityLevel = '{{Severity}}'; + CloudAppEvents + | where ActionType has "DLPRuleMatch" + and RawEventData.UserId has user + and parse_json(tostring(parse_json(tostring(parse_json(tostring(RawEventData.PolicyDetails))[0].Rules))[0].Severity)) == severityLevel + | extend PolicyName = tostring(parse_json(tostring(RawEventData.PolicyDetails))[0].PolicyName) + | extend SensitiveInformationTypeName = tostring(parse_json(tostring(parse_json(tostring(parse_json(tostring(parse_json(tostring(RawEventData.PolicyDetails))[0].Rules))[0].ConditionsMatched)).SensitiveInformation))[0].SensitiveInformationTypeName) + | extend FileName = tostring(parse_json(tostring(parse_json(tostring(RawEventData.ExchangeMetaData)).AttachmentDetails))[0].Name) + | extend FileSize = toint(parse_json(tostring(parse_json(tostring(RawEventData.ExchangeMetaData)).AttachmentDetails))[0].Size) + | extend ConfidenceLevel = toint(parse_json(tostring(parse_json(tostring(parse_json(tostring(parse_json(tostring(RawEventData.PolicyDetails))[0].Rules))[0].ConditionsMatched)).SensitiveInformation))[0].Confidence) + | extend Actions = tostring(parse_json(tostring(parse_json(tostring(parse_json(tostring(RawEventData.PolicyDetails))[0].Rules))[0])).Actions) + | extend Recipient = tostring(parse_json(tostring(parse_json(tostring(RawEventData.ExchangeMetaData)).To))[0]) + | extend Sender = tostring(parse_json(tostring(RawEventData.ExchangeMetaData)).From) + | extend IncidentId = tostring(parse_json(tostring(RawEventData)).IncidentId) + | extend Timestamp = todatetime(RawEventData.CreationTime) + | project Timestamp, PolicyName, SensitiveInformationTypeName, FileName, FileSize, ConfidenceLevel, Actions, Sender, Recipient, Severity = severityLevel, UserId = RawEventData.UserId, IncidentId + | order by Timestamp desc + + - Name: DLPAlertsTopUsersOverview + DisplayName: Get Top Users Triggering DLP Alerts + Description: This KQL query analyzes DLP rule matches and alerts from CloudAppEvents, identifying users with the highest sensitive info detections and alert counts across SharePoint, OneDrive, Teams, and Endpoints. It distinguishes between alerts with sensitive information types (higher regulatory risk) and alerts without SITs (potential data exfiltration risks), then aggregates alerts by severity, joins them with sensitive info detections, and ranks the top 10 users with the most DLP incidents to highlight potential compliance violations and security threats. + Settings: + Target: Defender + Template: |- + let timerange = 30d; // Define the time range for the query + // First create a temporary result of sensitive info counts per user + let SensitiveInfoCounts = CloudAppEvents + | where TimeGenerated > ago(timerange) // Filter events within the specified time range + | where ActionType == "DlpRuleMatch" // Filter for DLP rule match events + | extend parsedEventData = parse_json(RawEventData) // Parse RawEventData into a structured format + | extend Workload = parsedEventData.Workload // Extract the workload + //| where Workload != "Exchange" // Uncomment Filter out Exchange events + | extend UserPrincipalName = tostring(parsedEventData.UserId) // Extract the user principal name + | extend PolicyDetails = parsedEventData.PolicyDetails // Extract policy details + | mv-expand PolicyDetails // Expand the PolicyDetails array (one row per policy match) + | extend Rules = PolicyDetails.Rules // Extract the Rules field + | mv-expand Rules // Expand the Rules array (one row per rule match) + | extend ConditionsMatched = parse_json(Rules.ConditionsMatched) // Parse ConditionsMatched to access sensitive info + | extend SensitiveInformation = ConditionsMatched.SensitiveInformation // Extract the SensitiveInformation array + | mv-expand SensitiveInformation // Expand the SensitiveInformation array (one row per type) + | where isnotempty(SensitiveInformation) // Remove rows where SensitiveInformation is empty + | summarize SensitiveInfoAlertCount = count() by UserPrincipalName; // Count sensitive info alerts per user + // Main query + CloudAppEvents + | where Timestamp > ago(timerange) // Filter events within the specified time range + | extend RawData = parse_json(RawEventData) // Parse RawEventData into a structured format + | where RawData.Category == "DataLossPrevention" // Filter for Data Loss Prevention (DLP) events + | where RawData.Operation == "AlertTriggered" // Filter for alerts that were triggered + | extend DLPAlertId = tostring(RawData.AlertId) // Extract the DLP alert ID + | extend Data = parse_json(tostring(RawData.Data)) // Parse the Data field for more details + | extend UserPrincipalName = tostring(Data.f3u) // Extract the user principal name + | extend MessageType = case( // Categorize the type of message + Data.wl == "SharePoint" or Data.wl == "OneDrive", "file", + Data.wl == "MicrosoftTeams", "teams", + Data.wl == "Endpoint", "endpoint", + Data.wl == "Exchange", "exchange", + "other" + ) + //| where MessageType != "Exchange" // Uncomment this to include exchange alerts + | extend Severity = tostring(RawData.Severity) // Extract severity level of the alert + | summarize + AlertCount = count(), // Count total alerts + FileAlerts = countif(MessageType == "file"), // Count file-related alerts + TeamsAlerts = countif(MessageType == "teams"), + ExchangeAlerts = countif(MessageType == "exchange"), // Count Exchange-related alerts + EndpointAlerts = countif(MessageType == "endpoint"), // Count endpoint-related alerts + ExternalCount = countif(Data.dmc has "IncludeExternalUsers") // Count alerts involving external users + by UserPrincipalName, Severity + | summarize + AlertsByUser = make_bag(pack(Severity, AlertCount)), // Organize alerts by severity + FileAlerts = sum(FileAlerts), // Sum of file-related alerts + TeamsAlerts = sum(TeamsAlerts), // Sum of Teams-related alerts + ExchangeAlerts = sum(ExchangeAlerts), // Sum of Exchange-related alerts + EndpointAlerts = sum(EndpointAlerts), // Sum of endpoint-related alerts + ExternalCount = sum(ExternalCount), // Sum of alerts involving external users + TotalAlerts = sum(AlertCount) // Total alerts per user + by UserPrincipalName + | extend + HighAlerts = tolong(AlertsByUser.High), // Extract high-severity alerts count + MediumAlerts = tolong(AlertsByUser.Medium), // Extract medium-severity alerts count + LowAlerts = tolong(AlertsByUser.Low), // Extract low-severity alerts count + ExternalPercentage = round((ExternalCount * 100.0 / TotalAlerts), 2) // Calculate external alerts percentage + // Join with the sensitive info counts + | lookup SensitiveInfoCounts on UserPrincipalName + | project + UserPrincipalName, // User who triggered the alerts + TotalAlerts, // Total number of alerts + SensitiveInfoAlertCount = coalesce(SensitiveInfoAlertCount, 0), // Count of sensitive info alerts per user + HighAlerts = coalesce(HighAlerts, 0), // Count of high-severity alerts + MediumAlerts = coalesce(MediumAlerts, 0), // Count of medium-severity alerts + LowAlerts = coalesce(LowAlerts, 0), // Count of low-severity alerts + FileAlerts, // Count of file-related alerts + TeamsAlerts, // Count of Teams-related alerts + EndpointAlerts, // Count of endpoint-related alerts + ExchangeAlerts, // Count of exchange-related alerts + ExternalCount, // Count of alerts involving external users + ExternalPercentage // Percentage of external-related alerts + | top 10 by TotalAlerts desc // Retrieve the top 10 users with the most alerts + + - Name: DLPAlertDetailsWithPurviewLink + DisplayName: Get Detailed DLP Alert Info with Purview Alert URL + Description: This query retrieves detailed information about DLP alerts, including alert ID, policy name, severity, user details, and generates a Purview URL for quick navigation. + Inputs: + - Name: UPN + Description: UPN of the user to search + Required: true + Settings: + Target: Defender + Template: |- + let timerange = 30d; // Define the time range for the query + let searchuser = "{{UPN}}"; // Define the user principal name (UPN) to search for + CloudAppEvents + | where Timestamp > ago(timerange) // Filter events within the specified time range + | extend RawData = parse_json(RawEventData) // Parse RawEventData into a structured format + | where RawData.Category == "DataLossPrevention" // Filter for Data Loss Prevention (DLP) events + | where RawData.Operation == "AlertTriggered" // Filter for alerts that were triggered + | extend DataJson = parse_json(tostring(RawData.Data)) // Parse the Data field for more details + | extend + User = tostring(DataJson.f3u), // Extract the user who triggered the event + Subject = tostring(DataJson.von), // Extract the subject of the event + To = tostring(DataJson.["To"]), // Extract recipient information + From = tostring(DataJson.mfrm), // Extract sender information + Workload = tostring(DataJson.wl) // Extract workload information + | where User == searchuser // Filter events specific to the searched user + //| where Workload != "Exchange" // Exclude Exchange-related events + | project + Timestamp, // When the event occurred + User, // User who triggered the event + Workload, // Workload associated with the event + PolicyName = tostring(RawData.Name), // Name of the DLP policy that triggered the event + Severity = tostring(RawData.Severity), // Severity level of the alert + Subject, // Email or document subject + PurviewURL = strcat("https://purview.microsoft.com/datalossprevention/alertspage/fullpage?tid=", + tostring(RawEventData.OrganizationId), + "&alertsviewid=overview&id=", + tostring(RawData.AlertId)) // Construct the Purview alert URL + | sort by User desc // Sort results by User in descending order + + - Name: DLPAlertsSensitiveInfoTypes + DisplayName: DLP Alerts with Sensitive Information Types + Description: This query retrieves detailed DLP events, highlighting sensitive information types, detection confidence, impacted files or items, and triggered policies across workloads. It provides security analysts with actionable insights to investigate and mitigate potential data leakage incidents effectively. + Inputs: + - Name: UPN + Description: UPN of the user to search + Required: true + Settings: + Target: Defender + Template: |- + // Define the user principal name (UPN) to search for + let searchUser = "{{UPN}}"; + CloudAppEvents + | where ActionType == "DlpRuleMatch" // Filter for DLP rule matches only + | where TimeGenerated > ago(30d) // Look at events from the last 30 days + | extend parsedEventData = parse_json(RawEventData) // Parse the RawEventData JSON field into a structured format + | where parsedEventData.UserId == searchUser // Filter for events specific to the searched user + | extend Workload = parsedEventData.Workload // Extract the workload + //| where Workload != "Exchange" // Exclude Exchange-related events + | extend PolicyDetails = parsedEventData.PolicyDetails // Extract DLP policy details + | mv-expand PolicyDetails // Expand the PolicyDetails array (one row per policy match) + | extend Rules = PolicyDetails.Rules // Extract the Rules field + | mv-expand Rules // Expand the Rules array (one row per rule match) + | extend ConditionsMatched = parse_json(Rules.ConditionsMatched) // Parse ConditionsMatched to access sensitive info + | extend SensitiveInformation = ConditionsMatched.SensitiveInformation // Extract the SensitiveInformation array + | mv-expand SensitiveInformation // Expand the SensitiveInformation array (one row per type) + | where isnotempty(SensitiveInformation) // Remove rows where SensitiveInformation is empty + | project + TimeGenerated, // When the event occurred + UserId = parsedEventData.UserId, // User who triggered the event + SensitiveInformationType = SensitiveInformation.SensitiveInformationTypeName, // Type of sensitive info found + Confidence = SensitiveInformation.Confidence, // Confidence level of the match + Count = SensitiveInformation.Count, // Number of instances found + Location = SensitiveInformation.Location, // Where in the content it was found + UniqueCount = SensitiveInformation.UniqueCount, // Number of unique instances + Workload // Workload associated with the event diff --git a/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/README.md b/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/README.md index d1ffdec4..306da4c1 100644 --- a/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/README.md +++ b/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/README.md @@ -7,17 +7,32 @@ ## Overview This plugin suite enables users to enrich various entities in security operations & investigations. Detailed README's are available for each enrichment type. -### User Enrichment: A collection of tools to identify who accessed a URL, analyze click patterns, and correlate URL activity with other security events. Designed for security analysts in Defender and Copilot for Security. +### Alert Enrichment +Enhances security investigations by retrieving and correlating alert-related data. Allows analysts to search for alerts by title, retrieve associated evidence, and assess security incidents efficiently. -### URL Enrichment: Designed to enhance security investigations by providing detailed insights into user interactions with URLs. It allows analysts to track URL clicks, analyze click patterns, and correlate URL activity with other security events for comprehensive risk assessments. +### User Enrichment +A collection of tools designed to enhance security investigations by analyzing user activities, detecting anomalies in sign-in behavior, tracking password changes, monitoring MFA modifications, and verifying user location information. Helps security teams identify threats and ensure compliance with security policies. -### Incident Enrichment: This suite of incident enrichment plugins is designed to enhance security investigations by providing insights into incident-related alerts. These tools enable security analysts to efficiently correlate, retrieve, and analyze alerts linked to specific incidents for targeted investigations. +### URL Enrichment +Enhances security investigations by providing detailed insights into user interactions with URLs. Allows analysts to track URL clicks, analyze click patterns, and correlate URL activity with other security events for comprehensive risk assessments. -### File Enrichment: This suite of file enrichment skills is designed to enhance security investigations by providing detailed insights into file activity on devices. These tools allow analysts to identify potentially malicious files, assess security threats, and recommend remediation steps. +### Incident Enrichment +Provides insights into incident-related alerts to improve security investigations. Enables security analysts to efficiently correlate, retrieve, and analyze alerts linked to specific incidents for targeted investigations. -### Email Enrichment: This suite of email enrichment skill is designed to enhance security investigations by providing detailed insights into email activity. These tools allow analysts to identify email interactions based on domains, recipients, or senders and investigate related activity for comprehensive threat analysis. +### File Enrichment +Enhances security investigations by providing detailed insights into file activity on devices. Helps analysts identify potentially malicious files, assess security threats, and recommend remediation steps. -### Device Enrichment: This suite of device enrichment skills is designed to enhance security investigations by providing insights into device behavior, vulnerabilities, and activity patterns. By leveraging these tools, security teams can monitor device exposure, analyze logon events, and identify vulnerabilities for targeted remediation. +### Email Enrichment +Enhances security investigations by providing insights into email activity. Allows analysts to identify email interactions based on domains, recipients, senders, and quarantine requests, supporting a comprehensive threat analysis. + +### Device Enrichment +Provides insights into device behavior, vulnerabilities, and activity patterns. Helps security teams monitor device exposure, analyze logon events, and identify vulnerabilities for targeted remediation. + +### IP Enrichment +Enhances security investigations by analyzing IP-related activities such as failed login attempts, brute-force attacks, and login anomalies. Supports detection of suspicious login behavior and potential threats. + +### Purview Enrichment +Designed to enhance security investigations by querying DLP events and file activities for users and devices. Helps security analysts correlate data loss prevention (DLP) alerts with other event types to aid in investigation and response efforts. **Note** Ensure you have sufficient permissions and access to required datasets within Microsoft Defender or Sentinel before invoking any skills. @@ -30,4 +45,5 @@ Ensure you have sufficient permissions and access to required datasets within Mi ## Select or upload the attached manifest file into your Copilot for Security console 1. Click on the `Sources` button and go to `Custom Plugins`. 2. Click `Security Copilot plugin Custom .yaml or .json` -3. Some plugins leverage Sentinel, if so, enter your Azure information as requested. \ No newline at end of file +3. Some plugins leverage Sentinel, if so, enter your Azure information as requested. + diff --git a/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/URLEnrichment.yaml b/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/URLEnrichment.yaml index be3ff640..12f2ee3a 100644 --- a/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/URLEnrichment.yaml +++ b/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/URLEnrichment.yaml @@ -36,21 +36,21 @@ SkillGroups: // It retrieves user activity, including the time of click and additional context like network or threat details. let urlToSearch = "{{url}}"; UrlClickEvents - | where TimeGenerated >= ago(30d) + | where Timestamp >= ago(30d) | where Url has urlToSearch // Uncomment this for customers //| where IsClickedThrough == true - | project TimeGenerated, Url, ActionType, AccountUpn, Workload, NetworkMessageId, ThreatTypes, IPAddress, IsClickedThrough, UrlChain, ReportId + | project Timestamp, Url, ActionType, AccountUpn, Workload, NetworkMessageId, ThreatTypes, IPAddress, IsClickedThrough, UrlChain, ReportId - Name: UrlClickCount DisplayName: URL Click Count Last 30 Days DescriptionForModel: |- Performs a KQL query on the `UrlClickEvents` table to analyze how many times a specific URL has been clicked in the last 30 days. Key features include: - **Click Count**: Calculates the total number of clicks for the specified URL. - - **Access Timeline**: Identifies the timestamps of the first and last clicks for trend analysis. + - **Access Timeline**: Identifies the Timestamps of the first and last clicks for trend analysis. - **Context**: Focuses on Safe Links clicks from email, Microsoft Teams, and Office 365 apps (supported desktop, mobile, and web apps). This skill is useful for assessing the frequency and recency of URL access, helping security analysts understand usage patterns and evaluate potential risks associated with the URL. - Description: Analyze the frequency and recency of clicks for a specific URL over the past 30 days. Provides the total number of clicks, along with the timestamps of the first and last clicks, to support threat assessment and investigation. + Description: Analyze the frequency and recency of clicks for a specific URL over the past 30 days. Provides the total number of clicks, along with the Timestamps of the first and last clicks, to support threat assessment and investigation. Inputs: - Name: url Description: The URL to search for click counts. Example https://example.com @@ -72,7 +72,7 @@ SkillGroups: Performs a KQL query to correlate a specific URL with multiple security-related events over the last 30 days. Key features include: - **Event Correlation**: Aggregates data from URL click events, security alerts, email events, and network events. - **Details Provided**: - - `TimeGenerated`: Timestamp of the event. + - `Timestamp`: Timestamp of the event. - `AccountUpn`: User associated with the event. - `Url`: URL involved in the event. - `EventType`: Type of event (e.g., URL click, security alert, email event, or network event). @@ -94,7 +94,7 @@ SkillGroups: let urlClicks = UrlClickEvents | where Timestamp >= ago(timeframe) | where Url has targetUrl - | project TimeGenerated, AccountUpn, Url, EventType = "UrlClickEvent", AlertId = "", IncidentId = ""; + | project Timestamp, AccountUpn, Url, EventType = "UrlClickEvent", AlertId = "", IncidentId = ""; // Find security alerts where the URL is mentioned in the RemoteUrl field let alertsContainingUrl = AlertEvidence @@ -105,7 +105,7 @@ SkillGroups: | extend ExtendedPropertiesJson = parse_json(ExtendedProperties) | project VendorOriginalId, IncidentId = tostring(ExtendedPropertiesJson.IncidentId) ) on $left.AlertId == $right.VendorOriginalId - | project TimeGenerated, AccountUpn, Url = tostring(RemoteUrl), EventType = "AlertEvidence", AlertId = VendorOriginalId, IncidentId; + | project Timestamp, AccountUpn, Url = tostring(RemoteUrl), EventType = "AlertEvidence", AlertId = VendorOriginalId, IncidentId; // Find email events related to the target URL using EmailUrlInfo and EmailEvents let emailUrlInfo = EmailUrlInfo @@ -116,22 +116,22 @@ SkillGroups: let emailEvents = EmailEvents | where Timestamp >= ago(timeframe) | where NetworkMessageId in (emailUrlInfo) - | project TimeGenerated, AccountUpn = RecipientEmailAddress, Url = targetUrl, EventType = "EmailEvent", AlertId = "", IncidentId = ""; + | project Timestamp, AccountUpn = RecipientEmailAddress, Url = targetUrl, EventType = "EmailEvent", AlertId = "", IncidentId = ""; // Find network events where the URL is accessed via RemoteUrl in network connections let deviceNetworkEvents = DeviceNetworkEvents | where Timestamp >= ago(timeframe) | where RemoteUrl has targetUrl - | project TimeGenerated, AccountUpn = InitiatingProcessAccountName, Url = RemoteUrl, EventType = "DeviceNetworkEvent", AlertId = "", IncidentId = ""; + | project Timestamp, AccountUpn = InitiatingProcessAccountName, Url = RemoteUrl, EventType = "DeviceNetworkEvent", AlertId = "", IncidentId = ""; // Combine all results from the different tables (URL clicks, alerts, email events, network events) urlClicks | union isfuzzy=true alertsContainingUrl, emailEvents, deviceNetworkEvents - // Order the combined results by the event timestamp for easy analysis - | order by TimeGenerated desc + // Order the combined results by the event Timestamp for easy analysis + | order by Timestamp desc // Return only distinct rows to avoid duplicates - | distinct TimeGenerated, AccountUpn, Url, EventType, AlertId, IncidentId + | distinct Timestamp, AccountUpn, Url, EventType, AlertId, IncidentId // If any `IncidentId`s are present, highlight them for the user - | summarize Alerts = make_list(IncidentId) by Url, AccountUpn, TimeGenerated, EventType, AlertId + | summarize Alerts = make_list(IncidentId) by Url, AccountUpn, Timestamp, EventType, AlertId | extend IncidentNotes = iff(array_length(Alerts) > 0, strcat("Related incidents have been observed: ", tostring(Alerts)), "") 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 28092254..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 @@ -1,50 +1,94 @@ # Enrichment Plugin Suite **Author: Craig Freyman** -## UserEnrichment 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, and location analysis. By leveraging these tools, security teams can identify potential threats, monitor user risks, and respond to suspicious behavior effectively. +## 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, 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. + +--- + +### SuspiciousMailboxActivities +Investigate recent mailbox activities to detect potential security threats, such as: +- 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. + +--- + +### 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. -Example Prompt: Detect impossible travel events for mscott@paper.com in the past 7 days. +--- 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 2174cb8d..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,91 +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 and prioritize medium and high-risk detections for a specific user over the past 7 days. Provides insights into risk events, including types, details, timing, and locations, to support quick, prioritized investigations and actions. - DescriptionForModel: |- - Performs a KQL query on the `AADUserRiskEvents` table to analyze medium and high-risk detections for a specified user within the last 7 days. Key features include: - - - **Risk Analysis**: - - Focuses on `RiskLevel` values: medium and high. - - Provides details such as `RiskEventType`, `RiskDetail`, and `DetectedDateTime` to understand potential attack vectors and event specifics. - - - **Anomaly Detection**: - - Uses `IpAddress` and `Location` fields to analyze physical locations and detect anomalies like atypical travel or VPN usage. - - Highlights `DetectionTimingType` to differentiate real-time threats from delayed detections, ensuring timely response to active risks. - - - **Insights and Prioritization**: - - Surfaces patterns in risk detections to evaluate whether incidents indicate sustained or escalating threats. - - Supports security analysts in taking swift action, such as enforcing MFA, resetting passwords, or further investigation. - - This skill helps monitor and respond to unresolved or ongoing user risks, prioritizing high-severity incidents for effective threat mitigation. + - 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 RiskLevel == "medium" or RiskLevel == "high" - | where UserPrincipalName == "{{upn}}" - - - 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 @@ -431,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: |- @@ -449,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), @@ -468,3 +530,130 @@ SkillGroups: | where UserPrincipalName == "{{upn}}" | project UserPrincipalName, TimeGenerated, PreviousTime, IPAddress, PreviousIPAddress, DistanceMiles, TimeDifferenceHours, VelocityMph, Country, PreviousCountry + - Name: SuspiciousMailboxActivities + DisplayName: Suspicious Mailbox Activities + Description: |- + - Investigate any recent Mailbox Permission for this User + - Are there any rules created or modified that could redirect or filter emails in a suspicious manner? + - Focus on identifying any newly created rules that may redirect incoming emails to unusual folders or external addresses, as this could indicate an attempt to manipulate email flow for malicious purposes + - If no anomalies are found, confirm the user's activity is consistent. + Inputs: + - Name: upn + Description: User principle name. i.e., mscott@test.com + Required: true + Settings: + Target: Defender + Template: |- + // Mailbox Investigation of suspicious users. To detect potential suspicious activities, we filter Exchange Online action types that could be used for exfiltration and BEC. This allows us to investigate whether the targeted end-user is involved in any suspicious actions. + // Examine activities of a user from Suspicious Mailbox actions and UPN risk in Entra Id and Defender + let UPN = "{{upn}}"; // Replace with the specific users email + let TimeFrame = ago(30d); + // Identify the most frequently used IP address for the user. + CloudAppEvents + | where TimeGenerated > TimeFrame + | extend parsed = parse_json(RawEventData) + | where ActionType in ("Add-MailboxPermission", "New-ManagementRoleAssignment", "Add-MailboxFolderPermission", "New-InboxRule", "Set-InboxRule", "Set-Mailbox", "New-TransportRule", "Set-TransportRule") + | where isnotempty(RawEventData.UserId) + | where isnotempty(RawEventData.ObjectId) + | extend parsed = parse_json(RawEventData) + | extend UPN = tostring(parsed.UserId) + | where UPN =~ UPN + | extend Parameters = parsed.Parameters + | mv-expand Parameters + | extend Name = tostring(Parameters.Name) + | extend Value = tostring(Parameters.Value) + | 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) + + - 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 Defender XDR Custom Plugin Scenarios/MDA Advanced Threat Protection (ATP) Detection/MDA_Advanced_Threat_Protection_Detection.yaml b/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/MDA Advanced Threat Protection (ATP) Detection/MDA_Advanced_Threat_Protection_Detection.yaml new file mode 100644 index 00000000..daa88f1e --- /dev/null +++ b/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/MDA Advanced Threat Protection (ATP) Detection/MDA_Advanced_Threat_Protection_Detection.yaml @@ -0,0 +1,87 @@ +Descriptor: + Name: MDAAdvancedThreatProtection + DisplayName: MDA Advanced Threat Protection + Description: Skills to query MDA tables to get insights about files quarantined by Advanced Threat Protection (ATP) + +SkillGroups: + - Format: KQL + Skills: + - Name: GetCloudAppATPEvents + DisplayName: Get Cloud App ATP Events + Description: Fetches a list of Advanced Threat Protection events within a specified timeframe + ExamplePrompt: + - 'OneDrive Malware' + - 'SharePoint Malware' + - 'Cloud App ATP' + - 'Cloud App Advanced Threat Protection' + - 'Get a list of Advanced Threat Protection malware events from Cloud Apps for the past 24 hours' + - 'Get a list of Advanced Threat Protection malware events from Cloud Apps for the past 30 days' + - 'Get a list of ATP malware events from Cloud Apps for the past 24 hours' + - 'Get a list of ATP malware events from Cloud Apps for the past 30 days' + - 'Fetch a list of Advanced Threat Protection malware events from Cloud Apps for the past 24 hours' + - 'Fetch a list of Advanced Threat Protection malware events from Cloud Apps for the past 30 days' + - 'Fetch a list of ATP malware events from Cloud Apps for the past 24 hours' + - 'Fetch a list of ATP malware events from Cloud Apps for the past 30 days' + - 'List the Advanced Threat Protection malware events from Cloud Apps for the past 24 hours' + - 'List the Advanced Threat Protection malware events from Cloud Apps for the past 30 days' + - 'List the ATP malware events from Cloud Apps for the past 24 hours' + - 'List the ATP malware events from Cloud Apps for the past 30 days' + Inputs: + - Name: hourorday + Description: hours (h) or days (d) + Required: false + DefaultValue: d + - Name: unit + Description: number of hours or days + Required: false + DefaultValue: 7 + Settings: + Target: Defender + Template: |- + CloudAppEvents + | where Timestamp >= ago({{unit}}{{hourorday}}) + | where ActionType == @"AtpDetection" + | extend FileName=parse_json(RawEventData).FileData.FileName, FilePath=parse_json(RawEventData).FileData.FilePath, MalwareFamily=parse_json(RawEventData).FileData.MalwareFamily + | project Timestamp, ActionType, Application, AccountObjectId, AccountDisplayName, FileName, FilePath, MalwareFamily + - Format: KQL + Skills: + - Name: GetOfficeActivityATPEvents + DisplayName: Get Office Activity ATP Events + Description: Fetches a list of Advanced Threat Protection events within a specified timeframe + ExamplePrompt: + - 'OneDrive Malware' + - 'SharePoint Malware' + - 'Office Activity ATP' + - 'Office Activity Advanced Threat Protection' + - 'Get a list of Advanced Threat Protection malware events from Office Activity for the past 24 hours' + - 'Get a list of Advanced Threat Protection malware events from Office Activity for the past 30 days' + - 'Get a list of ATP malware events from Office Activity for the past 24 hours' + - 'Get a list of ATP malware events from Office Activity for the past 30 days' + - 'Fetch a list of Advanced Threat Protection malware events from Office Activity for the past 24 hours' + - 'Fetch a list of Advanced Threat Protection malware events from Office Activity for the past 30 days' + - 'Fetch a list of ATP malware events from Office Activity for the past 24 hours' + - 'Fetch a list of ATP malware events from Office Activity for the past 30 days' + - 'List the Advanced Threat Protection malware events from Office Activity for the past 24 hours' + - 'List the Advanced Threat Protection malware events from Office Activity for the past 30 days' + - 'List the ATP malware events from Office Activity for the past 24 hours' + - 'List the ATP malware events from Office Activity for the past 30 days' + Inputs: + - Name: hourorday + Description: hours (h) or days (d) + Required: false + DefaultValue: d + - Name: unit + Description: number of hours or days + Required: false + DefaultValue: 7 + Settings: + Target: Sentinel + Template: |- + OfficeActivity + | where TimeGenerated >= ago({{unit}}{{hourorday}}) + | where Operation == "FileMalwareDetected" and RecordType == "SharePointFileOperation" + | project TimeGenerated, Operation, OfficeWorkload, OfficeObjectId, ClientIP, Site_Url, SourceRelativeUrl, SourceFileName + TenantId: + SubscriptionId: + ResourceGroupName: + WorkspaceName: diff --git a/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/MDA Advanced Threat Protection (ATP) Detection/readme.md b/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/MDA Advanced Threat Protection (ATP) Detection/readme.md new file mode 100644 index 00000000..ef7d16ea --- /dev/null +++ b/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/MDA Advanced Threat Protection (ATP) Detection/readme.md @@ -0,0 +1,36 @@ +# Copilot for Security Plugin: MDA Advanced Threat Protection (ATP) Detection + +### **This KQL plugin enables SOC analysts and managers to get insights about potential malware in their Cloud App environments based on the Defender for Cloud Apps CloudAppEvents table and Sentinel OfficeActivity table.** + +### Prerequisites + +- [Copilot for Security enabled](https://learn.microsoft.com/en-us/security-copilot/get-started-security-copilot#onboarding-to-microsoft-security-copilot) +- [Access to upload custom plugins](https://learn.microsoft.com/en-us/security-copilot/manage-plugins?tabs=securitycopilotplugin#managing-custom-plugins) + +### Instructions + +#### Upload the Custom Plugin + +1. Obtain the file MDA_Advanced_Threat_Protection_Detection.yaml from this directory. +2. Upload the custom plugin + +### Plugin Utilisation + +#### Skills + +- **GetCloudAppATPEvents**: Fetches a list of Advanced Threat Protection events within a specified timeframe +- **GetOfficeActivityATPEvents**: Fetches a list of Advanced Threat Protection events within a specified timeframe + +#### Example Prompts + +- Get a list of Advanced Threat Protection malware events from Cloud Apps for the past 24 hours +- Fetch a list of Advanced Threat Protection malware events from Office Activity for the past 30 days +- List the ATP malware events from Cloud Apps for the past 24 hours +- Get a list of ATP malware events from Office Activity for the past 30 days + +#### Example Usage + +1. A SOC manager is drafting a report for senior leadership about the impact of malware in the organisation. +2. The GetCloudAppATPEvents skill summarises all the quarantined files from the 30 days. +3. The Microsoft Defender Threat Intelligence (MDTI) plugin is used to get context about the malware signatures and families that were detected. +4. The Generic plugin is used to summarise the previous prompts and provide recommendations about additional controls that could be used to protect against the malware families seen. diff --git a/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/MDA Cloud App Events/MDA_Cloud_Apps_Events.yaml b/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/MDA Cloud App Events/MDA_Cloud_Apps_Events.yaml new file mode 100644 index 00000000..3ba1af1a --- /dev/null +++ b/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/MDA Cloud App Events/MDA_Cloud_Apps_Events.yaml @@ -0,0 +1,157 @@ +Descriptor: + Name: MDECloudAppEvents + DisplayName: MDE Cloud App Events + Description: Skills to query MDA CloudAppEvents tables to get insights about Cloud App activity + +SkillGroups: + - Format: KQL + Skills: + - Name: GetOneDriveActivityForUser + DisplayName: Get OneDrive Activity For User + Description: Fetches a list of a user's OneDrive activity within a specified timeframe + ExamplePrompt: + - 'OneDrive User Activity' + - 'Get a list of OneDrive activity for someone@example.com from the past 2 hours' + - 'Get a list of OneDrive activity for someone@example.com from the past 1 day' + - 'Fetch a list of OneDrive activity for someone@example.com from the past 2 hours' + - 'Fetch a list of OneDrive activity for someone@example.com from the past 1 day' + - 'List all the OneDrive activity for someone@example.com from the past 2 hours' + - 'List all the OneDrive activity for someone@example.com from the past 1 day' + Inputs: + - Name: hourorday + Description: hours (h) or days (d) + Required: false + DefaultValue: d + - Name: unit + Description: number of hours or days + Required: false + DefaultValue: 1 + - Name: accountupn + Description: account UPN (such as someone@example.com) + Required: true + Settings: + Target: Defender + Template: |- + let AccountUPN = toscalar(IdentityInfo | where AccountUpn =~ "{{accountupn}}" | project AccountObjectId | summarize max(AccountObjectId)); + CloudAppEvents + | where Timestamp >= ago({{unit}}{{hourorday}}) + | where Application =~ @"Microsoft OneDrive for Business" + | where AccountObjectId =~ AccountUPN or AccountId =~ AccountUPN + | extend FileName = extract("([^/]+)$", 1, tostring(parse_json(ActivityObjects)[0].Name)),TargetObject=parse_json(ActivityObjects)[0].Name, TargetFolder=parse_json(ActivityObjects)[1].Name + | project Timestamp, ActionType, IPAddress, FileName, tostring(TargetObject), tostring(TargetFolder) + | summarize min(Timestamp), max(Timestamp) by ActionType, IPAddress, FileName, TargetObject, TargetFolder + | project ActionType, IPAddress, FileName, TargetObject, TargetFolder, FirstTimestamp = min_Timestamp, LastTimestamp = max_Timestamp + - Format: KQL + Skills: + - Name: GetSharePointActivityForUser + DisplayName: Get SharePoint Activity For User + Description: Fetches a list of a user's SharePoint activity within a specified timeframe + ExamplePrompt: + - 'SharePoint User Activity' + - 'Get a list of SharePoint activity for someone@example.com from the past 2 hours' + - 'Get a list of SharePoint activity for someone@example.com from the past 1 day' + - 'Fetch a list of SharePoint activity for someone@example.com from the past 2 hours' + - 'Fetch a list of SharePoint activity for someone@example.com from the past 1 day' + - 'List all the SharePoint activity for someone@example.com from the past 2 hours' + - 'List all the SharePoint activity for someone@example.com from the past 1 day' + Inputs: + - Name: hourorday + Description: hours (h) or days (d) + Required: false + DefaultValue: d + - Name: unit + Description: number of hours or days + Required: false + DefaultValue: 1 + - Name: accountupn + Description: account UPN (such as someone@example.com) + Required: true + Settings: + Target: Defender + Template: |- + let AccountUPN = toscalar(IdentityInfo | where AccountUpn =~ "{{accountupn}}" | project AccountObjectId | summarize max(AccountObjectId)); + CloudAppEvents + | where Timestamp >= ago({{unit}}{{hourorday}}) + | where Application =~ @"Microsoft SharePoint Online" + | where AccountObjectId =~ AccountUPN or AccountId =~ AccountUPN + | extend FileName=extract("([^/]+)$", 1, tostring(parse_json(ActivityObjects)[0].Name)),TargetObject=parse_json(ActivityObjects)[0].Name, TargetFolder=parse_json(ActivityObjects)[1].Name + | extend FileName=iff(isnotempty(FileName), FileName, tostring(parse_json(RawEventData).ListName)) + | extend TargetObject=iff(isnotempty(TargetObject), TargetObject, tostring(parse_json(RawEventData).ListUrl)) + | project Timestamp, ActionType, IPAddress, FileName, tostring(TargetObject), tostring(TargetFolder) + | summarize min(Timestamp), max(Timestamp) by ActionType, IPAddress, FileName, TargetObject, TargetFolder + | project ActionType, IPAddress, FileName, TargetObject, TargetFolder, FirstTimestamp = min_Timestamp, LastTimestamp = max_Timestamp + - Format: KQL + Skills: + - Name: GetExchangeOnlineActivityForUser + DisplayName: Get Exchange Online Activity For User + Description: Fetches a list of a user's Exchange Online activity within a specified timeframe + ExamplePrompt: + - 'Exchange Online User Activity' + - 'Get a list of Exchange Online activity for someone@example.com from the past 2 hours' + - 'Get a list of Exchange Online activity for someone@example.com from the past 1 day' + - 'Fetch a list of Exchange Online activity for someone@example.com from the past 2 hours' + - 'Fetch a list of Exchange Online activity for someone@example.com from the past 1 day' + - 'List all the Exchange Online activity for someone@example.com from the past 2 hours' + - 'List all the Exchange Online activity for someone@example.com from the past 1 day' + Inputs: + - Name: hourorday + Description: hours (h) or days (d) + Required: false + DefaultValue: d + - Name: unit + Description: number of hours or days + Required: false + DefaultValue: 1 + - Name: accountupn + Description: account UPN (such as someone@example.com) + Required: true + Settings: + Target: Defender + Template: |- + let AccountUPN = toscalar(IdentityInfo | where AccountUpn =~ "{{accountupn}}" | project AccountObjectId | summarize max(AccountObjectId)); + CloudAppEvents + | where Timestamp >= ago({{unit}}{{hourorday}}) + | where Application =~ @"Microsoft Exchange Online" + | where AccountObjectId =~ AccountUPN or AccountId =~ AccountUPN + | extend ObjectName=iff(isnotempty(ObjectName), ObjectName, parse_json(ActivityObjects)[2].Name) + | extend ObjectType=iff(isnotempty(ObjectType), ObjectType, parse_json(ActivityObjects)[2].Type) + | project Timestamp, ActionType, ActivityType, ObjectName, ObjectType ,IPAddress + | summarize min(Timestamp), max(Timestamp) by ActionType, ActivityType, ObjectName, ObjectType ,IPAddress + | project ActionType, ActivityType, ObjectName, ObjectType, IPAddress, FirstTimestamp = min_Timestamp, LastTimestamp = max_Timestamp + - Format: KQL + Skills: + - Name: GetTeamsActivityForUser + DisplayName: Get Teams Activity For User + Description: Fetches a list of a user's Teams activity within a specified timeframe + ExamplePrompt: + - 'Teams User Activity' + - 'Get a list of Teams activity for someone@example.com from the past 2 hours' + - 'Get a list of Teams activity for someone@example.com from the past 1 day' + - 'Fetch a list of Teams activity for someone@example.com from the past 2 hours' + - 'Fetch a list of Teams activity for someone@example.com from the past 1 day' + - 'List all the Teams activity for someone@example.com from the past 2 hours' + - 'List all the Teams activity for someone@example.com from the past 1 day' + Inputs: + - Name: hourorday + Description: hours (h) or days (d) + Required: false + DefaultValue: d + - Name: unit + Description: number of hours or days + Required: false + DefaultValue: 1 + - Name: accountupn + Description: account UPN (such as someone@example.com) + Required: true + Settings: + Target: Defender + Template: |- + let AccountUPN = toscalar(IdentityInfo | where AccountUpn =~ "{{accountupn}}" | project AccountObjectId | summarize max(AccountObjectId)); + CloudAppEvents + | where Timestamp >= ago({{unit}}{{hourorday}}) + | where Application =~ @"Microsoft Teams" + | where AccountObjectId =~ AccountUPN or AccountId =~ AccountUPN + | extend ChatThreadId=parse_json(RawEventData).ChatThreadId, ChatItemName=parse_json(RawEventData).ItemName, CommunicationType=parse_json(RawEventData).CommunicationType, ParticipantInfo_HasForeignTenantUsers=parse_json(RawEventData).ParticipantInfo.HasForeignTenantUsers, ParticipantInfo_HasGuestUsers=parse_json(RawEventData).ParticipantInfo.HasGuestUsers + | project Timestamp, ActionType, IPAddress, ChatItemName, ChatThreadId, CommunicationType, ParticipantInfo_HasForeignTenantUsers, ParticipantInfo_HasGuestUsers + | summarize min(Timestamp), max(Timestamp) by ActionType, IPAddress, tostring(ChatItemName), tostring(ChatThreadId), tostring(CommunicationType), tostring(ParticipantInfo_HasForeignTenantUsers), tostring(ParticipantInfo_HasGuestUsers) + | project ActionType, IPAddress, ChatItemName, ChatThreadId, CommunicationType, ParticipantInfo_HasForeignTenantUsers, ParticipantInfo_HasGuestUsersFirstTimestamp = min_Timestamp, LastTimestamp = max_Timestamp diff --git a/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/MDA Cloud App Events/Readme.md b/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/MDA Cloud App Events/Readme.md new file mode 100644 index 00000000..ba5855a4 --- /dev/null +++ b/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/MDA Cloud App Events/Readme.md @@ -0,0 +1,44 @@ +# Copilot for Security Plugin: MDA Cloud App Events + +### This KQL plugin enables SOC analysts to investigate events related to accounts and objects in Office 365 and other cloud applications and services, using the Defender for Cloud Apps data from the CloudAppEvents table. + +The CloudAppEvents table contains enriched logs from all SaaS applications connected to MDA, including Office 365 Applications such as: +- OneDrive +- SharePoint +- Exchange Online +- Teams + +### Prerequisites + +- [Copilot for Security enabled](https://learn.microsoft.com/en-us/security-copilot/get-started-security-copilot#onboarding-to-microsoft-security-copilot) +- [Access to upload custom plugins](https://learn.microsoft.com/en-us/security-copilot/manage-plugins?tabs=securitycopilotplugin#managing-custom-plugins) + +### Instructions + +#### Upload the Custom Plugin + +1. Obtain the file MDA_Cloud_App_Events.yaml from this directory. +2. Upload the custom plugin + +### Plugin Utilisation + +#### Skills + +- **GetOneDriveActivtyForUser**: Feteches a list of a user's OneDrive activity within a specified timeframe +- **GetSharePointActivityForUser**: Fetches a list of a user's SharePoint activity within a specified timeframe +- **GetExchangeOnlineActivityForUser**: Fetches a list of a user's Exchange Online activity within a specified timeframe +- **GetTeamsActivityForUser**: Fetches a list of a user's Teams activity within a specified timeframe + +#### Example Prompts + +- Get a list of OneDrive activity for someone@example.com from the past 2 hours +- Fetch a list of SharePoint activity for someone@example.com from the past 1 day +- List all the Exchange Online activity for someone@example.com from the past 2 hours +- Get a list of Teams activity for someone@example.com from the past 1 day + +#### Example Usage + +1. A SOC analyst is investigating an alert from Purview Insider Risk Management. +2. The GetOneDriveActivtyForUser skill is used to get a summary of the users' activity 1 hour before the alert. +3. The GetTeamsActivityForUser skill is used to identify if the user has shared any content via Teams. +4. The Entra ID plugin is used to summarise the users' risk level. 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 Experts/QR Code AiTM Phishing Detection Plugin/HuntingQrCodeAITM.yml b/Plugins/Community Based Plugins/Microsoft Security Experts/QR Code AiTM Phishing Detection Plugin/HuntingQrCodeAITM.yml index c45651d9..7855e60f 100644 --- a/Plugins/Community Based Plugins/Microsoft Security Experts/QR Code AiTM Phishing Detection Plugin/HuntingQrCodeAITM.yml +++ b/Plugins/Community Based Plugins/Microsoft Security Experts/QR Code AiTM Phishing Detection Plugin/HuntingQrCodeAITM.yml @@ -176,7 +176,7 @@ SkillGroups: Settings: Target: Defender Template: |- - let suspiciousClusters = EmailEvents + let suspiciousClusters = EmailEvents | where Timestamp > ago(7d) | where EmailDirection == "Inbound" | where NetworkMessageId in ("") 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/ASIM Hunting queries/DNSEssentials_HuntingQueries.yaml b/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/ASIM Hunting queries/DNSEssentials_HuntingQueries.yaml new file mode 100644 index 00000000..fc1e1893 --- /dev/null +++ b/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/ASIM Hunting queries/DNSEssentials_HuntingQueries.yaml @@ -0,0 +1,501 @@ +Descriptor: + Name: DNS Essentials Hunting Queries - ASIM + DisplayName: DNS Essentials Hunting Queries- ASIM (Preview) + Description: Network Session normalization schema represents an IP network activity, such as network connections and network sessions. Such events are reported, for example, by operating systems, routers, firewalls, and intrusion prevention systems. + Settings: + - Name: TenantId + Required: true + - Name: WorkspaceName + Required: true + - Name: SubscriptionId + Required: true + - Name: ResourceGroupName + Required: true + SupportedAuthTypes: + - None + +SkillGroups: + - Format: KQL + Skills: + - Name: Top 25 DNS queries with most failures in last 24 hours + DisplayName: Top 25 DNS queries with most failures in last 24 hours (Preview) + Description: This query searches for DNS queries that resulted in errors. This query utilizes ASIM normalization and is applied to any source that supports the ASIM DNS schema. + Settings: + Target: Sentinel + # The ID of the AAD Organization that the Sentinel workspace is in. + TenantId: '{{TenantId}}' + # The id of the Azure Subscription that the Sentinel workspace is in. + SubscriptionId: '{{SubscriptionId}}' + # The name of the Resource Group that the Sentinel workspace is in. + ResourceGroupName: '{{ResourceGroupName}}' + # The name of the Sentinel workspace. + WorkspaceName: '{{WorkspaceName}}' + # This query detects potential network beaconing activity + Template: |- + let timeframe = 1d; + _Im_Dns(starttime=ago(timeframe), endtime=now()) + | where EventSubType =~ 'response' and DnsResponseCodeName != 'NOERROR' + | summarize Count=count() by DnsQuery, DnsResponseCodeName + | order by Count + | take 25 + | extend DNS_0_DomainName = DnsQuery + - Format: KQL + Skills: + - Name: Top 25 Domains with large number of Subdomains + DisplayName: Top 25 Domains with large number of Subdomains (Preview) + Description: A large number of subdomains for a domain may be an indicator of a suspicious domain. This query returns the top 25 domains by number of subdomains. + Settings: + Target: Sentinel + # The ID of the AAD Organization that the Sentinel workspace is in. + TenantId: '{{TenantId}}' + # The id of the Azure Subscription that the Sentinel workspace is in. + SubscriptionId: '{{SubscriptionId}}' + # The name of the Resource Group that the Sentinel workspace is in. + ResourceGroupName: '{{ResourceGroupName}}' + # The name of the Sentinel workspace. + WorkspaceName: '{{WorkspaceName}}' + # This query detects potential network beaconing activity + Template: |- + let lookback=1d; + _Im_Dns(starttime=ago(lookback),endtime=now()) + | distinct DnsQuery + | extend DomainParts = split(DnsQuery,'.') + | extend DomainName = strcat(DomainParts[toint(array_length(DomainParts)-2)],'.',DomainParts[toint(array_length(DomainParts)-1)]) + | summarize SubDomainCount=dcount(DnsQuery),make_list(DnsQuery) by DomainName + | order by SubDomainCount + | take 25 + | extend DNS_0_DomainName = DomainName + - Format: KQL + Skills: + - Name: Top 25 Sources(Clients) with high number of errors in last 24 hours + DisplayName: Top 25 Sources(Clients) with high number of errors in last 24 hours (Preview) + Description: This query searches for the top 25 clients with the most errors. + Settings: + Target: Sentinel + # The ID of the AAD Organization that the Sentinel workspace is in. + TenantId: '{{TenantId}}' + # The id of the Azure Subscription that the Sentinel workspace is in. + SubscriptionId: '{{SubscriptionId}}' + # The name of the Resource Group that the Sentinel workspace is in. + ResourceGroupName: '{{ResourceGroupName}}' + # The name of the Sentinel workspace. + WorkspaceName: '{{WorkspaceName}}' + # This query detects potential network beaconing activity + Template: |- + let timeframe = 1d; + _Im_Dns(starttime=ago(timeframe), endtime=now()) + | where EventSubType == 'response' and DnsResponseCodeName != 'NOERROR' + | where isnotempty(SrcIpAddr) + | summarize Count=count() by SrcIpAddr, DnsResponseCodeName + | order by Count + | take 25 + | extend IP_0_Address = SrcIpAddr + - Format: KQL + Skills: + - Name: Unexpected top level domains + DisplayName: Unexpected top level domains (Preview) + Description: This query looks for top-level domains that are longer than four characters. + Settings: + Target: Sentinel + # The ID of the AAD Organization that the Sentinel workspace is in. + TenantId: '{{TenantId}}' + # The id of the Azure Subscription that the Sentinel workspace is in. + SubscriptionId: '{{SubscriptionId}}' + # The name of the Resource Group that the Sentinel workspace is in. + ResourceGroupName: '{{ResourceGroupName}}' + # The name of the Sentinel workspace. + WorkspaceName: '{{WorkspaceName}}' + # This query detects potential network beaconing activity + Template: |- + // Check in last 24hours + let looback=1d; + _Im_Dns(starttime=ago(looback),endtime=now()) + | summarize Count=count() by SrcIpAddr, DnsQuery + | extend TopLevelDomain = tostring(split(DnsQuery, ".")[-1]) + | where strlen(TopLevelDomain) > 4 + | order by Count + | take 25 + | extend IP_0_Address = SrcIpAddr + | extend DNS_0_DomainName = DnsQuery + - Format: KQL + Skills: + - Name: Potential beaconing activity + DisplayName: Potential beaconing activity (Preview) + Description: This query identifies beaconing patterns from DNS logs based on recurrent frequency patterns. Such a potential outbound beaconing pattern to untrusted public networks should be investigated for any malware callbacks or data exfiltration attempts. + Settings: + Target: Sentinel + # The ID of the AAD Organization that the Sentinel workspace is in. + TenantId: '{{TenantId}}' + # The id of the Azure Subscription that the Sentinel workspace is in. + SubscriptionId: '{{SubscriptionId}}' + # The name of the Resource Group that the Sentinel workspace is in. + ResourceGroupName: '{{ResourceGroupName}}' + # The name of the Sentinel workspace. + WorkspaceName: '{{WorkspaceName}}' + # This query detects potential network beaconing activity + Template: |- + let querystarttime = 2d; + let queryendtime = 1d; + let TimeDeltaThreshold = 10; + let TotalEventsThreshold = 15; + let PercentBeaconThreshold = 80; + _Im_Dns(starttime=ago(querystarttime), endtime=ago(queryendtime)) + | where isnotempty(SrcIpAddr) + | project TimeGenerated, SrcIpAddr, DnsQuery + | sort by SrcIpAddr asc,TimeGenerated asc + | serialize + | extend nextTimeGenerated = next(TimeGenerated, 1), nextSrcIpAddr = next(SrcIpAddr, 1) + | extend TimeDeltainSeconds = datetime_diff('second',nextTimeGenerated,TimeGenerated) + | where SrcIpAddr == nextSrcIpAddr + //Whitelisting criteria/ threshold criteria + | where TimeDeltainSeconds > TimeDeltaThreshold + | project TimeGenerated, TimeDeltainSeconds, SrcIpAddr, DnsQuery + | summarize count(), make_list(TimeDeltainSeconds) by TimeDeltainSeconds, bin(TimeGenerated, 1h), SrcIpAddr, DnsQuery + | summarize (MostFrequentTimeDeltaCount, MostFrequentTimeDeltainSeconds) = arg_max(count_, TimeDeltainSeconds), TotalEvents=sum(count_) + by bin(TimeGenerated, 1h), SrcIpAddr, DnsQuery + | where TotalEvents > TotalEventsThreshold + | extend BeaconPercent = MostFrequentTimeDeltaCount/toreal(TotalEvents) * 100 + | where BeaconPercent > PercentBeaconThreshold + | order by BeaconPercent + | take 50 + | extend IP_0_Address = SrcIpAddr + | extend DNS_0_DomainName = DnsQuery + - Format: KQL + Skills: + - Name: Possible DNS Tunneling or Data Exfiltration Activity + DisplayName: Possible DNS Tunneling or Data Exfiltration Activity (Preview) + Description: Typical domain name lengths are short, whereas domain name queries used for data exfiltration or tunneling can often be very large in size. The hunting query looks for DNS queries that are more than 150 characters long. + Settings: + Target: Sentinel + # The ID of the AAD Organization that the Sentinel workspace is in. + TenantId: '{{TenantId}}' + # The id of the Azure Subscription that the Sentinel workspace is in. + SubscriptionId: '{{SubscriptionId}}' + # The name of the Resource Group that the Sentinel workspace is in. + ResourceGroupName: '{{ResourceGroupName}}' + # The name of the Sentinel workspace. + WorkspaceName: '{{WorkspaceName}}' + # This query detects potential network beaconing activity + Template: |- + // Setting URI length threshold count, shorter URI's may cause noise, change as needed + let lookback=1day; + let uriThreshold = 150; + let ExcludeDomains=dynamic(["cnr.io", "kr0.io", "arcticwolf.net", "webcfs00.com", "barracudabrts.com", "trendmicro.com", "sophosxl.net", + "spotify.com", "e5.sk", "mcafee.com", "opendns.com", "spameatingmonkey.net", "_ldap", "_kerberos", "modsecurity.org", + "fdmarc.net", "ipass.com", "wpad"]); + _Im_Dns(starttime=ago(lookback),endtime=now()) + | summarize count() by SrcIpAddr, DnsQuery + | where not(DnsQuery has_any (ExcludeDomains)) + | extend Urilength = strlen(DnsQuery) + | where Urilength >= uriThreshold + | order by Urilength + | extend IP_0_Address = SrcIpAddr + | extend DNS_0_DomainName = DnsQuery + - Format: KQL + Skills: + - Name: Increase in DNS Requests by client than the daily average count + DisplayName: Increase in DNS Requests by client than the daily average count (Preview) + Description: Checking for a threefold increase or more in Full Name lookups per client IP for today vs. the daily average for the previous week. + Settings: + Target: Sentinel + # The ID of the AAD Organization that the Sentinel workspace is in. + TenantId: '{{TenantId}}' + # The id of the Azure Subscription that the Sentinel workspace is in. + SubscriptionId: '{{SubscriptionId}}' + # The name of the Resource Group that the Sentinel workspace is in. + ResourceGroupName: '{{ResourceGroupName}}' + # The name of the Sentinel workspace. + WorkspaceName: '{{WorkspaceName}}' + # This query detects potential network beaconing activity + Template: |- + let starttime = now(); + let endtime = now(); + let lookback = ago(7d); + //example of excluding Saturday and Sunday in Average as those are potentially low volume and decrease the average, feel free to change + let excludedDays = dynamic(["Saturday", "Sunday"]); + // average is across 5 days as we are dropping weekends, change as needed + let numDays = 5; + // limit to over 1000 lookups somewhat random but helps focus in on higher lookups, change as needed + let avglookupThreshold = 3; + let lookupThreshold = 1000; + //Setting to startofday so we get 7 days prior to today + _Im_Dns(starttime=startofday(lookback),endtime=startofday(starttime)) + //getting the associated number of the day of the week so we can map to a given day for later parsing if needed + | extend DayNumberofWeek = tostring(dayofweek(TimeGenerated)) + //Setting the Day of the week value so that certain days could be excluded if needed + | extend DayofWeek = iff(DayNumberofWeek == "00:00:00", "Sunday", + (iff(DayNumberofWeek == "1.00:00:00", "Monday", + (iff(DayNumberofWeek == "2.00:00:00", "Tuesday", + (iff(DayNumberofWeek == "3.00:00:00", "Wednesday", + (iff(DayNumberofWeek == "4.00:00:00", "Thursday", + (iff(DayNumberofWeek == "5.00:00:00", "Friday", + (iff(DayNumberofWeek == "6.00:00:00", "Saturday", DayNumberofWeek))))))))))))) + | where DayofWeek !in~ (excludedDays) + | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), count() by SrcIpAddr, DnsQuery + | project StartTime, EndTime, SrcIpAddr, FullNameLookup = DnsQuery, DailyAvgLookupCountOverLastWeek = count_/numDays + | join ( _Im_Dns + | where TimeGenerated between(startofday(starttime)..endofday(endtime)) + | summarize count() by SrcIpAddr, FullNameLookup = DnsQuery + | project SrcIpAddr, LookupCountToday = count_, FullNameLookup + ) + on SrcIpAddr, FullNameLookup + | where LookupCountToday > (DailyAvgLookupCountOverLastWeek * avglookupThreshold) and LookupCountToday >= lookupThreshold + | project StartTime, EndTime, SrcIpAddr, LookupCountToday, DailyAvgLookupCountOverLastWeek, FullNameLookup + | order by LookupCountToday desc nulls last + | extend timestamp = StartTime + | extend IP_0_Address = SrcIpAddr + | extend DNS_0_DomainName = FullNameLookup + - Format: KQL + Skills: + - Name: Connection to Unpopular Website Detected + DisplayName: Connection to Unpopular Website Detected (Preview) + Description: This query lists DNS queries not found in the top 1 million queries in the past 14 days. + Settings: + Target: Sentinel + # The ID of the AAD Organization that the Sentinel workspace is in. + TenantId: '{{TenantId}}' + # The id of the Azure Subscription that the Sentinel workspace is in. + SubscriptionId: '{{SubscriptionId}}' + # The name of the Resource Group that the Sentinel workspace is in. + ResourceGroupName: '{{ResourceGroupName}}' + # The name of the Sentinel workspace. + WorkspaceName: '{{WorkspaceName}}' + # This query detects potential network beaconing activity + Template: |- + let min_t = ago(14d); + let max_t = now(); + let dt = 1d; + // calculate avg. eps(events per second) + let eps = materialize (_Im_Dns + | project TimeGenerated + | where TimeGenerated > ago(5m) + | count + | extend Count = Count / 300); + let maxSummarizedTime = toscalar ( + union isfuzzy=true + ( + DNS_Summarized_Logs_ip_CL + | where EventTime_t >= min_t + | summarize max_TimeGenerated=max(EventTime_t) + | extend max_TimeGenerated = datetime_add('hour', 1, max_TimeGenerated) + ), + ( + print(min_t) + | project max_TimeGenerated = print_0 + ) + | summarize maxTimeGenerated = max(max_TimeGenerated) + ); + let summarizationexist = materialize( + union isfuzzy=true + ( + DNS_Summarized_Logs_ip_CL + | where EventTime_t > ago(1d) + | project v = int(2) + ), + ( + print int(1) + | project v = print_0 + ) + | summarize maxv = max(v) + | extend sumexist = (maxv > 1) + ); + let allData = ( union isfuzzy=true + ( + (datatable(exists: int, sumexist: bool)[1, false] + | where toscalar(eps) > 1000 + | join (summarizationexist) on sumexist) + | join ( + _Im_Dns(starttime=todatetime(ago(2d)), endtime=ago(dt)) + | where TimeGenerated > maxSummarizedTime + | summarize Count=count() by DnsQuery + | top 1000000 by Count + | summarize TopOneMillionDNSQuery=make_list(DnsQuery,1000000) + | extend exists=int(1) + ) + on exists + | project-away exists, maxv, sum* + ), + ( + (datatable(exists: int, sumexist: bool)[1, false] + | where toscalar(eps) between (501 .. 1000) + | join (summarizationexist) on sumexist) + | join ( + _Im_Dns(starttime=todatetime(ago(3d)), endtime=ago(dt)) + | where TimeGenerated > maxSummarizedTime + | summarize Count=count() by DnsQuery + | top 1000000 by Count + | summarize TopOneMillionDNSQuery=make_list(DnsQuery,1000000) + | extend exists=int(1) + ) + on exists + | project-away exists, maxv, sum* + ), + ( + (datatable(exists: int, sumexist: bool)[1, false] + | where toscalar(eps) <= 500 + | join (summarizationexist) on sumexist) + | join ( + _Im_Dns(starttime=todatetime(ago(4d)), endtime=ago(dt)) + | where TimeGenerated > maxSummarizedTime + | summarize Count=count() by DnsQuery + | top 1000000 by Count + | summarize TopOneMillionDNSQuery=make_list(DnsQuery,1000000) + | extend exists=int(1) + ) + on exists + | project-away exists, maxv, sum* + ), + ( + DNS_Summarized_Logs_ip_CL + | where EventTime_t between (min_t .. ago(dt)) and isnotempty(DnsQuery_s) + | project-rename + DnsQuery=DnsQuery_s, + Count=count__d + | extend Count = toint(Count) + | summarize TotalCount=toint(sum(Count)) by DnsQuery + | top 1000000 by TotalCount + | summarize TopOneMillionDNSQuery=make_list(DnsQuery,1000000) + ) + ); + _Im_Dns(starttime=ago(dt),endtime=now()) + | summarize Count=count() by DnsQuery + | where isnotempty(DnsQuery) and DnsQuery !in (allData) + | extend DNS_0_DomainName = DnsQuery + - Format: KQL + Skills: + - Name: Anomalous Increase in DNS activity by clients + DisplayName: Anomalous Increase in DNS activity by clients (Preview) + Description: Checks for an anomalous increase in DNS activity per client in the last 24 hours as compared to the last 14 days. + Settings: + Target: Sentinel + # The ID of the AAD Organization that the Sentinel workspace is in. + TenantId: '{{TenantId}}' + # The id of the Azure Subscription that the Sentinel workspace is in. + SubscriptionId: '{{SubscriptionId}}' + # The name of the Resource Group that the Sentinel workspace is in. + ResourceGroupName: '{{ResourceGroupName}}' + # The name of the Sentinel workspace. + WorkspaceName: '{{WorkspaceName}}' + # This query detects potential network beaconing activity + Template: |- + let threshold = 2.5; + let min_t = ago(14d); + let max_t = now(); + let dt = 1d; + // calculate avg. eps(events per second) + let eps = materialize (_Im_Dns + | project TimeGenerated + | where TimeGenerated > ago(5m) + | count + | extend Count = Count / 300); + let maxSummarizedTime = toscalar ( + union isfuzzy=true + ( + DNS_Summarized_Logs_ip_CL + | where EventTime_t >= min_t + | summarize max_TimeGenerated=max(EventTime_t) + | extend max_TimeGenerated = datetime_add('hour', 1, max_TimeGenerated) + ), + ( + print(min_t) + | project max_TimeGenerated = print_0 + ) + | summarize maxTimeGenerated = max(max_TimeGenerated) + ); + let summarizationexist = materialize( + union isfuzzy=true + ( + DNS_Summarized_Logs_ip_CL + | where EventTime_t > ago(1d) + | project v = int(2) + ), + ( + print int(1) + | project v = print_0 + ) + | summarize maxv = max(v) + | extend sumexist = (maxv > 1) + ); + let allData = union isfuzzy=true + ( + (datatable(exists: int, sumexist: bool)[1, false] + | where toscalar(eps) > 1000 + | join (summarizationexist) on sumexist) + | join ( + _Im_Dns(starttime=todatetime(ago(2d)), endtime=now()) + | where TimeGenerated > maxSummarizedTime and isnotempty(SrcIpAddr) + | summarize Count=count() by SrcIpAddr, bin(TimeGenerated, 1h) + | extend EventTime = TimeGenerated, Count = toint(Count), exists=int(1) + ) + on exists + | project-away exists, maxv, sum* + ), + ( + (datatable(exists: int, sumexist: bool)[1, false] + | where toscalar(eps) between (501 .. 1000) + | join (summarizationexist) on sumexist) + | join ( + _Im_Dns(starttime=todatetime(ago(3d)), endtime=now()) + | where TimeGenerated > maxSummarizedTime and isnotempty(SrcIpAddr) + | summarize Count=count() by SrcIpAddr, bin(TimeGenerated, 1h) + | extend EventTime = TimeGenerated, Count = toint(Count), exists=int(1) + ) + on exists + | project-away exists, maxv, sum* + ), + ( + (datatable(exists: int, sumexist: bool)[1, false] + | where toscalar(eps) <= 500 + | join (summarizationexist) on sumexist) + | join ( + _Im_Dns(starttime=todatetime(ago(4d)), endtime=now()) + | where TimeGenerated > maxSummarizedTime and isnotempty(SrcIpAddr) + | summarize Count=count() by SrcIpAddr, bin(TimeGenerated, 1h) + | extend EventTime = TimeGenerated, Count = toint(Count), exists=int(1) + ) + on exists + | project-away exists, maxv, sum* + ), + ( + DNS_Summarized_Logs_ip_CL + | where EventTime_t > min_t and isnotempty(SrcIpAddr_s) + | summarize Count=toint(sum(count__d)) by SrcIpAddr=SrcIpAddr_s, bin(EventTime=EventTime_t, 1h) + ); + allData + | make-series TotalEventCountPerDay= sum(Count) on EventTime from min_t to max_t step dt by SrcIpAddr + | extend (anomalies, score, baseline) = series_decompose_anomalies(TotalEventCountPerDay, threshold, -1, 'linefit') + | mv-expand anomalies, score, baseline, EventTime, TotalEventCountPerDay + | extend + anomalies = toint(anomalies), + score = toint(score), + baseline = toint(baseline), + EventTime = todatetime(EventTime), + TotalEvents = tolong(TotalEventCountPerDay) + | where EventTime >= ago(dt) + | where score >= threshold * 2 + | order by score + | extend IP_0_Address = SrcIpAddr + - Format: KQL + Skills: + - Name: CVE-2020-1350 (SIGRED) exploitation pattern + DisplayName: CVE-2020-1350 (SIGRED) exploitation pattern (Preview) + Description: This query detects the exploitation pattern of the CVE-2020-1350 (SIGRED) vulnerability. This query utilizes ASIM normalization and is applied to any source that supports the ASIM DNS schema. + Settings: + Target: Sentinel + # The ID of the AAD Organization that the Sentinel workspace is in. + TenantId: '{{TenantId}}' + # The id of the Azure Subscription that the Sentinel workspace is in. + SubscriptionId: '{{SubscriptionId}}' + # The name of the Resource Group that the Sentinel workspace is in. + ResourceGroupName: '{{ResourceGroupName}}' + # The name of the Sentinel workspace. + WorkspaceName: '{{WorkspaceName}}' + # This query detects potential network beaconing activity + Template: |- + let threshold = 15; + _Im_Dns(starttime=ago(1d),endtime=now()) + | where DnsQueryTypeName in~ ('SIG', 'RRSIG') + | where NetworkProtocol =~ 'TCP' + | summarize Count=count() by SrcIpAddr, bin(TimeGenerated, 1m) + | where Count > threshold + | extend IP_0_Address = SrcIpAddr \ No newline at end of file diff --git a/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/ASIM Hunting queries/NetworkSessionsEssentials_HuntingQueries.yaml b/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/ASIM Hunting queries/NetworkSessionsEssentials_HuntingQueries.yaml new file mode 100644 index 00000000..5cab300d --- /dev/null +++ b/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/ASIM Hunting queries/NetworkSessionsEssentials_HuntingQueries.yaml @@ -0,0 +1,339 @@ +Descriptor: + Name: Network Session Essentials - ASIM + DisplayName: Network Session Essentials - Hunting Queries (Preview) + Description: Network Session normalization schema represents an IP network activity, such as network connections and network sessions. Such events are reported, for example, by operating systems, routers, firewalls, and intrusion prevention systems. + Settings: + - Name: TenantId + Required: true + - Name: WorkspaceName + Required: true + - Name: SubscriptionId + Required: true + - Name: ResourceGroupName + Required: true + SupportedAuthTypes: + - None + +SkillGroups: + - Format: KQL + Skills: + - Name: Detect Outbound LDAP Traffic (ASIM Network Session schema) + DisplayName: Detect Outbound LDAP Traffic (ASIM Network Session schema) (Preview) + Description: Malicious actors often abuse misconfigured LDAP servers or applications that use the LDAP servers in organizations. Outbound LDAP traffic should not be allowed outbound through your perimeter firewall. + Settings: + Target: Sentinel + # The ID of the AAD Organization that the Sentinel workspace is in. + TenantId: '{{TenantId}}' + # The id of the Azure Subscription that the Sentinel workspace is in. + SubscriptionId: '{{SubscriptionId}}' + # The name of the Resource Group that the Sentinel workspace is in. + ResourceGroupName: '{{ResourceGroupName}}' + # The name of the Sentinel workspace. + WorkspaceName: '{{WorkspaceName}}' + # This query detects potential network beaconing activity + Template: |- + _Im_NetworkSession(starttime=ago(1d)) + | where EventResult=="Failure" and ipv4_is_private(SrcIpAddr) and not(ipv4_is_private(DstIpAddr)) and SrcIpAddr != DstIpAddr + | where tostring(DstPortNumber) has_any ("389", "636") + | summarize Starttime= min(TimeGenerated),EndTime= max(TimeGenerated),Eventscount=sum(EventCount), EventVendors=make_set(EventVendor,10) by SrcIpAddr,DstIpAddr,DstPortNumber,NetworkProtocol,EventResult + | extend IP_0_Address = SrcIpAddr + - Format: KQL + Skills: + - Name: Detect port misuse by anomaly (ASIM Network Session schema) + DisplayName: Detect port misuse by anomaly (ASIM Network Session schema) (Preview) + Description: This hunting query detect anomalous pattern in port usage with ASIM normalization. + Settings: + Target: Sentinel + # The ID of the AAD Organization that the Sentinel workspace is in. + TenantId: '{{TenantId}}' + # The id of the Azure Subscription that the Sentinel workspace is in. + SubscriptionId: '{{SubscriptionId}}' + # The name of the Resource Group that the Sentinel workspace is in. + ResourceGroupName: '{{ResourceGroupName}}' + # The name of the Sentinel workspace. + WorkspaceName: '{{WorkspaceName}}' + # This query detects potential network beaconing activity + Template: |- + let lookback = 14d; + let mapping = _GetWatchlist('NetworkSession_Monitor_Configuration') + | where Type == "Hunting" and ThresholdType == "Anomaly" and Severity != "Disabled" + | extend Ports = split(Ports,","), + App = split(App,","), + Protocol = split(Protocol,","), + Direction = split(Direction,","), + Action = split(Action,",") + | project Ports, App, Protocol, Direction, Action, Type, ThresholdType, Threshold, Severity, Tactic, Name, Description + | mv-expand Ports + | mv-expand App + | mv-expand Protocol + | mv-expand Direction + | mv-expand Action + | extend Ports = tostring(Ports), App = tostring(App), Protocol = tostring(Protocol), Direction = tostring(Direction), Action = tostring(Action), Threshold = toint(Threshold) + ; + let AnomalyThreshold = 2.5; + let eps = materialize (_Im_NetworkSession | project TimeGenerated | where TimeGenerated > ago(5m) | count | extend Count = Count/300); + let maxSummarizedTime = toscalar ( + union isfuzzy=true + ( + NetworkCustomAnalytics_protocol_CL + | where EventTime_t > ago(lookback) + | summarize max_TimeGenerated=max(EventTime_t) + | extend max_TimeGenerated = datetime_add('minute',10,max_TimeGenerated) + ), + ( + print(ago(lookback)) + | project max_TimeGenerated = print_0 + ) + | summarize maxTimeGenerated = max(max_TimeGenerated) + ); + let nosummary = materialize( + union isfuzzy=true + ( + NetworkCustomAnalytics_protocol_CL + | where EventTime_t > ago(1d) + | project v = int(2) + ), + ( + print int(1) + | project v = print_0 + ) + | summarize maxv = max(v) + | extend nosum = (maxv > 1) + ); + let allData = union isfuzzy=true + ( + (datatable(exists:int, nosum:bool)[1,false] | where toscalar(eps) > 1000 | join (nosummary) on nosum) | join ( + _Im_NetworkSession(starttime=todatetime(ago(2d)), endtime=now()) + | where TimeGenerated > maxSummarizedTime + | summarize Count=count() by NetworkProtocol, DstPortNumber, DstAppName, NetworkDirection, DvcAction, bin(TimeGenerated,10m) + | extend EventTime = TimeGenerated, Count = toint(Count), DstPortNumber = toint(DstPortNumber), exists=int(1) + ) on exists + | project-away exists, maxv, nosum* + ), + ( + (datatable(exists:int, nosum:bool)[1,false] | where toscalar(eps) between (501 .. 1000) | join (nosummary) on nosum) | join ( + _Im_NetworkSession(starttime=todatetime(ago(3d)), endtime=now()) + | where TimeGenerated > maxSummarizedTime + | summarize Count=count() by NetworkProtocol, DstPortNumber, DstAppName, NetworkDirection, DvcAction, bin(TimeGenerated,10m) + | extend EventTime = TimeGenerated, Count = toint(Count), DstPortNumber = toint(DstPortNumber), exists=int(1) + ) on exists + | project-away exists, maxv, nosum* + ), + ( + (datatable(exists:int, nosum:bool)[1,false] | where toscalar(eps) <= 500 | join (nosummary) on nosum) | join ( + _Im_NetworkSession(starttime=todatetime(ago(4d)), endtime=now()) + | where TimeGenerated > maxSummarizedTime + | summarize Count=count() by NetworkProtocol, DstPortNumber, DstAppName, NetworkDirection, DvcAction, bin(TimeGenerated,10m) + | extend EventTime = TimeGenerated, Count = toint(Count), DstPortNumber = toint(DstPortNumber), exists=int(1) + ) on exists + | project-away exists, maxv, nosum* + ), + ( + NetworkCustomAnalytics_protocol_CL + | where EventTime_t > ago(lookback) + | project-rename NetworkProtocol=NetworkProtocol_s, DstPortNumber=DstPortNumber_d, DstAppName=DstAppName_s, NetworkDirection=NetworkDirection_s, DvcAction=DvcAction_s, Count=count__d, EventTime=EventTime_t + | extend Count = toint(Count),DstPortNumber = toint(DstPortNumber) + ) + ; + allData + | where isnotempty(DstPortNumber) + | make-series Total=count() on EventTime from ago(lookback) to now() step 1d by DstPortNumber, NetworkProtocol, NetworkDirection, DvcAction + | extend (anomalies, score, baseline) = series_decompose_anomalies(Total, AnomalyThreshold, -1, 'linefit') + | mv-expand anomalies, score, baseline, EventTime, Total + | extend anomalies = toint(anomalies), score = toint(score), baseline = toint(baseline), EventTime = todatetime(EventTime), Total = tolong(Total) + | where EventTime >= ago(1d) + | extend DstPortNumber = trim_end(".0",tostring(DstPortNumber)) + | where score > 2*AnomalyThreshold + | join kind=inner ['mapping'] where Ports == DstPortNumber + | where (Protocol == "*" or Protocol has NetworkProtocol) + and (Direction == "*" or Direction has NetworkDirection) + and (Action == "*" or Action has DvcAction) + | project Name, Description, NetworkProtocol, DstPortNumber, NetworkDirection, DvcAction, Severity, Tactic + | summarize NetworkProtocols=make_set_if(NetworkProtocol,isnotempty(NetworkProtocol),20), + NetworkDirections=make_set_if(NetworkDirection,isnotempty(NetworkDirection),5), + DvcActions=make_set_if(DvcAction,isnotempty(DvcAction),10) by Name, Severity, Tactic, DstPortNumber, Description + - Format: KQL + Skills: + - Name: Detect port misuse by static threshold (ASIM Network Session schema) + DisplayName: Detect port misuse by static threshold (ASIM Network Session schema) (Preview) + Description: There is an normal amount of traffic that goes on a particular port in any organization. This hunting query identifies port usage higher than threshold defined in 'NetworkSession_Monitor_Configuration' watchlist to determine high port usage. + Settings: + Target: Sentinel + # The ID of the AAD Organization that the Sentinel workspace is in. + TenantId: '{{TenantId}}' + # The id of the Azure Subscription that the Sentinel workspace is in. + SubscriptionId: '{{SubscriptionId}}' + # The name of the Resource Group that the Sentinel workspace is in. + ResourceGroupName: '{{ResourceGroupName}}' + # The name of the Sentinel workspace. + WorkspaceName: '{{WorkspaceName}}' + # This query detects potential network beaconing activity + Template: |- + let lookback = 10m; + let mapping = _GetWatchlist('NetworkSession_Monitor_Configuration') + | where Type == "Hunting" and ThresholdType == "Static" and Severity != "Disabled" + | extend Ports = split(Ports,","), + App = split(App,","), + Protocol = split(Protocol,","), + Direction = split(Direction,","), + Action = split(Action,",") + | project Ports, App, Protocol, Direction, Action, Type, ThresholdType, Threshold, Severity, Tactic, Name, Description + | mv-expand Ports + | mv-expand App + | mv-expand Protocol + | mv-expand Direction + | mv-expand Action + | extend Ports = tostring(Ports), App = tostring(App), Protocol = tostring(Protocol), Direction = tostring(Direction), Action = tostring(Action), Threshold = toint(Threshold) + ; + let nosummary = materialize( + union isfuzzy=true + ( + NetworkCustomAnalytics_protocol_CL + | project v = int(2) + ), + ( + print int(1) + | project v = print_0 + ) + | summarize maxv = max(v) + | extend nosum = (maxv > 1) + ); + let allData = union isfuzzy=true + ( + (datatable(exists:int, nosum:bool)[1,false] | join (nosummary) on nosum) | join ( + _Im_NetworkSession(starttime=bin(now(-10m),10m), endtime=bin(now(),10m)) + | where TimeGenerated > bin(now(-10m),10m) + | summarize Count=count() by NetworkProtocol, DstPortNumber, DstAppName, NetworkDirection, DvcAction, bin(TimeGenerated,10m) + | extend EventTime = TimeGenerated, Count = toint(Count), DstPortNumber = toint(DstPortNumber), exists=int(1) + ) on exists + | project-away exists, maxv, nosum* + ), + ( + NetworkCustomAnalytics_protocol_CL + | where EventTime_t == toscalar(NetworkCustomAnalytics_protocol_CL | summarize max(EventTime_t)) + | project-rename NetworkProtocol=NetworkProtocol_s, DstPortNumber=DstPortNumber_d, DstAppName=DstAppName_s, NetworkDirection=NetworkDirection_s, DvcAction=DvcAction_s, Count=count__d, EventTime=EventTime_t + | extend Count = toint(Count),DstPortNumber = toint(DstPortNumber) + ) + ; + allData + | where isnotempty(DstPortNumber) + | summarize Sum=sum(Count) by DstPortNumber, NetworkProtocol, NetworkDirection, DvcAction + | join kind=inner ['mapping'] where Ports has tostring(DstPortNumber) + | where Sum > Threshold + and (Protocol == "*" or Protocol has NetworkProtocol) + and (Direction == "*" or Direction has NetworkDirection) + and (Action == "*" or Action has DvcAction) + | project Name, Description, NetworkProtocol, DstPortNumber, NetworkDirection, DvcAction, Severity, Tactic + | summarize NetworkProtocols=make_set_if(NetworkProtocol,isnotempty(NetworkProtocol),20), + NetworkDirections=make_set_if(NetworkDirection,isnotempty(NetworkDirection),5), + DvcActions=make_set_if(DvcAction,isnotempty(DvcAction),10) by Name, Severity, Tactic, DstPortNumber, Description + Skills: + - Name: Detects several users with the same MAC address (ASIM Network Session schema) + DisplayName: Detects several users with the same MAC address (ASIM Network Session schema) (Preview) + Description: this hunting query will identify if same MAC ID is associated with more than one user which can be a case of MAC spoofing attack + Settings: + Target: Sentinel + # The ID of the AAD Organization that the Sentinel workspace is in. + TenantId: '{{TenantId}}' + # The id of the Azure Subscription that the Sentinel workspace is in. + SubscriptionId: '{{SubscriptionId}}' + # The name of the Resource Group that the Sentinel workspace is in. + ResourceGroupName: '{{ResourceGroupName}}' + # The name of the Sentinel workspace. + WorkspaceName: '{{WorkspaceName}}' + # This query detects potential network beaconing activity + Template: |- + let lookback = 1h; + _Im_NetworkSession(starttime=ago(lookback),endtime=now()) + | where isnotempty(DstMacAddr) and isnotempty(DstUsername) + | summarize UserSet = make_set(DstUsername,10) by DstMacAddr + | extend UniqueUsers = array_length(UserSet) + | where UniqueUsers >= 2 + - Format: KQL + Skills: + - Name: Mismatch between Destination App name and Destination Port (ASIM Network Session schema) + DisplayName: Mismatch between Destination App name and Destination Port (ASIM Network Session schema) (Preview) + Description: Every standard app has a port associated with it. This query will identify if destination port associated with destination app is not standard which can be a case of network spoofing attack. + Settings: + Target: Sentinel + # The ID of the AAD Organization that the Sentinel workspace is in. + TenantId: '{{TenantId}}' + # The id of the Azure Subscription that the Sentinel workspace is in. + SubscriptionId: '{{SubscriptionId}}' + # The name of the Resource Group that the Sentinel workspace is in. + ResourceGroupName: '{{ResourceGroupName}}' + # The name of the Sentinel workspace. + WorkspaceName: '{{WorkspaceName}}' + # This query detects potential network beaconing activity + Template: |- + _Im_NetworkSession(starttime=ago(1d),endtime=now()) + | where DstAppName in ("dns","web-browsing","http","ssl","smtp") and DstPortNumber in (53, 80, 8080, 8000, 443, 8443, 25) + | extend DstAppName = tolower(DstAppName) + | where + (DstAppName == "dns" and not(DstPortNumber == 53)) or + (DstAppName in ("web-browsing","http") and not(DstPortNumber in (80, 8080, 8000))) or + (DstAppName == "ssl" and not(DstPortNumber in (443, 8443))) or + (DstAppName == "smtp" and not(DstPortNumber == 25)) + | summarize Instances = count(), EventStartTime = min(EventStartTime), EventEndTime = max(EventEndTime) by SrcIpAddr, DstIpAddr, DstAppName, DstPortNumber + | extend IP_0_Address = SrcIpAddr + | extend IP_1_Address = DstIpAddr + - Format: KQL + Skills: + - Name: Protocols passing authentication in cleartext (ASIM Network Session schema) + DisplayName: Protocols passing authentication in cleartext (ASIM Network Session schema) (Preview) + Description: This hunting query identifies cleartext protocols like telnet, POP3, IMAP, and non-anonymous FTP that could leak sensitive information. These protocols may use SSL, but usually on different ports. + Settings: + Target: Sentinel + # The ID of the AAD Organization that the Sentinel workspace is in. + TenantId: '{{TenantId}}' + # The id of the Azure Subscription that the Sentinel workspace is in. + SubscriptionId: '{{SubscriptionId}}' + # The name of the Resource Group that the Sentinel workspace is in. + ResourceGroupName: '{{ResourceGroupName}}' + # The name of the Sentinel workspace. + WorkspaceName: '{{WorkspaceName}}' + # This query detects potential network beaconing activity + Template: |- + // Filter events from last 1 day and not failed + _Im_NetworkSession(starttime=ago(1d)) + // Filter for private source IP and public destination IP + | where EventResult != "Failure" and ipv4_is_private(SrcIpAddr) and not(ipv4_is_private(DstIpAddr)) + // Filter for specific destination ports or non-anonymous FTP + | where tostring(DstPortNumber) has_any ("23", "143", "110") or (tostring(DstPortNumber) == "21" and SrcUsername != "anonymous") + // Summarize data by session parameters + | summarize Starttime= min(TimeGenerated),EndTime= max(TimeGenerated),Eventscount=sum(EventCount), EventVendors=make_set(EventVendor,10) by SrcIpAddr,DstIpAddr,DstPortNumber,NetworkProtocol,EventResult + | extend PortUsage = case( + DstPortNumber == 23, "Telnet", + DstPortNumber == 143, "IMAP", + DstPortNumber == 110, "POP3", + DstPortNumber == 21, "FTP", + "Other" + ) + | extend IP_0_Address = SrcIpAddr + - Format: KQL + Skills: + - Name: Remote Desktop Network Traffic(ASIM Network Session schema) + DisplayName: Remote Desktop Network Traffic(ASIM Network Session schema) (Preview) + Description: This hunting query looks for unusual remote desktop activity by monitoring TCP/3389 traffic. While RDP is common, focus on atypical connections to identify potential threats. + Settings: + Target: Sentinel + # The ID of the AAD Organization that the Sentinel workspace is in. + TenantId: '{{TenantId}}' + # The id of the Azure Subscription that the Sentinel workspace is in. + SubscriptionId: '{{SubscriptionId}}' + # The name of the Resource Group that the Sentinel workspace is in. + ResourceGroupName: '{{ResourceGroupName}}' + # The name of the Sentinel workspace. + WorkspaceName: '{{WorkspaceName}}' + # This query detects potential network beaconing activity + Template: |- + // Filter events from the last day that were not failures + _Im_NetworkSession(starttime=ago(1d)) + // Filter events where the source IP is private, the destination IP is not private, and the source and destination IPs are not the same + | where EventResult == "Failure" and ipv4_is_private(SrcIpAddr) and not(ipv4_is_private(DstIpAddr)) and SrcIpAddr != DstIpAddr + // Filter events where the destination port number is 3389 (commonly used for Microsoft Remote Desktop (RDP)) + | where tostring(DstPortNumber) has_any ("3389") + // Summarize the data by source IP, destination IP, destination port number, network protocol, and event result + // For each group, calculate the start time, end time, event count, and a set of up to 10 event vendors + | summarize Starttime= min(TimeGenerated),EndTime= max(TimeGenerated),Eventscount=sum(EventCount), EventVendors=make_set(EventVendor,10) by SrcIpAddr,DstIpAddr,DstPortNumber,NetworkProtocol,EventResult + | extend IP_0_Address = SrcIpAddr \ No newline at end of file diff --git a/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/ASIM Hunting queries/NetworkThreatProtectionEssentials_HuntingQueries.yaml b/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/ASIM Hunting queries/NetworkThreatProtectionEssentials_HuntingQueries.yaml new file mode 100644 index 00000000..0207f243 --- /dev/null +++ b/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/ASIM Hunting queries/NetworkThreatProtectionEssentials_HuntingQueries.yaml @@ -0,0 +1,163 @@ +Descriptor: + Name: Network Threat Protection Essentials - ASIM + DisplayName: Network Threat Protection Essentials - Hunting Queries (Preview) + Description: identifies suspicious network behavior based on various data sources ingested in Sentinel. The solution contains queries to detect common network-based attacks - things like malicious user agents, mining pools, Base64 encoded IPv4 address in request URL etc. + Settings: + - Name: TenantId + Required: true + - Name: WorkspaceName + Required: true + - Name: SubscriptionId + Required: true + - Name: ResourceGroupName + Required: true + SupportedAuthTypes: + - None + +SkillGroups: + - Format: KQL + Skills: + - Name: Base64 encoded IPv4 address in request url + DisplayName: Base64 encoded IPv4 address in request url (Preview) + Description: This query detects Base64-encoded IPv4 addresses in outbound request URLs. It uses pre-computed base64 offsets for IPv4 sequences, eliminating the need for decoding. After identifying a candidate,the query extracts the plaintext IPv4 address pattern. + Settings: + Target: Sentinel + # The ID of the AAD Organization that the Sentinel workspace is in. + TenantId: '{{TenantId}}' + # The id of the Azure Subscription that the Sentinel workspace is in. + SubscriptionId: '{{SubscriptionId}}' + # The name of the Resource Group that the Sentinel workspace is in. + ResourceGroupName: '{{ResourceGroupName}}' + # The name of the Sentinel workspace. + WorkspaceName: '{{WorkspaceName}}' + # This query detects potential network beaconing activity + Template: |- + let starttime = todatetime('{{StartTimeISO}}'); + let endtime = todatetime('{{EndTimeISO}}'); + let ipv4_plaintext_extraction_regex = @"((?:(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])(?:\.)){3}(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5]){1,3})"; + let ipv4_encoded_identification_regex = @"\=([a-zA-Z0-9\/\+]*(?:(?:MC|Au|wL|MS|Eu|xL|Mi|Iu|yL|My|Mu|zL|NC|Qu|0L|NS|Uu|1L|Ni|Yu|2L|Ny|cu|3L|OC|gu|4L|OS|ku|5L){1}[a-zA-Z0-9\/\+]{2,4}){3}[a-zA-Z0-9\/\+\=]*)"; + let ipv4_decoded_hex_extract = @"((?:(?:61|62|63|64|65|66|67|68|69|6a|6b|6c|6d|6e|6f|70|71|72|73|74|75|76|77|78|79|7a|41|42|43|44|45|46|47|48|49|4a|4b|4c|4d|4e|4f|50|51|52|53|54|55|56|57|58|59|5a|2f|2b|3d),){7,15})"; + CommonSecurityLog + | where TimeGenerated between(starttime .. endtime) + | where isnotempty(RequestURL) + // Identify requests with encoded IPv4 addresses + | where RequestURL matches regex ipv4_encoded_identification_regex + | project TimeGenerated, RequestURL + // Extract IP candidates in their base64 encoded format, significantly reducing the dataset + | extend extracted_encoded_ip_candidate = extract_all(ipv4_encoded_identification_regex, RequestURL) + // We could have more than one candidate, expand them out + | mv-expand extracted_encoded_ip_candidate to typeof(string) + | summarize Start=min(TimeGenerated), End=max(TimeGenerated), make_set(RequestURL,100) by extracted_encoded_ip_candidate + // Pad if we need to + | extend extracted_encoded_ip_candidate = iff(strlen(extracted_encoded_ip_candidate) % 2 == 0, extracted_encoded_ip_candidate, strcat(extracted_encoded_ip_candidate, "=")) + // Now decode the candidate to a long array, we cannot go straight to string as it cannot handle non-UTF8, we need to strip that first + | extend extracted_encoded_ip_candidate = tostring(base64_decode_toarray(extracted_encoded_ip_candidate)) + // Extract the IP candidates from the array + | extend hex_extracted = extract_all(ipv4_decoded_hex_extract, extracted_encoded_ip_candidate) + // Expand, it's still possible that we might have more than 1 IP + | mv-expand hex_extracted + // Now we should have a clean string. We need to put it back into a dynamic array for converting back to a string + | extend hex_extracted = trim_end(",", tostring(hex_extracted)) + | extend hex_extracted = strcat("[",hex_extracted,"]") + | extend hex_extracted = todynamic(hex_extracted) + | extend extracted_encoded_ip_candidate = todynamic(extracted_encoded_ip_candidate) + // Convert the array back into a string + | extend decoded_ip_candidate = make_string(hex_extracted) + | summarize by decoded_ip_candidate, tostring(set_RequestURL), Start, End + // Now the IP candidates will be in plaintext, extract the IPs using a regex + | extend ipmatch = extract_all(ipv4_plaintext_extraction_regex, decoded_ip_candidate) + // If it's not an IP, throw it out + | where isnotnull(ipmatch) + | mv-expand ipmatch to typeof(string) + | extend timestamp = Start + | extend IP_0_Address = ipmatch + - Format: KQL + Skills: + - Name: Risky base64 encoded command in URL + DisplayName: Risky base64 encoded command in URL (Preview) + Description: This query detects risky Base64-encoded commands in web requests. It identifies potential C2 server communication and illuminates injected webshells. Note that base64 is case-sensitive, requiring multiple permutations for full coverage. + Settings: + Target: Sentinel + # The ID of the AAD Organization that the Sentinel workspace is in. + TenantId: '{{TenantId}}' + # The id of the Azure Subscription that the Sentinel workspace is in. + SubscriptionId: '{{SubscriptionId}}' + # The name of the Resource Group that the Sentinel workspace is in. + ResourceGroupName: '{{ResourceGroupName}}' + # The name of the Sentinel workspace. + WorkspaceName: '{{WorkspaceName}}' + # This query detects potential network beaconing activity + Template: |- + let mapping = datatable (CommandFound:string, match_list:dynamic) [ + "whoami", dynamic(['d2hvYW1p', 'dob2Fta', '3aG9hbW']), + "net share", dynamic(['bmV0IHNoYXJl', '5ldCBzaGFyZ', 'uZXQgc2hhcm']), + "net use", dynamic(['bmV0IHVzZ', '5ldCB1c2', 'uZXQgdXNl']), + "net view", dynamic(['bmV0IHZpZX', '5ldCB2aWV3', 'uZXQgdmlld']), + "ipconfig", dynamic(['aXBjb25maWc', 'lwY29uZmln', 'pcGNvbmZpZ']), + "net sh", dynamic(['bmV0c2gg', '5ldHNoI', 'uZXRzaC']), + "schtasks", dynamic(['2NodGFza3', 'NjaHRhc2tz', 'zY2h0YXNrc']), + "Invoke- ", dynamic(['SW52b2tlL', 'ludm9rZS', 'JbnZva2Ut']), + ]; + let riskyCommandRegex = @"(d2hvYW1p|dob2Fta|3aG9hbW|bmV0IHNoYXJl|5ldCBzaGFyZ|uZXQgc2hhcm|bmV0IHVzZ|5ldCB1c2|uZXQgdXNl|bmV0IHZpZX|5ldCB2aWV3|uZXQgdmlld|aXBjb25maWc|lwY29uZmln|pcGNvbmZpZ|bmV0c2gg|5ldHNoI|uZXRzaC|2NodGFza3|NjaHRhc2tz|zY2h0YXNrc|SW52b2tlL|ludm9rZS|JbnZva2Ut)"; + CommonSecurityLog + | where TimeGenerated > ago(3d) + | where RequestURL matches regex riskyCommandRegex + | extend B64MatchData = extract_all(riskyCommandRegex, RequestURL) + | where isnotempty(B64MatchData) + | mv-expand B64MatchData to typeof(string) + | join kind=leftouter ( + mapping + | mv-expand match_list to typeof(string) + ) on $left.B64MatchData == $right.match_list + | project TimeGenerated, B64MatchData, CommandFound, RequestURL, RequestMethod, DestinationHostName, DestinationIP, SourceIP, DeviceVendor, DeviceProduct, Activity + | extend timestamp = TimeGenerated + | extend HostName = tostring(split(DestinationHostName, '.', 0)[0]), DnsDomain = tostring(strcat_array(array_slice(split(DestinationHostName, '.'), 1, -1), '.')) + | extend IP_0_Address = SourceIP + | extend IP_1_Address = DestinationIP + | extend DNS_0_HostName = HostName + | extend DNS_0_DnsDomain = DnsDomain + - Format: KQL + Skills: + - Name: Exploit and Pentest Framework User Agent + DisplayName: Exploit and Pentest Framework User Agent (Preview) + Description: This query detects suspicious user agent strings used by exploit and pen test frameworks. + Settings: + Target: Sentinel + # The ID of the AAD Organization that the Sentinel workspace is in. + TenantId: '{{TenantId}}' + # The id of the Azure Subscription that the Sentinel workspace is in. + SubscriptionId: '{{SubscriptionId}}' + # The name of the Resource Group that the Sentinel workspace is in. + ResourceGroupName: '{{ResourceGroupName}}' + # The name of the Sentinel workspace. + WorkspaceName: '{{WorkspaceName}}' + # This query detects potential network beaconing activity + Template: |- + let UserAgentList = "Internet Explorer |Mozilla/4\\.0 \\(compatible; MSIE 6\\.0; Windows NT 5\\.1; SV1; InfoPath\\.2\\)|Mozilla/5\\.0 \\(Windows NT 10\\.0; Win32; x32; rv:60\\.0\\)|Mozilla/4\\.0 \\(compatible; Metasploit RSPEC\\)|Mozilla/4\\.0 \\(compatible; MSIE 6\\.1; Windows NT\\)|Mozilla/4\\.0 \\(compatible; MSIE 6\\.0; Windows NT 5\\.1\\)|Mozilla/4\\.0 \\(compatible; MSIE 8\\.0; Windows NT 6\\.0; Trident/4\\.0\\)|Mozilla/4\\.0 \\(compatible; MSIE 7\\.0; Windows NT 6\\.0; Trident/4\\.0; SIMBAR={7DB0F6DE-8DE7-4841-9084-28FA914B0F2E}; SLCC1; \\.N\\)|Mozilla/5\\.0 \\(Windows; U; Windows NT 5\\.1; en-US\\) AppleWebKit/525\\.13 \\(KHTML, like Gecko\\) Chrome/4\\.0\\.221\\.6 Safari/525\\.13|Mozilla/5\\.0 \\(compatible; MSIE 9\\.0; Windows NT 6\\.1; WOW64; Trident/5\\.0; MAAU\\)|Mozilla/5\\.0[^\\s]|Mozilla/4\\.0 \\(compatible; SPIPE/1\\.0\\)|Mozilla/5\\.0 \\(Windows NT 6\\.3; rv:39\\.0\\) Gecko/20100101 Firefox/35\\.0|Sametime Community Agent|X-FORWARDED-FOR|DotDotPwn v2\\.1|SIPDROID|wordpress hash grabber|exploit|okhttp/"; + // Excluding for IIS, as the main malicious usage for okhttp that we have seen was in the OfficeActivity logs and this can create noise for IIS. + let ExcludeIIS = "okhttp/"; + (union isfuzzy=true + (OfficeActivity + | where ExtendedProperties has "UserAgent" + | extend UserAgent = extractjson("$[0].Value", ExtendedProperties, typeof(string)) + | where UserAgent matches regex UserAgentList + | extend SourceIP = ClientIP + | project TimeGenerated, Type, UserAgent, SourceIP + ), + ( + W3CIISLog + | extend UserAgent = replace('\\+', ' ', csUserAgent) + | where UserAgent matches regex UserAgentList + | where UserAgent !startswith ExcludeIIS + | extend SourceIP = cIP + | project TimeGenerated, Type, UserAgent, SourceIP + ), + ( + AWSCloudTrail + | where UserAgent matches regex UserAgentList + | extend SourceIP = SourceIpAddress + | project TimeGenerated, Type, UserAgent, SourceIP + )) + | summarize min(TimeGenerated), max(TimeGenerated), count() by Type, UserAgent, SourceIP + | extend timestamp = min_TimeGenerated + | extend IP_0_Address = SourceIP \ No newline at end of file diff --git a/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/ASIM Hunting queries/Readme.md b/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/ASIM Hunting queries/Readme.md new file mode 100644 index 00000000..02eda48a --- /dev/null +++ b/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/ASIM Hunting queries/Readme.md @@ -0,0 +1,70 @@ +# DNS,Network, Threat Protectionand Web Sessions Essentials ASIM Schemas Hunting Queries + +This repository contains a collection of Advanced Security Information Model (ASIM) schema hunting queries that are designed to help detect various network anomalies and potential security threats. + +## DNS Essentials ASIM Schema Queries + +- **Top 25 DNS queries with most failures in last 24 hours** +- **Potential beaconing activity** +- **Possible DNS Tunneling or Data Exfiltration Activity** +- **Increase in DNS Requests by client than the daily average count** +- **Connection to Unpopular Website Detected** +- **Anomalous Increase in DNS activity by clients** +- **CVE-2020-1350 (SIGRED) exploitation pattern** + +## Network Essentials Schema Queries + +- **Detect Outbound LDAP Traffic (ASIM Network Session schema)** +- **Detect port misuse by anomaly (ASIM Network Session schema)** +- **Detect port misuse by static threshold (ASIM Network Session schema)** +- **Detects several users with the same MAC address (ASIM Network Session schema)** +- **Mismatch between Destination App name and Destination Port (ASIM Network Session schema)** +- **Protocols passing authentication in cleartext (ASIM Network Session schema)** +- **Remote Desktop Network Traffic (ASIM Network Session schema)** + +## Network Threat Protection Schema/Essentials + +- **Remote Desktop Network Traffic (ASIM Network Session schema)** +- **Risky base64 encoded command in URL** +- **Exploit and Pentest Framework User Agent** + +## Web Session Essentials + +- **Empty User Agent Detected (ASIM Web Session)** +- **Excessive number of forbidden requests detected (ASIM Web Session)** +- **Detect IP Address in the requested URL (ASIM Web Session)** +- **Detect Kali Linux UserAgent (ASIM Web Session)** +- **Beaconing traffic based on common user agents visiting limited number of domains (ASIM Web Session)** +- **Potential beaconing detected - Similar sent bytes (ASIM Web Session)** +- **Potential beaconing detected (ASIM Web Session)** +- **Request from bots and crawlers (ASIM Web Session)** +- **Detect threat information in web requests (ASIM Web Session)** + +## Usage in Security Copilot Plugin + +### Instructions + +### Pre-requisites + +- [Security Copilot Enabled](https://learn.microsoft.com/en-us/security-copilot/get-started-security-copilot#onboarding-to-microsoft-security-copilot) +- [Access to upload custom plugins](https://learn.microsoft.com/en-us/security-copilot/manage-plugins?tabs=securitycopilotplugin#managing-custom-plugins) +- [Microsoft Sentinel Workspace](https://learn.microsoft.com/en-us/azure/sentinel/quickstart-onboard) created. +- Parameters for KQL Plugin - Microsoft Sentinel Workspace Name, Subscription ID, Resource Group Name and Entra Tenant ID +#### Upload the Custom Plugin + +1. Obtain the desired YAML file from this directory: - [DNSEssentials_HuntingQueries.yaml](https://github.com/Azure/Copilot-For-Security/blob/main/Plugins/Community%20Based%20Plugins/Microsoft%20Sentinel%20Custom%20Plugin%20Scenarios/ASIM%Hunting%quries/DNSEssentials_HuntingQueries.yaml). + + [NetworkSessionsEssentials_HuntingQueries.yaml](https://github.com/Azure/Copilot-For-Security/blob/main/Plugins/Community%20Based%20Plugins/Microsoft%20Sentinel%20Custom%20Plugin%20Scenarios/ASIM%Hunting%quries/NetworkSessionsEssentials_HuntingQueries.yaml). + + [NetworkThreatProtectionEssentials_HuntingQaueries.yaml](https://github.com/Azure/Copilot-For-Security/blob/main/Plugins/Community%20Based%20Plugins/Microsoft%20Sentinel%20Custom%20Plugin%20Scenarios/ASIM%Hunting%quries/NetworkThreatProtectionEssentials_HuntingQaueries.yaml). + + [WebSessionEssentials_HuntingQueries.yaml](https://github.com/Azure/Copilot-For-Security/blob/main/Plugins/Community%20Based%20Plugins/Microsoft%20Sentinel%20Custom%20Plugin%20Scenarios/ASIM%Hunting%quries/NetworkThreatProtectionEssentials_HuntingQaueries.yaml). + +2. Modify the KQL query as needed to suit your specific requirements. + + +3. [Upload the custom plugin](https://learn.microsoft.com/en-us/security-copilot/manage-plugins?tabs=securitycopilotplugin#add-custom-plugins) and enter your Tenant ID, Subscription ID, Workspace name, Resource group that hosts your Sentinel worksapce in the resulting dialog box. Verify that the plugin is activated. + +![CopilotForSecurity](https://learn.microsoft.com/en-us/security-copilot/media/add-plugin-button.png) + + diff --git a/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/ASIM Hunting queries/WebSessionEssentials_HuntingQueries.yaml b/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/ASIM Hunting queries/WebSessionEssentials_HuntingQueries.yaml new file mode 100644 index 00000000..4c9b2e14 --- /dev/null +++ b/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/ASIM Hunting queries/WebSessionEssentials_HuntingQueries.yaml @@ -0,0 +1,390 @@ +Descriptor: + Name: Web Session Essentials - Hunting Queries (ASIM) + DisplayName: Web Session Essentials - ASIM (Preview) + Description: identifies suspicious network behavior based on various data sources ingested in Sentinel. The solution contains queries to detect common network-based attacks - things like malicious user agents, mining pools, Base64 encoded IPv4 address in request URL etc. + Settings: + - Name: TenantId + Required: true + - Name: WorkspaceName + Required: true + - Name: SubscriptionId + Required: true + - Name: ResourceGroupName + Required: true + SupportedAuthTypes: + - None + +SkillGroups: + - Format: KQL + Skills: + - Name: Empty User Agent Detected (ASIM Web Session) + DisplayName: Empty User Agent Detected (ASIM Web Session)(Preview) + Description: This rule helps to identify instances of empty user agent requests originating from IP addresses that have previously reported user agent at least once within the same time period. + Settings: + Target: Sentinel + # The ID of the AAD Organization that the Sentinel workspace is in. + TenantId: '{{TenantId}}' + # The id of the Azure Subscription that the Sentinel workspace is in. + SubscriptionId: '{{SubscriptionId}}' + # The name of the Resource Group that the Sentinel workspace is in. + ResourceGroupName: '{{ResourceGroupName}}' + # The name of the Sentinel workspace. + WorkspaceName: '{{WorkspaceName}}' + # This query detects potential network beaconing activity + Template: |- + let lookBack = 1d; + let WebData = + _Im_WebSession(starttime=ago(lookBack), eventresult='Success') + | where isempty(HttpUserAgent) and isnotempty( SrcIpAddr) + | summarize + EventCount=count(), + EventStartTime=min(TimeGenerated), + EventEndTime=max(TimeGenerated) + by SrcIpAddr, SrcUsername, SrcHostname, Url, DstIpAddr, DstPortNumber; + WebData + | join kind=inner (_Im_WebSession(starttime=ago(lookBack), eventresult='Success') + | project HttpUserAgent, SrcIpAddr + | where isnotempty(HttpUserAgent) and isnotempty( SrcIpAddr) + | summarize FoundUserAgents=make_set(HttpUserAgent,100) by SrcIpAddr + ) + on SrcIpAddr + | extend + Name = iif(SrcUsername contains "@", tostring(split(SrcUsername, '@', 0)[0]), SrcUsername), + UPNSuffix = iif(SrcUsername contains "@", tostring(split(SrcUsername, '@', 1)[0]), "") + | project-away SrcIpAddr1 + | order by EventCount desc + | extend IP_0_Address = SrcIpAddr + | extend IP_1_Address = DstIpAddr + | extend Account_0_Name = Name + | extend Account_0_UPNSuffix = UPNSuffix + | extend URL_0_Url = Url + | extend Host_0_HostName = SrcHostname + - Format: KQL + Skills: + - Name: Excessive number of forbidden requests detected (ASIM Web Session) + DisplayName: Excessive number of forbidden requests detected (ASIM Web Session) (Preview) + Description: T + Settings: + Target: Sentinel + # The ID of the AAD Organization that the Sentinel workspace is in. + TenantId: '{{TenantId}}' + # The id of the Azure Subscription that the Sentinel workspace is in. + SubscriptionId: '{{SubscriptionId}}' + # The name of the Resource Group that the Sentinel workspace is in. + ResourceGroupName: '{{ResourceGroupName}}' + # The name of the Sentinel workspace. + WorkspaceName: '{{WorkspaceName}}' + # This query detects potential network beaconing activity + Template: |- + // Please refer this for more details: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status + let threshold=100; // Update threshold as per your environment + let lookBack = 1d; + let ErrorCode=dynamic([403]); + _Im_WebSession(starttime=ago(lookBack)) + | where EventResultDetails in~ (ErrorCode) + | summarize ErrorCount = count(), EventStartTime= min(TimeGenerated), EventEndTime=max(TimeGenerated), Urls=make_set(Url,100) by SrcIpAddr, SrcUsername, SrcHostname, DstIpAddr + | where ErrorCount > threshold + | extend Name = iif(SrcUsername contains "@", tostring(split(SrcUsername,'@',0)[0]),SrcUsername), UPNSuffix = iif(SrcUsername contains "@",tostring(split(SrcUsername,'@',1)[0]),"") + | order by ErrorCount desc + | extend IP_0_Address = SrcIpAddr + | extend IP_1_Address = DstIpAddr + | extend Host_0_HostName = SrcHostname + | extend Account_0_Name = Name + | extend Account_0_UPNSuffix = UPNSuffix + - Format: KQL + Skills: + - Name: Detect IP Address in the requested URL (ASIM Web Session) + DisplayName: Detect IP Address in the requested URL (ASIM Web Session) (Preview) + Description: This rule detects IPAddress in the requested URL + Settings: + Target: Sentinel + # The ID of the AAD Organization that the Sentinel workspace is in. + TenantId: '{{TenantId}}' + # The id of the Azure Subscription that the Sentinel workspace is in. + SubscriptionId: '{{SubscriptionId}}' + # The name of the Resource Group that the Sentinel workspace is in. + ResourceGroupName: '{{ResourceGroupName}}' + # The name of the Sentinel workspace. + WorkspaceName: '{{WorkspaceName}}' + # This query detects potential network beaconing activity + Template: |- + let lookback = 1d; + // IPAddress Regex Pattern + let PlainText_IPv4_Regex_Pattern = @"\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b"; + // Identified base64 encoded IPv4 addresses + let ipv4_encoded_identification_regex = @"\=([a-zA-Z0-9\/\+]*(?:(?:MC|Au|wL|MS|Eu|xL|Mi|Iu|yL|My|Mu|zL|NC|Qu|0L|NS|Uu|1L|Ni|Yu|2L|Ny|cu|3L|OC|gu|4L|OS|ku|5L){1}[a-zA-Z0-9\/\+]{2,4}){3}[a-zA-Z0-9\/\+\=]*)"; + // Extractes IPv4 addresses as hex values + let ipv4_decoded_hex_extract = @"((?:(?:61|62|63|64|65|66|67|68|69|6a|6b|6c|6d|6e|6f|70|71|72|73|74|75|76|77|78|79|7a|41|42|43|44|45|46|47|48|49|4a|4b|4c|4d|4e|4f|50|51|52|53|54|55|56|57|58|59|5a|2f|2b|3d),){6,14}(?:61|62|63|64|65|66|67|68|69|6a|6b|6c|6d|6e|6f|70|71|72|73|74|75|76|77|78|79|7a|41|42|43|44|45|46|47|48|49|4a|4b|4c|4d|4e|4f|50|51|52|53|54|55|56|57|58|59|5a|2f|2b|3d))"; + let ipV4_FromPlainString = _Im_WebSession(starttime=ago(lookback), eventresult='Success') + | where Url matches regex PlainText_IPv4_Regex_Pattern and not(ipv4_is_private(DstIpAddr)) // take URLs containing IPV4 address and the requests going to internet + | project TimeGenerated, Url, SrcIpAddr, SrcUsername, SrcHostname + | extend ip_candidate = extract(PlainText_IPv4_Regex_Pattern, 0, Url) + | summarize EventEndTime = max(TimeGenerated), EventStartTime = min(TimeGenerated), Urls=tostring(make_set(Url,100)) by ip_candidate, SrcIpAddr, SrcUsername, SrcHostname; + let ipV4_FromEncodedString = _Im_WebSession(starttime=ago(lookback), eventresult='Success') + // Identify requests with encoded IPv4 addresses + | where Url matches regex ipv4_encoded_identification_regex and not(ipv4_is_private(DstIpAddr)) // take URLs containing encoded IPV4 address and the requests going to internet + | project TimeGenerated, Url, SrcIpAddr, SrcUsername, SrcHostname + // Extract IP candidates in their base64 encoded format, significantly reducing the dataset + | extend extracted_encoded_ip_candidate = extract_all(ipv4_encoded_identification_regex, Url) + // We could have more than one candidate, expand them out + | mv-expand extracted_encoded_ip_candidate to typeof(string) + | summarize EventStartTime=min(TimeGenerated), EventEndTime=max(TimeGenerated), make_set(Url,100) by extracted_encoded_ip_candidate, SrcIpAddr, SrcUsername, SrcHostname + // Pad if we need to + | extend extracted_encoded_ip_candidate = iff(strlen(extracted_encoded_ip_candidate) % 2 == 0, extracted_encoded_ip_candidate, strcat(extracted_encoded_ip_candidate, "=")) + // Now decode the candidate to a long array, we cannot go straight to string as it cannot handle non-UTF8, we need to strip that first + | extend extracted_encoded_ip_candidate = tostring(base64_decode_toarray(extracted_encoded_ip_candidate)) + // Extract the IP candidates from the array + | extend hex_extracted = extract_all(ipv4_decoded_hex_extract, extracted_encoded_ip_candidate) + // Expand, it's still possible that we might have more than 1 IP + | mv-expand hex_extracted + // Now we should have a clean string. We need to put it back into a dynamic array to convert back to a string. + | extend hex_extracted = trim_end(",", tostring(hex_extracted)) + | extend hex_extracted = strcat("[", hex_extracted, "]") + | extend hex_extracted = todynamic(hex_extracted) + // Convert the array back into a string + | extend decoded_ip_candidate = unicode_codepoints_to_string(hex_extracted) + | where decoded_ip_candidate matches regex PlainText_IPv4_Regex_Pattern + | summarize by ip_candidate=decoded_ip_candidate, Urls=tostring(set_Url), EventStartTime, EventEndTime, SrcIpAddr, SrcUsername, SrcHostname; + union ipV4_FromPlainString,ipV4_FromEncodedString + | extend Name = iif(SrcUsername contains "@", tostring(split(SrcUsername,'@',0)[0]),SrcUsername), UPNSuffix = iif(SrcUsername contains "@",tostring(split(SrcUsername,'@',1)[0]),"") + | extend IP_0_Address = SrcIpAddr + | extend IP_1_Address = ip_candidate + | extend Account_0_Name = Name + | extend Account_0_UPNSuffix = UPNSuffix + | extend Host_0_HostName = SrcHostname + - Format: KQL + Skills: + - Name: Detect Kali Linux UserAgent (ASIM Web Session) + DisplayName: Detect Kali Linux UserAgent (ASIM Web Session) (Preview) + Description: This rule helps to detect usage of Kali Linux in your environment. Attackers might utilize Kali Linux's tools and features for unauthorized penetration testing, reconnaissance, or exploitation attempts. + Settings: + Target: Sentinel + # The ID of the AAD Organization that the Sentinel workspace is in. + TenantId: '{{TenantId}}' + # The id of the Azure Subscription that the Sentinel workspace is in. + SubscriptionId: '{{SubscriptionId}}' + # The name of the Resource Group that the Sentinel workspace is in. + ResourceGroupName: '{{ResourceGroupName}}' + # The name of the Sentinel workspace. + WorkspaceName: '{{WorkspaceName}}' + # This query detects potential network beaconing activity + Template: |- + let lookback = 1d; + _Im_WebSession (starttime=ago(lookback)) + | project HttpUserAgent, SrcIpAddr, SrcUsername, SrcHostname, Url, TimeGenerated + | where HttpUserAgent has_all ("Linux", "Kali") + | summarize EventCount=count(), EventStartTime=min(TimeGenerated), EventEndTime = max(TimeGenerated), URLs = make_set(Url,100) by HttpUserAgent, SrcIpAddr, SrcUsername, SrcHostname + | extend Name = iif(SrcUsername contains "@", tostring(split(SrcUsername,'@',0)[0]),SrcUsername), UPNSuffix = iif(SrcUsername contains "@",tostring(split(SrcUsername,'@',1)[0]),"") + | order by EventCount desc + | extend IP_0_Address = SrcIpAddr + | extend Account_0_Name = Name + | extend Account_0_UPNSuffix = UPNSuffix + | extend Host_0_HostName = SrcHostname + - Format: KQL + Skills: + - Name: Beaconing traffic based on common user agents visiting limited number of domains (ASIM Web Session) + DisplayName: Beaconing traffic based on common user agents visiting limited number of domains (ASIM Web Session)(Preview) + Description: This query searches web proxy logs for a specific type of beaconing behavior by caparing with a known request pattern + Settings: + Target: Sentinel + # The ID of the AAD Organization that the Sentinel workspace is in. + TenantId: '{{TenantId}}' + # The id of the Azure Subscription that the Sentinel workspace is in. + SubscriptionId: '{{SubscriptionId}}' + # The name of the Resource Group that the Sentinel workspace is in. + ResourceGroupName: '{{ResourceGroupName}}' + # The name of the Sentinel workspace. + WorkspaceName: '{{WorkspaceName}}' + # This query detects potential network beaconing activity + Template: |- + let timeframe = 1d; // Timeframe during which to search for beaconing behavior. + let lookback = 7d; // lookback period to find if browser was used for other domains by user. + let min_requests = 50; // Minimum number of requests to consider it beacon traffic. + let min_hours=8; // Minimum number of different hours during which connections were made to consider it beacon traffic. + let trusted_user_count=10; // If visited by these many users a domain is considered 'trusted'. + let Total_Visited_Sites=3; // Number of different sites visited using this user-agent. + // Client-specific query to obtain 'browser-like' traffic from proxy logs. + let BrowserTraffic = materialize ( + _Im_WebSession(starttime=ago(timeframe)) + | project TimeGenerated, DestHostname = tostring(parse_url(Url)["Host"]), HttpUserAgent, SrcIpAddr, DstIpAddr + | where not(ipv4_is_private(DstIpAddr)) // Take only those requests that are going to internet + | where HttpUserAgent has_any ("Mozilla","Gecko") + ); + let CommonDomains = BrowserTraffic + | summarize source_count=dcount(SrcIpAddr) by DestHostname + | where source_count > trusted_user_count + | project DestHostname; // These are list of common domains that we will filter out. + let CommonUA = BrowserTraffic + | summarize source_count=dcount(SrcIpAddr), dhost_count=dcount(DestHostname) by HttpUserAgent + | where source_count >trusted_user_count and dhost_count > 100 // Normal browsers are used by many people visiting many different sites. + | project HttpUserAgent; + // Some users only use the browsers to visit a very limited set of sites. + // These are considered suspicious, since they might be an attacker masquerading a beacon as a legitimate browser. + let SuspiciousClients = BrowserTraffic + | where HttpUserAgent in (CommonUA) + | summarize Destination_Hosts = make_set(DestHostname,100), request_count=count() by HttpUserAgent, SrcIpAddr + | where array_length(Destination_Hosts) <=Total_Visited_Sites and request_count >= min_requests + | project HttpUserAgent, SrcIpAddr, Destination_Hosts; + // Just reporting on suspicious browsers gives too many false positives. + // For example, users that have the browser open on the login screen of 1 specific application. + // In the suspicious browsers we can search for 'beacon-like' behavior. + // Get all browser traffic by the suspicious browsers. + let PotentialBeaconingTraffic=SuspiciousClients + | join kind=inner (BrowserTraffic) on HttpUserAgent, SrcIpAddr + // Find beaconing-like traffic - i.e. contacting the same host in many different hours. + | summarize hour_count=dcount(bin(TimeGenerated,1h)), Destination_Hosts=take_any(Destination_Hosts), request_count=count() by HttpUserAgent, SrcIpAddr, DestHostname + | where hour_count >=min_hours and request_count >= min_requests + // Remove common domains i.e. visited by atleast trusted_user_count number of users. + | join kind=leftanti CommonDomains on DestHostname // leftanti=> Returns all the records from the left side that don't have matches from the right + | summarize RareHosts=make_set(DestHostname,100), TotalRequestCount=sum(request_count), BrowserHosts=take_any(Destination_Hosts) by HttpUserAgent, SrcIpAddr + // Remove browsers that visit any common domains. + | where array_length(RareHosts) == array_length(BrowserHosts); + // Look back for X days to see if the browser was not used to visit more hosts. + // This is to exclude someone that started up the browser a long time ago, and left only a single tab open + PotentialBeaconingTraffic + | join kind=inner (_Im_WebSession(starttime=lookback) + | project TimeGenerated, DestHostname = tostring(parse_url(Url)["Host"]), HttpUserAgent, SrcIpAddr, DstIpAddr + | where not(ipv4_is_private(DstIpAddr)) // Take only those requests that are going to internet + | where HttpUserAgent has_any ("Mozilla","Gecko") + ) on SrcIpAddr, HttpUserAgent + | summarize RareHosts=take_any(RareHosts),BrowserHosts1d=take_any(BrowserHosts),BrowserHostsLookback=make_set(DestHostname,100) by SrcIpAddr, HttpUserAgent + | where array_length(RareHosts) == array_length(BrowserHostsLookback) + | extend IP_0_Address = SrcIpAddr + - Format: KQL + Skills: + - Name: Potential beaconing detected - Similar sent bytes (ASIM Web Session) + DisplayName: Potential beaconing detected - Similar sent bytes (ASIM Web Session) (Preview) + Description: The presence of a high count of repetitive identical SrcBytes could potentially indicate beaconing activity. + Settings: + Target: Sentinel + # The ID of the AAD Organization that the Sentinel workspace is in. + TenantId: '{{TenantId}}' + # The id of the Azure Subscription that the Sentinel workspace is in. + SubscriptionId: '{{SubscriptionId}}' + # The name of the Resource Group that the Sentinel workspace is in. + ResourceGroupName: '{{ResourceGroupName}}' + # The name of the Sentinel workspace. + WorkspaceName: '{{WorkspaceName}}' + # This query detects potential network beaconing activity + Template: |- + let lookback = 1d; + let TotalEventsThreshold = 25; // Minimum events expected in 1 day. Assuming atleast 25 events. + _Im_WebSession(starttime=ago(lookback), eventresult='Success') + | where isnotempty(SrcIpAddr) and isnotempty(DstIpAddr) and isnotempty(SrcBytes) + | summarize + EventCount = count(), + make_set(TimeGenerated, 10000), + EventStartTime=min(TimeGenerated), + EventEndTime=max(TimeGenerated) + by SrcIpAddr, DstIpAddr, DstPortNumber, SrcBytes, SrcUsername, SrcHostname // Same SrcBytes between SrcIPAddress and DstIpAddress is a potential sign of beaconing activity. + | where EventCount > TotalEventsThreshold + | extend Name = iif(SrcUsername contains "@", tostring(split(SrcUsername,'@',0)[0]),SrcUsername), UPNSuffix = iif(SrcUsername contains "@",tostring(split(SrcUsername,'@',1)[0]),"") + | sort by EventCount desc // Higher the count, more is the possibility of beaconing activity. + // Analyze set_TimeGenerated to know if request being sent at specific intervals. + | extend IP_0_Address = SrcIpAddr + | extend IP_1_Address = DstIpAddr + | extend Account_0_Name = Name + | extend Account_0_UPNSuffix = UPNSuffix + | extend Host_0_HostName = SrcHostname + - Format: KQL + Skills: + - Name: Potential beaconing detected (ASIM Web Session) + DisplayName: Potential beaconing detected (ASIM Web Session) (Preview) + Description: Identifies beaconing patterns from web traffic logs based on recurrent timedelta patterns. + Settings: + Target: Sentinel + # The ID of the AAD Organization that the Sentinel workspace is in. + TenantId: '{{TenantId}}' + # The id of the Azure Subscription that the Sentinel workspace is in. + SubscriptionId: '{{SubscriptionId}}' + # The name of the Resource Group that the Sentinel workspace is in. + ResourceGroupName: '{{ResourceGroupName}}' + # The name of the Sentinel workspace. + WorkspaceName: '{{WorkspaceName}}' + # This query detects potential network beaconing activity + Template: |- + let starttime = 2d; + let endtime = 1d; + let TimeDeltaThreshold = 25; // Minimum time difference in seconds between two events of same SourceIP and DestinationIP + let TotalEventsThreshold = 30; // Total number of events in an hour + let MostFrequentTimeDeltaThreshold = 25; // Most frequent time difference in seconds between two events of same SourceIP + let PercentBeaconThreshold = 80; // what percent of times the requests were sent at same interval within an hour + _Im_WebSession(starttime=startofday(ago(starttime)), endtime=startofday(ago(endtime))) + | where isnotempty(SrcIpAddr) and isnotempty(DstIpAddr) and isnotempty(SrcBytes) + | where ipv4_is_private(DstIpAddr)== false // Focus only on traffic going to internet + | project TimeGenerated, SrcIpAddr, DstIpAddr, SrcBytes, DstBytes, SrcUsername + | sort by SrcIpAddr asc,TimeGenerated asc, DstIpAddr asc + | serialize + | extend NextTimeGenerated = next(TimeGenerated,1), NextSrcIpAddr = next(SrcIpAddr,1) + | extend TimeDeltaInSeconds = datetime_diff('second', NextTimeGenerated, TimeGenerated) + | where SrcIpAddr == NextSrcIpAddr + | where TimeDeltaInSeconds > TimeDeltaThreshold + | summarize EventCount=count(), sum(DstBytes), sum(SrcBytes) by TimeDeltaInSeconds, bin(TimeGenerated,1h), SrcUsername, SrcIpAddr, DstIpAddr + | summarize (MostFrequentTimeDeltaCount, MostFrequentTimeDeltainSeconds) = arg_max(EventCount, TimeDeltaInSeconds), TotalEvents=sum(EventCount), TotalSentBytes = sum(sum_SrcBytes), TotalReceivedBytes = sum(sum_DstBytes) by bin(TimeGenerated,1h), SrcUsername, SrcIpAddr, DstIpAddr + | where TotalEvents > TotalEventsThreshold and MostFrequentTimeDeltaCount > MostFrequentTimeDeltaThreshold + | extend BeaconPercent = MostFrequentTimeDeltaCount/toreal(TotalEvents) * 100 + | where BeaconPercent > PercentBeaconThreshold + | extend Name = iif(SrcUsername contains "@", tostring(split(SrcUsername,'@',0)[0]),SrcUsername), UPNSuffix = iif(SrcUsername contains "@",tostring(split(SrcUsername,'@',1)[0]),"") + | extend IP_0_Address = SrcIpAddr + | extend IP_1_Address = DstIpAddr + | extend Account_0_Name = Name + | extend Account_0_UPNSuffix = UPNSuffix + - Format: KQL + Skills: + - Name: Request from bots and crawlers (ASIM Web Session) + DisplayName: Request from bots and crawlers (ASIM Web Session) (Preview) + Description: While most of these values are associated with legitimate bots or crawlers, malicious actors may sometimes spoof or manipulate user agent headers to disguise their activities. It is important to investigate their activities. + Settings: + Target: Sentinel + # The ID of the AAD Organization that the Sentinel workspace is in. + TenantId: '{{TenantId}}' + # The id of the Azure Subscription that the Sentinel workspace is in. + SubscriptionId: '{{SubscriptionId}}' + # The name of the Resource Group that the Sentinel workspace is in. + ResourceGroupName: '{{ResourceGroupName}}' + # The name of the Sentinel workspace. + WorkspaceName: '{{WorkspaceName}}' + # This query detects potential network beaconing activity + Template: |- + let lookback = 1d; + // You can add or remove items from this list as per your requirement. + let BotandCrawlersList = dynamic (['bot','crawler','baiduspider','80legs','ia_archiver','voyager','wget','yahoo','slurp','mediapartners-google','facebookexternalhit','facebookcatalog','okhttp','cURL','Bytespider']); + _Im_WebSession (starttime=ago(lookback)) + | where HttpUserAgent has_any (BotandCrawlersList) + | summarize EventCount = count(), EventStartTime=min(TimeGenerated), EventEndTime=max(TimeGenerated), UserAgentList = make_set(HttpUserAgent,100), URLs = make_set(Url,100) by SrcIpAddr, SrcUsername, SrcHostname + | extend Name = iif(SrcUsername contains "@", tostring(split(SrcUsername,'@',0)[0]),SrcUsername), UPNSuffix = iif(SrcUsername contains "@",tostring(split(SrcUsername,'@',1)[0]),"") + | order by EventCount desc + | extend IP_0_Address = SrcIpAddr + | extend Account_0_Name = Name + | extend Account_0_UPNSuffix = UPNSuffix + | extend Host_0_HostName = SrcHostname + - Format: KQL + Skills: + - Name: Detect threat information in web requests (ASIM Web Session) + DisplayName: Detect threat information in web requests (ASIM Web Session) (Preview) + Description: This query identifies the presence of threat information in fields such as EventSeverity, ThreatName, and ThreatCategory + Settings: + Target: Sentinel + # The ID of the AAD Organization that the Sentinel workspace is in. + TenantId: '{{TenantId}}' + # The id of the Azure Subscription that the Sentinel workspace is in. + SubscriptionId: '{{SubscriptionId}}' + # The name of the Resource Group that the Sentinel workspace is in. + ResourceGroupName: '{{ResourceGroupName}}' + # The name of the Sentinel workspace. + WorkspaceName: '{{WorkspaceName}}' + # This query detects potential network beaconing activity + Template: |- + let lookback= 1d; + let exludeString = dynamic (["/", "None"]); + _Im_WebSession(starttime=ago(lookback)) + | project EventSeverity, ThreatName, ThreatCategory, ThreatRiskLevel, ThreatOriginalConfidence, ThreatField, TimeGenerated, SrcIpAddr, SrcUsername, SrcHostname, Url + | where (ThreatName !in~ (exludeString) and isnotempty(ThreatName)) or (ThreatCategory !in~ (exludeString) and isnotempty(ThreatCategory)) or ThreatRiskLevel > 60 or toint(ThreatOriginalConfidence) > 0 or EventSeverity in ('Medium','High') or isnotempty(ThreatField) + | summarize EventCount = count(), EventStartTime=min(TimeGenerated), EvenEndTime=max(TimeGenerated) by SrcIpAddr, SrcUsername, SrcHostname, Url, ThreatName, ThreatCategory, ThreatRiskLevel, ThreatOriginalConfidence, ThreatField + | extend Name = iif(SrcUsername contains "@", tostring(split(SrcUsername,'@',0)[0]),SrcUsername), UPNSuffix = iif(SrcUsername contains "@",tostring(split(SrcUsername,'@',1)[0]),"") + | order by EventCount desc + | extend IP_0_Address = SrcIpAddr + | extend Account_0_Name = Name + | extend Account_0_UPNSuffix = UPNSuffix + | extend Host_0_HostName = SrcHostname + | extend URL_0_Url = Url \ No newline at end of file diff --git a/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/FetchIncidentCommentsfromSentinel/FetchIncidentComments.yaml b/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/FetchIncidentCommentsfromSentinel/FetchIncidentComments.yaml index 6fdfeeab..dc7e26f8 100644 --- a/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/FetchIncidentCommentsfromSentinel/FetchIncidentComments.yaml +++ b/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/FetchIncidentCommentsfromSentinel/FetchIncidentComments.yaml @@ -1,23 +1,23 @@ Descriptor: - Name: fetch sentinel incident comments - DisplayName: "fetch sentinel incident comments" - Description: pullback sentinel incident comments + Name: Fetch Sentinel incident comments + DisplayName: Fetch Sentinel incident comments + Description: Pullback Sentinel incident comments SkillGroups: - Format: KQL Skills: - - Name: fetch sentinel incident comments - DisplayName: fetch sentinel incident comments - Description: fetch sentinel incident comments + - Name: SentinelIncidentComments + DisplayName: Fetch Sentinel incident comments + Description: Fetch Sentinel incident comments Inputs: - Name: incidentNumber Description: incident number Required: true Settings: Target: Sentinel - TenantId: xxxxx - SubscriptionId: xxxxx - ResourceGroupName: xxxxx - WorkspaceName: xxxxx + TenantId: + SubscriptionId: + ResourceGroupName: + WorkspaceName: Template: |- SecurityIncident | where TimeGenerated > ago(14d) diff --git a/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/FetchIncidentCommentsfromSentinel/Readme.md b/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/FetchIncidentCommentsfromSentinel/Readme.md new file mode 100644 index 00000000..9b5aa41d --- /dev/null +++ b/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/FetchIncidentCommentsfromSentinel/Readme.md @@ -0,0 +1,50 @@ +# Fetch Sentinel incident comments + +## DESCRIPTION +This plugin shows how to retrieve comments associated with a Sentinel Incident to add context to an investigation performed with Security Copilot. + +--- + +## TYPE AND REQUIREMENTS +**TYPE**: KQL (Sentinel)
+**SOURCE**: _SecurityIncident_ table
+**REQUIREMENTS**: Log Analytics Workspace with Sentinel enabled + +--- + +## SKILLS + + + + + + + + + + + + + + +
SkillNameDescriptionParameters
SentinelIncidentCommentsFetch Sentinel incident comments +
    +
  • + incidentNumber
    +
  • +
+
+ + +--- + +## SAMPLE PROMPTS + +- `« Fetch Sentinel comments for incident and infer what was performed to investigate it »` + +--- + +## SCREENSHOTS +
+ +
diff --git a/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/GCP Audit Logs/KQL-Sentinel-GCP.yaml b/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/GCP Audit Logs/KQL-Sentinel-GCP.yaml index 821e4bed..0f12ffce 100644 --- a/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/GCP Audit Logs/KQL-Sentinel-GCP.yaml +++ b/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/GCP Audit Logs/KQL-Sentinel-GCP.yaml @@ -4,6 +4,35 @@ Descriptor: Description: Get the recent audit logs from GCP (Google Cloud Platform) for a certain user, ip address and time range from logs in Microsoft Sentinel #Can you describe in detail over a few paragraphs the GCP Audit Log operations associated with user@domain.com over the last 90 days?? Be sure to also include all the logs in a format I can export. + Settings: + - Name: TenantId + Label: TenantId + Description: Azure tenant ID where Sentinel exists. + HintText: Azure tenant ID where Sentinel exists. + SettingType: String + Required: true + + - Name: SubscriptionId + Label: SubscriptionId + Description: Azure subscription ID where Sentinel exists. + HintText: Azure subscription ID where Sentinel exists. + SettingType: String + Required: true + + - Name: WorkspaceName + Label: WorkspaceName + Description: Log Analytics workspace name for Sentinel. + HintText: Log Analytics workspace name for Sentinel. + SettingType: String + Required: true + + - Name: ResourceGroupName + Label: ResourceGroupName + Description: Resource group where Sentinel workspace exists. + HintText: Resource group where Sentinel workspace exists. + SettingType: String + Required: true + SkillGroups: - Format: KQL Skills: @@ -25,14 +54,10 @@ SkillGroups: Required: true Settings: Target: Sentinel - # The ID of the AAD Organization that the Sentinel workspace is in. - TenantId: **ENTRA TENANT GUID** - # The id of the Azure Subscription that the Sentinel workspace is in. - SubscriptionId: **AZURE SUBSCRIPTION ID** - # The name of the Resource Group that the Sentinel workspace is in. - ResourceGroupName: **LOG ANALYTICS RESOURCE GROUP NAME** - # The name of the Sentinel workspace. - WorkspaceName: **LOG ANALYTICS WORKSPACE NAME** + TenantId: "{{TenantId}}" + SubscriptionId: "{{SubscriptionId}}" + ResourceGroupName: "{{ResourceGroupName}}" + WorkspaceName: "{{WorkspaceName}}" Template: |- let userPrincipalName='{{userPrincipalName}}'; let ip='{{ip}}'; 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/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/Network Anomaly Detection/NetworkAnomalyDetection.yaml b/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/Network Anomaly Detection/NetworkAnomalyDetection.yaml index 6ddba014..817df209 100644 --- a/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/Network Anomaly Detection/NetworkAnomalyDetection.yaml +++ b/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/Network Anomaly Detection/NetworkAnomalyDetection.yaml @@ -2,6 +2,36 @@ Descriptor: Name: Network (CEF) anomaly detection activity DisplayName: "Sentinel KQL: Network (CEF) anomaly detection activity" Description: Skills that looking up network CEF CommonSecurityLog Sentinel data anomaly activity - events. + + Settings: + - Name: TenantId + Label: TenantId + Description: Azure tenant ID where Sentinel exists. + HintText: Azure tenant ID where Sentinel exists. + SettingType: String + Required: true + + - Name: SubscriptionId + Label: SubscriptionId + Description: Azure subscription ID where Sentinel exists. + HintText: Azure subscription ID where Sentinel exists. + SettingType: String + Required: true + + - Name: WorkspaceName + Label: WorkspaceName + Description: Log Analytics workspace name for Sentinel. + HintText: Log Analytics workspace name for Sentinel. + SettingType: String + Required: true + + - Name: ResourceGroupName + Label: ResourceGroupName + Description: Resource group where Sentinel workspace exists. + HintText: Resource group where Sentinel workspace exists. + SettingType: String + Required: true + SkillGroups: - Format: KQL Skills: @@ -9,9 +39,9 @@ SkillGroups: DisplayName: Detect Network (CEF) anomaly detection activity Description: Fetches all Network (CEF) anomaly detection activity in Microsoft Sentinel CommonSecurityLog, how it works is first to find and list all of the anomalies in a time series, The series_decompose_anomalies() function takes a series of values as input and extracts anomalies then looking at the query results, you can see that the function (Calculates an expected daily usage for each table, Compares actual daily usage to expected usage, Assigns an anomaly score to each data point, indicating the extent of the deviation of actual usage from expected usage, Identifies positive (1) and negative (-1) anomalies in each table) ExamplePrompt: - - 'Analyze network CEF data in Sentinel if there is anomaly' - - 'Anomaly data detection if any in Palo Alto logs' - - 'Scan my network and firewall data if there is any data anomaly activity' + - 'Analyze network CEF data in Sentinel if there is anomaly' + - 'Anomaly data detection if any in Palo Alto logs' + - 'Scan my network and firewall data if there is any data anomaly activity' Inputs: - Name: StartTime Description: Set starTime Number of Days for the localback Network (CEF) anomaly detection activity in Microsoft Sentinel @@ -23,23 +53,19 @@ SkillGroups: DefaultValue: 0d Settings: Target: Sentinel - # The ID of the AAD Organization that the Sentinel workspace is in. - TenantId: xxxx-xxxx-xxxx-xxxx-xxxx-xxxx - # The id of the Azure Subscription that the Sentinel workspace is in. - SubscriptionId: xxxxx-xxxx-xxxx-xxxx-xxxxxxx - # The name of the Resource Group that the Sentinel workspace is in. - ResourceGroupName: My-RG - # The name of the Sentinel workspace. - WorkspaceName: Woodgrove-LogAnalyiticsWorkspace + TenantId: "{{TenantId}}" + SubscriptionId: "{{SubscriptionId}}" + ResourceGroupName: "{{ResourceGroupName}}" + WorkspaceName: "{{WorkspaceName}}" Template: |- - let starttime=datetime('{{StartTime}}'); - let endtime = datetime('{{EndTime}}'); - let timeframe = 1d; - CommonSecurityLog - | where TimeGenerated between (startofday(ago(21d))..startofday(ago(0d))) - | make-series ActualUsage=count() default = 0 on TimeGenerated from startofday(ago(21d)) to startofday(ago(0d)) step 1d by DeviceVendor - | extend(Anomalies, AnomalyScore, ExpectedUsage) = series_decompose_anomalies(ActualUsage,1.5,-1,'avg',1) - | mv-expand ActualUsage to typeof(double), TimeGenerated to typeof(datetime), Anomalies to typeof(double),AnomalyScore to typeof(double), ExpectedUsage to typeof(long) - | where Anomalies != 0 // Returns all positive and negative deviations from expected usage - | project TimeGenerated,ActualUsage,ExpectedUsage,AnomalyScore,Anomalies,DeviceVendor - | sort by abs(AnomalyScore) desc \ No newline at end of file + let starttime=datetime('{{StartTime}}'); + let endtime = datetime('{{EndTime}}'); + let timeframe = 1d; + CommonSecurityLog + | where TimeGenerated between (startofday(ago(21d))..startofday(ago(0d))) + | make-series ActualUsage=count() default = 0 on TimeGenerated from startofday(ago(21d)) to startofday(ago(0d)) step 1d by DeviceVendor + | extend(Anomalies, AnomalyScore, ExpectedUsage) = series_decompose_anomalies(ActualUsage,1.5,-1,'avg',1) + | mv-expand ActualUsage to typeof(double), TimeGenerated to typeof(datetime), Anomalies to typeof(double),AnomalyScore to typeof(double), ExpectedUsage to typeof(long) + | where Anomalies != 0 // Returns all positive and negative deviations from expected usage + | project TimeGenerated,ActualUsage,ExpectedUsage,AnomalyScore,Anomalies,DeviceVendor + | sort by abs(AnomalyScore) desc \ No newline at end of file diff --git a/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/Network beaconing ASIM plugin/Readme.md b/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/Network beaconing ASIM plugin/Readme.md index 492a4a49..ac4f62cc 100644 --- a/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/Network beaconing ASIM plugin/Readme.md +++ b/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/Network beaconing ASIM plugin/Readme.md @@ -1,6 +1,6 @@ # Security Copilot Plugin: Leveraging Security Copilot with ASIM-based KQL plugins for investigation scenarios -### **Bring in insights from Microsoft Sentinel using source-agnostic queries based on ASIM - Advanced Security Informatoin Model ** +### **Bring in insights from Microsoft Sentinel using source-agnostic queries based on ASIM - Advanced Security Information Model** ### Pre-requisites @@ -19,8 +19,4 @@ ### Link to blog post -Refer to the blog in this [link](https://techcommunity.microsoft.com/blog/securitycopilotblog/leveraging-asim-based-kql-plugins-in-microsoft-security-copilot-for-investigatio/4357680?previewMessage=true) - -### Link to blog post - -Refer to the blog in this [link](https://techcommunity.microsoft.com/blog/securitycopilotblog/leveraging-asim-based-kql-plugins-in-microsoft-security-copilot-for-investigatio/4357680?previewMessage=true) for more details \ No newline at end of file +Refer to the blog in this [link](https://techcommunity.microsoft.com/blog/securitycopilotblog/leveraging-asim-based-kql-plugins-in-microsoft-security-copilot-for-investigatio/4357680) diff --git a/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/Sentinel Cost Query Plugin/KQL-SentinelCost.yaml b/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/Sentinel Cost Query Plugin/KQL-SentinelCost.yaml index 8ab92749..18e26c91 100644 --- a/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/Sentinel Cost Query Plugin/KQL-SentinelCost.yaml +++ b/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/Sentinel Cost Query Plugin/KQL-SentinelCost.yaml @@ -4,6 +4,36 @@ Descriptor: Description: Skills that lookup recent Sentinel Cost Details from Sentinel workspace. #Can you lookup what my Sentinel costs are for the past 7 days? #Can you provide a detailed summary of my total Sentinel costs for the past 90 days? Be sure to explain what each table is used for within Sentinel and the security value it provides. Also total up all the costs to give me an average monthly cost. Also be sure to give any cost saving recommendations based on my data ingestion. + + Settings: + - Name: TenantId + Label: TenantId + Description: Azure tenant ID where Sentinel exists. + HintText: Azure tenant ID where Sentinel exists. + SettingType: String + Required: true + + - Name: SubscriptionId + Label: SubscriptionId + Description: Azure subscription ID where Sentinel exists. + HintText: Azure subscription ID where Sentinel exists. + SettingType: String + Required: true + + - Name: WorkspaceName + Label: WorkspaceName + Description: Log Analytics workspace name for Sentinel. + HintText: Log Analytics workspace name for Sentinel. + SettingType: String + Required: true + + - Name: ResourceGroupName + Label: ResourceGroupName + Description: Resource group where Sentinel workspace exists. + HintText: Resource group where Sentinel workspace exists. + SettingType: String + Required: true + SkillGroups: - Format: KQL Skills: @@ -19,14 +49,10 @@ SkillGroups: Required: true Settings: Target: Sentinel - # The ID of the AAD Organization that the Sentinel workspace is in. - TenantId: **ENTRA TENANT GUID** - # The id of the Azure Subscription that the Sentinel workspace is in. - SubscriptionId: **AZURE SUBSCRIPTION ID** - # The name of the Resource Group that the Sentinel workspace is in. - ResourceGroupName: **LOG ANALYTICS RESOURCE GROUP NAME** - # The name of the Sentinel workspace. - WorkspaceName: **LOG ANALYTICS WORKSPACE NAME** + TenantId: "{{TenantId}}" + SubscriptionId: "{{SubscriptionId}}" + ResourceGroupName: "{{ResourceGroupName}}" + WorkspaceName: "{{WorkspaceName}}" Template: |- let fromDateTime=datetime('{{fromDateTime}}'); let toDateTime=datetime('{{toDateTime}}'); diff --git a/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/Sentinel Table Ingestion/KQL_Plugin_BadTables.yaml b/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/Sentinel Table Ingestion/KQL_Plugin_BadTables.yaml index be209deb..e7078767 100644 --- a/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/Sentinel Table Ingestion/KQL_Plugin_BadTables.yaml +++ b/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/Sentinel Table Ingestion/KQL_Plugin_BadTables.yaml @@ -16,4 +16,4 @@ SkillGroups: ResourceGroupName: WorkspaceName: Template: |- - union withsource=BadTable * | where TimeGenerated > ago(3d) | summarize Entries = count(), last_log = datetime_diff("second",now(), max(TimeGenerated)), estimate = sumif(_BilledSize, _IsBillable==true) by BadTable | where last_log >= 259200 | project BadTable + union withsource=BadTable * | where TimeGenerated > ago(3d) | summarize Entries = count(), last_log = datetime_diff("second",now(), max(TimeGenerated)), estimate = sumif(_BilledSize, _IsBillable==true) by BadTable | where last_log >= 259200 | project BadTable diff --git a/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/Sentinel Watchlists/KQL_Plugin_Watchlist.yaml b/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/Sentinel Watchlists/Manifest_WatchlistKQL.yaml similarity index 100% rename from Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/Sentinel Watchlists/KQL_Plugin_Watchlist.yaml rename to Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/Sentinel Watchlists/Manifest_WatchlistKQL.yaml diff --git a/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/Sentinel Watchlists/Readme.md b/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/Sentinel Watchlists/Readme.md index a028cb9e..ae3062ad 100644 --- a/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/Sentinel Watchlists/Readme.md +++ b/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/Sentinel Watchlists/Readme.md @@ -1,21 +1,53 @@ -# Creating a Security Copilot KQL Plugin to Query Sentinel Watchlists +# Sentinel Watchlist integration in Security Copilot -## Utilize the information you maintain in Microsoft Sentinel Watchlists in Security Copilot. +## DESCRIPTION +This plugin demonstrates the possibility of integrating data from a Microsoft Sentinel Watchlist with Security Copilot.
+The manifest refers to a watchlist containing trusted users (**TrustedUsers**), but it can easily be adapted to different watchlists. -### Pre-requisites +``` kql +Template: |- + _GetWatchlist('TrustedUsers') | distinct TrustedUsers +``` +--- -- [Security Copilot Enabled](https://learn.microsoft.com/en-us/security-copilot/get-started-security-copilot#onboarding-to-microsoft-security-copilot) -- [Access to upload custom plugins](https://learn.microsoft.com/en-us/security-copilot/manage-plugins?tabs=securitycopilotplugin#managing-custom-plugins) -- [Microsoft Sentinel Workspace](https://learn.microsoft.com/en-us/azure/sentinel/quickstart-onboard) created. -- Parameters for KQL Plugin - Microsoft Sentinel Workspace Name, Subscription ID, Resource Group Name and Entra Tenant ID +## TYPE AND REQUIREMENTS +**TYPE**: KQL (Sentinel)
+**SOURCE**: any watchlist in Sentinel
+**REQUIREMENTS**: Log Analytics Workspace with Sentinel enabled -### Instructions +--- -#### Upload the Custom Plugin +## SKILLS -1. Obtain the file KQL_Plugin_Watchlist.yaml from this directory. -2. Modify the yaml file to specify your specific Entra TentantId, SubscriptionId, ResourceGroupName and WorkspaceName for your Sentinel instance. -3. Modify the KQL query to reference the Microsoft Sentinel Watchlist for which you want Security Copilot to access. -4. Upload the custom plugin + + + + + + + + + + + + + +
SkillNameDescriptionParameters
GetTrustedUsersFetches the list of trusted users that is being maintained as a Microsoft Sentinel watchlist +
    +
+
+ + +--- + +## SAMPLE PROMPTS + +- `« Fetch the trusted users in Sentinel »` +--- + +## SCREENSHOTS +
+ +
For more information, see: [Creating a Security Copilot KQL Plugin to Query Sentinel Watchlists](https://rodtrent.substack.com/p/creating-a-copilot-for-security-kql) diff --git a/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/SentinelDailyOperations/KQL_SentinelDailyOperationsSample.yml b/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/SentinelDailyOperations/KQL_SentinelDailyOperationsSample.yml index 8f2fece3..277c2436 100644 --- a/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/SentinelDailyOperations/KQL_SentinelDailyOperationsSample.yml +++ b/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/SentinelDailyOperations/KQL_SentinelDailyOperationsSample.yml @@ -3,6 +3,36 @@ Descriptor: DisplayName: Sentinel Daily Operations Description: Sentinel Scenarios we use repeatedly for our daily operations. + Settings: + - Name: TenantId + Label: TenantId + Description: Azure tenant ID where Sentinel exists. + HintText: Azure tenant ID where Sentinel exists. + SettingType: String + Required: true + + - Name: SubscriptionId + Label: SubscriptionId + Description: Azure subscription ID where Sentinel exists. + HintText: Azure subscription ID where Sentinel exists. + SettingType: String + Required: true + + - Name: WorkspaceName + Label: WorkspaceName + Description: Log Analytics workspace name for Sentinel. + HintText: Log Analytics workspace name for Sentinel. + SettingType: String + Required: true + + - Name: ResourceGroupName + Label: ResourceGroupName + Description: Resource group where Sentinel workspace exists. + HintText: Resource group where Sentinel workspace exists. + SettingType: String + Required: true + + SkillGroups: - Format: KQL Skills: @@ -12,14 +42,14 @@ SkillGroups: Inputs: - Name: region Description: provide any specific location you want to consider - Required: true + Required: true Settings: - Target: Sentinel - TenantId: xxxxx - SubscriptionId: xxxx - ResourceGroupName: xxxx - WorkspaceName: xxxx - Template: |- + Target: Sentinel + TenantId: "{{TenantId}}" + SubscriptionId: "{{SubscriptionId}}" + ResourceGroupName: "{{ResourceGroupName}}" + WorkspaceName: "{{WorkspaceName}}" + Template: |- AzureNetworkAnalyticsIPDetails_CL | where isnotempty(ThreatType_s) | where ThreatType_s != '[]' @@ -27,5 +57,5 @@ SkillGroups: | where Location_s == '{{region}}' | project ThreatType_s,ThreatDescription_s,PublicIPDetails_s,Location_s,FlowIntervalStartTime_t,FlowIntervalEndTime_t |order by FlowIntervalStartTime_t - + diff --git a/Plugins/Community Based Plugins/Purview/KQL_DataAnalyst.yml b/Plugins/Community Based Plugins/Purview/KQL_DataAnalyst.yml index 53f062b2..870a9c62 100644 --- a/Plugins/Community Based Plugins/Purview/KQL_DataAnalyst.yml +++ b/Plugins/Community Based Plugins/Purview/KQL_DataAnalyst.yml @@ -144,8 +144,8 @@ SkillGroups: Target: Defender Template: |- let policyvar = '{{policy}}'; - let workload = ''; - let EndPointAction = datatable(ActionName: string, Action: int) [ + let workload = ''; + let EndPointAction = datatable(ActionName: string, Action: int) [ 'None', '0', 'Audit', '1', 'Warn', '2', @@ -406,7 +406,7 @@ SkillGroups: | where path.Filename has document | extend DirectoryPath = tostring(path.DirectoryPath) | summarize by DirectoryPath ); - let officedata = ( + let officedata = ( CloudAppEvents | where RawEventData.Operation == "SecureLinkUsed" or RawEventData.Operation == "AddedToSecureLink" | extend ObjectId_ = tostring(RawEventData.ObjectId) @@ -997,7 +997,7 @@ SkillGroups: Template: |- let site = '{{site}}'; let sitvar = '{{sitname}}'; - let document = '{{document}}'; + let document = '{{document}}'; CloudAppEvents | where RawEventData.Workload has 'EndPoint' | where RawEventData.TargetDomain has site @@ -1089,7 +1089,7 @@ SkillGroups: let user = '{{user}}'; let document = '{{document}}'; let AlertEvidence = (AlertEvidence - | where Timestamp > ago(31d) + | where Timestamp > ago(31d) //| where ProcessCommandLine =='' | extend upnsuffix = todynamic (AdditionalFields).UPNSuffix | extend upn1 = iff(isempty(todynamic (AdditionalFields).Recipient),todynamic (AdditionalFields).Upn,todynamic (AdditionalFields).Recipient) @@ -1136,7 +1136,7 @@ SkillGroups: | extend UserId = tolower(RawEventData.UserId) | extend sit_SensitiveInfoTypeName = tostring(sit.SensitiveInformationTypeName) | where url_decode(ObjectName) has document - | join kind=inner + | join kind=inner (CloudAppEvents | where ActionType !has 'DLPRuleMatch' | where ISP has ispvar @@ -1367,12 +1367,12 @@ SkillGroups: DisplayName: Show Sensitive document operations across based on Sensitive Information Type, department and location Description: Fetches the sensitive document operations across based on Sensitive Information Type, department and location Inputs: - - Name: department - Description: Specify the department for which the sensitive document operations are to be fetched - Required: false - - Name: sitname - Description: Specify the sensitive information type for which the sensitive document operations are to be fetched - Required: false + - Name: department + Description: Specify the department for which the sensitive document operations are to be fetched + Required: false + - Name: sitname + Description: Specify the sensitive information type for which the sensitive document operations are to be fetched + Required: false Settings: Target: Defender Template: |- @@ -1495,33 +1495,33 @@ SkillGroups: Template: |- let ispvar = '{{ISP}}'; let iptagvar = '{{IPTag}}'; - CloudAppEvents - | where Timestamp > ago(14d) - | where RawEventData.MDATPDeviceId != '' - | where ISP has ispvar - | extend IPTags = tostring(IPTags) - | where IPTags has iptagvar - | where ActionType in ('FileCopiedToClipboard', 'FileCopiedToNetworkShare', 'FileCopiedToRemoteDesktopSession', 'FileCopiedToRemovableMedia', 'FilePrinted', 'FileSensitivityLabelApplied', 'FileUploadedToCloud','FileUploaded','SharingSet', 'FileRead','FileRenamed', 'MipLabel', 'SensitivityLabelApplied', 'SensitivityLabeledFileOpened', 'SensitivityLabelRemoved', 'SensitivityLabelUpdated', 'ArchiveCreated', 'FileAccessedByUnallowedApp', 'FileArchived','FileAccessed', 'FileCreatedOnNetworkShare', 'FileCreatedOnRemovableMedia', 'FileModified', 'RemoveProtection','FileTransferredByBluetooth', 'WebpagePrinted', 'WebpageCopiedToClipboard', 'WebpageSavedToLocal', 'PastedToBrowser','FileSensitivityLabelChanged','FileDownloaded','SensitivityLabelPolicyMatched','SharingLinkCreated','SharingLinkUsed') - | where Application <> 'Microsoft Exchange Online' - | extend ObjectId_ = tostring(RawEventData.ObjectId) - | where ObjectId_ !has 'AppData' - | where ActivityType <> 'Send' - | extend LabelGUID1 = parse_json(tostring(RawEventData.SensitivityLabelEventData)).SensitivityLabelId - | extend LabelGUID2 = iff(isempty(tostring(RawEventData.LabelId)), LabelGUID1, tostring(RawEventData.LabelId)) - | extend LabelGUID3 = iff(isempty(tostring(RawEventData.SensitivityLabelId)), LabelGUID2, tostring(RawEventData.SensitivityLabelId)) - | extend OldSensitivityLabelId = tostring(parse_json(tostring(RawEventData.SensitivityLabelEventData)).OldSensitivityLabelId) - | extend LabelGUID = iff(isempty(tostring(LabelGUID3)),OldSensitivityLabelId, tostring(LabelGUID3)) - | where LabelGUID !='' - | extend userid = tolower(RawEventData.UserId) - | extend DeviceId = tostring(RawEventData.MDATPDeviceId) - | extend AppUsed = tostring(RawEventData.Application) - | summarize EventCount=count() by userid,LabelGUID,ISP,tostring(IPTags),AppUsed, bin(Timestamp,30m) - | order by Timestamp - | summarize EventCount=make_list(EventCount),TimeGenerated=make_list(Timestamp) by userid,ISP,tostring(IPTags),AppUsed - | extend outliers=series_decompose_anomalies(EventCount) - | mv-expand TimeGenerated, EventCount, outliers - | where outliers == 1 - | summarize aggregate=sum(todecimal(EventCount)) by userid,ISP,tostring(IPTags),AppUsed + CloudAppEvents + | where Timestamp > ago(14d) + | where RawEventData.MDATPDeviceId != '' + | where ISP has ispvar + | extend IPTags = tostring(IPTags) + | where IPTags has iptagvar + | where ActionType in ('FileCopiedToClipboard', 'FileCopiedToNetworkShare', 'FileCopiedToRemoteDesktopSession', 'FileCopiedToRemovableMedia', 'FilePrinted', 'FileSensitivityLabelApplied', 'FileUploadedToCloud','FileUploaded','SharingSet', 'FileRead','FileRenamed', 'MipLabel', 'SensitivityLabelApplied', 'SensitivityLabeledFileOpened', 'SensitivityLabelRemoved', 'SensitivityLabelUpdated', 'ArchiveCreated', 'FileAccessedByUnallowedApp', 'FileArchived','FileAccessed', 'FileCreatedOnNetworkShare', 'FileCreatedOnRemovableMedia', 'FileModified', 'RemoveProtection','FileTransferredByBluetooth', 'WebpagePrinted', 'WebpageCopiedToClipboard', 'WebpageSavedToLocal', 'PastedToBrowser','FileSensitivityLabelChanged','FileDownloaded','SensitivityLabelPolicyMatched','SharingLinkCreated','SharingLinkUsed') + | where Application <> 'Microsoft Exchange Online' + | extend ObjectId_ = tostring(RawEventData.ObjectId) + | where ObjectId_ !has 'AppData' + | where ActivityType <> 'Send' + | extend LabelGUID1 = parse_json(tostring(RawEventData.SensitivityLabelEventData)).SensitivityLabelId + | extend LabelGUID2 = iff(isempty(tostring(RawEventData.LabelId)), LabelGUID1, tostring(RawEventData.LabelId)) + | extend LabelGUID3 = iff(isempty(tostring(RawEventData.SensitivityLabelId)), LabelGUID2, tostring(RawEventData.SensitivityLabelId)) + | extend OldSensitivityLabelId = tostring(parse_json(tostring(RawEventData.SensitivityLabelEventData)).OldSensitivityLabelId) + | extend LabelGUID = iff(isempty(tostring(LabelGUID3)),OldSensitivityLabelId, tostring(LabelGUID3)) + | where LabelGUID !='' + | extend userid = tolower(RawEventData.UserId) + | extend DeviceId = tostring(RawEventData.MDATPDeviceId) + | extend AppUsed = tostring(RawEventData.Application) + | summarize EventCount=count() by userid,LabelGUID,ISP,tostring(IPTags),AppUsed, bin(Timestamp,30m) + | order by Timestamp + | summarize EventCount=make_list(EventCount),TimeGenerated=make_list(Timestamp) by userid,ISP,tostring(IPTags),AppUsed + | extend outliers=series_decompose_anomalies(EventCount) + | mv-expand TimeGenerated, EventCount, outliers + | where outliers == 1 + | summarize aggregate=sum(todecimal(EventCount)) by userid,ISP,tostring(IPTags),AppUsed - Name: SensitiveEndpointactivitybyCVEorVendor DisplayName: Get the sensitive file activity based on CVE or Vendor diff --git a/Plugins/Community Based Plugins/Purview/eDiscovery/Readme.md b/Plugins/Community Based Plugins/Purview/eDiscovery/Readme.md index 0b103e14..ee34b442 100644 --- a/Plugins/Community Based Plugins/Purview/eDiscovery/Readme.md +++ b/Plugins/Community Based Plugins/Purview/eDiscovery/Readme.md @@ -1,74 +1,87 @@ -# eDiscovery Plugin for Copilot for Security +# eDiscovery Plugin for Security Copilot Author: Amit Singh -This custom plugin for Copilot for Security offers a range of functionalities to enhance your organization's eDiscovery capabilities in Microsoft Purview. Use this plugin if you create the case and then export the artifacts. If you attach search to review set & review those in eDiscovery then this plugin will not work as currently constructed. +This custom Security Copilot plugin enhances your organization's eDiscovery capabilities in Microsoft Purview. It is designed for workflows where you create a case and export artifacts directly. You can initiate an export from Search after indexing and estimating the artifacts. However, this plugin does not support workflows that involve attaching searches to a review set for further analysis in eDiscovery. Attaching searches to a review set is a manual process that must be completed within the case in the Purview portal. -Pre-requisites +### Prerequisites for Using Security Copilot -* [Copilot for Security Enabled](https://learn.microsoft.com/en-us/security-copilot/get-started-security-copilot#onboarding-to-microsoft-security-copilot) -* [Access to upload custom plugins](https://learn.microsoft.com/en-us/security-copilot/manage-plugins?tabs=securitycopilotplugin#managing-custom-plugins) -* Make sure your application has the following delegated graph (https://graph.microsoft.com) permissions: offline_access user.read eDiscovery.Read.All & eDiscovery.ReadWrite.All. See this link for App registration [EntraID Application Registration created](https://learn.microsoft.com/en-us/graph/auth-register-app-v2#register-an-application) +- **Ensure Security Copilot is Enabled** + Before you start, make sure Security Copilot is enabled in your environment. Follow the onboarding steps here: + [Get Started with Security Copilot](https://learn.microsoft.com/en-us/security-copilot/get-started-security-copilot#onboarding-to-microsoft-security-copilot) -### Instructions -#### Upload the Custom Plugin +- **Check Your Access to Upload Custom Plugins** + To use custom plugins, you need the proper permissions to upload them in Security Copilot. Learn how to manage and enable this feature here: + [Managing Custom Plugins](https://learn.microsoft.com/en-us/security-copilot/manage-plugins?tabs=securitycopilotplugin#managing-custom-plugins) -* Obtain the file [eDiscovery_OAuth_API_manifest.yaml](https://github.com/samitks77/Copilot-For-Security/blob/main/Plugins/Community%20Based%20Plugins/Purview/eDiscovery/eDiscovery_OAuth_API_manifest.yaml) from this directory. This is the yaml that you will upload in Copilot for Security. From your EntraID App registration, add the ClientId & tenant ID. ![alt text](EntraID-ClientID-TenantID.png) -* During the upload process as a custom plugin in Copilot for Security you will need to have your Secret Value from Certificates & secrets. This will be your API key for this plugin to work. Copy your secret value once the secret is created because if you navigate away from this screen, your secret value will not be human readable ![alt text](EntraID-SecretValue.png) Follow these steps to create the secrets(https://learn.microsoft.com/en-us/graph/auth-register-app-v2#add-credentials). -* Upload eDiscovery_OAuth_API_manifest.yaml to Copilot for Security by navigating to custom plugins at the bottom of the Manage Sources page. ![alt text](CfS-add-plugin.png) -* Upload the file by clicking on Copilot for Security plugin. ![alt text](CfS-add-plugin-part2.png) -* Paste the Client Secret Value from EntraID App Registration & click connect ![alt text](CfS-Secret.png) It should open another window where you will sign in. +- **Set Up the Required Permissions for Your Application** + Your application must have the following permissions for Microsoft Graph (`https://graph.microsoft.com`): + - `offline_access` + - `user.read` + - `eDiscovery.Read.All` + - `eDiscovery.ReadWrite.All` + + To register your application and configure these permissions, follow the steps here: + [Register an Application in Entra ID](https://learn.microsoft.com/en-us/graph/auth-register-app-v2#register-an-application) + +## Setting Up the Custom Plugin in Security Copilot + +### Uploading the Custom Plugin + +1. **Download the Required File** + - Obtain the file **[`eDiscovery_OAuth_API_manifest.yaml`](https://github.com/samitks77/Copilot-For-Security/blob/main/Plugins/Community%20Based%20Plugins/Purview/eDiscovery/eDiscovery_OAuth_API_manifest.yaml)** from the designated directory. This YAML file will be uploaded to **Security Copilot** during the setup process. + +2. **Add Client ID & Tenant ID** + - From your **Entra ID App Registration**, make sure to add your **Client ID** and **Tenant ID** before proceeding with the + upload. + ![alt text](EntraID-ClientID-TenantID.png) + +3. **Generate and Copy Your Secret Value** + - During the plugin upload process in **Security Copilot**, you will need a **Secret Value** from **Certificates & Secrets** in your Entra ID App. + - This **Secret Value** acts as your **API key** for the plugin to function correctly. + - **Important:** Copy your **Secret Value** immediately after creating it. If you navigate away from the page, the value will no longer be readable. + + ![alt text](EntraID-SecretValue.png) + +4. **Create Secrets in Entra ID** + + - Follow these steps to create and manage secrets for your application: + **[`How to Add Credentials in Entra ID`](https://learn.microsoft.com/en-us/graph/auth-register-app-v2#add-credentials)** + +5. **Navigate to Custom Plugins** + + - Go to the **Manage Sources** on the home page of **Security Copilot**, and scroll down to the **Custom Plugins** section. + + ![alt text](scp-upload-custom-plugins.png) + + ![alt text](scp-upload-custom-plugins-2.png) +6. **Upload the Plugin File** + + - Click on **Security Copilot Plugin**, then upload the **`eDiscovery_OAuth_API_manifest.yaml`** file. + + ![alt text](scp-upload-custom-plugins-api-manifest.png) + +7. **Enter the Client Secret and Connect** + + - Paste the **Client Secret Value** from your **Entra ID App Registration**. + - Click **Connect** to proceed. + + ![alt text](CfS-Secret.png) + +8. **Sign-In** + + Once you click **Connect**, a new window will open where you will need to sign in with your credentials. + +### Plugin Setup Complete + +You have successfully set up the **eDiscovery Plugin** in **Security Copilot**. With the plugin uploaded, Client ID, Tenant ID, and Secret Value configured, and authentication completed, your integration is now ready to use. Security Copilot can now leverage this plugin for enhanced **eDiscovery capabilities**, allowing you to streamline investigations and data retrieval processes. If you encounter any issues, verify your Entra ID settings, ensure your Client Secret is valid, and check your Security Copilot permissions. ### Skills & Prompts -1. Create eDiscovery Case: - - Example Prompt(s): - - Create eDiscovery case in Purview with the Case name "Test-123", response should be a bulleted list - - Inputs: [Case name] -2. Add custodian to eDiscovery Case: - - Example Prompt(s): - - Add custodian to the eDiscovery case in Purview, use the case id from above or use this Case ID "1xxxx234-XXXX-XXXX-a1cd- - fxxxxxxxxxx0" and add this email for the custodian "test@test123.com" - - Inputs: [Email] - - Inputs: [Case ID] -3. Apply hold on eDiscovery Custodian - - Example Prompt(s): - - Apply hold on eDiscovery custodians using the custodian id from above or use this Custodian ID "2xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx6" - - Inputs: [Custodian ID] -4. Add new userSource objects to Custodians - - Example Prompt(s): - - add new userSource object associated with the eDiscovery custodian id "2xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx6", include this - custodian email "test@test123.com" and Mailbox, site for sources. - - Inputs: [Custodian ID] - - Inputs: [Email] -5. Add new eDiscovery Search object to the Case - - Example Prompt(s): - - Add a new ediscoverySearch object with this Case ID "1xxxx234-XXXX-XXXX-a1cd-fxxxxxxxxxx0" with the display name "Test-123- - search", also add dataSourceScopes to allTenantMailboxes, allTenantSites, allCaseCustodians. - - Inputs: [Case ID] - - Inputs: [Display Name] -6. Run an estimate on eDiscovery Case: - - Example Prompt(s): - - Run an estimate of the number of emails and documents in eDiscovery with this Search ID "2xxxxxxx-xxx4-xxxd-4xxx- - 5xxxxxxxxxxx" or the Search ID from above - - Inputs: [Search ID] -7. Create new eDiscovery ReviewSet: - - Example Prompt(s): - - Create new ediscoveryReviewSet for the above case id or use this Case ID "1xxxx234-XXXX-XXXX-a1cd- - fxxxxxxxxxx0" with the Display Name of "Test-123-reviewset" - - Inputs: [Case ID] -8. Initiate an Export in eDiscovery: - - Example Prompt(s): - - Use this Case ID "1xxxx234-XXXX-XXXX-a1cd-fxxxxxxxxxx0" or the Case ID from above & this ReviewSetId "dxxxxxxx-xxxx-xxxx-xxxx- - xxxxxxxxxxxx" or the ReviewSetId from above and initiate an export - - Inputs: [Case ID] - - Inputs: [Review Set ID] -9. Get a list of Case Operation in eDiscovery: - - Example Prompt(s): - - Use the Case ID from above or use this Case ID "1xxxx234-XXXX-XXXX-a1cd-fxxxxxxxxxx0", get a list of the caseOperation objects - and their properties - - Inputs: [Case ID] - -### eDiscovery prompt book based on variables like CaseID etc -![alt text](CfS-Prompt-Sample.png) + +🔹 **These are **suggested prompts** that you can use to interact with this plugin in **Security Copilot**. Each prompt helps you execute specific **eDiscovery actions efficiently**. You can modify these prompts based on your use case and organizational requirements. Below, you'll find structured input fields required for each action, ensuring precise execution of eDiscovery tasks. For a cleaner, structured view of the prompts, check out the HTML version below:** + +👉 **[`Suggested Prompts in HTML`](https://htmlpreview.github.io/?https://raw.githubusercontent.com/samitks77/Copilot-For-Security/main/Plugins/Community%20Based%20Plugins/Purview/eDiscovery/sampleprompts.html)** + + diff --git a/Plugins/Community Based Plugins/Purview/eDiscovery/eDiscovery_API_Plugin.yaml b/Plugins/Community Based Plugins/Purview/eDiscovery/eDiscovery_API_Plugin.yaml index bc9ed1aa..30b17d71 100644 --- a/Plugins/Community Based Plugins/Purview/eDiscovery/eDiscovery_API_Plugin.yaml +++ b/Plugins/Community Based Plugins/Purview/eDiscovery/eDiscovery_API_Plugin.yaml @@ -1,17 +1,24 @@ openapi: 3.0.0 info: - title: eDiscovery Case Plugin - description: Skills for creating a plugin that will help create eDiscovery case & additional workflows in Purview via Graph API Call + title: Security Copilot eDiscovery Assistant + description: Skills that use this plugin to create eDiscovery cases and orchestrate additional workflows in Purview via Microsoft Graph API. version: "v1" servers: - - url: https://graph.microsoft.com/v1.0/security/cases + - url: https://graph.microsoft.com/v1.0/security/cases paths: /ediscoveryCases: post: - operationId: Create eDiscovery case in Purview - description: Create eDiscovery case based on user supplied name + operationId: CreateeDiscoverycaseinPurview + description: Create eDiscovery case based on user supplied name. + descriptionForModel: This operation creates a new eDiscovery case in Purview by accepting key details such as displayName, description, and externalId, which can be used to trigger further case-specific workflows. + examplePrompts: + - Create a new eDiscovery case called "InvestigationCase". + - Create eDiscovery case in Purview with the Case name "Test-AS-Feb20-V2" + - Initiate an eDiscovery case in Purview with the name "Case2025". + - Start an eDiscovery case and set the external ID for reference. + - Generate an eDiscovery case with description "Data breach investigation". requestBody: required: true content: @@ -38,8 +45,15 @@ paths: /ediscoveryCases/{ediscoveryCaseId}/custodians: post: - operationId: Add custodian to eDiscovery case in Purview + operationId: AddCustodianToediscoveryCaseInPurview description: Add custodian to eDiscovery case in Purview + descriptionForModel: This operation adds a new custodian to an existing eDiscovery case in Purview. It accepts the custodian's email address and associates it with the specified case, enabling further case-specific workflows. + examplePrompts: + - Add custodian with email "user@example.com" to eDiscovery case name "12345". + - Include custodian "john.doe@example.com" in the case with name "case6789". + - Assign custodian "alice@example.com" to the eDiscovery case named "LegalInvestigation". + - Register custodian "bob@example.com" under the eDiscovery case called "ComplianceReview". + - Add custodian to the eDiscovery case in Purview, use the case id from above and add this email for the custodian "john.doe@example.com". parameters: - in: path name: ediscoveryCaseId @@ -67,8 +81,15 @@ paths: /ediscoveryCases/{ediscoveryCaseId}/custodians/{custodianId}/userSources: post: - operationId: Add new userSource object associated with an eDiscovery custodian - description: Add a new userSource object associated with an eDiscovery custodian in Purview + operationId: AddUserSourceToCustodianInediscoveryCase + description: Add a new userSource object associated with an eDiscovery custodian in Purview. + descriptionForModel: This operation creates a new userSource for a specified custodian within an eDiscovery case in Purview. It requires the custodian's email address and a list of included sources (e.g., mailbox, site) to be specified, enabling further data collection and processing for the case. + examplePrompts: + - Add a userSource for custodian "jane.doe@example.com" with sources "mailbox, site" to the eDiscovery case "Case123". + - Include userSource for custodian "john.doe@example.com" with sources "mailbox, site" in the eDiscovery case. + - Register a new userSource for custodian "alice@example.com" including sources "mailbox, site" in the case. + - Assign userSource to custodian "bob@example.com" with included sources "mailbox, site" for the eDiscovery case. + - add new userSource object associated with the eDiscovery custodian id in the above prompt, include this custodian email "example@example.com" and Mailbox, site for sources. In the results include userSourceID, CustodianID and all additional details. parameters: - in: path name: ediscoveryCaseId @@ -141,8 +162,15 @@ paths: /ediscoveryCases/{ediscoveryCaseId}/custodians/applyHold: post: - operationId: Applying hold on eDiscovery custodians - description: Applying hold on eDiscovery custodians in Purview + operationId: ApplyHoldOnediscoveryCustodians + description: Applying hold on eDiscovery custodians in Purview. + descriptionForModel: This operation applies a hold on custodians within an eDiscovery case in Purview. It accepts a comma-separated list of custodian IDs to hold and returns the location URL of the hold operation. + examplePrompts: + - Apply hold on custodian "user@example.com" in the eDiscovery case "Case123". + - Place a hold on custodian "john.doe@example.com" in the case with name "LegalReview". + - Initiate a hold on custodians with IDs "id1,id2" for eDiscovery case 'Investigation2025'. + - Apply hold on custodian "alice@example.com" in the eDiscovery case "ComplianceCheck". + - Apply hold using the above Case Id and Custodian ID. parameters: - in: path name: ediscoveryCaseId @@ -182,8 +210,24 @@ paths: /ediscoveryCases/{ediscoveryCaseId}/searches: post: - operationId: Add a new ediscoverySearch object - description: Add a new ediscoverySearch object in Purview + operationId: AddeDiscoverySearchObject + description: Add a new ediscoverySearch object in Purview. + descriptionForModel: This operation creates a new ediscoverySearch object in Purview, enabling users to define search parameters for retrieving case-related data. It requires a displayName, while additional properties such as description, contentQuery, and dataSourceScopes are optional. + examplePrompts: + - Create a new ediscovery search with display name 'InvestigationSearch' for case "Case123". + - Add an ediscovery search named "DataReview" with a query string in the case "Case456". + - Initiate an ediscovery search with display name "ComplianceSearch" in the specified case." + - Generate an ediscovery search in case "LegalCase2025' with display name "LegalReviewSearch". + - "Create an eDiscovery search that retrieves all email activity, all OneDrive content interactions, and all Teams instant messaging communications across the organization. Example \"contentQuery\": \"(kind=email) OR (Path:\\\"onedrive\\\") OR (kind=IM)\"" + - "Generate a content search to find all emails and OneDrive files that contain the keyword 'Confidential' in the subject, body, or filename. Example \"contentQuery\": \"(kind=email AND subject:Confidential) OR (Path:\\\"onedrive\\\" AND filename:Confidential)\"" + - "Create a search that retrieves all Teams instant messages where users discussed 'Project X' in chat conversations. Example \"contentQuery\": \"(kind=IM AND body:\\\"Project X\\\")\"" + - "Find all emails sent by Alice (example@example.com), including those she sent internally and externally. Example \"contentQuery\": \"(kind=email AND from:alice@contoso.com)\"" + - "Generate a search to locate all files in OneDrive that were created or shared by John Doe (john.doe@example.com). Example \"contentQuery\": \"(Path:\\\"onedrive\\\") AND (Author:john.doe@example.com)\"" + - "Find all emails that contain one or more attachments, regardless of file type or sender. Example \"contentQuery\": \"(kind=email AND hasAttachments:true)\"" + - "Retrieve all emails and Teams messages exchanged between Example 1 (example1@example.com) and Answer 1 (answer1@example.com) to track their collaboration. Example \"contentQuery\": \"(kind=email AND (from:example1@example.com AND to:answer1@example.com)) OR (kind=IM AND (participants:example1@example.com AND participants:answer1@example.com))\"" + - "Generate a search to retrieve all emails sent between January 1, 2024, and March 31, 2024, across the organization. Example \"contentQuery\": \"(kind=email AND sent>=01/01/2024 AND sent<=03/31/2024)\"" + - "Find all PDF files stored in OneDrive, regardless of who created or modified them. Example \"contentQuery\": \"(Path:\\\"onedrive\\\" AND filetype:pdf)\"" + - "Locate all Teams instant messages where users discussed 'budget' to identify financial planning conversations. Example \"contentQuery\": \"(kind=IM AND body:\\\"budget\\\")\"" parameters: - in: path name: ediscoveryCaseId @@ -217,11 +261,119 @@ paths: description: OK content: application/json: + + /ediscoveryCases/{ediscoveryCaseId}/custodians/updateIndex: + post: + operationId: AddIndexOperationForCustodians + description: Add the process of indexing data sources, including custodians and noncustodial data sources in Purview for all custodians attached to a case + descriptionForModel: This operation initiates the indexing process for data sources associated with all custodians attached to a specific eDiscovery case in Purview. It accepts an optional list of custodian IDs to limit the indexing scope, facilitating improved search and retrieval capabilities. + examplePrompts: + examplePrompts: + - Initiate indexing for all custodians in the eDiscovery case "Case123". + - Start the index operation for custodians with IDs "id1,id2" in eDisocvery case "InvestigationCase". + - Trigger the indexing process for all custodians in the case "LegalReview". + - Run an index operation for custodians in eDiscovery case "Compliance2025" with specific custodian IDs. + parameters: + - in: path + name: ediscoveryCaseId + schema: + type: string + required: true + description: caseId in eDiscovery + requestBody: + required: false + content: + application/json: + schema: + type: object + properties: + ids: + type: string + description: The IDs of custodians to index. Optional. Maximum number of IDs that can be provided is 1000, supplied by the user + responses: + "202": + description: If successful, this action returns a 202 Accepted response code, Indexing operation initiated successfully. + content: + application/json: + schema: + type: object + properties: + status: + type: string + description: The status of the operation. + message: + type: string + description: Additional information about the operation. + "400": + description: Bad Request - Invalid input or missing required parameters. + "404": + description: Not Found - The specified eDiscovery case does not exist. + "500": + description: Internal Server Error - A problem occurred on the server while processing the request. + + /ediscoveryCases/{ediscoveryCaseId}/custodians/{custodianId}/updateIndex: + post: + operationId: AddIndexOperationForSpecificCustodian + description: Add the process of indexing data sources, including custodians and noncustodial data sources in Purview for specific custodian. + descriptionForModel: This operation initiates an indexing process for a specific custodian within an eDiscovery case in Purview. It accepts an optional custodian ID to limit the indexing to that particular custodian, enabling targeted data retrieval and improved search capabilities. + examplePrompts: + - Initiate an index operation for custodian 'user@example.com' in the eDiscovery case "Case123". + - Start indexing for specific custodian with ID "custodianID123" in case "LegalReview". + - Trigger an indexing process for custodian "alice@example.com" in the eDiscovery case "Investigation2025". + - Run an index operation for custodian with ID "custID456" in the above specified case. + parameters: + - in: path + name: ediscoveryCaseId + schema: + type: string + required: true + description: caseId in eDiscovery + - in: path + name: custodianId + schema: + type: string + required: true + description: custodianId in eDiscovery + requestBody: + required: false + content: + application/json: + schema: + type: object + properties: + ids: + type: string + description: The ID of a specific custodian to index. Optional. Maximum number of IDs that can be provided is 1000, supplied by the user + responses: + "202": + description: If successful, this action returns a 202 Accepted response code, Indexing operation initiated successfully for the specified custodian. + content: + application/json: + schema: + type: object + properties: + status: + type: string + description: The status of the operation. + message: + type: string + description: Additional information about the operation. + "400": + description: Bad Request - Invalid input or missing required parameters. + "404": + description: Not Found - The specified eDiscovery case or custodian does not exist. /ediscoveryCases/{ediscoveryCaseId}/searches/{ediscoverySearchId}/estimateStatistics: post: - operationId: Run an estimate in the eDiscovery search + operationId: RunEstimateInEdiscoverySearch description: Run an estimate of the number of emails and documents in the eDiscovery search + descriptionForModel: This operation initiates an estimation process for an eDiscovery search, calculating the number of emails and documents that match the search criteria. The response includes a Location header that provides the URL for tracking the estimate operation. + examplePrompts: + - Run an estimate for eDiscovery search "Search123" in case "CaseABC". + - Estimate the number of emails and documents for search "InvestigationSearch" in case "Case456". + - Initiate an estimate for eDiscovery search "ComplianceSearch" and track the estimate location. + - Estimate results for eDiscovery search "LegalReview" in case "Case789". + - "Run an estimate on the number of emails and documents in eDiscovery search for this Search ID: \\{Search ID\\}\"" parameters: - in: path name: ediscoveryCaseId @@ -252,8 +404,15 @@ paths: /ediscoveryCases/{ediscoveryCaseId}/reviewSets: post: - operationId: Create a new ediscovery ReviewSet object + operationId: CreateediscoveryReviewSetObject description: Create new ediscoveryReviewSet object in Purview + descriptionForModel: This operation creates a new ediscovery review set within a specified eDiscovery case in Purview. It requires the display name of the review set and triggers further workflows for managing review sets. + examplePrompts: + - Create a new ediscovery review set named "ReviewSet1" in case "Case123". + - Initiate a review set called "InvestigationReview" for case "Case456". + - Add a new review set with display name "LegalReview" to the eDiscovery case "Case789". + - Generate a review set named "ComplianceReview" for the case "CaseABC". + - Create a new eDiscovery review set in the above case id with the name "ReviewSet1". parameters: - in: path name: ediscoveryCaseId @@ -281,8 +440,15 @@ paths: /ediscoveryCases/{ediscoveryCaseId}/reviewSets/{eDiscoveryReviewSetId}/export: post: - operationId: Initiate an export from a ediscoveryReviewSet + operationId: InitiateExportFromEdiscoveryReviewSet description: Initiate an export from a ediscoveryReviewSet in Purview + descriptionForModel: This operation starts the export process for an ediscovery review set within a specified eDiscovery case in Purview. It requires an outputName (and optionally description, exportOptions, and exportStructure) to initiate the export. The response includes a Location header with the URL to track the export operation. + examplePrompts: + - Initiate an export for review set "ExportSet1" in case "Case123" and return the export operation location. + - Start export of review set "LegalReview" in case "Case456" and provide the location URL of the operation. + - Export review set "ComplianceSet" from case "Case789" and get the export operation location in the response. + - Run an export for review set "InvestigationExport" in case "CaseABC" and fetch the export location. + - Use the Case ID & ReviewSetId from above and initiate an export with the outputName "ExportSet1". parameters: - in: path name: ediscoveryCaseId @@ -331,8 +497,15 @@ paths: /ediscoveryCases/{ediscoveryCaseId}/operations: get: - operationId: Get a list of the caseOperation objects - description: Get a list of the caseOperation objects and their properties in Purview + operationId: GetCaseOperationObjects + description: Get a list of the caseOperation objects and their properties in Purview. + descriptionForModel: This operation retrieves a list of caseOperation objects for a specified eDiscovery case in Purview. The response includes detailed properties of each operation and a Location header with a URL for tracking further actions. + examplePrompts: + - Retrieve all case operations for eDiscovery case "Case123". + - Fetch case operations for case "InvestigationCase" and check the response location header. + - List operations for eDiscovery case "ComplianceReview" and note the export operation location. + - Get case operations for eDiscovery case "CaseABC" and verify the location URL in the response. + - Use the Case ID from above, get a list of the caseOperation objects and their properties parameters: - in: path name: ediscoveryCaseId @@ -355,7 +528,70 @@ paths: example: "https://graph.microsoft.com/v1.0/security/cases/ediscoverycases('{ediscoveryCaseId}')/operations('{ediscoverySearchId}')" content: application/json: - - - + /ediscoveryCases/{ediscoveryCaseId}/searches/{ediscoverySearchId}/exportResult: + post: + operationId: RunExportFromEdiscoverySearch + description: Run the export from the eDiscovery search. + descriptionForModel: This operation initiates the export process for an eDiscovery search, generating export results based on the provided criteria. It requires a displayName, exportCriteria, and exportFormat, with additionalOptions available optionally. The response includes a Location header with the URL of the export operation. + examplePrompts: + - Run the export for eDiscovery search "SearchXYZ" in case "Case123" and return the export location. + - Export the results for eDiscovery search "InvestigationSearch" in case "Case456" and include the response's Location header. + - Initiate export for eDiscovery search "ComplianceSearch" in case "Case789" with displayName "Export1", exportCriteria "searchHits", and exportFormat "pst", then check the export location in the response. + - Run export for eDiscovery search "LegalReview" in case "CaseABC" and verify the location URL provided in the response. + - "Initiate an export from the above Search, use this Search ID: \\{Search ID\\}, and this Case ID = \\{Case ID\\}. Additional parameters include, displayName= \\\"Export 1 - simple PST-V2\\\", exportCriteria = \\\"searchHits\\\", additionalOptions = \\\"none\\\", and exportFormat = \\\"pst\\\"" + - "Initiate an export of the search results using the default PST format and include all available data. Example \"exportOptions\": \"{exportFormat:\\\"pst\\\", exportCriteria:\\\"allContent\\\", additionalOptions:\\\"none\\\"}\"" + - "Export all search hits for a specific eDiscovery case in PST format. Example \"exportOptions\": \"{exportFormat:\\\"pst\\\", exportCriteria:\\\"searchHits\\\", additionalOptions:\\\"none\\\"}\"" + - "Generate an export of all Exchange email results in CSV format for further review. Example \"exportOptions\": \"{exportFormat:\\\"csv\\\", exportCriteria:\\\"emailsOnly\\\", additionalOptions:\\\"none\\\"}\"" + - "Create a ZIP export of all documents found in OneDrive related to the search. Example \"exportOptions\": \"{exportFormat:\\\"zip\\\", exportCriteria:\\\"documentsOnly\\\", additionalOptions:\\\"none\\\"}\"" + - "Export all search results but exclude unindexed items from the export. Example \"exportOptions\": \"{exportFormat:\\\"pst\\\", exportCriteria:\\\"allContent\\\", additionalOptions:\\\"excludeUnindexedItems\\\"}\"" + - "Initiate an export in PST format while preserving all metadata for legal review. Example \"exportOptions\": \"{exportFormat:\\\"pst\\\", exportCriteria:\\\"allContent\\\", additionalOptions:\\\"preserveMetadata\\\"}\"" + - "Export only emails with attachments in CSV format for filtering and processing. Example \"exportOptions\": \"{exportFormat:\\\"csv\\\", exportCriteria:\\\"emailsWithAttachments\\\", additionalOptions:\\\"none\\\"}\"" + - "Perform an export including both emails and documents, excluding encrypted files. Example \"exportOptions\": \"{exportFormat:\\\"pst\\\", exportCriteria:\\\"" + parameters: + - in: path + name: ediscoveryCaseId + schema: + type: string + required: true + description: caseId in eDiscovery + - in: path + name: ediscoverySearchId + schema: + type: string + required: true + description: eDiscoverySearchId in eDiscovery + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + displayName: + type: string + description: The display name of the export result. Required + exportCriteria: + type: string + description: The portion of the estimate results to be exported. The possible values are, searchHits, partiallyIndexed, unknownFutureValue. Required. + additionalOptions: + type: string + description: The additional options for the export. The possible values are, none, teamsAndYammerConversations, cloudAttachments, allDocumentVersions, subfolderContents, listAttachments, unknownFutureValue, htmlTranscripts, advancedIndexing, allItemsInFolder, includeFolderAndPath, condensePaths, friendlyName, splitSource, optimizedPartitionSize, includeReport. Use the Prefer, include-unknown-enum-members request header to get the following values from this evolvable enum, htmlTranscripts, advancedIndexing, allItemsInFolder, includeFolderAndPath, condensePaths, friendlyName, splitSource, optimizedPartitionSize, includeReport. The advancedIndexing value is only available if exportCriteria includes partiallyIndexed. Optional + exportFormat: + type: string + description: The desired format of the exported emails. The possible values are, pst, msg, eml, unknownFutureValue. Required. + required: + - displayName + - exportCriteria + - exportFormat + responses: + "200": + description: OK + headers: + location: + description: If successful, this method returns a 200 OK response code and a collection of microsoft.graph.security.caseOperation objects in the response body + schema: + type: string + example: "https://graph.microsoft.com/v1.0/security/cases/ediscoverycases('{ediscoveryCaseId}')/operations('{ediscoverySearchId}')" + content: + application/json: diff --git a/Plugins/Community Based Plugins/Purview/eDiscovery/eDiscovery_OAuth_API_manifest.yaml b/Plugins/Community Based Plugins/Purview/eDiscovery/eDiscovery_OAuth_API_manifest.yaml index 1beffe43..9c851fcd 100644 --- a/Plugins/Community Based Plugins/Purview/eDiscovery/eDiscovery_OAuth_API_manifest.yaml +++ b/Plugins/Community Based Plugins/Purview/eDiscovery/eDiscovery_OAuth_API_manifest.yaml @@ -15,4 +15,4 @@ Descriptor: SkillGroups: - Format: API Settings: - OpenApiSpecUrl: https://raw.githubusercontent.com/Azure/Copilot-For-Security/refs/heads/main/Plugins/Community%20Based%20Plugins/Purview/eDiscovery/eDiscovery_API_Plugin.yaml + OpenApiSpecUrl: https://raw.githubusercontent.com/Azure/Copilot-For-Security/main/Plugins/Community%20Based%20Plugins/Purview/eDiscovery/eDiscovery_API_Plugin.yaml diff --git a/Plugins/Community Based Plugins/Purview/eDiscovery/sampleprompts.html b/Plugins/Community Based Plugins/Purview/eDiscovery/sampleprompts.html new file mode 100644 index 00000000..ab63088f --- /dev/null +++ b/Plugins/Community Based Plugins/Purview/eDiscovery/sampleprompts.html @@ -0,0 +1,114 @@ + + + + + + eDiscovery Skills & Prompts + + + +
+

eDiscovery Skills & Prompts

+ +
+

1. Create eDiscovery Case

+

Example Prompt: Create an eDiscovery case in Purview with the Case name "Test-123", response should be a bulleted list.

+

Inputs: [Case name]

+
+ +
+

2. Add Custodian to eDiscovery Case

+

Example Prompt: Add a custodian to the eDiscovery case in Purview, use the Case ID "1xxxx234-XXXX-XXXX-a1cd-fxxxxxxxxxx0" and add this email "test@test123.com".

+

Inputs: [Email], [Case ID]

+
+ +
+

3. Apply Hold on eDiscovery Custodian

+

Example Prompt: Apply hold on eDiscovery custodian using the Custodian ID "2xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx6".

+

Inputs: [Custodian ID]

+
+ +
+

4. Add New userSource Objects to Custodians

+

Example Prompt: Add new userSource object associated with Custodian ID "2xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx6", include custodian email "test@test123.com" and specify sources as Mailbox, Site.

+

Inputs: [Custodian ID], [Email]

+
+ +
+

5. Add New eDiscovery Search Object to the Case

+

Example Prompt: Add a new eDiscovery Search object with Case ID "1xxxx234-XXXX-XXXX-a1cd-fxxxxxxxxxx0" with the display name "Test-123-search", and add dataSourceScopes to allTenantMailboxes, allTenantSites, allCaseCustodians.

+

Inputs: [Case ID], [Display Name]

+
+ +
+

6. Run an Estimate on eDiscovery Case

+

Example Prompt: Run an estimate of the number of emails and documents in eDiscovery with Search ID "2xxxxxxx-xxx4-xxxd-4xxx-5xxxxxxxxxxx".

+

Inputs: [Search ID]

+
+ +
+

7. Create New eDiscovery ReviewSet

+

Example Prompt: Create a new eDiscovery ReviewSet for Case ID "1xxxx234-XXXX-XXXX-a1cd-fxxxxxxxxxx0" with Display Name "Test-123-reviewset".

+

Inputs: [Case ID]

+
+ +
+

8. Initiate an Export in eDiscovery

+

Example Prompt: Use Case ID "1xxxx234-XXXX-XXXX-a1cd-fxxxxxxxxxx0" and ReviewSet ID "dxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" to initiate an export.

+

Inputs: [Case ID], [Review Set ID]

+
+ +
+

9. Initiate an Export in eDiscovery Search

+

Example Prompt: Initiate an export from the Search with Case ID "1xxxx234-XXXX-XXXX-a1cd-fxxxxxxxxxx0" and Search ID "dxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx". Additional parameters: displayName="Export 1 - simple PST-V2", exportCriteria="searchHits", additionalOptions="none", exportFormat="pst".

+

Inputs: [Case ID], [Search ID]

+
+ +
+

10. Update Index on Custodian/s

+

Example Prompt: Use the Case ID from above or use this Case ID "1xxxx234-XXXX-XXXX-a1cd-fxxxxxxxxxx0", and update index on Custodian.

+

Inputs: [Case ID]

+
+ +
+

11. Get a List of Case Operations in eDiscovery

+

Example Prompt: Use the Case ID from above or use this Case ID "1xxxx234-XXXX-XXXX-a1cd-fxxxxxxxxxx0", get a list of the caseOperation objects and their properties.

+

Inputs: [Case ID]

+
+
+ + diff --git a/Plugins/Community Based Plugins/Purview/eDiscovery/scp-upload-custom-plugins-2.png b/Plugins/Community Based Plugins/Purview/eDiscovery/scp-upload-custom-plugins-2.png new file mode 100644 index 00000000..b705e8a0 Binary files /dev/null and b/Plugins/Community Based Plugins/Purview/eDiscovery/scp-upload-custom-plugins-2.png differ diff --git a/Plugins/Community Based Plugins/Purview/eDiscovery/scp-upload-custom-plugins-api-manifest.png b/Plugins/Community Based Plugins/Purview/eDiscovery/scp-upload-custom-plugins-api-manifest.png new file mode 100644 index 00000000..66796576 Binary files /dev/null and b/Plugins/Community Based Plugins/Purview/eDiscovery/scp-upload-custom-plugins-api-manifest.png differ diff --git a/Plugins/Community Based Plugins/Purview/eDiscovery/scp-upload-custom-plugins.png b/Plugins/Community Based Plugins/Purview/eDiscovery/scp-upload-custom-plugins.png new file mode 100644 index 00000000..e51cd983 Binary files /dev/null and b/Plugins/Community Based Plugins/Purview/eDiscovery/scp-upload-custom-plugins.png differ diff --git a/Plugins/Community Based Plugins/QR Code AiTM Phishing Detection Plugin/HuntingQrCodeAITM.yml b/Plugins/Community Based Plugins/QR Code AiTM Phishing Detection Plugin/HuntingQrCodeAITM.yml index c45651d9..7855e60f 100644 --- a/Plugins/Community Based Plugins/QR Code AiTM Phishing Detection Plugin/HuntingQrCodeAITM.yml +++ b/Plugins/Community Based Plugins/QR Code AiTM Phishing Detection Plugin/HuntingQrCodeAITM.yml @@ -176,7 +176,7 @@ SkillGroups: Settings: Target: Defender Template: |- - let suspiciousClusters = EmailEvents + let suspiciousClusters = EmailEvents | where Timestamp > ago(7d) | where EmailDirection == "Inbound" | where NetworkMessageId in ("") diff --git a/Plugins/Community Based Plugins/Redact PII/RedactPIIGPT.yaml b/Plugins/Community Based Plugins/Redact PII/Manifest_RedactPIIGPT.yaml similarity index 100% rename from Plugins/Community Based Plugins/Redact PII/RedactPIIGPT.yaml rename to Plugins/Community Based Plugins/Redact PII/Manifest_RedactPIIGPT.yaml diff --git a/Plugins/Community Based Plugins/Redact PII/Readme.md b/Plugins/Community Based Plugins/Redact PII/Readme.md index 02702e6b..1860bfdd 100644 --- a/Plugins/Community Based Plugins/Redact PII/Readme.md +++ b/Plugins/Community Based Plugins/Redact PII/Readme.md @@ -1,18 +1,52 @@ -# Security Copilot Plugin: Rdact PII +# Redact Personally Identifiable Information -### **This plugin will redcat PII content from a session or a prompt ouput. It will also redact Entra ID object IDs theat may also be sensistive, for example GUIDs for an identity** +## DESCRIPTION +This plugin will redcat PII content from a session or a prompt ouput. It will also redact Entra ID object IDs theat may also be sensistive, for example GUIDs for an identity -### Pre-requisites +--- -- [Security Copilot Enabled](https://learn.microsoft.com/en-us/security-copilot/get-started-security-copilot#onboarding-to-microsoft-security-copilot) -- [Access to upload custom plugins](https://learn.microsoft.com/en-us/security-copilot/manage-plugins?tabs=securitycopilotplugin#managing-custom-plugins) -- [Microsoft Defencer XDR](https://learn.microsoft.com/en-us/defender-xdr/microsoft-365-defender) +## TYPE AND REQUIREMENTS +**TYPE**: GPT
+**SOURCE**: None
+**REQUIREMENTS**: None -- Plugin uses permissions of the user setting it up +--- -### Instructions +## SKILLS -#### Upload the Custom Plugin + + + + + + + + + + + + + +
SkillNameDescriptionParameters
RedactPIIRedacts Personally Identifiable Information (PII) from the provided text +
    +
  • + text +
  • +
+
+ + +--- + +## SAMPLE PROMPTS + +- `« Redacts Personally Identifiable Information (PII) from the provided text: »` + +--- + +## SCREENSHOTS +
+ +
-1. Obtain the file RedactPII.yaml from this directory. diff --git a/Plugins/MSFT_Plugin_Samples/API/APIPlugin_CustomizableEndpointURL.yaml b/Plugins/MSFT_Plugin_Samples/API/APIPlugin_CustomizableEndpointURL.yaml index a95ce375..fdd7222b 100644 --- a/Plugins/MSFT_Plugin_Samples/API/APIPlugin_CustomizableEndpointURL.yaml +++ b/Plugins/MSFT_Plugin_Samples/API/APIPlugin_CustomizableEndpointURL.yaml @@ -9,7 +9,7 @@ Descriptor: Required: true SkillGroups: - - Format: API - Settings: - OpenApiSpecURL: https://example.com/openapi.json - EndpointUrlSettingName: InstanceURL \ No newline at end of file + - Format: API + Settings: + OpenApiSpecURL: https://example.com/openapi.json + EndpointUrlSettingName: InstanceURL \ No newline at end of file diff --git a/Plugins/MSFT_Plugin_Samples/KQL/KQL_Azure_Data_Explorer_ADX_Template.yaml b/Plugins/MSFT_Plugin_Samples/KQL/KQL_Azure_Data_Explorer_ADX_Template.yaml new file mode 100644 index 00000000..a5b60d9b --- /dev/null +++ b/Plugins/MSFT_Plugin_Samples/KQL/KQL_Azure_Data_Explorer_ADX_Template.yaml @@ -0,0 +1,50 @@ +# Author Rick Kotlarz +# Updated 2024-12-12 + +Descriptor: + Name: Azure Data Explorer (ADX) template + DisplayName: Azure Data Explorer (ADX) plugin + Description: This Azure Data Explorer (ADX) plugin .... + Icon: https://raw.githubusercontent.com/MicrosoftDocs/architecture-center/refs/heads/main/docs/_images/azbb.svg +# Source: https://github.com/MicrosoftDocs/architecture-center/blob/main/docs/_images/azbb.svg + + SupportedAuthTypes: + - None + +SkillGroups: + - Format: KQL + Skills: + + - Name: GetADXlogs + DisplayName: GetADXlogs + Description: | + Gets top n rows of the ADX cluster sorted by TimeGenerated +# This area can be multiple lines when using the pipe character after the colon character. +# You'll want to include additional information that is relevant for the Orchestreator to understand + ExamplePrompts: + - Gets the top 10 rows of the 'TableYouWantToQuery' table from the 'Your-Cluster-Name-01' ADX cluster sort by TimeGenerated. +# - An example prompt that a user might type to invoke this skill +# - Another example prompt. +# - Make sure you exclude named entities that are meant to change from user input (e.g. IP, username, IoCs, etc.) as these will actually lower skill selection probability. + Inputs: + - Name: numberOfRows + PlaceholderValue: Number of rows you wish to return from the top (e.g. 10) + Description: Number of rows to return from the top + DefaultValue: 3 + Required: true +# - Name: variableNameYouWantFromTheUserThatIsReferencedInYourKQL +# PlaceholderValue: Description or examples that user will see before filling out this variable. Note this field is displayed to the user. +# Description: Description that the orchestrator will use which includes any situations that explain when this input field should or should not be skipped. For example 'this field should be only used if an IP address is in IPv4 format'. Do not use the 'PlaceholderValue' field if you don't have any specific instructions for the orchestrator. Also this field is not displayed to the user. +# DefaultValue: defaultValue used if the 'Required' field is set to 'False' +# Required: true or false + Settings: + Target: Kusto + Cluster: '{{ClusterURL}}' + Database: '{{DatabaseName}}' +# Cluster: https://YourClusterName.eastus.kusto.windows.net +# Database: ADX-01 + Template: |- + //KQL comment to help the Orchestrator or users responsible for maintaining the code understand what the skill does + TableYouWantToQuery + | sort by TimeGenerated desc + | take {{numberOfRows}} diff --git a/Promptbook samples/Anomalous Sign-Ins detection.md b/Promptbook samples/Anomalous Sign-Ins detection.md new file mode 100644 index 00000000..4fffaf73 --- /dev/null +++ b/Promptbook samples/Anomalous Sign-Ins detection.md @@ -0,0 +1,37 @@ +![Security CoPilot Logo](https://github.com/Azure/Copilot-For-Security/blob/main/Images/ic_fluent_copilot_64_64%402x.png) + +# Accelerating the Anomalous Sign-Ins detection with Microsoft Entra ID and Security Copilot + +In today’s complex threat landscape, identity protection is critical for securing organizational assets. A common sign of compromise is user activity indicating connections from multiple locations separated by over X- kilometers within a short period. Such events might represent risky sign-ins, requiring Security Analysts to determine whether they are true positives (indicating malicious activity) or false positives (such as misconfigured settings or benign anomalies). + +To enhance efficiency and accelerate the investigation process, organizations can leverage AI tools like Microsoft Security Copilot. By integrating Security Copilot with Microsoft Entra ID mainly AADUserRiskEvent and developing custom Promptbooks, organizations can investigate risky sign-ins, reduce manual workloads, and enable proactive decision-making to boost SOC efficiency in such scenarios. + +Disclaimer: Please know these are sample prompts and are subject to Change + + + + +**Required plugins** : Microsoft Defender XDR, Public Web + +**Required Input**: + +**Required Input**: + +**Required Input**: + +**Description**: Organizations face significant challenges in investigating and triaging identity protection alerts for sign-in anomalies, especially when users appear to log in from geographically disparate locations within hours. These challenges include: + +- Volume of Alerts: Large organizations generate numerous risky sign-in events daily. +- False Positives: Legitimate activities, such as VPN connections or device relocations, may be flagged. +- Resource Constraints: Security teams must efficiently prioritize true positives for investigation. + +Using Microsoft Security Copilot with a tailored Promptbook, Security Analysts can automate the initial triage process and focus on meaningful insights. This approach combines data querying, AI-driven analysis, and actionable recommendations to improve investigation workflows. + +1. First Prompt: Data Retrieval from Defender XDR via KQL Query + ``` + Retrieve Defender XDR information using this KQL query: let riskyusers = AADUserRiskEvents | where TimeGenerated is greater than or equal ago() | project UserPrincipalName, TimeGenerated, Location, IpAddress, RiskState, Id, RiskEventType; riskyusers | join kind=inner ( riskyusers | extend TimeGenerated1 = TimeGenerated, LocationDetails1 = Location ) on UserPrincipalName | where TimeGenerated is less than TimeGenerated1 and datetime_diff('hour', TimeGenerated1, TimeGenerated) is less than or equal | extend latyy = Location.geoCoordinates.latitude | extend longy= Location.geoCoordinates.longitude | extend latyy1 = LocationDetails1.geoCoordinates.latitude | extend longy1 = LocationDetails1.geoCoordinates.longitude | extend distance = geo_distance_2points(todouble(Location.geoCoordinates.latitude), todouble(Location.geoCoordinates.longitude), todouble(LocationDetails1.geoCoordinates.latitude), todouble(LocationDetails1.geoCoordinates.longitude)) | where distance is greater than or equal | summarize arg_max(TimeGenerated, *) by Id | where RiskState is not equal @"dismissed" | project UserPrincipalName, TimeGenerated, IpAddress, Location, TimeGenerated1, IpAddress1, LocationDetails1, RiskEventType, distance + ``` +2. Second Prompt: AI Analysis for Patterns and Recommendations + ``` +/AnalyzeSecurityData Provide your insights as Security Analyst about what anomalies or similarity patterns can you identify. Provide a list of prompts for Security Copilot to investigate further and a list of recommendations. Use as input security data the information in the table from the previous prompt in this session. + ``` \ No newline at end of file diff --git a/Promptbook samples/Threat Bulletin Promptbook.md b/Promptbook samples/Threat Bulletin Promptbook.md index 65128a41..679549c2 100644 --- a/Promptbook samples/Threat Bulletin Promptbook.md +++ b/Promptbook samples/Threat Bulletin Promptbook.md @@ -13,7 +13,7 @@ Share the latest Threat articles published in the last in a table ``` 2. Check if there are any CVEs mentioned in the article and their associated technologies ``` -If there is any CVEs sighted in these articles , please provide a summary of these CVEs and sight the technologies impacted +If there is any CVEs sighted in these articles , please provide a summary of these CVEs and cite the technologies impacted ``` 3. Summarize the articles in a report format , with action items etc ``` diff --git a/Technical Workshops/Custom Plugin Calling Webservice/FlaskWebsite/app.py b/Technical Workshops/Custom Plugin Calling Webservice/FlaskWebsite/app.py deleted file mode 100644 index 7d96d277..00000000 --- a/Technical Workshops/Custom Plugin Calling Webservice/FlaskWebsite/app.py +++ /dev/null @@ -1,153 +0,0 @@ -# To install flask run: pip install flask -from flask import Flask, request, jsonify, send_file - -#-------- Define classes that will be serialized -class ReflectorSimple: - def __init__(self,data,ip,useragent): - self.object = "Reflector Simple" - self.userdata = data - self.sourceip = ip - self.useragent=useragent - - def getDict(self): - - return self.__dict__ - -# Use this class to reflect back the parameters passed via GET query -class ReflectorJson: - def __init__(self,data,json,ip,useragent): - self.object = "Reflector Json" - self.userdata = data - self.value1 = json["value1"] - self.value2 = json["value2"] - self.sourceip = ip - self.useragent=useragent - - def getDict(self): - return self.__dict__ - -class ReflectorJsonAuth: - def __init__(self,data,authToken,json,ip,useragent): - self.object = "Reflector Json" - self.userdata = data - self.value1 = json["value1"] - self.value2 = json["value2"] - self.AuthToken = authToken - self.sourceip = ip - self.useragent=useragent - - def getDict(self): - return self.__dict__ -#-------------------------------------------- - -app = Flask(__name__) - -# Show the REST APIs supported and format as a basic HTML. -# When the main page is visited via browswer this page will be shown -@app.route('/') -def main(): - text = "

Welcome to Copilot for Security test REST APIs. Please use the following REST API routes to get specific JSON results.

" - text += "

- GET /simple/<data> - Will return a JSON contains <data> and few other fields " - text += "

- GET /params/<data> - Will return a JSON contains <data> and few other fields passed in URL Params" - text += "

- GET /params_auth_header/<data> - Will return a JSON contains <data> and few other fields passed in URL Params, along with Authorization field passed in header" - text += "

- GET /params_auth_data/<data> - Will return a JSON contains <data> and few other fields passed in URL Params, along with Authorization field passed in data" - text += "

- POST /json/<data> - Will return a JSON contains <data> and few other fields passed in payload JSON" - return text - -# ---------------- GET methods -@app.route('/simple/', methods=['GET']) -def get_simple(data): - obj = ReflectorSimple(data,getIpAddr(request),request.user_agent.string) - response = jsonify(obj.getDict()) - return response - -@app.route('/simple/multiple/', methods=['GET']) -def get_simple_multiple(data): - count = int(data) - objs = [] - for i in range(0,count): - obj = ReflectorSimple("Data_"+str(i),getIpAddr(request),request.user_agent.string) - objs.append(obj.getDict()) - response = jsonify(objs) - return response - -# This method accepts query parameters,passes them to create a ReflectorJson JSON object -@app.route('/params/', methods=['GET']) -def get_params_data(data): - args = request.args - jsonData = args - obj = ReflectorJson(data,jsonData,request.remote_addr,request.user_agent.string) - response = jsonify(obj.getDict()) - return response - -@app.route('/params_auth_header/', methods=['GET']) -def get_params_auth_header(data): - args = request.args - jsonData = args - # Extract the Authorization Value from the Header - authToken = "via Header:" + request.headers["Authorization"] - obj = ReflectorJsonAuth(data,authToken,jsonData,request.remote_addr,request.user_agent.string) - response = jsonify(obj.getDict()) - return response - -@app.route('/params_auth_data/', methods=['GET']) -def get_params_auth_data(data): - args = request.args - jsonData = args - # Extract the Authorization Value from the Parameter - authToken = args["Authorization"] - obj = ReflectorJsonAuth(data,authToken,jsonData,request.remote_addr,request.user_agent.string) - response = jsonify(obj.getDict()) - return response - -# ---------------- POST methods -@app.route('/json/', methods=['POST']) -def get_json(data): - jsonData = "" - if(request.is_json): - try: - # Handle Error - jsonData = request.get_json(force=True) #request.json - print("Received JSON:",jsonData) - except Exception as e: - print("Error",e) - print("Request Content", str(jsonData)) - result = {'Error': "'" + str(e) + "'"} - return result, 400 # Return error as HTTP 400 - obj = ReflectorJson(data,jsonData,request.remote_addr,request.user_agent.string) - response = jsonify(obj.getDict()) - return response - - -#---- OPTIONAL: Use this URL to return YAML files if you do not want to the host the files in FTP/Azure/Blob -#---- NOTE: All files should be placed in the ./Files folder -@app.route('/file/') -def get_file(fileName): - try: - # Sanitize the fileName - fileName = str(fileName).replace('/','') - fileName = fileName.replace('\\','') - if(fileName.count('.')) > 1: # Reject - result = {'Error': "Illegal file name"} - return result, 400 - filePath = 'Files\\'+fileName - return send_file(filePath,download_name=fileName,as_attachment=True) - except Exception as e: - result = {'Error': "Exception"} - print(e) - return result,400 - -# Private function to return IP Address -def getIpAddr(request): - if request.headers.getlist("X-Forwarded-For"): - ip_addr = request.headers.getlist("X-Forwarded-For")[0] - else: - ip_addr = request.remote_addr - return ip_addr - -if __name__ == '__main__': - # Specify Network interface to bind to and the port to run - app.run(host='0.0.0.0', port=5000) - - - 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 diff --git a/Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_GET_Params.yaml b/Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_GET_Params.yaml index cc83cedc..fd90ae7b 100644 --- a/Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_GET_Params.yaml +++ b/Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_GET_Params.yaml @@ -3,8 +3,9 @@ Descriptor: Name: Elman's Reflection API plug-in using GET params v1 DisplayName: Elman's Reflection API plug-in using GET params v1 Description: Skills for getting a GET REST API call reflection based on parameters that are passed v1 - DescriptionForModel: Skills for getting a GET REST API call reflection based on parameters that are passed. - This can be called with a prompt like "Get Elman's Reflection Data for data1 with value1 and value2". + DescriptionForModel: |- + Skills for getting a GET REST API call reflection based on parameters that are passed. + This can be called with a prompt like "Get Elman's Reflection Data for data1 with value1 and value2". SkillGroups: - Format: API Settings: diff --git a/Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_GET_Params_Custom_Setting.yaml b/Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_GET_Params_Custom_Setting.yaml index 380be30b..98b93f51 100644 --- a/Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_GET_Params_Custom_Setting.yaml +++ b/Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_GET_Params_Custom_Setting.yaml @@ -3,8 +3,9 @@ Descriptor: Name: Elman's Reflection API plug-in using GET params v1 DisplayName: Elman's Reflection API plug-in using GET params v1 Description: Skills for getting a GET REST API call reflection based on parameters that are passed v1 - DescriptionForModel: Skills for getting a GET REST API call reflection based on parameters that are passed. - This can be called with a prompt like "Get Elman's Reflection Data for data1 with value1 and value2". + DescriptionForModel: |- + Skills for getting a GET REST API call reflection based on parameters that are passed. + This can be called with a prompt like "Get Elman's Reflection Data for data1 with value1 and value2". SupportedAuthTypes: - ApiKey Authorization: diff --git a/Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_GET_Params_Defaults.yaml b/Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_GET_Params_Defaults.yaml index 1ba8812a..7680c0ca 100644 --- a/Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_GET_Params_Defaults.yaml +++ b/Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_GET_Params_Defaults.yaml @@ -2,10 +2,12 @@ Descriptor: Name: Elman's Reflection API plug-in using GET params v2 DisplayName: Elman's Reflection API plug-in using GET params v2 - Description: Skills for getting a GET REST API call reflection based on parameters that are passed. - Both query params have default values - DescriptionForModel: Skills for getting a GET REST API call reflection based on parameters that are passed. - This can be called with a prompt like "Get Elman's Reflection Data for data1 with value1 and value2 + Description: |- + Skills for getting a GET REST API call reflection based on parameters that are passed. + Both query params have default values + DescriptionForModel: |- + Skills for getting a GET REST API call reflection based on parameters that are passed. + This can be called with a prompt like "Get Elman's Reflection Data for data1 with value1 and value2 SkillGroups: - Format: API diff --git a/Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_GET_Params_Partial_JSON.yaml b/Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_GET_Params_Partial_JSON.yaml index 76f9d548..9105d686 100644 --- a/Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_GET_Params_Partial_JSON.yaml +++ b/Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_GET_Params_Partial_JSON.yaml @@ -2,10 +2,12 @@ Descriptor: Name: Elman's Reflection API plug-in using GET params v1 DisplayName: Elman's Reflection API plug-in using GET params v1 - Description: Skills for getting a GET REST API call reflection based on parameters that are passed v1. - This is similat to the Params YAML, but in the OpenAPI spec only the partial JSON returned by the API is defined. - DescriptionForModel: Skills for getting a GET REST API call reflection based on parameters that are passed. - This can be called with a prompt like "Get Elman's Reflection Data for data1 with value1 and value2". + Description: |- + Skills for getting a GET REST API call reflection based on parameters that are passed v1. + This is similat to the Params YAML, but in the OpenAPI spec only the partial JSON returned by the API is defined. + DescriptionForModel: |- + Skills for getting a GET REST API call reflection based on parameters that are passed. + This can be called with a prompt like "Get Elman's Reflection Data for data1 with value1 and value2". SkillGroups: - Format: API Settings: diff --git a/Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_GET_Simple_Header.yaml b/Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_GET_Simple_Header.yaml index 409b818d..7b8f778b 100644 --- a/Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_GET_Simple_Header.yaml +++ b/Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_GET_Simple_Header.yaml @@ -9,8 +9,4 @@ SkillGroups: - Format: API Settings: # Replace this with your own URL where the OpenAPI spec file is located. - OpenApiSpecUrl: http://172.13.112.25:5000/file/API_Plugin_Reflection_OAP - I_GET_Simple_Header.yaml - - - + OpenApiSpecUrl: http://172.13.112.25:5000/file/API_Plugin_Reflection_OAP/I_GET_Simple_Header.yaml \ No newline at end of file diff --git a/Technical Workshops/Markdown Workshop/Module 1 - Formatting with markdown/images/001_prompt_no_Markdown.png b/Technical Workshops/Markdown Workshop/Module 1 - Formatting with markdown/images/001_prompt_no_Markdown.png new file mode 100644 index 00000000..13fbe812 Binary files /dev/null and b/Technical Workshops/Markdown Workshop/Module 1 - Formatting with markdown/images/001_prompt_no_Markdown.png differ diff --git a/Technical Workshops/Markdown Workshop/Module 1 - Formatting with markdown/images/002_AskGPT_Markdown_formatting.png b/Technical Workshops/Markdown Workshop/Module 1 - Formatting with markdown/images/002_AskGPT_Markdown_formatting.png new file mode 100644 index 00000000..68684a4a Binary files /dev/null and b/Technical Workshops/Markdown Workshop/Module 1 - Formatting with markdown/images/002_AskGPT_Markdown_formatting.png differ diff --git a/Technical Workshops/Markdown Workshop/Module 1 - Formatting with markdown/images/003_AskGPT_formatting_as_a_table_prompt.png b/Technical Workshops/Markdown Workshop/Module 1 - Formatting with markdown/images/003_AskGPT_formatting_as_a_table_prompt.png new file mode 100644 index 00000000..229e53fb Binary files /dev/null and b/Technical Workshops/Markdown Workshop/Module 1 - Formatting with markdown/images/003_AskGPT_formatting_as_a_table_prompt.png differ diff --git a/Technical Workshops/Markdown Workshop/Module 1 - Formatting with markdown/images/004_AskGPT_formatting_as_a_list_prompt.png b/Technical Workshops/Markdown Workshop/Module 1 - Formatting with markdown/images/004_AskGPT_formatting_as_a_list_prompt.png new file mode 100644 index 00000000..d42d55b8 Binary files /dev/null and b/Technical Workshops/Markdown Workshop/Module 1 - Formatting with markdown/images/004_AskGPT_formatting_as_a_list_prompt.png differ diff --git a/Technical Workshops/Markdown Workshop/Module 1 - Formatting with markdown/images/005_AskGPT_formatting_as_a_list_result.png b/Technical Workshops/Markdown Workshop/Module 1 - Formatting with markdown/images/005_AskGPT_formatting_as_a_list_result.png new file mode 100644 index 00000000..cd57f3e0 Binary files /dev/null and b/Technical Workshops/Markdown Workshop/Module 1 - Formatting with markdown/images/005_AskGPT_formatting_as_a_list_result.png differ diff --git a/Technical Workshops/Markdown Workshop/Module 1 - Formatting with markdown/images/006_prompt_that_includes_AskGPT_formatting.png b/Technical Workshops/Markdown Workshop/Module 1 - Formatting with markdown/images/006_prompt_that_includes_AskGPT_formatting.png new file mode 100644 index 00000000..a5762129 Binary files /dev/null and b/Technical Workshops/Markdown Workshop/Module 1 - Formatting with markdown/images/006_prompt_that_includes_AskGPT_formatting.png differ diff --git a/Technical Workshops/Markdown Workshop/Module 1 - Formatting with markdown/readme.md b/Technical Workshops/Markdown Workshop/Module 1 - Formatting with markdown/readme.md new file mode 100644 index 00000000..eee97186 --- /dev/null +++ b/Technical Workshops/Markdown Workshop/Module 1 - Formatting with markdown/readme.md @@ -0,0 +1,101 @@ +## Module 1 - Formatting with Markdown in Microsoft Security Copilot + +![Security CoPilot Logo](https://github.com/Azure/Copilot-For-Security/blob/main/Images/ic_fluent_copilot_64_64%402x.png) + +#### ⌛ Estimated time to complete this lab: 15 minutes +#### 🎓 Level: 100 (Beginner) + +The following example prompts demonstrate how users can modify the output from a plugin skill using Markdown. Large Language Models (LLMs) interpret context and follow instructions more effectively when delimiters and Markdown are included in prompts. Although natural language can be used, it often requires more detailed explanations than most users are willing to provide. By offering clear instructions and utilizing Markdown, as covered in this module, you can reduce the likelihood of output variance. + +1. [Initial prompt](#initial-prompt) +2. [Formatting as a table with AskGPT](#formatting-as-a-table-with-askgpt) +3. [Formatting as a list with AskGPT](#formatting-as-a-list-with-askgpt) +4. [Combining a prompt with Markdown formatting instructions](#combining-a-prompt-with-Markdown-formatting-instructions) +5. [Available Markdown](#available-markdown) +6. [Increasing efficiency](#increasing-efficiency) + + +### Initial prompt + +Running a prompt without specifying output expectations can lead to inconsistent formatting, such as alternating between tables and bullet points. When users don't provide detailed instructions on the desired output format, the skill will return all available data. In this example, the output starts with "Incident ID," followed by "Display Name," "Severity," and so on. + +``` +List the last 3 incidents from Defender. +``` +![Image](./images/001_prompt_no_Markdown.png) + +### Formatting as a table with AskGPT + +To modify the default output, we can use the /AskGPT skill and instruct the model to take no action other than reading the instructions. Then, specify that subsequent outputs should follow the provided Markdown format. Keep in mind that this will not reformat existing results; the instruction must be set **before** the prompt for which you want to change the output format. +``` +/AskGPT No action is needed at this time, simply review the following instructions and respond with 'Ready!'. Instructions: All additional responses will be formatted to conform to the following Markdown example. +## Markdown example +| **Created Date** | **Incident ID** | **Status** | **Incident URL** | **Title** | **Severity** | **Assigned To** | **Classification** | **Determination** | **Last Updated** | +|-------------------------|-----------------|------------|-------------------|----------------------|--------------|-----------------|--------------------|-------------------|------------------------------| +| 2025-01-08T12:09:40.47Z | 1234 | Active | https://12.aka.ms | Multi-stage incident | High | John.Doe | Malware | True Positive | 2025-01-22T23:33:21.1733333Z | +``` +![Image](./images/002_AskGPT_Markdown_formatting.png) + +Notice that re-running the [Initial prompt](#initial-prompt) now results with the first column of "Created Date," followed by "Incident ID," and then "Status," instead of "Incident ID," "Display Name," and "Severity." **Be sure to apply these instructions before formatting other prompts.** For better organization and easier access, consider saving this prompt in a promptbook. + +![Image](./images/003_AskGPT_formatting_as_a_table_prompt.png) + +### Formatting as a list with AskGPT + +Another example of Markdown formatting is shown below, using bullets, indentations, and a horizontal bar after each incident. In this example, the "Assigned To," "Classification," and "Determination" fields have been excluded from the formatted output by removing them from the Markdown example. +``` +/AskGPT No action is needed at this time, simply review the following instructions and respond with 'Ready!'. Instructions: All additional responses will be formatted to conform to the following Markdown example. +- **Created Date**: `2025-01-08T12:09:40.47Z` + - **Incident ID**: `1234` + - **Status**: `Active` + - **Title**: `Multi-stage incident` + - **Severity**: `High` + - **Last Updated**: `2025-01-22T23:33:21.1733333Z` + - **Incident URL**: [Incident Details](https://12.aka.ms) +--- +``` + + +![Image](./images/004_AskGPT_formatting_as_a_list_prompt.png) + + +![Image](./images/005_AskGPT_formatting_as_a_list_result.png) + +--- + + +### Combining a prompt with Markdown formatting instructions + +Instead of separating the request, you can include the formatting within the prompt itself. Combining both prompt and formatting instructions can be useful when used with Logic Apps and helps reduce SCU utilization. + + ``` +List the last 3 incidents from Defender. Ensure the output is formatted to conform to the following Markdown example. +## Markdown example +| **Incident ID** | **Status** | **Incident URL** | **Title** | **Severity** | **Assigned To** | **Classification** | **Determination** | **Created Date** | **Last Updated** | +|-----------------|------------|-------------------|----------------------|--------------|-----------------|--------------------|-------------------|--------------------------|-----------------------------| +| 1234 | Active | https://12.aka.ms | Multi-stage incident | High | John.Doe | Malware | True Positive | 2025-01-08T12:09:40.47Z | 2025-01-22T23:33:21.1733333Z | +``` +![Image](./images/006_prompt_that_includes_AskGPT_formatting.png) + + +--- +### Available Markdown + +Given the speed of change within Security Copilot, the best way to visualize which Markdown syntax elements are available to you is by using the following prompt. + +Please note that the standalone instance of Security Copilot currently does not support rendering all Markdown syntax elements (e.g., certain headings and images). While you can still include these elements in your prompts, the output will need to be processed and rendered by another service. + +``` +/AskGPT Assume the role of a Markdown syntax expert. Provide a comprehensive list of Markdown syntax elements, including tables, numbered lists, task lists, and horizontal rules. For each element, provide instructions on how I can replicate them myself and what it would look like when rendered. +``` +For the official Markdown specification visit [CommonMark](https://commonmark.org/help/) + +### Increasing efficiency + +All the Markdown formatting methods above include extra characters to help users visualize the format. To increase efficiency, you can remove double spaces and extra hyphens. Here's an example of what this looks like: + +``` +| **Incident ID** | **Status** | **Incident URL** | +| --- | --- | --- | +| 1234 | Active | https://12.aka.ms | +``` \ No newline at end of file diff --git a/Technical Workshops/Markdown Workshop/Module 2 - Enhancing reasoning and formatting with markdown/images/001_module2_prompt_no_Markdown.png b/Technical Workshops/Markdown Workshop/Module 2 - Enhancing reasoning and formatting with markdown/images/001_module2_prompt_no_Markdown.png new file mode 100644 index 00000000..608d68b1 Binary files /dev/null and b/Technical Workshops/Markdown Workshop/Module 2 - Enhancing reasoning and formatting with markdown/images/001_module2_prompt_no_Markdown.png differ diff --git a/Technical Workshops/Markdown Workshop/Module 2 - Enhancing reasoning and formatting with markdown/images/002_module2_summarize_reason_and_reccomend.png b/Technical Workshops/Markdown Workshop/Module 2 - Enhancing reasoning and formatting with markdown/images/002_module2_summarize_reason_and_reccomend.png new file mode 100644 index 00000000..76cb8bb9 Binary files /dev/null and b/Technical Workshops/Markdown Workshop/Module 2 - Enhancing reasoning and formatting with markdown/images/002_module2_summarize_reason_and_reccomend.png differ diff --git a/Technical Workshops/Markdown Workshop/Module 2 - Enhancing reasoning and formatting with markdown/images/003_module2_creating_links_from_alert_IDs.png b/Technical Workshops/Markdown Workshop/Module 2 - Enhancing reasoning and formatting with markdown/images/003_module2_creating_links_from_alert_IDs.png new file mode 100644 index 00000000..ae978833 Binary files /dev/null and b/Technical Workshops/Markdown Workshop/Module 2 - Enhancing reasoning and formatting with markdown/images/003_module2_creating_links_from_alert_IDs.png differ diff --git a/Technical Workshops/Markdown Workshop/Module 2 - Enhancing reasoning and formatting with markdown/images/004_module2_final_prompt_with_Markdown.png b/Technical Workshops/Markdown Workshop/Module 2 - Enhancing reasoning and formatting with markdown/images/004_module2_final_prompt_with_Markdown.png new file mode 100644 index 00000000..303717b0 Binary files /dev/null and b/Technical Workshops/Markdown Workshop/Module 2 - Enhancing reasoning and formatting with markdown/images/004_module2_final_prompt_with_Markdown.png differ diff --git a/Technical Workshops/Markdown Workshop/Module 2 - Enhancing reasoning and formatting with markdown/images/005_module2_promptbook_step_1.png b/Technical Workshops/Markdown Workshop/Module 2 - Enhancing reasoning and formatting with markdown/images/005_module2_promptbook_step_1.png new file mode 100644 index 00000000..840a8ba7 Binary files /dev/null and b/Technical Workshops/Markdown Workshop/Module 2 - Enhancing reasoning and formatting with markdown/images/005_module2_promptbook_step_1.png differ diff --git a/Technical Workshops/Markdown Workshop/Module 2 - Enhancing reasoning and formatting with markdown/images/005_module2_promptbook_step_2.png b/Technical Workshops/Markdown Workshop/Module 2 - Enhancing reasoning and formatting with markdown/images/005_module2_promptbook_step_2.png new file mode 100644 index 00000000..fc4a4aa5 Binary files /dev/null and b/Technical Workshops/Markdown Workshop/Module 2 - Enhancing reasoning and formatting with markdown/images/005_module2_promptbook_step_2.png differ diff --git a/Technical Workshops/Markdown Workshop/Module 2 - Enhancing reasoning and formatting with markdown/images/005_module2_promptbook_step_3.png b/Technical Workshops/Markdown Workshop/Module 2 - Enhancing reasoning and formatting with markdown/images/005_module2_promptbook_step_3.png new file mode 100644 index 00000000..48aed057 Binary files /dev/null and b/Technical Workshops/Markdown Workshop/Module 2 - Enhancing reasoning and formatting with markdown/images/005_module2_promptbook_step_3.png differ diff --git a/Technical Workshops/Markdown Workshop/Module 2 - Enhancing reasoning and formatting with markdown/images/005_module2_promptbook_step_4.png b/Technical Workshops/Markdown Workshop/Module 2 - Enhancing reasoning and formatting with markdown/images/005_module2_promptbook_step_4.png new file mode 100644 index 00000000..f7b9eb0c Binary files /dev/null and b/Technical Workshops/Markdown Workshop/Module 2 - Enhancing reasoning and formatting with markdown/images/005_module2_promptbook_step_4.png differ diff --git a/Technical Workshops/Markdown Workshop/Module 2 - Enhancing reasoning and formatting with markdown/images/005_module2_promptbook_step_5.png b/Technical Workshops/Markdown Workshop/Module 2 - Enhancing reasoning and formatting with markdown/images/005_module2_promptbook_step_5.png new file mode 100644 index 00000000..f1995898 Binary files /dev/null and b/Technical Workshops/Markdown Workshop/Module 2 - Enhancing reasoning and formatting with markdown/images/005_module2_promptbook_step_5.png differ diff --git a/Technical Workshops/Markdown Workshop/Module 2 - Enhancing reasoning and formatting with markdown/images/005_module2_promptbook_step_6.png b/Technical Workshops/Markdown Workshop/Module 2 - Enhancing reasoning and formatting with markdown/images/005_module2_promptbook_step_6.png new file mode 100644 index 00000000..93872118 Binary files /dev/null and b/Technical Workshops/Markdown Workshop/Module 2 - Enhancing reasoning and formatting with markdown/images/005_module2_promptbook_step_6.png differ diff --git a/Technical Workshops/Markdown Workshop/Module 2 - Enhancing reasoning and formatting with markdown/readme.md b/Technical Workshops/Markdown Workshop/Module 2 - Enhancing reasoning and formatting with markdown/readme.md new file mode 100644 index 00000000..4ece09dc --- /dev/null +++ b/Technical Workshops/Markdown Workshop/Module 2 - Enhancing reasoning and formatting with markdown/readme.md @@ -0,0 +1,172 @@ + + + +## Module 2 - Enhancing reasoning and formatting with Markdown + +![Security Copilot Logo](https://github.com/Azure/Copilot-For-Security/blob/main/Images/ic_fluent_copilot_64_64%402x.png) + +#### ⌛ Estimated time to complete this lab: 15 minutes +#### 🎓 Level: 200 (Intermediate) + +The following example prompts demonstrate how users can enhance both the output and the reasoning behind that output by using Markdown. Large Language Models (LLMs) interpret context and follow instructions more effectively when prompts include delimiters and Markdown. While natural language can be used, it often requires more detailed explanations, which many users may not be willing to provide. By offering clear instructions and utilizing Markdown, as outlined in this module, users can reduce the likelihood of output inconsistencies. + +LLMs often aren't fine-tuned to fully understand the specific context of a user's query. While platforms like Security Copilot help improve prompting by including system prompts that emphasize Responsible AI (RAI) and the intended use of plugins or skills, these might not always meet the user's expectations. To bridge this gap, users should provide additional context and direction, aligning with both the [Security Copilot prompt engineering best practices](https://learn.microsoft.com/en-us/copilot/security/prompting-tips) and general LLM prompt engineering best practices. + +This module illustrates how users can combine the referenced Security Copilot prompt engineering best practices with general LLM prompt engineering best practices. + +1. [Large Language Model prompt engineering best practices](#large-language-model-prompt-engineering-best-practices) +2. [Initial prompt](#initial-prompt) +3. [Summarizing, reasoning, and recommendations](#summarizing-reasoning-and-recommendations) +4. [Creating links from alert IDs](#creating-links-from-alert-ids) +5. [Ensuring consistent formatting](#ensuring-consistent-formatting) +6. [Leveraging Promptbooks](#leveraging-promptbooks) + +### Large Language Model prompt engineering best practices + +Effective prompting is key to obtaining accurate, relevant, and useful responses from language models. By following this set of best practices for effective prompting, users can ensure that the model's outputs align more closely with the desired results. + +1. **Assign a Persona** - Define a specific role to tailor responses appropriately. For example, instructing the model to act as a data analyst can lead to insights that focus on data interpretation. +2. **Use Delimiters** - Employ delimiters, like triple quotes, to clearly indicate specific text segments for tasks such as translation or summarization. This ensures the model processes the correct portion of text. Markdown can simplify this process. +3. **Provide Step-by-Step Instructions** - Break down complex tasks into sequential steps to guide the model through the reasoning process. This approach often results in more accurate outcomes. +4. **Include Examples** - Provide one or more examples to show the desired pattern or style. This helps the model infer and replicate the expected response format. +5. **Be Clear and Specific** - Craft precise, unambiguous prompts, supplying enough context to help the model understand and fulfill the request accurately. +6. **Specify Tone** - If tone is important, specify it in the prompt. Review the model’s outputs iteratively and refine your prompts to improve the quality of responses. +7. **Iterative Refinement** - Continuously review the model's outputs and adjust your prompts as needed to improve response quality. + + +### Initial prompt + +Running a prompt without specifying output expectations can lead to inconsistent formatting, such as alternating between tables and bullet points. When users don't provide detailed instructions on the desired output format, the skill will return all available data. + +In this example, I prompt Security Copilot to use the Purview plugin and retrieve the 10 DLP alerts with a severity of high over the past 30 days: + +``` +Using the Purview plugin, get the last 10 DLP alerts with a severity of high over the past 30 days. Format the output in a table. +``` +I can download the response output by selecting **"Export to Excel"** or simply view it from within the browser by selecting the icon to the right of the "Export to Excel" button. + +![Image](./images/001_module2_prompt_no_Markdown.png) + + +### Summarizing, reasoning, and recommendations + +Looking over these alerts, I can see that it includes a few of the same users. Rather than viewing each alert line-by-line in a table and having to mentally group alerts, I'll use the **/AskGPT** skill, as illustrated in [Module 1 - Formatting with markdown](https://github.com/Azure/Security-Copilot/tree/main/Technical%20Workshops/Markdown%20Workshop/Module%201%20-%20Formatting%20with%20markdown), to instruct Security Copilot to: + +- Summarize all data, grouped by users. +- Highlight significant trends, behaviors, or issues observed. +- Identify patterns, similarities, or contributing factors among the alerts. +- Provide a summary with actionable recommendations to mitigate risk, prioritize incidents, and improve compliance. + +``` +/AskGPT For each user, write a concise, actionable summary addressed to their manager. Begin the summary with the user's User Principal Name (UPN). Highlight significant trends, behaviors, or issues observed in their DLP alerts using chain-of-thought reasoning to identify patterns, similarities, or contributing factors across the alerts. Provide clear, actionable recommendations to mitigate risk, prioritize incidents, and improve compliance. +``` +![Image](./images/002_module2_summarize_reason_and_recommend.png) +### Creating links from alert IDs + +Being satisfied with the summarizing, reasoning, and recommendations output, I'd like to include clickable links that make it easier to view the actual alert. While the initial output does not include a clickable link, it does include the Alert ID. + +Given that Defender XDR, Sentinel, and Purview use standardized URL formats, I can use the alert ID to create an alert link. Purview specifically uses the following URL format, where `{my-Tenant-ID}` would be replaced with my Tenant ID and `{Purview-alert-ID}` would be replaced by the Purview Alert ID: +`https://purview.microsoft.com/datalossprevention/alertspage/fullpage?tid={my-Tenant-ID}&alertsviewid=overview&id={Purview-alert-ID}`. + +The finished URL will look something like this:
+`https://purview.microsoft.com/datalossprevention/alertspage/fullpage?tid=0527ecb7-06fb-4769-b324-fd4a3bb865eb&alertsviewid=overview&id=da7618d0-35a6-71b8-3400-08dd5766cd5a` + +To create a link, I simply need to ensure that the data required to support a given URL format is present in the output or provided some other way. In this case, the output contains a list of DLP alerts along with their associated alert ID. Since my Tenant ID is missing, I'll need to get it. There are multiple methods for getting this, but the easiest is to simply view the [Tenant Properties within the Azure Portal](https://portal.azure.com/#view/Microsoft_AAD_IAM/TenantProperties.ReactView). + +Now that I have the Tenant ID, I can hardcode that part of the URL. From my experience working in Defender XDR and Purview, I know that alert IDs associated with Purview include a preceding 'dl' that I'll need to remove if I want to view these alerts from the Purview portal. + +To generate this URL, I will again use the **/AskGPT** skill and prompt Security Copilot to: +- Create a link for the alerts. +- Provide it with the root URL that contains my Tenant ID. +- Ask it to remove the first two characters of the alert ID. + +⚠️ Please read next prompt before executing and don't forget to insert your Tenant ID in the URL before pasting. + +``` +/AskGPT For each user include a direct link to the alert in Microsoft Purview portal using the following URL format: +Purview: [{Alert Title}](https://purview.microsoft.com/datalossprevention/alertspage/fullpage?tid={Replace-with-your-tenant-ID-and-remove-curly-brackets}&alertsviewid=overview&id={alertId with the first two 'dl' characters of the AlertID removed}) +``` + +⚠️ If I'd prefer working out of Defender XDR instead of Purview, I can leave the preceding 'dl' characters in the Alert ID and use the Defender XDR URL `https://security.microsoft.com/alerts/{alert ID}`, modifying the prompt accordingly. To avoid confusing the LLM, I'll choose either Purview or Defender XDR before pasting the prompt. + +``` +/AskGPT For each user include a direct link to the alert in Defender XDR portal using the following URL format: +[{Alert Title}](https://security.microsoft.com/alerts/{alert Id}) +``` + +If the above prompt failed, verify that you updated the Tenant ID. + +![Image](./images/003_module2_creating_links_from_alert_IDs.png) + + +### Ensuring consistent formatting + +While I value the friendly, conversational, and non-repetitive nature that Generative AI provides, I need the output to be consistently formatted each time. To achieve this, I'll provide clear instructions that integrate my previous prompts, URL modifications, and additional format guidelines. + +Since large language models (LLMs) work best when using specific delimiters, and Markdown is a well-established language for such delimiters, I will combine both the [Security Copilot prompt engineering best practices](https://learn.microsoft.com/en-us/copilot/security/prompting-tips) and the [general best practices for effective prompting of LLMs](#general-best-practices-for-effective-prompting-llms) outlined at the top of this page. By leveraging both of these resources, I’ve not only harnessed the power of Security Copilot, but I will also ensure that the reasoning output is consistently formatted each time I run this prompt. + +``` +Using the Purview plugin, get the last 10 DLP alerts with a severity of high. Assume the role of an expert **Data Loss Prevention (DLP) engineer** tasked with generating a **professional, manager-focused summary** of a DLP triage workflow for DLP alerts. + +- For **each user**, write a **concise, actionable summary** addressed to their manager. +- Begin the summary with the user's **User Principal Name (UPN), Job Title, and Department** to establish context. +- Highlight **significant trends, behaviors, or issues** observed in their **DLP alerts** using **chain-of-thought reasoning** to identify patterns, similarities, or contributing factors across the alerts. +- Provide **clear, actionable recommendations** to mitigate risk, prioritize incidents, and improve compliance. +- Include a direct **link to the alert in Microsoft Purview** using the following URL format: [{Alert Title}](https://purview.microsoft.com/datalossprevention/alertspage/fullpage?tid=0527ecb7-06fb-4769-b324-fd4a3bb865eb&alertsviewid=overview&id={alert Id} +- Note that if the alert ID begins with 'dl' this portion of the alertId should be removed before creating the URL. Example: https://purview.microsoft.com/datalossprevention/alertspage/fullpage?tid=0527ecb7-06fb-4769-b324-fd4a3bb865eb&alertsviewid=overview&id=dlda7618d0-35a6-71b8-9200-08dd5767802a +...becomes... +https://purview.microsoft.com/datalossprevention/alertspage/fullpage?tid=0527ecb7-06fb-4769-b324-fd4a3bb865eb&alertsviewid=overview&id=da7618d0-35a6-71b8-9200-08dd5767802a + +**Tone and Style:** Write in clear, professional, and manager-focused language, emphasizing risk, mitigation strategies, and actionable insights. + +## Output format ## +**User**: {User Principal Name (UPN)} + +**Summary:** +{Concise, actionable summary of the DLP alerts for the user, highlighting significant trends, behaviors, or issues observed in their DLP alerts using chain-of-thought reasoning to identify patterns, similarities, or contributing factors across the alerts. Summary should be under 150 words.} + +**Recommendations:** +1. **Recommendation category**: {Any recommendation that supports mitigate risk, prioritize incidents, and improve compliance} +2. **Recommendation category**: {Any additional recommendations that supports mitigate risk, prioritize incidents, and improve compliance} +3. etc... + +**Alert Links:** +- [Alert Title](Alert Link) +- [Alert Title](Alert Link) +--- +``` + +![Image](./images/004_module2_final_prompt_with_Markdown.png) + +--- + +### Leveraging Promptbooks + +To make repeating the execution of this prompt easier, I'll create a promptbook. Promptbooks can store multiple prompts and support workflows that allow users to easily rerun one or more prompts at a later time. For advanced prompts like this that include multiple lines of instructions and formatting requirements, using a promptbook reduces the likelihood that I'll incur errors from typing, saves me time from having to manually copy and paste prompts from an external library, and allows me to share it with others within my organization. + +To create a promptbook from within a session, I simply need to click the checkbox on the top left corner of the prompt, and select the "Create promptbook" icon at the top. However, it's important to note that promptbooks use angle brackets`< >` to denote variables. As such, the existence of them must only be used for variables without spaces. + +![Image](./images/005_module2_promptbook_step_1.png) + +Next, I'll provide a name, relevant tags (hit ENTER after each tag), and a description. After this, I'll hover my mouse over the prompt box and click the pencil icon to edit the prompt. + +![Image](./images/005_module2_promptbook_step_2.png) + +Since I don't always want "10" alerts exactly, I'll make this a variable by replacing the number "10" with `` and surrounding it with angle brackets (like this ``), followed by the checkbox icon located where the pencil icon was previously. + +![Image](./images/005_module2_promptbook_step_3.png) + +After scrolling down, I can see that the variable was accepted. Additionally, if I want to share this promptbook with others, I can do so here. + +![Image](./images/005_module2_promptbook_step_4.png) + +Finally, I'll save it by selecting "Create" at the bottom left of the promptbook. + +![Image](./images/005_module2_promptbook_step_5.png) + +I can now view and execute the promptbook from the Home screen by filtering on "Promptbooks" or from the Promptbook Library. + +![Image](./images/005_module2_promptbook_step_6.png) +--- + +For the official Markdown specification, visit [CommonMark](https://commonmark.org/help/) \ No newline at end of file diff --git a/Technical Workshops/Markdown Workshop/readme.md b/Technical Workshops/Markdown Workshop/readme.md new file mode 100644 index 00000000..31daba26 --- /dev/null +++ b/Technical Workshops/Markdown Workshop/readme.md @@ -0,0 +1,33 @@ +# Welcome to Microsoft Copilot for Security Labs! + +![Security Copilot Logo](https://github.com/Azure/Copilot-For-Security/blob/main/Images/ic_fluent_copilot_64_64%402x.png) + +## Introduction + +This workshop is designed to help you get up to speed with Microsoft Security Copilot. It offers hands-on experience in using Markdown, which can significantly enhance how our Large Language Models (LLMs) reason through data and format the results you receive. + +## Recommendations + +It's highly recommended that you review the following two pages prior to starting these modules. + - [Prompting in Microsoft Security Copilot](https://learn.microsoft.com/en-us/copilot/security/prompting-security-copilot) + - [Create effective prompts](https://learn.microsoft.com/en-us/copilot/security/prompting-tips) + +## What is markdown + +Markdown is a lightweight markup language developed by John Gruber in 2004 that is used to format plain text. It allows you to easily add formatting elements such as headers, lists, links, images, bold or italic text, and more, using simple symbols or characters. + +## Why use markdown? +Markdown improves human readability and acts as a delimiter, helping Large Language Models (LLMs) better understand user intent and desired output by providing structure and clarity. + +Using Markdown provides: + - Clear separation of content + - Enhancing context recognition + - Distinguishing between input and output + - Highlighting intent with emphasis + - Prompting LLMs with specific formatting + - Facilitating conditional output + +## Workshop Modules + +- [**Module 1 – Using Markdown for formatting**](./Module%201%20-%20Formatting%20with%20markdown) +- [**Module 2 – Enhancing reasoning and formatting with markdown**](./Module%202%20-%20Enhancing%20reasoning%20and%20formatting%20with%20markdown)