diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..1c6fde84 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,10 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file +version: 2 +updates: + - package-ecosystem: "" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "weekly" 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/Customer Guides/Nl2KQL Guide/Readme.md b/Customer Guides/Nl2KQL Guide/Readme.md index 2881d7c4..0fc4dc7b 100644 --- a/Customer Guides/Nl2KQL Guide/Readme.md +++ b/Customer Guides/Nl2KQL Guide/Readme.md @@ -1,21 +1,21 @@ ![Security CoPilot Logo](https://github.com/Azure/Copilot-For-Security/blob/main/Images/ic_fluent_copilot_64_64%402x.png) -# Microsoft Copilot For Security +# Microsoft Security Copilot ## Featured Capability: Generate Hunting Queries ### Introduction -**Advanced Hunting in Copilot For Security ** -Copilot For Security is now enabled with purpose-built skills that leverage the generative AI capabilities of GPT-4 to reason over a natural-language prompt and generate a KQL query capable of retrieving data from Advanced Hunting tables in Microsoft Defender and top data tables in Microsoft Sentinel. See the full list of supported tables in [Appendix](#appendix). +**Advanced Hunting in Security Copilot ** +Security Copilot is now enabled with purpose-built skills that leverage the generative AI capabilities of GPT-4 to reason over a natural-language prompt and generate a KQL query capable of retrieving data from Advanced Hunting tables in Microsoft Defender and top data tables in Microsoft Sentinel. See the full list of supported tables in [Appendix](#appendix). This new functionality replaces an earlier implementation with a vastly improved algorithm and access to the latest Azure OpenAI models. Research is underway that would extend the functionality to additional data tables in Sentinel. ### Getting Started **Prerequisites** -1. Access to Microsoft Copilot For Security -2. Access to Defender Advanced Hunting and Microsoft Sentinel with the same credentials used to access Copilot For Security . +1. Access to Microsoft Security Copilot +2. Access to Defender Advanced Hunting and Microsoft Sentinel with the same credentials used to access Security Copilot . 3. Access to the necessary features and licenses in Defender and Sentinel to activate all supported tables. **Activating the Skillset** - Enable “Natural language to Defender 365 KQL” and “Natural language to Sentinel KQL” in the admin console under “My connections”. -- The NL2KQL skills can be automatically selected by Copilot For Security based on your prompt. +- The NL2KQL skills can be automatically selected by Security Copilot based on your prompt. - In the prompt box, type “/kql” to select the skill explicitly before entering a prompt. ### Use Case Scenarios @@ -40,7 +40,7 @@ You can also ask questions such as: 1. Be unambiguous: try to ask questions with a clear subject. 2. Ask one question at a time: ask for a single task/type of information at a time when possible. 3. Be specific: if you know anything about the data you are looking for, help provide that information in your question. -4. Use other skills: You can use Copilot For Security 's other skills to help gather information that might provide useful context while writing hunting queries. +4. Use other skills: You can use Security Copilot 's other skills to help gather information that might provide useful context while writing hunting queries. **Example guidelines:** | Ambiguous | Good | Explanation | diff --git a/Customer Guides/Prompting Tips for Copilot For Security/readme.md b/Customer Guides/Prompting Tips for Copilot For Security/readme.md index b30e3788..cd779e78 100644 --- a/Customer Guides/Prompting Tips for Copilot For Security/readme.md +++ b/Customer Guides/Prompting Tips for Copilot For Security/readme.md @@ -1,6 +1,6 @@ ![Security CoPilot Logo](https://github.com/Azure/Copilot-For-Security/blob/main/Images/ic_fluent_copilot_64_64%402x.png) -# Prompting Tips with Microsoft Copilot for Security +# Prompting Tips with Microsoft Security Copilot | Best Practice | Description | |-------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| @@ -11,7 +11,7 @@ | **Be Positive and Respectful** | Use positive instructions instead of negative ones. Be respectful and appropriate for the workplace and avoid any biased, inappropriate, or violent content. Use the singular "they" pronoun to refer to people and avoid guessing their gender, roles, or feelings. | | **Treat Copilot as your Compadre** | Address Copilot as "you" instead of "model" or "assistant". | | **Treat new Copilot sessions as if they are a Stranger** | Sessions currently cannot reference other sessions. Meaning, if you ask a question in relation to an answer provided in a previous session, your new Copilot struggle will likely struggle to address without enough context. Please be mindful of this working with Copilot. | -| **Make Copilot do the Heavy Lifting when it comes to summarizing your sessions** | Concluding your session, leverage Copilot for Security to summarize the prompts’ responses for whatever audience you choose. | +| **Make Copilot do the Heavy Lifting when it comes to summarizing your sessions** | Concluding your session, leverage Security Copilot to summarize the prompts’ responses for whatever audience you choose. | | **Bridge your KQL knowledge gap with NL2KQL skills and KQL custom plugins** | Use the natural language to KQL (NL2KQL) plugin to hunt for information in your data sources using plain English. Junior KQL users will appreciate our KQL custom plugins to execute common KQL queries via natural language. | | **Automate wherever possible** | Use promptbooks and custom promptbooks to automate common security workflows, which require iterative responses to collate and summarize the information you seek. | | **It's okay to experiment. In fact, it’s encouraged!** | We’re venturing unchartered territory as a market with Security LLMs. Don’t be afraid to experiment with different ways of framing your prompts and evaluate the results based on your needs and expectations. You may be pleasantly surprised to find new things you never thought possible. Share your learnings with us in the comments of our blog. | diff --git a/Customer Guides/Readme.md b/Customer Guides/Readme.md index 0f22410f..61362511 100644 --- a/Customer Guides/Readme.md +++ b/Customer Guides/Readme.md @@ -1,19 +1,19 @@ ![Security CoPilot Logo](https://github.com/Azure/Copilot-For-Security/blob/main/Images/ic_fluent_copilot_64_64%402x.png) -# Microsoft Copilot for Security - Customer Guide -Welcome to the Microsoft Copilot for Security Customer Guide! This document serves as your comprehensive resource for leveraging specific skill sets, querying sample prompts, managing identities, and addressing various security scenarios within Microsoft Copilot for Security. Whether you're new to the platform or looking to deepen your understanding, this guide will equip you with the knowledge and tools needed to maximize the effectiveness of CMicrosoft Copilot for Security +# Microsoft Security Copilot - Customer Guide +Welcome to the Microsoft Security Copilot Customer Guide! This document serves as your comprehensive resource for leveraging specific skill sets, querying sample prompts, managing identities, and addressing various security scenarios within Microsoft Security Copilot. Whether you're new to the platform or looking to deepen your understanding, this guide will equip you with the knowledge and tools needed to maximize the effectiveness of Microsoft Security Copilot. # Introduction -Microsoft Copilot for Security is a powerful platform designed to empower security professionals with advanced capabilities for threat detection, incident response, and security operations. By harnessing the latest advancements in artificial intelligence and machine learning, Microsoft Copilot for Security enables organizations to proactively defend against cyber threats, streamline security workflows, and protect critical assets. +Microsoft Security Copilot is a powerful platform designed to empower security professionals with advanced capabilities for threat detection, incident response, and security operations. By harnessing the latest advancements in artificial intelligence and machine learning, Microsoft Security Copilot enables organizations to proactively defend against cyber threats, streamline security workflows, and protect critical assets. # Key Features # 1. Specific Skill Sets -Microsoft Copilot for Security offers a diverse range of skill sets tailored to address specific security challenges and scenarios. These skill sets are designed to provide actionable insights, automate repetitive tasks, and enhance overall security posture. +Microsoft Security Copilot offers a diverse range of skill sets tailored to address specific security challenges and scenarios. These skill sets are designed to provide actionable insights, automate repetitive tasks, and enhance overall security posture. # 2. Sample Prompts -To effectively utilize Microsoft Copilot for Security's capabilities, it's essential to understand how to formulate queries and prompts that yield relevant insights. Sample prompts cover a variety of security topics. +To effectively utilize Microsoft Security Copilot's capabilities, it's essential to understand how to formulate queries and prompts that yield relevant insights. Sample prompts cover a variety of security topics. # 3. Identity Management -Managing identities and access controls is critical for maintaining security and compliance within an organization. Microsoft Copilot for Security provides robust identity management features, allowing you to authenticate users, authorize access, and monitor user activity. +Managing identities and access controls is critical for maintaining security and compliance within an organization. Microsoft Security Copilot provides robust identity management features, allowing you to authenticate users, authorize access, and monitor user activity. # 4. Scenarios -Microsoft Copilot for Security for Security can be applied to a wide range of security scenarios, including but not limited to threat hunting, incident response, and compliance monitoring. +Microsoft Security Copilot can be applied to a wide range of security scenarios, including but not limited to threat hunting, incident response, and compliance monitoring. diff --git a/Images/Azure Automation Images/_AA.png b/Images/Azure Automation Images/_AA.png new file mode 100644 index 00000000..92c39392 Binary files /dev/null and b/Images/Azure Automation Images/_AA.png differ diff --git a/Images/Azure Automation Images/_CreateAA.png b/Images/Azure Automation Images/_CreateAA.png new file mode 100644 index 00000000..9748c3f4 Binary files /dev/null and b/Images/Azure Automation Images/_CreateAA.png differ diff --git a/Images/Azure Automation Images/_IAM.png b/Images/Azure Automation Images/_IAM.png new file mode 100644 index 00000000..a691ba78 Binary files /dev/null and b/Images/Azure Automation Images/_IAM.png differ diff --git a/Images/Azure Automation Images/_IAM2.png b/Images/Azure Automation Images/_IAM2.png new file mode 100644 index 00000000..5bb715c2 Binary files /dev/null and b/Images/Azure Automation Images/_IAM2.png differ diff --git a/Images/Azure Automation Images/_runbook.png b/Images/Azure Automation Images/_runbook.png new file mode 100644 index 00000000..9fc1e6df Binary files /dev/null and b/Images/Azure Automation Images/_runbook.png differ diff --git a/Images/Azure Automation Images/_runbook2.png b/Images/Azure Automation Images/_runbook2.png new file mode 100644 index 00000000..809c68f1 Binary files /dev/null and b/Images/Azure Automation Images/_runbook2.png differ diff --git a/Images/Azure Automation Images/_schedule.png b/Images/Azure Automation Images/_schedule.png new file mode 100644 index 00000000..0ee3b203 Binary files /dev/null and b/Images/Azure Automation Images/_schedule.png differ diff --git a/Images/Azure Automation Images/_system.png b/Images/Azure Automation Images/_system.png new file mode 100644 index 00000000..928b5896 Binary files /dev/null and b/Images/Azure Automation Images/_system.png differ diff --git a/Images/Azure Automation Images/readme.md b/Images/Azure Automation Images/readme.md new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/Images/Azure Automation Images/readme.md @@ -0,0 +1 @@ + diff --git a/Images/Community Plugins/DefenderDailyOperations/GetDefenderDevices.png b/Images/Community Plugins/DefenderDailyOperations/GetDefenderDevices.png new file mode 100644 index 00000000..780b102f Binary files /dev/null and b/Images/Community Plugins/DefenderDailyOperations/GetDefenderDevices.png differ diff --git a/Images/Community Plugins/DefenderDailyOperations/GetLatestEmailsByRecipient.png b/Images/Community Plugins/DefenderDailyOperations/GetLatestEmailsByRecipient.png new file mode 100644 index 00000000..3829932c Binary files /dev/null and b/Images/Community Plugins/DefenderDailyOperations/GetLatestEmailsByRecipient.png differ 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/Images/KQL Plugin images/Workspacedetails.png b/Images/KQL Plugin images/Workspacedetails.png new file mode 100644 index 00000000..d23fd293 Binary files /dev/null and b/Images/KQL Plugin images/Workspacedetails.png differ diff --git a/Images/Logicapp images/Dailythreatexposure.png b/Images/Logicapp images/Dailythreatexposure.png index 127751d1..7414acf9 100644 Binary files a/Images/Logicapp images/Dailythreatexposure.png and b/Images/Logicapp images/Dailythreatexposure.png differ diff --git a/Images/Logicapp images/Dailythreatexposure2.png b/Images/Logicapp images/Dailythreatexposure2.png index ea83af69..f3da7763 100644 Binary files a/Images/Logicapp images/Dailythreatexposure2.png and b/Images/Logicapp images/Dailythreatexposure2.png differ diff --git a/Images/Logicapp images/Echo1.jpg b/Images/Logicapp images/Echo1.jpg new file mode 100644 index 00000000..e540ffdd Binary files /dev/null and b/Images/Logicapp images/Echo1.jpg differ diff --git a/Images/Logicapp images/Echo2.jpg b/Images/Logicapp images/Echo2.jpg new file mode 100644 index 00000000..8adc17d5 Binary files /dev/null and b/Images/Logicapp images/Echo2.jpg differ diff --git a/Images/Logicapp images/Echo3.jpg b/Images/Logicapp images/Echo3.jpg new file mode 100644 index 00000000..d24b88e0 Binary files /dev/null and b/Images/Logicapp images/Echo3.jpg differ diff --git a/Images/Logicapp images/Echo4.jpg b/Images/Logicapp images/Echo4.jpg new file mode 100644 index 00000000..e521b562 Binary files /dev/null and b/Images/Logicapp images/Echo4.jpg differ diff --git a/Images/Logicapp images/Get-CFS-Risky-Incidents-Report.png b/Images/Logicapp images/Get-CFS-Risky-Incidents-Report.png new file mode 100644 index 00000000..4be3e241 Binary files /dev/null and b/Images/Logicapp images/Get-CFS-Risky-Incidents-Report.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/Copilot-SendSummaryToJira/readme.md b/Logic Apps/Copilot-SendSummaryToJira/readme.md index 624ffb82..c3d162b3 100644 --- a/Logic Apps/Copilot-SendSummaryToJira/readme.md +++ b/Logic Apps/Copilot-SendSummaryToJira/readme.md @@ -3,7 +3,7 @@ Author: Yaniv Shasha - + diff --git a/Logic Apps/Copilot-Sentinel_investigation-DynamicSev/azuredeploy.json b/Logic Apps/Copilot-Sentinel_investigation-DynamicSev/azuredeploy.json index aba533c1..4329696b 100644 --- a/Logic Apps/Copilot-Sentinel_investigation-DynamicSev/azuredeploy.json +++ b/Logic Apps/Copilot-Sentinel_investigation-DynamicSev/azuredeploy.json @@ -66,12 +66,12 @@ }, "Compose": { "runAfter": { - "Parse_JSON_4": [ + "Tags_collection": [ "Succeeded" ] }, "type": "Compose", - "inputs": "@split(body('Parse_JSON_4')?['Evaluation Result Content'],',')" + "inputs": "@split(body('Tags_collection')?['EvaluationResultContent'],',')" }, "Create_HTML_table": { "runAfter": { @@ -94,12 +94,13 @@ "type": "ApiConnection", "inputs": { "body": { - "PromptContent": "/SummarizeText Explain why you selected this classifiction. Format as bullet points?", - "SessionId": "@body('classify_incident')?['sessionId']" + "PromptContent": "Explain why you selected this classifiction.? Format as bullet points", + "SessionId": "@body('classify_incident')?['sessionId']", + "SkillName": "SummarizeData" }, "host": { "connection": { - "name": "@parameters('$connections')['securitycopilot']['connectionId']" + "name": "@parameters('$connections')['securitycopilot_1']['connectionId']" } }, "method": "post", @@ -115,12 +116,13 @@ "type": "ApiConnection", "inputs": { "body": { - "PromptContent": "/GetEntraUserDetails Show the authentication methods setup for each user involved in that incident. Especially indicate whether they have MFA enabled.", - "SessionId": "@body('GetReputationsByIpAddresses')?['sessionId']" + "PromptContent": "Show the authentication methods setup for each user involved in that incident. Especially indicate whether they have MFA enabled.", + "SessionId": "@body('GetReputationsByIpAddresses')?['sessionId']", + "SkillName": "GetEntraUserDetails" }, "host": { "connection": { - "name": "@parameters('$connections')['securitycopilot']['connectionId']" + "name": "@parameters('$connections')['securitycopilot_1']['connectionId']" } }, "method": "post", @@ -136,12 +138,13 @@ "type": "ApiConnection", "inputs": { "body": { - "PromptContent": "/GetIncidentEntities Tell me about the entities associated with that incident.", - "SessionId": "@body('GetSentinelIncident')?['sessionId']" + "PromptContent": "Tell me about the entities associated with that incident.", + "SessionId": "@body('GetSentinelIncident')?['sessionId']", + "SkillName": "GetIncidentEntities" }, "host": { "connection": { - "name": "@parameters('$connections')['securitycopilot']['connectionId']" + "name": "@parameters('$connections')['securitycopilot_1']['connectionId']" } }, "method": "post", @@ -157,78 +160,94 @@ "type": "ApiConnection", "inputs": { "body": { - "PromptContent": "/GetIntuneDevices If a user is listed in the incident details, show which devices they have used recently and indicate whether they are compliant with policies.", - "SessionId": "@body('GeUserDetails')?['sessionId']" + "PromptContent": "If a user is listed in the incident details, show which devices they have used recently and indicate whether they are compliant with policies. also show details from Intune on the one that checked in most recently. Especially indicate if it is current on all operating system updates.", + "SessionId": "@body('GeUserDetails')?['sessionId']", + "SkillName": "GetIntuneDevices" }, "host": { "connection": { - "name": "@parameters('$connections')['securitycopilot']['connectionId']" + "name": "@parameters('$connections')['securitycopilot_1']['connectionId']" } }, "method": "post", "path": "/process-prompt" } }, - "GetIntuneDevices2": { + "GetReputationsByIpAddresses": { "runAfter": { - "GetIntuneDevices": [ + "GetIncidentEntities": [ "Succeeded" ] }, "type": "ApiConnection", "inputs": { "body": { - "PromptContent": "/GetIntuneDevices If any devices are listed in the previous output, show details from Intune on the one that checked in most recently. Especially indicate if it is current on all operating system updates.", - "SessionId": "@body('GetIntuneDevices')?['sessionId']" + "PromptContent": "What are the reputation score for the IPv4 addresses on that incident?", + "SessionId": "@body('GetIncidentEntities')?['sessionId']", + "SkillName": "GetReputationsForIndicators" }, "host": { "connection": { - "name": "@parameters('$connections')['securitycopilot']['connectionId']" + "name": "@parameters('$connections')['securitycopilot_1']['connectionId']" } }, "method": "post", "path": "/process-prompt" } }, - "GetReputationsByIpAddresses": { + "GetSentinelIncident": { "runAfter": { - "GetIncidentEntities": [ + "Parse_JSON": [ "Succeeded" ] }, "type": "ApiConnection", "inputs": { "body": { - "PromptContent": "/GetReputationsByIpAddresses What are the reputation score for the IPv4 addresses on that incident?", - "SessionId": "@body('GetIncidentEntities')?['sessionId']" + "PromptContent": "Summarize Sentinel Incident @{body('Parse_JSON')?['object']?['properties']?['incidentNumber']}", + "SkillName": "GetSentinelIncident" }, "host": { "connection": { - "name": "@parameters('$connections')['securitycopilot']['connectionId']" + "name": "@parameters('$connections')['securitycopilot_1']['connectionId']" } }, "method": "post", "path": "/process-prompt" } }, - "GetSentinelIncident": { + "Initialize_variable": { "runAfter": { - "Parse_JSON": [ + "Report": [ "Succeeded" ] }, - "type": "ApiConnection", + "type": "InitializeVariable", "inputs": { - "body": { - "PromptContent": "/GetSentinelIncident Summarize Sentinel Incident @{body('Parse_JSON')?['object']?['properties']?['object']?['properties']?['properties']?['properties']?['incidentNumber']}" - }, - "host": { - "connection": { - "name": "@parameters('$connections')['securitycopilot']['connectionId']" + "variables": [ + { + "name": "summrize_report", + "type": "string", + "value": "@body('Report')?['EvaluationResultContent']" } - }, - "method": "post", - "path": "/process-prompt" + ] + } + }, + "Initialize_variable_2": { + "runAfter": { + "classify_incident": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "sev", + "type": "string", + "value": "@trim(body('classify_incident')?['EvaluationResultContent'])" + } + ] } }, "Parse_JSON": { @@ -242,1272 +261,303 @@ "content": "@triggerBody()", "schema": { "properties": { - "content": { + "eventUniqueId": { + "type": "string" + }, + "object": { "properties": { - "eventUniqueId": { + "etag": { + "type": "string" + }, + "id": { + "type": "string" + }, + "name": { "type": "string" }, - "object": { + "properties": { "properties": { - "etag": { - "type": "string" - }, - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "properties": { + "additionalData": { "properties": { - "additionalData": { - "properties": { - "alertProductNames": { - "items": { - "type": "string" - }, - "type": "array" - }, - "alertsCount": { - "type": "integer" - }, - "bookmarksCount": { - "type": "integer" - }, - "commentsCount": { - "type": "integer" - }, - "tactics": { - "items": { - "type": "string" - }, - "type": "array" - }, - "techniques": { - "type": "array" - } + "alertProductNames": { + "items": { + "type": "string" }, - "type": "object" + "type": "array" }, - "alerts": { + "alertsCount": { + "type": "integer" + }, + "bookmarksCount": { + "type": "integer" + }, + "commentsCount": { + "type": "integer" + }, + "tactics": { "items": { + "type": "string" + }, + "type": "array" + }, + "techniques": { + "type": "array" + } + }, + "type": "object" + }, + "alerts": { + "items": { + "properties": { + "id": { + "type": "string" + }, + "kind": { + "type": "string" + }, + "name": { + "type": "string" + }, + "properties": { "properties": { - "id": { - "type": "string" - }, - "kind": { - "type": "string" - }, - "properties": { + "additionalData": { "properties": { - "additionalData": { - "properties": { - "Activity end time (UTC)": { - "type": "string" - }, - "Activity start time (UTC)": { - "type": "string" - }, - "AlertUri": { - "type": "string" - }, - "AssignedTo": { - }, - "Attacker source IP": { - "type": "string" - }, - "Attacker source computer name": { - "type": "string" - }, - "Category": { - "type": "string" - }, - "Classification": { - }, - "DetectionSource": { - "type": "string" - }, - "DetectorId": { - "type": "string" - }, - "Determination": { - }, - "EffectiveAzureResourceId": { - "type": "string" - }, - "EffectiveSubscriptionId": { - "type": "string" - }, - "IncidentId": { - "type": "string" - }, - "LastUpdated": { - "type": "string" - }, - "Number of existing accounts used by source to sign in": { - "type": "string" - }, - "Number of failed authentication attempts to host": { - "type": "string" - }, - "Number of nonexistent accounts used by source to sign in": { - "type": "string" - }, - "OriginSource": { - "type": "string" - }, - "OriginalAlertProductName": { - "type": "string" - }, - "OriginalAlertProviderName": { - "type": "string" - }, - "ProductComponentName": { - "type": "string" - }, - "ThreatFamilyName": { - }, - "ThreatName": { - }, - "Top accounts with failed sign in attempts (count)": { - "type": "string" - }, - "Was RDP session initiated": { - "type": "string" - }, - "WorkspaceResourceGroup": { - "type": "string" - }, - "WorkspaceSubscriptionId": { - "type": "string" - } - }, - "type": "object" - }, - "alertDisplayName": { - "type": "string" - }, - "alertLink": { + "Alert generation status": { "type": "string" }, - "alertType": { + "Analytic Rule Ids": { "type": "string" }, - "confidenceLevel": { + "Analytic Rule Name": { "type": "string" }, - "description": { + "Correlation Id": { "type": "string" }, - "endTimeUtc": { + "Data Sources": { "type": "string" }, - "friendlyName": { + "Event Grouping": { "type": "string" }, - "processingEndTime": { + "ProcessedBySentinel": { "type": "string" }, - "productName": { + "Query": { "type": "string" }, - "providerAlertId": { - "type": "string" - }, - "remediationSteps": { - "items": { - "type": "string" - }, - "type": "array" - }, - "resourceIdentifiers": { - "items": { - "properties": { - "type": { - "type": "string" - }, - "workspaceId": { - "type": "string" - } - }, - "required": [ - "type", - "workspaceId" - ], - "type": "object" - }, - "type": "array" - }, - "severity": { + "Query End Time UTC": { "type": "string" }, - "startTimeUtc": { + "Query Period": { "type": "string" }, - "status": { + "Query Start Time UTC": { "type": "string" }, - "systemAlertId": { + "Search Query Results Overall Count": { "type": "string" }, - "tactics": { - "items": { - "type": "string" - }, - "type": "array" - }, - "timeGenerated": { + "Trigger Operator": { "type": "string" }, - "vendorName": { + "Trigger Threshold": { "type": "string" } }, "type": "object" }, - "type": { + "alertDisplayName": { "type": "string" - } - }, - "required": [ - "id", - "type", - "kind", - "properties" - ], - "type": "object" - }, - "type": "array" - }, - "bookmarks": { - "type": "array" - }, - "comments": { - "type": "array" - }, - "createdTimeUtc": { - "type": "string" - }, - "firstActivityTimeUtc": { - "type": "string" - }, - "incidentNumber": { - "type": "integer" - }, - "incidentUrl": { - "type": "string" - }, - "labels": { - "type": "array" - }, - "lastActivityTimeUtc": { - "type": "string" - }, - "lastModifiedTimeUtc": { - "type": "string" - }, - "owner": { - "properties": { - "assignedTo": { - }, - "email": { - }, - "objectId": { - }, - "userPrincipalName": { - } - }, - "type": "object" - }, - "providerIncidentId": { - "type": "string" - }, - "providerName": { - "type": "string" - }, - "relatedAnalyticRuleIds": { - "type": "array" - }, - "relatedEntities": { - "items": { - "properties": { - "id": { + }, + "alertType": { "type": "string" }, - "kind": { + "confidenceLevel": { "type": "string" }, - "properties": { - "properties": { - "additionalData": { - "properties": { - "DefenderAvStatus": { - "type": "string" - }, - "DeviceDnsName": { - "type": "string" - }, - "FirstSeenDateTime": { - "type": "string" - }, - "HealthStatus": { - "type": "string" - }, - "MdatpDeviceId": { - "type": "string" - }, - "OnboardingStatus": { - "type": "string" - }, - "OsBuild": { - "type": "integer" - }, - "RbacGroupName": { - "type": "string" - }, - "RiskScore": { - "type": "string" - } + "description": { + "type": "string" + }, + "endTimeUtc": { + "type": "string" + }, + "friendlyName": { + "type": "string" + }, + "processingEndTime": { + "type": "string" + }, + "productComponentName": { + "type": "string" + }, + "productName": { + "type": "string" + }, + "providerAlertId": { + "type": "string" + }, + "resourceIdentifiers": { + "items": { + "properties": { + "type": { + "type": "string" }, - "type": "object" - }, - "friendlyName": { - "type": "string" - }, - "hostName": { - "type": "string" - }, - "osFamily": { - "type": "string" + "workspaceId": { + "type": "string" + } }, - "osVersion": { - "type": "string" - } + "required": [ + "type", + "workspaceId" + ], + "type": "object" }, - "type": "object" + "type": "array" + }, + "severity": { + "type": "string" + }, + "startTimeUtc": { + "type": "string" + }, + "status": { + "type": "string" + }, + "systemAlertId": { + "type": "string" + }, + "tactics": { + "items": { + "type": "string" + }, + "type": "array" + }, + "timeGenerated": { + "type": "string" }, - "type": { + "vendorName": { "type": "string" } }, - "required": [ - "id", - "type", - "kind", - "properties" - ], "type": "object" }, - "type": "array" - }, - "severity": { - "type": "string" - }, - "status": { - "type": "string" + "type": { + "type": "string" + } }, - "title": { - "type": "string" - } + "required": [ + "id", + "name", + "type", + "kind", + "properties" + ], + "type": "object" }, - "type": "object" + "type": "array" + }, + "bookmarks": { + "type": "array" }, - "type": { + "comments": { + "type": "array" + }, + "createdTimeUtc": { "type": "string" - } - }, - "type": "object" - }, - "objectEventType": { - "type": "string" - }, - "objectSchemaType": { - "type": "string" - }, - "workspaceId": { - "type": "string" - }, - "workspaceInfo": { - "properties": { - "ResourceGroupName": { + }, + "description": { "type": "string" }, - "SubscriptionId": { + "firstActivityTimeUtc": { "type": "string" }, - "WorkspaceName": { + "incidentNumber": { + "type": "integer" + }, + "incidentUrl": { + "type": "string" + }, + "labels": { + "type": "array" + }, + "lastActivityTimeUtc": { "type": "string" - } - }, - "type": "object" - } - }, - "type": "object" - }, - "schema": { - "properties": { - "properties": { - "properties": { - "eventUniqueId": { - "properties": { - "type": { - "type": "string" - } - }, - "type": "object" }, - "object": { + "lastModifiedTimeUtc": { + "type": "string" + }, + "owner": { "properties": { - "properties": { - "properties": { - "etag": { - "properties": { - "type": { - "type": "string" - } - }, - "type": "object" - }, - "id": { - "properties": { - "type": { - "type": "string" - } - }, - "type": "object" - }, - "name": { - "properties": { - "type": { - "type": "string" - } - }, - "type": "object" - }, - "properties": { - "properties": { - "properties": { - "properties": { - "additionalData": { - "properties": { - "properties": { - "properties": { - "alertProductNames": { - "properties": { - "items": { - "properties": { - "type": { - "type": "string" - } - }, - "type": "object" - }, - "type": { - "type": "string" - } - }, - "type": "object" - }, - "alertsCount": { - "properties": { - "type": { - "type": "string" - } - }, - "type": "object" - }, - "bookmarksCount": { - "properties": { - "type": { - "type": "string" - } - }, - "type": "object" - }, - "commentsCount": { - "properties": { - "type": { - "type": "string" - } - }, - "type": "object" - }, - "tactics": { - "properties": { - "items": { - "properties": { - "type": { - "type": "string" - } - }, - "type": "object" - }, - "type": { - "type": "string" - } - }, - "type": "object" - }, - "techniques": { - "properties": { - "items": { - "properties": { - "type": { - "type": "string" - } - }, - "type": "object" - }, - "type": { - "type": "string" - } - }, - "type": "object" - } - }, - "type": "object" - }, - "type": { - "type": "string" - } - }, - "type": "object" - }, - "alerts": { - "properties": { - "items": { - "properties": { - "properties": { - "properties": { - "id": { - "properties": { - "type": { - "type": "string" - } - }, - "type": "object" - }, - "kind": { - "properties": { - "type": { - "type": "string" - } - }, - "type": "object" - }, - "name": { - "properties": { - "type": { - "type": "string" - } - }, - "type": "object" - }, - "properties": { - "properties": { - "properties": { - "properties": { - "additionalData": { - "properties": { - "properties": { - "properties": { - "Alert generation status": { - "properties": { - "type": { - "type": "string" - } - }, - "type": "object" - }, - "Analytic Rule Ids": { - "properties": { - "type": { - "type": "string" - } - }, - "type": "object" - }, - "Analytic Rule Name": { - "properties": { - "type": { - "type": "string" - } - }, - "type": "object" - }, - "Analytics Template Id": { - "properties": { - "type": { - "type": "string" - } - }, - "type": "object" - }, - "Correlation Id": { - "properties": { - "type": { - "type": "string" - } - }, - "type": "object" - }, - "Data Sources": { - "properties": { - "type": { - "type": "string" - } - }, - "type": "object" - }, - "Event Grouping": { - "properties": { - "type": { - "type": "string" - } - }, - "type": "object" - }, - "ProcessedBySentinel": { - "properties": { - "type": { - "type": "string" - } - }, - "type": "object" - }, - "Query": { - "properties": { - "type": { - "type": "string" - } - }, - "type": "object" - }, - "Query End Time UTC": { - "properties": { - "type": { - "type": "string" - } - }, - "type": "object" - }, - "Query Period": { - "properties": { - "type": { - "type": "string" - } - }, - "type": "object" - }, - "Query Start Time UTC": { - "properties": { - "type": { - "type": "string" - } - }, - "type": "object" - }, - "Search Query Results Overall Count": { - "properties": { - "type": { - "type": "string" - } - }, - "type": "object" - }, - "Trigger Operator": { - "properties": { - "type": { - "type": "string" - } - }, - "type": "object" - }, - "Trigger Threshold": { - "properties": { - "type": { - "type": "string" - } - }, - "type": "object" - } - }, - "type": "object" - }, - "type": { - "type": "string" - } - }, - "type": "object" - }, - "alertDisplayName": { - "properties": { - "type": { - "type": "string" - } - }, - "type": "object" - }, - "alertType": { - "properties": { - "type": { - "type": "string" - } - }, - "type": "object" - }, - "confidenceLevel": { - "properties": { - "type": { - "type": "string" - } - }, - "type": "object" - }, - "description": { - "properties": { - "type": { - "type": "string" - } - }, - "type": "object" - }, - "endTimeUtc": { - "properties": { - "type": { - "type": "string" - } - }, - "type": "object" - }, - "friendlyName": { - "properties": { - "type": { - "type": "string" - } - }, - "type": "object" - }, - "processingEndTime": { - "properties": { - "type": { - "type": "string" - } - }, - "type": "object" - }, - "productComponentName": { - "properties": { - "type": { - "type": "string" - } - }, - "type": "object" - }, - "productName": { - "properties": { - "type": { - "type": "string" - } - }, - "type": "object" - }, - "providerAlertId": { - "properties": { - "type": { - "type": "string" - } - }, - "type": "object" - }, - "resourceIdentifiers": { - "properties": { - "items": { - "properties": { - "properties": { - "properties": { - "type": { - "properties": { - "type": { - "type": "string" - } - }, - "type": "object" - }, - "workspaceId": { - "properties": { - "type": { - "type": "string" - } - }, - "type": "object" - } - }, - "type": "object" - }, - "required": { - "items": { - "type": "string" - }, - "type": "array" - }, - "type": { - "type": "string" - } - }, - "type": "object" - }, - "type": { - "type": "string" - } - }, - "type": "object" - }, - "severity": { - "properties": { - "type": { - "type": "string" - } - }, - "type": "object" - }, - "startTimeUtc": { - "properties": { - "type": { - "type": "string" - } - }, - "type": "object" - }, - "status": { - "properties": { - "type": { - "type": "string" - } - }, - "type": "object" - }, - "systemAlertId": { - "properties": { - "type": { - "type": "string" - } - }, - "type": "object" - }, - "tactics": { - "properties": { - "items": { - "properties": { - "type": { - "type": "string" - } - }, - "type": "object" - }, - "type": { - "type": "string" - } - }, - "type": "object" - }, - "timeGenerated": { - "properties": { - "type": { - "type": "string" - } - }, - "type": "object" - }, - "vendorName": { - "properties": { - "type": { - "type": "string" - } - }, - "type": "object" - } - }, - "type": "object" - }, - "type": { - "type": "string" - } - }, - "type": "object" - }, - "type": { - "properties": { - "type": { - "type": "string" - } - }, - "type": "object" - } - }, - "type": "object" - }, - "required": { - "items": { - "type": "string" - }, - "type": "array" - }, - "type": { - "type": "string" - } - }, - "type": "object" - }, - "type": { - "type": "string" - } - }, - "type": "object" - }, - "bookmarks": { - "properties": { - "type": { - "type": "string" - } - }, - "type": "object" - }, - "comments": { - "properties": { - "type": { - "type": "string" - } - }, - "type": "object" - }, - "createdTimeUtc": { - "properties": { - "type": { - "type": "string" - } - }, - "type": "object" - }, - "description": { - "properties": { - "type": { - "type": "string" - } - }, - "type": "object" - }, - "firstActivityTimeUtc": { - "properties": { - "type": { - "type": "string" - } - }, - "type": "object" - }, - "incidentNumber": { - "properties": { - "type": { - "type": "string" - } - }, - "type": "object" - }, - "incidentUrl": { - "properties": { - "type": { - "type": "string" - } - }, - "type": "object" - }, - "labels": { - "properties": { - "type": { - "type": "string" - } - }, - "type": "object" - }, - "lastActivityTimeUtc": { - "properties": { - "type": { - "type": "string" - } - }, - "type": "object" - }, - "lastModifiedTimeUtc": { - "properties": { - "type": { - "type": "string" - } - }, - "type": "object" - }, - "owner": { - "properties": { - "properties": { - "properties": { - "assignedTo": { - "properties": { - }, - "type": "object" - }, - "email": { - "properties": { - }, - "type": "object" - }, - "objectId": { - "properties": { - }, - "type": "object" - }, - "userPrincipalName": { - "properties": { - }, - "type": "object" - } - }, - "type": "object" - }, - "type": { - "type": "string" - } - }, - "type": "object" - }, - "providerIncidentId": { - "properties": { - "type": { - "type": "string" - } - }, - "type": "object" - }, - "providerName": { - "properties": { - "type": { - "type": "string" - } - }, - "type": "object" - }, - "relatedAnalyticRuleIds": { - "properties": { - "items": { - "properties": { - "type": { - "type": "string" - } - }, - "type": "object" - }, - "type": { - "type": "string" - } - }, - "type": "object" - }, - "relatedEntities": { - "properties": { - "items": { - "properties": { - "properties": { - "properties": { - "id": { - "properties": { - "type": { - "type": "string" - } - }, - "type": "object" - }, - "kind": { - "properties": { - "type": { - "type": "string" - } - }, - "type": "object" - }, - "name": { - "properties": { - "type": { - "type": "string" - } - }, - "type": "object" - }, - "properties": { - "properties": { - "properties": { - "properties": { - "address": { - "properties": { - "type": { - "type": "string" - } - }, - "type": "object" - }, - "friendlyName": { - "properties": { - "type": { - "type": "string" - } - }, - "type": "object" - } - }, - "type": "object" - }, - "type": { - "type": "string" - } - }, - "type": "object" - }, - "type": { - "properties": { - "type": { - "type": "string" - } - }, - "type": "object" - } - }, - "type": "object" - }, - "required": { - "items": { - "type": "string" - }, - "type": "array" - }, - "type": { - "type": "string" - } - }, - "type": "object" - }, - "type": { - "type": "string" - } - }, - "type": "object" - }, - "severity": { - "properties": { - "type": { - "type": "string" - } - }, - "type": "object" - }, - "status": { - "properties": { - "type": { - "type": "string" - } - }, - "type": "object" - }, - "title": { - "properties": { - "type": { - "type": "string" - } - }, - "type": "object" - } - }, - "type": "object" - }, - "type": { - "type": "string" - } - }, - "type": "object" - }, - "type": { - "properties": { - "type": { - "type": "string" - } - }, - "type": "object" - } - }, - "type": "object" + "assignedTo": { + }, + "email": { + }, + "objectId": { }, - "type": { - "type": "string" + "userPrincipalName": { } }, "type": "object" }, - "objectEventType": { - "properties": { - "type": { - "type": "string" - } - }, - "type": "object" + "providerIncidentId": { + "type": "string" }, - "objectSchemaType": { - "properties": { - "type": { - "type": "string" - } - }, - "type": "object" + "providerName": { + "type": "string" }, - "workspaceId": { - "properties": { - "type": { - "type": "string" - } + "relatedAnalyticRuleIds": { + "items": { + "type": "string" }, - "type": "object" + "type": "array" }, - "workspaceInfo": { - "properties": { + "relatedEntities": { + "items": { "properties": { + "id": { + "type": "string" + }, + "kind": { + "type": "string" + }, + "name": { + "type": "string" + }, "properties": { - "ResourceGroupName": { - "properties": { - "type": { - "type": "string" - } - }, - "type": "object" - }, - "SubscriptionId": { - "properties": { - "type": { - "type": "string" - } + "properties": { + "address": { + "type": "string" }, - "type": "object" + "friendlyName": { + "type": "string" + } }, - "WorkspaceName": { - "properties": { - "type": { - "type": "string" - } - }, - "type": "object" - } + "type": "object" }, - "type": "object" + "type": { + "type": "string" + } }, - "type": { - "type": "string" - } + "required": [ + "id", + "name", + "type", + "kind", + "properties" + ], + "type": "object" }, - "type": "object" + "type": "array" + }, + "severity": { + "type": "string" + }, + "status": { + "type": "string" + }, + "title": { + "type": "string" } }, "type": "object" @@ -1517,134 +567,29 @@ } }, "type": "object" - } - }, - "type": "object" - } - } - }, - "Parse_JSON_2": { - "runAfter": { - "classify_incident": [ - "Succeeded" - ] - }, - "type": "ParseJson", - "inputs": { - "content": "@body('classify_incident')", - "schema": { - "properties": { - "Evaluation Result Content": { - "type": "string" - }, - "Evaluation Result Type": { - "type": "string" - }, - "Next Prompt Suggestions": { - }, - "Prompt Content": { - "type": "string" - }, - "SessionId": { - "type": "string" - }, - "Skill Name": { - } - }, - "type": "object" - } - } - }, - "Parse_JSON_3": { - "runAfter": { - "Explain_classify": [ - "Succeeded" - ] - }, - "type": "ParseJson", - "inputs": { - "content": "@body('Explain_classify')", - "schema": { - "properties": { - "Evaluation Result Content": { - "type": "string" }, - "Evaluation Result Type": { + "objectEventType": { "type": "string" }, - "Next Prompt Suggestions": { - }, - "Prompt Content": { + "objectSchemaType": { "type": "string" }, - "SessionId": { + "workspaceId": { "type": "string" }, - "Skill Name": { - } - }, - "type": "object" - } - } - }, - "Parse_JSON_4": { - "runAfter": { - "Tags_collection": [ - "Succeeded" - ] - }, - "type": "ParseJson", - "inputs": { - "content": "@body('Tags_collection')", - "schema": { - "properties": { - "Evaluation Result Content": { - "type": "string" - }, - "Evaluation Result Type": { - "type": "string" - }, - "Next Prompt Suggestions": { - }, - "Prompt Content": { - "type": "string" - }, - "SessionId": { - "type": "string" - }, - "Skill Name": { - } - }, - "type": "object" - } - } - }, - "Parse_JSON_5": { - "runAfter": { - "adding_tags_for_the_incident": [ - "Succeeded" - ] - }, - "type": "ParseJson", - "inputs": { - "content": "@body('Tags_collection')", - "schema": { - "properties": { - "Evaluation Result Content": { - "type": "string" - }, - "Evaluation Result Type": { - "type": "string" - }, - "Next Prompt Suggestions": { - }, - "Prompt Content": { - "type": "string" - }, - "SessionId": { - "type": "string" - }, - "Skill Name": { + "workspaceInfo": { + "properties": { + "ResourceGroupName": { + "type": "string" + }, + "SubscriptionId": { + "type": "string" + }, + "WorkspaceName": { + "type": "string" + } + }, + "type": "object" } }, "type": "object" @@ -1653,19 +598,20 @@ }, "Report": { "runAfter": { - "GetIntuneDevices2": [ + "GetIntuneDevices": [ "Succeeded" ] }, "type": "ApiConnection", "inputs": { "body": { - "PromptContent": "/SummarizeData Write a report based on this investigation. Lead with your assessment of the incident and supporting evidence for your conclusion with a confidence level, including whether or not this incident is likely to be a true positive. Conclude with a set of recommendations.", - "SessionId": "@body('GetIntuneDevices2')?['sessionId']" + "PromptContent": "Write a report based on this investigation. Lead with your assessment of the incident and supporting evidence for your conclusion with a confidence level, including whether or not this incident is likely to be a true positive. Conclude with a set of recommendations.", + "SessionId": "@body('GetIntuneDevices')?['sessionId']", + "SkillName": "SummarizeData" }, "host": { "connection": { - "name": "@parameters('$connections')['securitycopilot']['connectionId']" + "name": "@parameters('$connections')['securitycopilot_1']['connectionId']" } }, "method": "post", @@ -1674,7 +620,7 @@ }, "Report_answer": { "runAfter": { - "Parse_JSON_5": [ + "adding_tags_for_the_incident": [ "Succeeded" ] }, @@ -1684,7 +630,7 @@ { "name": "Answer", "type": "array", - "value": "@array(body('Parse_JSON_5')?['Evaluation Result Content'])" + "value": "@array(body('Tags_collection')?['EvaluationResultContent'])" } ] } @@ -1699,14 +645,14 @@ "inputs": { "from": "@variables('Answer')", "select": { - "Copilot Report": "@variables('Answer')", - "Explain Classify": "@variables('Answer')" + "Copilot Report": "@variables('summrize_report')", + "Explain Classify": "@variables('classified explain')" } } }, "Switch": { "runAfter": { - "Parse_JSON_2": [ + "Initialize_variable_2": [ "Succeeded" ] }, @@ -1788,7 +734,7 @@ "actions": { } }, - "expression": "@toLower(body('Parse_JSON_2')?['Evaluation Result Content'])", + "expression": "@toLower(variables('sev'))", "type": "Switch" }, "Tags_collection": { @@ -1801,11 +747,12 @@ "inputs": { "body": { "PromptContent": "Derive a set of tags (5 tags) to describe this incident. the Tags will be used to group other incidents, include a tag for \"Security Copilot Triaged\", only respond with the category, write it as single line with comma between Tags to tags, and dont add bullet points.", - "SessionId": "@body('Explain_classify')?['sessionId']" + "SessionId": "@body('Explain_classify')?['sessionId']", + "SkillName": "SummarizeData" }, "host": { "connection": { - "name": "@parameters('$connections')['securitycopilot']['connectionId']" + "name": "@parameters('$connections')['securitycopilot_1']['connectionId']" } }, "method": "post", @@ -1888,7 +835,7 @@ }, "classified_explain": { "runAfter": { - "Parse_JSON_3": [ + "Explain_classify": [ "Succeeded" ] }, @@ -1898,26 +845,27 @@ { "name": "classified explain", "type": "string", - "value": "@body('Parse_JSON_3')?['Evaluation Result Content']" + "value": "@body('Explain_classify')?['EvaluationResultContent']" } ] } }, "classify_incident": { "runAfter": { - "Report": [ + "Initialize_variable": [ "Succeeded" ] }, "type": "ApiConnection", "inputs": { "body": { - "PromptContent": "Based on this investigation details, classify this incident based on the following rules. If it appears malicious, label it as \"high\". If it appears suspicious, label it as \"medium\". If it is not clear, label it is \"low\". Respond with the label only. ", - "SessionId": "@body('Report')?['sessionId']" + "PromptContent": "Based on this investigation details, classify this incident based on the following rules. If it appears malicious, label it as \"high\". If it appears suspicious, label it as \"medium\". If it is not clear, label it is \"low\". Only include the label in the response. ", + "SessionId": "@body('Report')?['sessionId']", + "SkillName": "AnalyzeSecurityData" }, "host": { "connection": { - "name": "@parameters('$connections')['securitycopilot']['connectionId']" + "name": "@parameters('$connections')['securitycopilot_1']['connectionId']" } }, "method": "post", @@ -1936,7 +884,7 @@ "connectionName": "[variables('MicrosoftSentinelConnectionName')]", "id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/Azuresentinel')]" }, - "securitycopilot": { + "securitycopilot_1": { "connectionId": "[resourceId('Microsoft.Web/connections', variables('SecuritycopilotConnectionName'))]", "connectionName": "[variables('SecuritycopilotConnectionName')]", "id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/Securitycopilot')]" @@ -1951,7 +899,7 @@ "tags": { "Created By": "Yaniv Shasha", "CreatedDate": "11/21/2023 8:37:47 PM", - "CreatorUPN": "yanivsh@woodgrove.ms" + "CreatorUPN": "" }, "apiVersion": "2017-07-01", "dependsOn": [ diff --git a/Logic Apps/Copilot-Sentinel_investigation-DynamicSev/readme.md b/Logic Apps/Copilot-Sentinel_investigation-DynamicSev/readme.md index ee7b49ff..f4d5b94f 100644 --- a/Logic Apps/Copilot-Sentinel_investigation-DynamicSev/readme.md +++ b/Logic Apps/Copilot-Sentinel_investigation-DynamicSev/readme.md @@ -1,7 +1,7 @@ -# Copilot-Sentinelinvestigation-DynamicSev +# Security-Copilot-Sentinelinvestigation-DynamicSev Author: Yaniv Shasha -### As of this moment, please deploy this logic app in West-US-2 region + @@ -36,7 +36,7 @@ Prior to beginning the installation process, it's crucial to confirm that you ha To deploy the above logic app, you need to
• Press on the Deploy option, select your subscription and the resource group (select the same tenant that Security Copilot is enabled)
- +
diff --git a/Logic Apps/Copilot-isUserTravel/readme.md b/Logic Apps/Copilot-isUserTravel/readme.md index 04814ebc..fe08e2ee 100644 --- a/Logic Apps/Copilot-isUserTravel/readme.md +++ b/Logic Apps/Copilot-isUserTravel/readme.md @@ -1,4 +1,4 @@ -# Copilot-isUserTravel +# Security Copilot-isUserTravel Author: Yaniv Shasha @@ -36,7 +36,7 @@ Prior to beginning the installation process, it's crucial to confirm that you ha To deploy the above logic app, you need to
• Press on the Deploy option, select your subscription and the resource group (select the same tenant that Security Copilot is enabled)
- + diff --git a/Logic Apps/DailyThreatExposureReport-Copilot/azuredeploy.json b/Logic Apps/DailyThreatExposureReport-Copilot/azuredeploy.json index 63839316..7d2f031c 100644 --- a/Logic Apps/DailyThreatExposureReport-Copilot/azuredeploy.json +++ b/Logic Apps/DailyThreatExposureReport-Copilot/azuredeploy.json @@ -1,298 +1,272 @@ { - "$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" + "$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": "DailyThreatExposureReport-Copilot", +"type": "string" +}, +"email": { +"type": "String", +"metadata": { + "description": "Enter value for email" +} +} +}, +"variables": { +"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" }, - "author": { - "name": "" + "email": { + "type": "String", + "defaultValue": "[parameters('email')]" } }, - "parameters": { - "PlaybookName": { - "defaultValue": "DailyThreatExposureReport-Copilot", - "type": "string" - }, - "email": { - "type": "string", - "metadata": { - "description": "Enter value for email" - } + "triggers": { + "Recurrence": { + "recurrence": { + "frequency": "Day", + "interval": 1 + }, + "evaluatedRecurrence": { + "frequency": "Day", + "interval": 1 + }, + "type": "Recurrence" } }, - "variables": { - "Office365ConnectionName": "[concat('Office365-', parameters('PlaybookName'))]", - "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" - }, - "email": { - "defaultValue": "[parameters('email')]", - "type": "string" - } - }, - "triggers": { - "Recurrence": { - "recurrence": { - "frequency": "Day", - "interval": 1 - }, - "evaluatedRecurrence": { - "frequency": "Day", - "interval": 1 - }, - "type": "Recurrence" - } - }, - "actions": { - "CVE_information": { - "runAfter": { - "Overview_of_latest_threats": [ - "Succeeded" - ] - }, - "type": "ApiConnection", - "inputs": { - "body": { - "PromptContent": "If there are CVEs listed above can you provide more detail on the CVE profile and the affected technologies associated with the CVE", - "SessionId": "@body('Overview_of_latest_threats')?['sessionId']" - }, - "host": { - "connection": { - "name": "@parameters('$connections')['securitycopilot_3']['connectionId']" - } - }, - "method": "post", - "path": "/process-prompt" - } - }, - "Compose": { - "runAfter": { - "Parse_JSON": [ - "Succeeded" - ] - }, - "type": "Compose", - "inputs": "@body('Parse_JSON')?['Evaluation Result Content']" - }, - "Compose_2": { - "runAfter": { - "Compose": [ - "Succeeded" - ] - }, - "type": "Compose", - "inputs": "@decodeUriComponent(replace(encodeUriComponent(outputs('Compose')), '%0A', '%3Cbr%3E'))" - }, - "Overview_of_latest_threats": { - "runAfter": { - }, - "type": "ApiConnection", - "inputs": { - "body": { - "PromptContent": "Give me an overview of the latest threats in my organization" - }, - "host": { - "connection": { - "name": "@parameters('$connections')['securitycopilot_3']['connectionId']" - } - }, - "method": "post", - "path": "/process-prompt" - } - }, - "Parse_JSON": { - "runAfter": { - "Summarize_the_investigation": [ - "Succeeded" - ] - }, - "type": "ParseJson", - "inputs": { - "content": "@body('Summarize_the_investigation')", - "schema": { - "properties": { - "Evaluation Result Content": { - "type": "string" - }, - "Evaluation Result Type": { - "type": "string" - }, - "Prompt Content": { - "type": "string" - }, - "SessionId": { - "type": "string" - }, - "Skill Name": { - }, - "Skill Sources": { - "type": "array" - } - }, - "type": "object" - } - } - }, - "Recommendations": { - "runAfter": { - "CVE_information": [ - "Succeeded" - ] - }, - "type": "ApiConnection", - "inputs": { - "body": { - "PromptContent": "What recommendations do you advise to put in place in regards to these vulnerabilities using defender threat intelligence", - "SessionId": "@body('CVE_information')?['sessionId']" - }, - "host": { - "connection": { - "name": "@parameters('$connections')['securitycopilot_3']['connectionId']" - } - }, - "method": "post", - "path": "/process-prompt" - } - }, - "Send_an_email_(V2)": { - "runAfter": { - "Compose_2": [ - "Succeeded" - ] - }, - "type": "ApiConnection", - "inputs": { - "body": { - "Body": "\u003cp\u003e@{outputs('Compose_2')}\u003c/p\u003e", - "Importance": "Normal", - "Subject": "Daily Threat Exposure Report ", - "To": "@parameters('email')" - }, - "host": { - "connection": { - "name": "@parameters('$connections')['office365_1']['connectionId']" - } - }, - "method": "post", - "path": "/v2/Mail" - } - }, - "Summarize_the_investigation": { - "runAfter": { - "Recommendations": [ - "Succeeded" - ] - }, - "type": "ApiConnection", - "inputs": { - "body": { - "PromptContent": "/ASkGpt Summarize this in a report and call it Daily Threat exposure report with the current date , capturing the overview of the latest threats in my organization above , CVEs associated and their mitigation steps , it should be for a technical audience ", - "SessionId": "@body('Recommendations')?['sessionId']" - }, - "host": { - "connection": { - "name": "@parameters('$connections')['securitycopilot_3']['connectionId']" - } - }, - "method": "post", - "path": "/process-prompt" - } - } - }, - "outputs": { + "actions": { + "CVE_information": { + "runAfter": { + "Overview_of_latest_threats": [ + "Succeeded" + ] + }, + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['securitycopilot_3']['connectionId']" } }, - "parameters": { - "$connections": { - "value": { - "office365_1": { - "connectionId": "[resourceId('Microsoft.Web/connections', variables('Office365ConnectionName'))]", - "connectionName": "[variables('Office365ConnectionName')]", - "id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/Office365')]" - }, - "securitycopilot_3": { - "connectionId": "[resourceId('Microsoft.Web/connections', variables('SecuritycopilotConnectionName'))]", - "connectionName": "[variables('SecuritycopilotConnectionName')]", - "id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/Securitycopilot')]" - } - } + "method": "post", + "body": { + "PromptContent": "If there are CVEs listed above can you provide more detail on the CVE profile and the affected technologies associated with the CVE", + "SessionId": "@body('Overview_of_latest_threats')?['sessionId']", + "SkillName": "FindThreatIntelligence" + }, + "path": "/process-prompt" + } + }, + "Convert_Response_into_HTML_Format_for_Proper_Output": { + "runAfter": { + "Summarize_the_investigation": [ + "Succeeded" + ] + }, + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['securitycopilot_3']['connectionId']" } + }, + "method": "post", + "body": { + "PromptContent": "/AskGpt Provide the previous Response in optimized professional HTML format", + "SessionId": "@body('Summarize_the_investigation')?['sessionId']" + }, + "path": "/process-prompt" + } + }, + "Overview_of_latest_threats": { + "runAfter": {}, + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['securitycopilot_3']['connectionId']" } }, - "name": "[parameters('PlaybookName')]", - "type": "Microsoft.Logic/workflows", - "location": "[resourceGroup().location]", - "tags": { - "Created By": "Sean Wasonga", - "CreatedDate": "1/31/2024 5:12:16 PM", - "CreatorUPN": "sean Wasonga", - "hidden-SentinelTemplateName": "DailyThreatExposureReport-Copilot", - "hidden-SentinelTemplateVersion": "1.0" + "method": "post", + "body": { + "PromptContent": "Give me an overview of the latest threats targeting my organization", + "SkillName": "FindThreatIntelligence" }, - "identity": { - "type": "SystemAssigned" + "path": "/process-prompt" + } + }, + "Recommendations": { + "runAfter": { + "CVE_information": [ + "Succeeded" + ] + }, + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['securitycopilot_3']['connectionId']" + } }, - "apiVersion": "2017-07-01", - "dependsOn": [ - "[resourceId('Microsoft.Web/connections', variables('Office365ConnectionName'))]", - "[resourceId('Microsoft.Web/connections', variables('SecuritycopilotConnectionName'))]" + "method": "post", + "body": { + "PromptContent": "What recommendations do you advise to put in place in regards to these vulnerabilities using defender threat intelligence", + "SessionId": "@body('CVE_information')?['sessionId']", + "SkillName": "FindThreatIntelligence" + }, + "path": "/process-prompt" + } + }, + "Send_an_email_(V2)": { + "runAfter": { + "Add_Copilot_Logo": [ + "Succeeded" ] }, - { - "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": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['office365_1']['connectionId']" } - } + }, + "method": "post", + "body": { + "To": "@parameters('email')", + "Subject": "Daily Threat Exposure Report - Microsoft Copilot for Security", + "Body": "

@{outputs('Add_Copilot_Logo')}
@{body('Convert_Response_into_HTML_Format_for_Proper_Output')?['EvaluationResultContent']}

", + "Importance": "Normal" + }, + "path": "/v2/Mail" + } + }, + "Summarize_the_investigation": { + "runAfter": { + "Recommendations": [ + "Succeeded" + ] }, - { - "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": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['securitycopilot_3']['connectionId']" } - } + }, + "method": "post", + "body": { + "PromptContent": "/AskGpt Generate a report titled \"Daily Threat Exposure Report\" for today's date. The report should provide a summary of the most recent threats detected within the organization, presented in the following structure:\n\nSummary of Latest Detected Threats:\n\nInclude a table with the following columns:\nThreat Name and CVE (hyperlinked to detailed CVE information)\n- List of Vulnerable Devices, Impacted Assets, or Alerts within the organization\n- Brief Description of the threat or vulnerability \nHighlight Critical Threats:\n\n- Emphasize any critical threats identified and the reason they have been deemed Critical, ensuring they stand out visually with mitigations highlighted above \n\nSection for 'Not Urgent' Threats:\n\nProvide a separate section for threats categorized as not urgent.\nAlso Include a list of these threats, their categorization rationale, relevant mitigations, and a table with vulnerable devices (with hyperlinks for more information) where appropriate.\nFinally, conclude the report with a sign-off:\n\"Generated by Microsoft Copilot for Security.\"", + "SessionId": "@body('Recommendations')?['sessionId']" + }, + "path": "/process-prompt" } - ] + }, + "Add_Copilot_Logo": { + "runAfter": { + "Convert_Response_into_HTML_Format_for_Proper_Output": [ + "Succeeded" + ] + }, + "type": "Compose", + "inputs": "\"Copilot" + } +}, +"outputs": {} +}, +"parameters": { +"$connections": { +"value": { + "securitycopilot_3": { + "connectionId": "[resourceId('Microsoft.Web/connections', variables('SecuritycopilotConnectionName'))]", + "connectionName": "[variables('SecuritycopilotConnectionName')]", + "id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/Securitycopilot')]" + }, + "office365_1": { + "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": { +"Created By": "Sean Wasonga", +"CreatedDate": "1/31/2024 5:12:16 PM", +"CreatorUPN": "sean Wasonga", +"hidden-SentinelTemplateName": "DailyThreatExposureReport-Copilot", +"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/DailyThreatExposureReport-Copilot/readme.md b/Logic Apps/DailyThreatExposureReport-Copilot/readme.md index 985a8ba6..f7bdce50 100644 --- a/Logic Apps/DailyThreatExposureReport-Copilot/readme.md +++ b/Logic Apps/DailyThreatExposureReport-Copilot/readme.md @@ -13,7 +13,8 @@ Prior to beginning the installation process, it's crucial to confirm that you ha ## Deployment - + diff --git a/Logic Apps/Get-CfS-Risky-Incidents-Report/Readme.md b/Logic Apps/Get-CfS-Risky-Incidents-Report/Readme.md new file mode 100644 index 00000000..e8ac49c2 --- /dev/null +++ b/Logic Apps/Get-CfS-Risky-Incidents-Report/Readme.md @@ -0,0 +1,152 @@ +# Tutorial: Generate Security Copilot Risky Incident Reports + +Intent: As a SecOps Analyst or Manager, I want to automatically create detailed incident reports summazrizing critical investigation tasks and evidence for the riskiest incidents in my organization. + +The [Incident Report Skill](https://learn.microsoft.com/en-us/defender-xdr/security-copilot-m365d-create-incident-report) in Copilot for Security generates a detailed incident report based on the automated actions, the analysts involved in the incident response, and the comments from the analysts. Whether security teams are using Microsoft Sentinel, Defender XDR, or both, all relevant incident data are added into the generated incident report. + +One way to automatically gather critical evidence pertaining to an incident is to utilze the [Sentinel Triage Assistant (STAT)](https://techcommunity.microsoft.com/t5/microsoft-sentinel-blog/introducing-the-microsoft-sentinel-triage-assistant-stat/ba-p/3845846). STAT gathers all related evidence pertaining to the incident's entities through various [STAT Modules](https://github.com/briandelmsft/SentinelAutomationModules/tree/main/Modules) and adds the related evidence as Comments on the incident. This means that the Copilot for Security Incident Report skill can utilize this evidence when generating an incident report. STAT also has a [Risk Scoring Module](https://github.com/briandelmsft/SentinelAutomationModules/tree/main/Modules/ScoringModule) that allows you to generate an integer value indicating the overall risk level of an incident. The Incident Risk Score allows us to tag incidents with an integer value associated with the total risk, we can regularly look for incidents with the highest risk level to generate incident reports using Copilot for Security. + +These incident reports could be run post mortem, during incident creation (after STAT runs), or on a schedule to spot check how analysts completed incident work. + +## Prerequisites + +Microsoft Sentinel configured with the new [Unified SOC Preview](https://learn.microsoft.com/en-us/azure/sentinel/connect-microsoft-365-defender?tabs=MDE) enabled + +[Sentinel Triage Assistant (STAT)](https://github.com/briandelmsft/SentinelAutomationModules) deployed with all modules adding comments, scoring module configured and incidents in Sentinel tagged as "Risk Score - totalRiskScore". + +[Microsoft Copilot for Security](https://learn.microsoft.com/en-us/copilot/security/get-started-security-copilot) deployed with available SCU Capacity. + +## Step 1: Deploy this logic app to your organization + +If your Azure environment meets the prerequisites, and you're familiar with using Azure Resource Manager templates, these steps help you sign in directly to Azure and open the Azure Resource Manager template in the Azure portal. For more information, see the article, [Deploy resources with Azure Resource Manager templates and Azure portal](https://docs.microsoft.com/azure/azure-resource-manager/templates/overview). + +Select the following image to sign in with your Azure account and open the logic app in the Azure portal: + + [![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%2FGet-CfS-Risky-Incidents-Report%2Fazuredeploy.json) + +1. In the portal, on the **Custom deployment** page, enter or select these values: + + | Property | Value | Description | + |----------|-------|-------------| + | **Subscription** | <*Azure-subscription-name*> | The name for the Azure subscription to use | + | **Resource group** | <*Azure-resource-group-name*> | The name for a new or existing Azure resource group | + | **Region** | <*Azure-region-for-all-resources*> | The Azure region to use for all resources, if different from the default value. This example uses the default value which is the resource group location. | + | **Playbook Name**| <*PlaybookName*> | This is the name for the deployed Logic App. | + | **Incident Count** | <*incidentCount*> | Maximum number of incident to return (ie. 10) | + | **Look Back Days** | <*lookBack*> | How many days to look back (ie. -7) | + | **Min Risk Score** | <*minRiskScore*> | Minimum STAT Risk Score of Incidents (ie. 0) | + +1. When you're done, select **Review + Create** and finally **Create**. + +## Step 2: Authenticate your logic app to Azure Monitor, Teams and Copilot for Security + +Edit the Logic App and modify the connections for the following Actions + +**Get Riskiest Incidents** - Modify this connection with an identity with permission to connect to your Log Analytics workspace where your Sentinel incidents reside. Ensure you select your Subscription, Resource Group, Resource Type and Resource Name to point to your Log Analytics Workspace. + +**Cfs Risk Incident Report** - Modify this connection with an identity with permissions to connect to your Copilot for Security instance. + +**Post Risk Incident Report in Teams Channel** - Modify this connection with an identity with permissions to post a message to a Teams Channel. Don't for get to choose your Team and Channel ID. + +**Add Risk Incident Report to Incident** and **Tag Risk Incident with Copilot Summary** - Modify these connections with an identity with access to modify incidents within your Sentinel workspace. + +![Get-CFS-Risky-Incidents-Report](https://github.com/Azure/Copilot-For-Security/blob/main/Images/Logicapp%20images/Get-CFS-Risky-Incidents-Report.png) + +## Next steps + +This Logic App is scheduled to run weekly, you can modify the schedule from the initial recurrance step. + +You can modify the KQL query in the **Get Riskiest Incidents action**, by default this query looks at the SecurityIncident table in Sentinel, and parses out the tagged STAT Risk Score from the labels column. It then outputs the top 10 riskiest incidents based on the total risk score tagged on STAT incidents. Feel free to modify this query to look at all incidents, or incidents with a certain status (ie. Closed or Resolved only), assigned to a certain analyst, etc. + +1. Manually trigger the Playbook or wait for its scheduled run. +2. Copilot for Security will generate Incident reports on each incident found, add the incident report to the incident via a Sentinel comment, tag the incident with "Copilot Summary", and post the Incident report to a desired Teams channel. + +## Sample Incident Reports + +The following are sample Incident Reports were generated with nothing other then a single prompt utilizing the [Incident Report Skill](https://learn.microsoft.com/en-us/defender-xdr/security-copilot-m365d-create-incident-report) from Copilot for Security on Incidents that were triaged using the [Sentinel Triage Assistant (STAT)](https://github.com/briandelmsft/SentinelAutomationModules). + +--------------------------------------------------------------------------------------------------------- + +Apr 26, 2024 10:15 AM +**Incident report** +by Copilot - Apr 26, 2024 10:15 AM + +Incident title +Multi-stage incident on one endpoint + +**Incident details** +**Analysts** Analyst@contoso.com +**Time created** 04/18/2024 02:29:44 +**First log** 04/26/2024 02:41:12 +**Last log** 04/26/2024 03:05:02 +**Time closed** 04/26/2024 02:59:53 + +**Incident summary** +The high severity incident titled 'Multi-stage incident on one endpoint' occurred between 2024-04-18 02:29:44 UTC and 2024-04-26 10:29:44 UTC. +**Impact:** The incident began on 2024-04-18 02:29:44 UTC when a threat intelligence (TI) match was found between an IP entity and a W3CIISLog. The IP 185.224.128.43 was impacting the machine 'CONTOSO-ADMIN'. +Later that day, at 2024-04-18 10:29:44 UTC, another TI match was found, this time involving two IP addresses, 45.56.108.128 and 57.129.23.166, again impacting the machine 'CONTOSO-ADMIN'. +**CredentialAccess:** The incident escalated on 2024-04-26 01:52:34 UTC when there were excessive failed login attempts to an IIS Web Server from unknown IP addresses. The IPs involved were 45.128.232.229 and 185.224.128.43, and the machine impacted was again 'CONTOSO-ADMIN'. This suggests an attempt to gain unauthorized access to the system. + +**Classification** +**Benign positive - Security testing** +The incident was part of a security testing exercise involving a honeypot. The machine involved in the incident, CONTOSO-ADMIN, accepted an inbound connection from an IP address associated with a botnet. However, no alerts related to the incident were found, and no entities were found to be at risk. + +**Investigation actions** +Megan Bowen was assigned to the incident by the 'LA-ASI-ShiftAssignment_v3' Playbook. +The IP address 185.224.128.43, located in Wormer, Noord-Holland, Netherlands, and associated with the organization Alsycon B.V., was identified as part of the incident. +The IP address was flagged by the Threat Intelligence data with a confidence score of 100, indicating it is part of a botnet. +The IP address was reported 8299 times for abuse and is listed on AbuseIPDB. +The machine involved in the incident, CONTOSO-ADMIN, accepted an inbound connection from the aforementioned IP address on port 80 using the TCP protocol. +The total calculated risk score for the incident was determined to be 20, primarily due to the Threat Intel Risk. +The incident was identified as part of a security testing exercise involving a honeypot. +The playbooks "LA-ASI-ShiftAssignment_v3" and "Get-SOCGPT-Triage-V2" were triggered during the incident. +No alerts related to the incident were found, and no entities were found to be at risk. + +**Remediation actions** +No remediation actions yet + +**Follow-up actions** +No follow-up actions yet + +--------------------------------------------------------------------------------------------------------- + +Apr 25, 2024 7:33 PM +**Incident report** +by Copilot - Apr 25, 2024 7:27 PM + +**Incident title** +Bitsadmin abuse by domain user account on one endpoint + +**Incident details** +Analysts analyst@contoso.com +Time created 04/26/2024 00:00:01 +First log 04/26/2024 00:15:44 +Last log 04/26/2024 02:22:17 +Time closed 04/26/2024 02:22:17 + +**Incident summary** +The high severity incident 'Bitsadmin abuse by domain user account on one endpoint' occurred at 2024-04-26 00:00:01 UTC. +DefenseEvasion: The incident involved user 'user' on the device 'contoso-util' (WindowsServer2019). The user was associated with the abuse of Bitsadmin, a command-line tool that administrators use to create download or upload jobs and monitor their progress. The abuse involved two processes, 'bitsadmin.exe' and 'cmd.exe', indicating a potential attempt to evade detection or defenses on the system. + +**Classification** +**Benign positive - Security testing** +The incident was classified as "BenignPositive" due to the fact that it was confirmed to be a simulation. The user involved, user@contoso.com, was a SecOps Analyst in the Security Operations department and held multiple administrative roles. Despite the presence of seven related alerts, the user's risk level was low, with no MFA failures, MFA frauds, Risk detections, or Suspicious Activity reports. The user was also on the VIP watchlist. The risk score of the devices used by the user and present in the incident was medium, with a low exposure level. The user's threat score was 70 and was trending up. However, no matching Threat Intelligence data was found, further supporting the classification. + +**Investigation actions** +The incident was assigned to Megan Bowen by the 'LA-ASI-ShiftAssignment_v3' Playbook for investigation. +A total of seven related alerts were found, including Bitsadmin abuse by domain user account, User and IP address reconnaissance (SMB), Firewall tampering by domain user account, User and group membership reconnaissance (SAMR), and Anomalous account lookups. +The user's risk level was assessed as low with no MFA failures, MFA frauds, Risk detections, or Suspicious Activity reports. +The user's presence on the VIP watchlist was noted. +A search for matching Threat Intelligence data was conducted, but none was found. +The risk score of the devices used by the user and present in the incident was evaluated as medium, with a low exposure level. +The user's threat score was calculated to be 70 and was trending up. +Two records of Endpoint DLP Activities for user@contoso.com were found in the Sentinel search, involving the copying of sensitive files to a network share. +The total calculated risk score for the incident was determined to be 106, with the highest risk coming from related alerts and the user being on the watchlist. + +Remediation actions +No remediation actions yet + +Follow-up actions +No follow-up actions yet + +--------------------------------------------------------------------------------------------------------- diff --git a/Logic Apps/Get-CfS-Risky-Incidents-Report/azuredeploy.json b/Logic Apps/Get-CfS-Risky-Incidents-Report/azuredeploy.json new file mode 100644 index 00000000..a8a87fee --- /dev/null +++ b/Logic Apps/Get-CfS-Risky-Incidents-Report/azuredeploy.json @@ -0,0 +1,578 @@ +{ + "$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": "Get-CfS-Risky-Incidents-Report", + "type": "string" + }, + "incidentCount": { + "type": "Int", + "defaultValue": 10, + "metadata": { + "description": "Maximum number of incidents to return." + } + }, + "lookBackDays": { + "type": "Int", + "defaultValue": -7, + "metadata": { + "description": "How many days to look back." + } + }, + "minRiskScore": { + "type": "Int", + "defaultValue": 0, + "metadata": { + "description": "Minimum STAT Risk Score." + } + } + }, + "variables": { + "AzuremonitorlogsConnectionName": "[concat('Azuremonitorlogs-', parameters('PlaybookName'))]", + "MicrosoftSentinelConnectionName": "[concat('MicrosoftSentinel-', parameters('PlaybookName'))]", + "SecuritycopilotConnectionName": "[concat('Securitycopilot-', parameters('PlaybookName'))]", + "TeamsConnectionName": "[concat('Teams-', 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" + }, + "incidentCount": { + "defaultValue": "[parameters('incidentCount')]", + "type": "Int" + }, + "lookBackDays": { + "defaultValue": "[parameters('lookBackDays')]", + "type": "Int" + }, + "minRiskScore": { + "defaultValue": "[parameters('minRiskScore')]", + "type": "Int" + } + }, + "triggers": { + "Recurrence": { + "recurrence": { + "frequency": "Week", + "interval": 1, + "schedule": { + "hours": [ + "7" + ], + "weekDays": [ + "Friday" + ] + }, + "timeZone": "Pacific Standard Time" + }, + "evaluatedRecurrence": { + "frequency": "Week", + "interval": 1, + "schedule": { + "hours": [ + "7" + ], + "weekDays": [ + "Friday" + ] + }, + "timeZone": "Pacific Standard Time" + }, + "type": "Recurrence" + } + }, + "actions": { + "For_Each_Risky_Incident_-_Generate_CfS_Incident_Report": { + "foreach": "@body('Parse_Risky_Incidents')", + "actions": { + "Add_Risky_Incident_Report_to_Incident": { + "runAfter": { + "Post_Risky_Incident_Report_in_Teams_Channel": [ + "Succeeded" + ] + }, + "type": "ApiConnection", + "inputs": { + "body": { + "incidentArmId": "@items('For_Each_Risky_Incident_-_Generate_CfS_Incident_Report')?['IncidentArmId']", + "message": "\u003cp\u003e@{body('Parse_Copilot_for_Security_Response')?['Evaluation Result Content']}\u003c/p\u003e" + }, + "host": { + "connection": { + "name": "@parameters('$connections')['azuresentinel']['connectionId']" + } + }, + "method": "post", + "path": "/Incidents/Comment" + } + }, + "Cfs_Risky_Incident_Report": { + "runAfter": { + }, + "type": "ApiConnection", + "inputs": { + "body": { + "PromptContent": "/GenerateIncidentReport Incident: @{items('For_Each_Risky_Incident_-_Generate_CfS_Incident_Report')['ProviderIncidentId']}" + }, + "host": { + "connection": { + "name": "@parameters('$connections')['securitycopilot']['connectionId']" + } + }, + "method": "post", + "path": "/process-prompt" + } + }, + "Compose_Adaptive_Card": { + "runAfter": { + "Parse_Copilot_for_Security_Response": [ + "Succeeded" + ] + }, + "type": "Compose", + "inputs": { + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + "actions": [ + { + "title": "Open Defender", + "type": "Action.OpenUrl", + "url": "https://security.microsoft.com/incident2/@{items('For_Each_Risky_Incident_-_Generate_CfS_Incident_Report')['ProviderIncidentId']}/overview" + }, + { + "title": "Open Copilot for Security", + "type": "Action.OpenUrl", + "url": "https://securitycopilot.microsoft.com/sessions/@{body('Parse_Copilot_for_Security_Response')?['SessionId']}" + } + ], + "body": [ + { + "color": "attention", + "size": "large", + "spacing": "Large", + "style": "heading", + "text": "**Copilot For Security Risky Incident Report**", + "type": "TextBlock", + "wrap": true + }, + { + "color": "attention", + "size": "large", + "spacing": "Large", + "style": "heading", + "text": "**Defender Incident ID:** @{items('For_Each_Risky_Incident_-_Generate_CfS_Incident_Report')['ProviderIncidentId']}", + "type": "TextBlock", + "wrap": true + }, + { + "color": "attention", + "size": "large", + "spacing": "Large", + "style": "heading", + "text": "**Incident Title:** @{items('For_Each_Risky_Incident_-_Generate_CfS_Incident_Report')?['Title']}", + "type": "TextBlock", + "wrap": true + }, + { + "size": "Medium", + "spacing": "Medium", + "text": "**Incident Created:** @{items('For_Each_Risky_Incident_-_Generate_CfS_Incident_Report')['TimeGenerated']}", + "type": "TextBlock", + "wrap": true + }, + { + "size": "Medium", + "spacing": "Medium", + "text": "**Incident Severity:** @{items('For_Each_Risky_Incident_-_Generate_CfS_Incident_Report')?['Severity']}", + "type": "TextBlock", + "wrap": true + }, + { + "size": "Medium", + "spacing": "Medium", + "text": "**Incident Risk Score:** @{items('For_Each_Risky_Incident_-_Generate_CfS_Incident_Report')['RiskScore']}", + "type": "TextBlock", + "wrap": true + }, + { + "size": "Medium", + "spacing": "Medium", + "text": "**Incident Assigned To:** @{items('For_Each_Risky_Incident_-_Generate_CfS_Incident_Report')?['Owner']} ", + "type": "TextBlock", + "wrap": true + }, + { + "size": "Medium", + "spacing": "Medium", + "text": "**Incident Status:** @{items('For_Each_Risky_Incident_-_Generate_CfS_Incident_Report')?['Status']} ", + "type": "TextBlock", + "wrap": true + }, + { + "size": "Medium", + "spacing": "Medium", + "text": "**Incident Classification:** @{items('For_Each_Risky_Incident_-_Generate_CfS_Incident_Report')?['Classification']} ", + "type": "TextBlock", + "wrap": true + }, + { + "size": "Medium", + "spacing": "Medium", + "text": "**Incident Classification Reason:** @{items('For_Each_Risky_Incident_-_Generate_CfS_Incident_Report')?['ClassificationReason']} ", + "type": "TextBlock", + "wrap": true + }, + { + "size": "default", + "spacing": "Medium", + "text": "**Copilot For Security Incident Report:**\n\n@{body('Parse_Copilot_for_Security_Response')?['Evaluation Result Content']}", + "type": "TextBlock", + "wrap": true + } + ], + "msteams": { + "width": "Full" + }, + "type": "AdaptiveCard", + "version": "1.2" + } + }, + "Parse_Copilot_for_Security_Response": { + "runAfter": { + "Cfs_Risky_Incident_Report": [ + "Succeeded" + ] + }, + "type": "ParseJson", + "inputs": { + "content": "@body('Cfs_Risky_Incident_Report')", + "schema": { + "properties": { + "Evaluation Result Content": { + "type": "string" + }, + "Evaluation Result Type": { + "type": "string" + }, + "Prompt Content": { + "type": "string" + }, + "SessionId": { + "type": "string" + }, + "Skill Name": { + "type": "string" + }, + "Skill Sources": { + "items": { + "properties": { + "displayName": { + "type": "string" + }, + "sourceContent": { + "type": "string" + }, + "sourceSerializedData": { + }, + "sourceType": { + "type": "string" + } + }, + "required": [ + "displayName", + "sourceType", + "sourceContent", + "sourceSerializedData" + ], + "type": "object" + }, + "type": "array" + } + }, + "type": "object" + } + } + }, + "Post_Risky_Incident_Report_in_Teams_Channel": { + "runAfter": { + "Compose_Adaptive_Card": [ + "Succeeded" + ] + }, + "type": "ApiConnection", + "inputs": { + "body": { + "messageBody": "@{outputs('Compose_Adaptive_Card')}", + "recipient": { + } + }, + "host": { + "connection": { + "name": "@parameters('$connections')['teams']['connectionId']" + } + }, + "method": "post", + "path": "/v1.0/teams/conversation/adaptivecard/poster/Flow bot/location/@{encodeURIComponent('Channel')}" + } + }, + "Tag_Risky_Incident_with_Copilot_Summary": { + "runAfter": { + "Add_Risky_Incident_Report_to_Incident": [ + "Succeeded" + ] + }, + "type": "ApiConnection", + "inputs": { + "body": { + "incidentArmId": "@items('For_Each_Risky_Incident_-_Generate_CfS_Incident_Report')?['IncidentArmId']", + "tagsToAdd": { + "TagsToAdd": [ + { + "Tag": "Copilot Summary" + } + ] + } + }, + "host": { + "connection": { + "name": "@parameters('$connections')['azuresentinel']['connectionId']" + } + }, + "method": "put", + "path": "/Incidents" + } + } + }, + "runAfter": { + "Parse_Risky_Incidents": [ + "Succeeded" + ] + }, + "type": "Foreach" + }, + "Get_Riskiest_Incidents": { + "runAfter": { + }, + "type": "ApiConnection", + "inputs": { + "body": "let fromDateTime=datetime('@{addDays(utcNow(),parameters('lookBackDays'))}');\nlet toDateTime=datetime('@{utcNow()}');\nlet minRisk='@{parameters('minRiskScore')}';\nSecurityIncident\n| extend Tactics = todynamic(AdditionalData.tactics)\n| extend Owner = todynamic(Owner.email) \n| extend IncidentUrl = tostring(IncidentUrl) // Ensure that the field is treated as a string\n| extend SubstringStart = indexof(IncidentUrl, \"/subscriptions\") // Find the index where \"/subscriptions\" appears in the URL\n| extend IncidentArmId = substring(IncidentUrl, SubstringStart) // Extract the substring starting from the index\n| parse Labels with * '\"Risk Score - ' RiskScore '\"' * \n| summarize arg_max(LastModifiedTime, *) by IncidentNumber\n| where datetime_utc_to_local(TimeGenerated, \"US/Pacific\") between ( fromDateTime .. toDateTime )\n| where toint(RiskScore) \u003e toint(minRisk)\n| where Labels !contains \"Copilot Summary\"\n| project TimeGenerated, IncidentArmId, ProviderIncidentId, Title, Description, Severity, toint(RiskScore), Status, Classification, ClassificationReason, ClassificationComment, Owner, Labels\n| order by RiskScore desc \n| limit @{parameters('incidentCount')}", + "host": { + "connection": { + "name": "@parameters('$connections')['azuremonitorlogs']['connectionId']" + } + }, + "method": "post", + "path": "/queryData", + "queries": { + "resourcegroups": "", + "resourcename": "", + "resourcetype": "", + "subscriptions": "", + "timerange": "Set in query" + } + } + }, + "Parse_Risky_Incidents": { + "runAfter": { + "Get_Riskiest_Incidents": [ + "Succeeded" + ] + }, + "type": "ParseJson", + "inputs": { + "content": "@body('Get_Riskiest_Incidents')?['value']", + "schema": { + "items": { + "properties": { + "Classification": { + }, + "ClassificationComment": { + }, + "ClassificationReason": { + }, + "Description": { + "type": "string" + }, + "IncidentArmId": { + "type": "string" + }, + "Labels": { + }, + "Owner": { + }, + "ProviderIncidentId": { + "type": "string" + }, + "RiskScore": { + "type": "integer" + }, + "Severity": { + "type": "string" + }, + "Status": { + "type": "string" + }, + "TimeGenerated": { + "type": "string" + }, + "Title": { + "type": "string" + } + }, + "required": [ + "TimeGenerated", + "ProviderIncidentId" + ], + "type": "object" + }, + "type": "array" + } + } + } + }, + "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')]" + }, + "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')]" + }, + "teams": { + "connectionId": "[resourceId('Microsoft.Web/connections', variables('TeamsConnectionName'))]", + "connectionName": "[variables('TeamsConnectionName')]", + "id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/Teams')]" + } + } + } + } + }, + "name": "[parameters('PlaybookName')]", + "type": "Microsoft.Logic/workflows", + "location": "[resourceGroup().location]", + "tags": { + "hidden-SentinelTemplateName": "Get-CfS-Risky-Incidents-Report", + "hidden-SentinelTemplateVersion": "1.0" + }, + "identity": { + "type": "SystemAssigned" + }, + "apiVersion": "2017-07-01", + "dependsOn": [ + "[resourceId('Microsoft.Web/connections', variables('AzuremonitorlogsConnectionName'))]", + "[resourceId('Microsoft.Web/connections', variables('MicrosoftSentinelConnectionName'))]", + "[resourceId('Microsoft.Web/connections', variables('SecuritycopilotConnectionName'))]", + "[resourceId('Microsoft.Web/connections', variables('TeamsConnectionName'))]" + ] + }, + { + "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('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('TeamsConnectionName')]", + "location": "[resourceGroup().location]", + "kind": "V1", + "properties": { + "displayName": "[variables('TeamsConnectionName')]", + "customParameterValues": { + }, + "api": { + "id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/Teams')]" + } + } + } + ] +} diff --git a/Logic Apps/InvestigateFailedSignins/azuredeploy.json b/Logic Apps/InvestigateFailedSignins/azuredeploy.json index bbd2857c..5eae335e 100644 --- a/Logic Apps/InvestigateFailedSignins/azuredeploy.json +++ b/Logic Apps/InvestigateFailedSignins/azuredeploy.json @@ -1,579 +1,275 @@ { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "PlaybookName": { - "defaultValue": "InvestigateFailedSignIns", - "type": "string" + "$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": "InvestigateFailedSignIns", +"type": "string" +}, +"email": { +"type": "String", +"metadata": { + "description": "Enter value for email" +} +} +}, +"variables": { +"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" }, - "email": { - "type": "string", - "metadata": { - "description": "Enter value for email" - } + "email": { + "type": "String", + "defaultValue": "[parameters('email')]" } }, - "variables": { - "Office365ConnectionName": "[concat('Office365-', parameters('PlaybookName'))]", - "SecuritycopilotConnectionName": "[concat('Securitycopilot-', parameters('PlaybookName'))]", - "TeamsConnectionName": "[concat('Teams-', parameters('PlaybookName'))]" + "triggers": { + "Recurrence": { + "recurrence": { + "frequency": "Day", + "interval": 1 + }, + "evaluatedRecurrence": { + "frequency": "Day", + "interval": 1 + }, + "type": "Recurrence" + } }, - "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" - }, - "email": { - "defaultValue": "[parameters('email')]", - "type": "string" - } - }, - "triggers": { - "Recurrence": { - "recurrence": { - "frequency": "Day", - "interval": 1 - }, - "evaluatedRecurrence": { - "frequency": "Day", - "interval": 1 - }, - "type": "Recurrence" - } + "actions": { + "Running_the_Copilot_for_Security_Prompts": { + "actions": { + "Pull_the_result_and_place_it_in_JSON_FORMAT": { + "runAfter": { + "Run_KQL_query_to_identify_Failed_sign_ins": [ + "Succeeded" + ] }, - "actions": { - "Compose_3": { - "runAfter": { - "Create_HTML_table_2": [ - "Succeeded" - ] - }, - "type": "Compose", - "inputs": "\u003chtml\u003e\n\u003cstyle\u003e\n#testID table, table th, table td {border: 1px solid #000}\n\u003c/style\u003e\n\u003cdiv id=\"testID\"\u003e\n@{body('Create_HTML_table_2')}\n\u003c/div\u003e\n\u003c/html\u003e" - }, - "Create_HTML_table_2": { - "runAfter": { - "Details_on_associated_IP_address": [ - "Succeeded" - ] - }, - "type": "Table", - "inputs": { - "format": "HTML", - "from": "@body('Parse_JSON_3')?['json']" + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['securitycopilot']['connectionId']" } }, - "Details_on_associated_IP_address": { - "actions": { - "Compose_4": { - "runAfter": { - "Parse_JSON_2": [ - "Succeeded" - ] - }, - "type": "Compose", - "inputs": "@body('Parse_JSON_2')?['Evaluation Result Content']" - }, - "Compose_5": { - "runAfter": { - "Compose_4": [ - "Succeeded" - ] - }, - "type": "Compose", - "inputs": "@decodeUriComponent(replace(encodeUriComponent(outputs('Compose_4')), '%0A', '%3Cbr%3E'))" - }, - "Parse_JSON_2": { - "runAfter": { - }, - "type": "ParseJson", - "inputs": { - "content": "@body('Run_Reputation_Scoring_for_IP_addresses_associated_with_Sign_Ins')", - "schema": { - "properties": { - "Evaluation Result Content": { - "type": "string" - }, - "Evaluation Result Type": { - "type": "string" - }, - "Prompt Content": { - "type": "string" - }, - "SessionId": { - "type": "string" - }, - "Skill Name": { - }, - "Skill Sources": { - "type": "array" - } - }, - "type": "object" - } - } - } - }, - "runAfter": { - "Parse_output_in_JSON_FORMAT": [ - "Succeeded" - ] - }, - "type": "Scope" + "method": "post", + "body": { + "PromptContent": "/AskGpt Provide a representation of the above table result in JSON FORMAT of the top 5 records ", + "SessionId": "@body('Run_KQL_query_to_identify_Failed_sign_ins')?['sessionId']" }, - "Parse_Recommendations_from_Security_Copilot": { - "actions": { - "Compose_6": { - "runAfter": { - "Compose_summary_Recommendations": [ - "Succeeded" - ] - }, - "type": "Compose", - "inputs": "@decodeUriComponent(replace(encodeUriComponent(outputs('Compose_summary_Recommendations')), '%0A', '%3Cbr%3E'))" - }, - "Compose_summary_Recommendations": { - "runAfter": { - "Parse_JSON_4": [ - "Succeeded" - ] - }, - "type": "Compose", - "inputs": "@body('Parse_JSON_4')?['Evaluation Result Content']" - }, - "Parse_JSON_4": { - "runAfter": { - }, - "type": "ParseJson", - "inputs": { - "content": "@body('Summary_and_recommendations')", - "schema": { - "properties": { - "Evaluation Result Content": { - "type": "string" - }, - "Evaluation Result Type": { - "type": "string" - }, - "Prompt Content": { - "type": "string" - }, - "SessionId": { - "type": "string" - }, - "Skill Name": { - }, - "Skill Sources": { - "type": "array" - } - }, - "type": "object" - } - } - } - }, - "runAfter": { - "Running_the_Copilot_for_Security_Prompts": [ - "Succeeded" - ] - }, - "type": "Scope" + "path": "/process-prompt" + } + }, + "Run_KQL_query_to_identify_Failed_sign_ins": { + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['securitycopilot']['connectionId']" + } }, - "Parse_output_in_JSON_FORMAT": { - "actions": { - "Compose": { - "runAfter": { - "Parse_JSON": [ - "Succeeded" - ] - }, - "type": "Compose", - "inputs": "@body('Parse_JSON')?['Evaluation Result Content']" - }, - "Compose_2": { - "runAfter": { - "Compose": [ - "Succeeded" - ] - }, - "type": "Compose", - "inputs": "{\"json\": [@{last(split(first(split(outputs('Compose'),']')),'['))}]}" - }, - "Parse_JSON": { - "runAfter": { - }, - "type": "ParseJson", - "inputs": { - "content": "@body('Pull_the_result_in_JSON_Format')", - "schema": { - "properties": { - "Evaluation Result Content": { - "type": "string" - }, - "Evaluation Result Type": { - "type": "string" - }, - "Prompt Content": { - "type": "string" - }, - "SessionId": { - "type": "string" - }, - "Skill Name": { - }, - "Skill Sources": { - "type": "array" - } - }, - "type": "object" - } - } - }, - "Parse_JSON_3": { - "runAfter": { - "Compose_2": [ - "Succeeded" - ] - }, - "type": "ParseJson", - "inputs": { - "content": "@json(outputs('Compose_2'))", - "schema": { - "properties": { - "json": { - "items": { - "properties": { - "ClientAppUsed": { - "type": "string" - }, - "IPAddress": { - "type": "string" - }, - "Identity": { - "type": "string" - }, - "Location": { - "type": "string" - }, - "SignInCount": { - "type": "integer" - } - }, - "required": [ - "Identity", - "IPAddress", - "Location", - "ClientAppUsed", - "SignInCount" - ], - "type": "object" - }, - "type": "array" - } - }, - "type": "object" - } - } - } - }, - "runAfter": { - "Parse_Recommendations_from_Security_Copilot": [ - "Succeeded" - ] - }, - "type": "Scope" + "method": "post", + "body": { + "PromptContent": "SigninLogs | where TimeGenerated >= ago(24h) // Filter events in the last 24 hours | summarize SignInCount = count() by Identity, IPAddress, Location, ClientAppUsed | extend AbnormalSignIn = SignInCount > 3 // Set your own threshold for abnormal sign-ins | project Identity, IPAddress, AbnormalSignIn, Location, ClientAppUsed, SignInCount | where AbnormalSignIn == true | sort by SignInCount", + "SkillName": "NL2KQLSentinel" }, - "Post_message_in_a_chat_or_channel": { - "runAfter": { - "Send_an_email_(V2)": [ - "Succeeded" - ] - }, - "type": "ApiConnection", - "inputs": { - "body": { - "messageBody": "\u003cp\u003e\u003cbr\u003e\n\u003c/p\u003e", - "recipient": { - "channelId": "19:6e707983d17d4e2dbb165aa7aa0fedf7@thread.skype", - "groupId": "7cc3d35a-37f2-4d8a-807a-817de6f482e4" - } - }, - "host": { - "connection": { - "name": "@parameters('$connections')['teams']['connectionId']" - } - }, - "method": "post", - "path": "/beta/teams/conversation/message/poster/Flow bot/location/@{encodeURIComponent('Channel')}" + "path": "/process-prompt" + } + }, + "Run_Reputation_Scoring_for_IP_addresses_associated_with_Sign_Ins": { + "runAfter": { + "Pull_the_result_and_place_it_in_JSON_FORMAT": [ + "Succeeded" + ] + }, + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['securitycopilot']['connectionId']" } }, - "Running_the_Copilot_for_Security_Prompts": { - "actions": { - "Check_User_Authentication_methods": { - "runAfter": { - "Check_if_customer_is_associated_on_Security_Groups_on_Entra": [ - "Succeeded" - ] - }, - "type": "ApiConnection", - "inputs": { - "body": { - "PromptContent": "Can you check the authentication methods of the user and if they have MFA enabled", - "SessionId": "@body('Check_if_customer_is_associated_on_Security_Groups_on_Entra')?['sessionId']", - "SkillName": "GetUserRiskSummary" - }, - "host": { - "connection": { - "name": "@parameters('$connections')['securitycopilot']['connectionId']" - } - }, - "method": "post", - "path": "/process-prompt" - } - }, - "Check_if_customer_is_associated_on_Security_Groups_on_Entra": { - "runAfter": { - "Run_Reputation_Scoring_for_IP_addresses_associated_with_Sign_Ins": [ - "Succeeded" - ] - }, - "type": "ApiConnection", - "inputs": { - "body": { - "PromptContent": "Determine whether the users mentioned above belong to any security groups on Entra", - "SessionId": "@body('Run_Reputation_Scoring_for_IP_addresses_associated_with_Sign_Ins')?['sessionId']" - }, - "host": { - "connection": { - "name": "@parameters('$connections')['securitycopilot']['connectionId']" - } - }, - "method": "post", - "path": "/process-prompt" - } - }, - "Device_query_and_compliance_view": { - "runAfter": { - "Check_User_Authentication_methods": [ - "Succeeded" - ] - }, - "type": "ApiConnection", - "inputs": { - "body": { - "PromptContent": "Check and see if these users have devices associated with them on intune", - "SessionId": "@body('Check_User_Authentication_methods')?['sessionId']" - }, - "host": { - "connection": { - "name": "@parameters('$connections')['securitycopilot']['connectionId']" - } - }, - "method": "post", - "path": "/process-prompt" - } - }, - "Pull_the_result_in_JSON_Format": { - "runAfter": { - "Run_KQL_query_to_identify_Failed_sign_ins": [ - "Succeeded" - ] - }, - "type": "ApiConnection", - "inputs": { - "body": { - "PromptContent": "/AskGPT Provide a representation of the above table result in JSON FORMAT of the top 5 records", - "SessionId": "@body('Run_KQL_query_to_identify_Failed_sign_ins')?['sessionId']" - }, - "host": { - "connection": { - "name": "@parameters('$connections')['securitycopilot']['connectionId']" - } - }, - "method": "post", - "path": "/process-prompt" - } - }, - "Run_KQL_query_to_identify_Failed_sign_ins": { - "runAfter": { - }, - "type": "ApiConnection", - "inputs": { - "body": { - "PromptContent": "SigninLogs | where TimeGenerated \u003e= ago(24h) // Filter events in the last 24 hours | summarize SignInCount = count() by Identity, IPAddress, Location, ClientAppUsed | extend AbnormalSignIn = SignInCount \u003e 3 // Set your own threshold for abnormal sign-ins | project Identity, IPAddress, AbnormalSignIn, Location, ClientAppUsed, SignInCount | where AbnormalSignIn == true | sort by SignInCount" - }, - "host": { - "connection": { - "name": "@parameters('$connections')['securitycopilot']['connectionId']" - } - }, - "method": "post", - "path": "/process-prompt" - } - }, - "Run_Reputation_Scoring_for_IP_addresses_associated_with_Sign_Ins": { - "runAfter": { - "Pull_the_result_in_JSON_Format": [ - "Succeeded" - ] - }, - "type": "ApiConnection", - "inputs": { - "body": { - "PromptContent": "Run Reputation scoring and Geo location for the IP addresses Identified in the Prompt above", - "SessionId": "@body('Pull_the_result_in_JSON_Format')?['sessionId']" - }, - "host": { - "connection": { - "name": "@parameters('$connections')['securitycopilot']['connectionId']" - } - }, - "method": "post", - "path": "/process-prompt" - } - }, - "Summary_and_recommendations": { - "runAfter": { - "Device_query_and_compliance_view": [ - "Succeeded" - ] - }, - "type": "ApiConnection", - "inputs": { - "body": { - "PromptContent": "/AskGPT Utilizing the findings from the aforementioned investigation, please assess whether these users pose a potential risk warranting further scrutiny due to anomalous sign-ins. Consider factors such as sign-in details, association with security groups, associated IPs,Authentication methods and any other relevant indicators. Summarize the risk level into a percentage scale, where 0-30% denotes it as Neutral status, 30%-50% categorizes as Low risk, 50%-74% as Medium risk, and anything exceeding 75% falls under the High-risk category, loop in some recommendations if you can. Present the rationale for your assessment in a bullet points and table format", - "SessionId": "@body('Device_query_and_compliance_view')?['sessionId']" - }, - "host": { - "connection": { - "name": "@parameters('$connections')['securitycopilot']['connectionId']" - } - }, - "method": "post", - "path": "/process-prompt" - } - } - }, - "runAfter": { - }, - "type": "Scope" + "method": "post", + "body": { + "PromptContent": "Run Reputation scoring and Geo location for the IP addresses Identified in the Prompt above", + "SessionId": "@body('Pull_the_result_and_place_it_in_JSON_FORMAT')?['sessionId']", + "SkillName": "GetReputationsForIndicators" }, - "Send_an_email_(V2)": { - "runAfter": { - "Compose_3": [ - "Succeeded" - ] - }, - "type": "ApiConnection", - "inputs": { - "body": { - "Body": "\u003cp\u003e\u003cspan style=\"font-size: 16px\"\u003e\u003cu\u003eReport Summary / Recommendations\u003c/u\u003e\u003c/span\u003e\u003cstrong\u003e\u003cbr\u003e\n\u003c/strong\u003e@{outputs('Compose_6')}\u003cbr\u003e\n\u003cstrong\u003e\u003cbr\u003e\n\u003c/strong\u003e\u003cspan style=\"font-size: 14px\"\u003e\u003cu\u003eIdenities Targeted\u003c/u\u003e\u003c/span\u003e\u003cbr\u003e\n\u003cstrong\u003e\u003c/strong\u003e\u003cstrong\u003e@{outputs('Compose_3')}\u003c/strong\u003e\u003cstrong\u003e\u003cbr\u003e\n\u003c/strong\u003e\u003cspan style=\"font-size: 14px\"\u003e\u003cu\u003eIP address Details\u003c/u\u003e\u003c/span\u003e\u003cbr\u003e\n@{outputs('Compose_5')}\u003cbr\u003e\n\u003c/p\u003e", - "Importance": "Normal", - "Subject": "Summary of Abnormal Failed Sign ins over the last 24 hours", - "To": "@parameters('email')" - }, - "host": { - "connection": { - "name": "@parameters('$connections')['office365']['connectionId']" - } - }, - "method": "post", - "path": "/v2/Mail" - } - } + "path": "/process-prompt" + } + }, + "Summary_and_recommendations": { + "runAfter": { + "Run_Reputation_Scoring_for_IP_addresses_associated_with_Sign_Ins": [ + "Succeeded" + ] }, - "outputs": { + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['securitycopilot']['connectionId']" + } + }, + "method": "post", + "body": { + "PromptContent": "/AskGpt Create a report with the findings from the above investigation, please assess whether these users pose a potential risk warranting further scrutiny due to anomalous sign-ins. Consider factors such as sign-in details, association with security groups, IPs reputation scoring , Authentication methods , Device compliance information and any other relevant indicators, this can be in a table\nSummarize the risk level into a percentage scale, where. \n- 0-30% denotes it as Neutral status, \n- 30%-50% categorizes as Low risk, \n- 50%-74% as Medium risk, and \n-anything exceeding 75% falls under the High-risk category\nif there is any thing in Medium risk or High risk please add some context to that account\nloop in some recommendations if you can. Present the rationale for your assessment in a bullet points. ( make the report technical)", + "SessionId": "@body('Run_Reputation_Scoring_for_IP_addresses_associated_with_Sign_Ins')?['sessionId']" + }, + "path": "/process-prompt" } }, - "parameters": { - "$connections": { - "value": { - "office365": { - "connectionId": "[resourceId('Microsoft.Web/connections', variables('Office365ConnectionName'))]", - "connectionName": "[variables('Office365ConnectionName')]", - "id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/Office365')]" - }, - "securitycopilot": { - "connectionId": "[resourceId('Microsoft.Web/connections', variables('SecuritycopilotConnectionName'))]", - "connectionName": "[variables('SecuritycopilotConnectionName')]", - "id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/Securitycopilot')]" - }, - "teams": { - "connectionId": "[resourceId('Microsoft.Web/connections', variables('TeamsConnectionName'))]", - "connectionName": "[variables('TeamsConnectionName')]", - "id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/Teams')]" + "Convert_Response_into_HTML_Format_for_Proper_Output": { + "runAfter": { + "Summary_and_recommendations": [ + "Succeeded" + ] + }, + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['securitycopilot']['connectionId']" } - } + }, + "method": "post", + "body": { + "PromptContent": "/AskGpt Provide the previous Response in optimized HTML format", + "SessionId": "@body('Summary_and_recommendations')?['sessionId']" + }, + "path": "/process-prompt" } + }, + "Add_Copilot_Logo": { + "runAfter": { + "Convert_Response_into_HTML_Format_for_Proper_Output": [ + "Succeeded" + ] + }, + "type": "Compose", + "inputs": "\"Copilot" } }, - "name": "[parameters('PlaybookName')]", - "type": "Microsoft.Logic/workflows", - "location": "[resourceGroup().location]", - "tags": { - "Created By": "Sean Wesonga", - "CreatorUPN": "sean Wasonga" - }, - "apiVersion": "2017-07-01", - "dependsOn": [ - "[resourceId('Microsoft.Web/connections', variables('Office365ConnectionName'))]", - "[resourceId('Microsoft.Web/connections', variables('SecuritycopilotConnectionName'))]", - "[resourceId('Microsoft.Web/connections', variables('TeamsConnectionName'))]" + "runAfter": {}, + "type": "Scope" + }, + "Send_an_email_(V2)": { + "runAfter": { + "Running_the_Copilot_for_Security_Prompts": [ + "Succeeded" ] }, - { - "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')]" - } - } - }, - { - "type": "Microsoft.Web/connections", - "apiVersion": "2016-06-01", - "name": "[variables('TeamsConnectionName')]", - "location": "[resourceGroup().location]", - "kind": "V1", - "properties": { - "displayName": "[variables('TeamsConnectionName')]", - "customParameterValues": { - }, - "api": { - "id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/Teams')]" + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['office365']['connectionId']" } - } + }, + "method": "post", + "body": { + "To": "@parameters('email')", + "Subject": "Summary of Abnormal Failed Sign ins over the last 24 hours", + "Body": "

@{outputs('Add_Copilot_Logo')}

@{body('Convert_Response_into_HTML_Format_for_Proper_Output')?['EvaluationResultContent']}

", + "Importance": "Normal" + }, + "path": "/v2/Mail" } - ] + } +}, +"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": { +"Created By": "Sean Wesonga", +"CreatorUPN": "sean Wasonga", +"hidden-SentinelTemplateName": "InvestigateFailedSignIns", +"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/InvestigateFailedSignins/investigatefailedsignins.png b/Logic Apps/InvestigateFailedSignins/investigatefailedsignins.png index cb789311..622f0d23 100644 Binary files a/Logic Apps/InvestigateFailedSignins/investigatefailedsignins.png and b/Logic Apps/InvestigateFailedSignins/investigatefailedsignins.png differ diff --git a/Logic Apps/InvestigateFailedSignins/readme.md b/Logic Apps/InvestigateFailedSignins/readme.md index 7d233560..a874d706 100644 --- a/Logic Apps/InvestigateFailedSignins/readme.md +++ b/Logic Apps/InvestigateFailedSignins/readme.md @@ -14,7 +14,7 @@ Prior to beginning the installation process, it's crucial to confirm that you ha ## Deployment - + @@ -26,5 +26,5 @@ Prior to beginning the installation process, it's crucial to confirm that you ha 3. Proceed to run the Logic app and Prepare for the results -![Investigate Failed Sign-Ins](https://raw.githubusercontent.com/KwachSean/SecurityCopilot/main/playbooks/InvestigateFailedSignins/investigatefailedsignins.png) +![Investigate Failed Sign-Ins](https://github.com/Azure/Copilot-For-Security/blob/main/Logic%20Apps/InvestigateFailedSignins/investigatefailedsignins.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 fbd5453d..c06441e7 100644 --- a/Logic Apps/Readme.md +++ b/Logic Apps/Readme.md @@ -1,14 +1,14 @@ -![Copilot for Security Overview](https://github.com/Azure/Copilot-For-Security/blob/main/Images/ic_fluent_copilot_64_64%402x.png) -# Microsoft Copilot for Security Logic Apps -Documentation : https://learn.microsoft.com/en-us/security-copilot/connector_logicapp +![Security Copilot Overview](https://github.com/Azure/Copilot-For-Security/blob/main/Images/ic_fluent_copilot_64_64%402x.png) +# Microsoft Security Copilot Logic Apps +Documentation : https://learn.microsoft.com/en-us/copilot/security/connector-logicapp -The Microsoft Copilot for Security Logic Apps connector allows you to call into Microsoft Copilot for Security from a Logic Apps workflow. This document provides an introduction to the new connector actions you can leverage as well as sample use cases you can deploy to automate investigations such as on Sentinel incidents, email phishing, and others. +The Microsoft Security Copilot Logic Apps connector allows you to call into Microsoft Security Copilot from a Logic Apps workflow. This document provides an introduction to the new connector actions you can leverage as well as sample use cases you can deploy to automate investigations such as on Sentinel incidents, email phishing, and others. The first iteration of the Logic Apps connector exposes two actions: -- Evaluate Prompt - Given a natural language prompt, this action will invoke a new evaluation within Microsoft Copilot for Security and return the output to your logic app workflow. The user can provide an optional sessionId, which will include relevant session context for the evaluation performed. If the sessionId is omitted, the action will create a new session. +- Evaluate Prompt - Given a natural language prompt, this action will invoke a new evaluation within Microsoft Security Copilot and return the output to your logic app workflow. The user can provide an optional sessionId, which will include relevant session context for the evaluation performed. If the sessionId is omitted, the action will create a new session. -- Evaluate Direct Skill - Given a natural language prompt, a skill name, and the skill required inputs, invoke a new evaluation and return its output. Use this action when you know the exact Microsoft Copilot for Security skill that is required for the task. This action also allows the option to provide a sessionId. +- Evaluate Direct Skill - Given a natural language prompt, a skill name, and the skill required inputs, invoke a new evaluation and return its output. Use this action when you know the exact Microsoft Security Copilot skill that is required for the task. This action also allows the option to provide a sessionId. Both the "Evaluate Prompt" and "Evaluate Direct Skill" actions allow the user to set an optional sessionId to execute the evaluation within the context of an existing session. If omitted, a new session will be created for the investigation. @@ -23,10 +23,10 @@ Here you will find an array of differnt playbooks aligned with CyberSec scenario - Compliance automation: Automated processes help ensure adherence to cybersecurity regulations and standards, reducing compliance risks. - Resource optimization: By automating routine tasks, cybersecurity professionals can focus on strategic initiatives and proactive threat hunting. -**Disclaimer**: Please be aware that the logic apps provided are examples intended for use with Copilot for Security. Users are encouraged to customize these samples to meet their specific requirements. These should serve as guidelines and inspiration for creating tailored logic apps for ones own use +**Disclaimer**: Please be aware that the logic apps provided are examples intended for use with Security Copilot. Users are encouraged to customize these samples to meet their specific requirements. These should serve as guidelines and inspiration for creating tailored logic apps for ones own use **Technical Disclaimer: Logic Apps, SCU Usage, and Best Practices** **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_parsing/FunctionApp.zip b/Logic Apps/SecCopilot-UserReportedPhishing-FuncApp_parsing/FunctionApp.zip new file mode 100644 index 00000000..5dd3a50c Binary files /dev/null and b/Logic Apps/SecCopilot-UserReportedPhishing-FuncApp_parsing/FunctionApp.zip differ diff --git a/Logic Apps/SecCopilot-UserReportedPhishing-FuncApp_parsing/README.md b/Logic Apps/SecCopilot-UserReportedPhishing-FuncApp_parsing/README.md new file mode 100644 index 00000000..b0b9d0f0 --- /dev/null +++ b/Logic Apps/SecCopilot-UserReportedPhishing-FuncApp_parsing/README.md @@ -0,0 +1,139 @@ +# User Submitted Phishing Analysis with Security Copilot +Author: Yaniv Shasha & Craig Freyman +This solution automates the analysis of user-submitted phishing emails using Security Copilot. It monitors a shared inbox, triggering a Logic App upon new email arrival. The Logic App processes the email using a Function App, which extracts relevant details, leverages the MDTI Security Copilot Skills for reputation insights, and sends the results to an email recipient. + +## Deploy the Solution + +### Step 1: Deploy the Function App + +Click the button below to deploy the Function App. You'll be prompted to select or create a resource group and provide a unique Function App name. Ensure the Function App is fully deployed before starting the Logic App deployment. + +[![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%2520Apps%2FSecCopilot-UserReportedPhishing-FuncApp_parsing%2Ffunctionapp_azuredeploy.json) + +If you run into issues, you can create a skelton/empty Function App and deploy via the commandline: +`az functionapp deployment source config-zip --resource-group yourresourcegroup --name youremptyfunctionapp --src .\FunctionApp.zip` <- ZIP downloaded to your local directory. + +### Step 2: Deploy the Logic App + +Click the button below to deploy the Logic App. Note your Function App name & resource group from the previous step. 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%2FCopilot-For-Security%2Fmain%2FLogic%2520Apps%2FSecCopilot-UserReportedPhishing-FuncApp_parsing%2Flogicapp_azuredeploy.json) + + +### Step 3: Initialize O365 and Security Copilot API connections + +Open your new Logic App. + +Click "API Connections" + +![alt text](image.png) + +Authorize both of the API connections. +![alt text](image-1.png) + +### Step 4: Enable the Logic App +![alt text](image-3.png) + +### Deployment Notes +If you want to change the function app code you can git clone the repo. Every time you change the FunctionApp code, be sure to repackage the FunctionApp.zip file. The zip file is what is referenced when the solution is deployed. The .python_packages folder is required in this deployment scenario. I can't figure out how to get Azure FunctionApps to install python libraries automatically. + +## Function Details +There are five functions in the functionapp: + +### 1. parse_email +This Python Azure Function App is designed to handle HTTP requests containing raw email data. Its primary capabilities include: + +**Email Parsing**: + - Extracts key metadata (e.g., sender, receiver, return path, subject, date). + - Retrieves the main body content, handling multipart emails, and cleans forwarded messages. + - Identifies DKIM, SPF, and DMARC authentication results from email headers. + - Processes attachments, computes SHA-256 hashes, and captures base64 content and MIME type. + +**URL Extraction and Processing**: + - Extracts URLs from both HTML and plain text email bodies. + - Decodes Microsoft SafeLinks and expands shortened URLs by following redirections. + - Deduplicates URLs to unique base URLs if the count exceeds 20, removing any HTML tags. + - Filters out image URLs and ensures URL validity. + +**Domain Extraction and IP Filtering**: + - Identifies domains and filters them to ensure validity. + - Distinguishes between public and non-public IPs using regex patterns and the `ipaddress` library. + - Returns extracted public IPs and URLs for security and analytics purposes. + +**Recursive Parsing**: + - Recursively parses content (including nested JSON) to extract domains, IPs, and URLs, facilitating structured email content analysis. + +**HTML and Content Cleaning**: + - Removes excessive newlines and strips HTML tags for cleaner text content in the response. + +**Error Handling**: + - Provides comprehensive error handling and logging for various failure points, including malformed data or parsing errors. + +The function returns a JSON response containing parsed email data, extracted URLs, public IPs, and cleaned domains, making it suitable for integration with security and analytics workflows. + +### 2. parse_virustotal_json +This Python Azure Function App is designed to handle HTTP requests containing a VT JSON obect for easy filtering. The VT API returns too much data for generative AI. + +**Request Parsing**: + - Accepts and parses a JSON object from the HTTP request, specifically expecting a `data` object containing `attributes` with `last_analysis_results`. + +**Malicious or Suspicious Detection**: + - Extracts analysis results from `last_analysis_results`, filtering entries where the `category` is labeled as either "malicious" or "suspicious." + - For each flagged entry, it gathers details like the `engine_name`, `method`, and `result` used by VirusTotal's analysis engines. + +**Response Generation**: + - Returns a JSON response listing all malicious or suspicious findings, including relevant detection engine details, making it useful for further threat intelligence processing or alerting. + +**Error Handling**: + - Logs errors if parsing fails or if the JSON structure is unexpected, returning a 500 error with a message. + +### 3. normalize_json +This Azure Function App is an HTTP-triggered function designed to clean and validate JSON input by removing markdown notations and handling `null` values. It's primary purpose is to accept the Security Copilot generated JSON notation in markdown and clean it up to be a JSON object. + +**Markdown Notation Removal**: + - Strips markdown annotations, including triple backticks (```json) and alternate markdown notations like `~~~json`, from the JSON input, cleaning the text for valid JSON parsing. + +**Null Value Replacement**: + - Recursively replaces all `null` values in the JSON object with the string `"None"`, allowing for cases where `null` needs to be preserved as text for output or further processing. + +**Response Generation**: + - Returns a cleaned and formatted JSON string as the response, making it easy to read and ready for further use. + +**Error Handling**: + - Catches JSON decoding errors if the input is improperly formatted and returns a 400 error with a helpful message. + - Logs any unexpected errors and returns a 500 error, ensuring robustness. + +### 4. json_to_html +This Azure Function App is an HTTP-triggered function designed to generate an HTML phishing analysis report from JSON data. + +**HTML Report Generation**: + - Uses Jinja2 templating to create a detailed, stylized HTML report from JSON input, with sections for "Final Evaluation," "Email Body Analysis," "Attachment Review," "Domain Evaluation," and "URL Evaluation." + - Highlights key findings with custom colors based on values such as `Classification`, `Confidence Level`, and `Phishing Likelihood`. + - Formats lists, tables, and indicators, creating a clear, structured layout for easy analysis. + +**JSON Parsing and Validation**: + - Parses JSON data from the HTTP request body and validates that it is a list of JSON objects, issuing a 400 error if not in the expected format. + +**Customizable Section Order**: + - Processes sections in a predefined order, ensuring consistent HTML structure, even if sections appear in a different order in the input JSON. + +**Error Handling**: + - Catches JSON parsing errors and other unexpected exceptions, returning informative HTTP responses (400 for invalid JSON format and 500 for other issues). + +### 5. parse_pdf +This Azure Function App is an HTTP-triggered function that processes a base64-encoded PDF file in the request body, extracts text, and returns it in a cleaned format. The text is then appended to the 'email_body' variable in the Logic App. + +**Base64 PDF Decoding**: + - Accepts the PDF as a base64-encoded string in the HTTP request body, either directly or within a JSON payload (`body` field). + - Supports optional data URI prefixes, stripping these before decoding. + +**PDF Text Extraction**: + - Uses `pdfminer` to extract raw text from the PDF file, handling binary data from the decoded base64 string. + +**Text Cleanup**: + - Performs post-processing on the extracted text to remove non-printable characters and reduce excessive whitespace, returning a clean and compacted text output. + +**Error Handling**: + - Logs decoding errors, JSON parsing issues, and unexpected exceptions, providing informative responses: + - 400 status for invalid base64 or JSON formatting. + - 500 status for unexpected processing errors, with detailed logging for debugging. diff --git a/Logic Apps/SecCopilot-UserReportedPhishing-FuncApp_parsing/functionapp_azuredeploy.json b/Logic Apps/SecCopilot-UserReportedPhishing-FuncApp_parsing/functionapp_azuredeploy.json new file mode 100644 index 00000000..c87208e6 --- /dev/null +++ b/Logic Apps/SecCopilot-UserReportedPhishing-FuncApp_parsing/functionapp_azuredeploy.json @@ -0,0 +1,217 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "FunctionName": { + "type": "String" + } + }, + "variables": {"FunctionName": "[concat(toLower(parameters('FunctionName')), uniqueString(resourceGroup().id))]", + "StorageAccountName":"[concat(toLower(parameters('FunctionName')),'funcapp')]"}, +"resources": [ + { + "type": "Microsoft.Insights/components", + "apiVersion": "2015-05-01", + "name": "[variables('FunctionName')]", + "location": "[resourceGroup().location]", + "kind": "web", + "properties": { + "Application_Type": "web", + "ApplicationId": "[variables('FunctionName')]" + } + }, + { + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2019-06-01", + "name": "[variables('StorageAccountName')]", + "location": "[resourceGroup().location]", + "sku": { + "name": "Standard_LRS", + "tier": "Standard" + }, + "kind": "StorageV2", + "properties": { + "networkAcls": { + "bypass": "AzureServices", + "virtualNetworkRules": [ + ], + "ipRules": [ + ], + "defaultAction": "Allow" + }, + "supportsHttpsTrafficOnly": true, + "encryption": { + "services": { + "file": { + "keyType": "Account", + "enabled": true + }, + "blob": { + "keyType": "Account", + "enabled": true + } + }, + "keySource": "Microsoft.Storage" + } + } + }, + { + "type": "Microsoft.Web/serverfarms", + "apiVersion": "2018-02-01", + "name": "[variables('FunctionName')]", + "location": "[resourceGroup().location]", + "sku": { + "name": "Y1", + "tier": "Dynamic" + }, + "kind": "functionapp,linux", + "properties": { + "name": "[variables('FunctionName')]", + "workerSize": "0", + "workerSizeId": "0", + "numberOfWorkers": "1", + "reserved": true + } + }, + { + "type": "Microsoft.Storage/storageAccounts/blobServices", + "apiVersion": "2019-06-01", + "name": "[concat(variables('StorageAccountName'), '/default')]", + "dependsOn": [ + "[resourceId('Microsoft.Storage/storageAccounts', variables('StorageAccountName'))]" + ], + "sku": { + "name": "Standard_LRS", + "tier": "Standard" + }, + "properties": { + "cors": { + "corsRules": [ + ] + }, + "deleteRetentionPolicy": { + "enabled": false + } + } + }, + { + "type": "Microsoft.Storage/storageAccounts/fileServices", + "apiVersion": "2019-06-01", + "name": "[concat(variables('StorageAccountName'), '/default')]", + "dependsOn": [ + "[resourceId('Microsoft.Storage/storageAccounts', variables('StorageAccountName'))]" + ], + "sku": { + "name": "Standard_LRS", + "tier": "Standard" + }, + "properties": { + "cors": { + "corsRules": [ + ] + } + } + }, + { + "type": "Microsoft.Web/sites", + "apiVersion": "2018-11-01", + "name": "[variables('FunctionName')]", + "location": "[resourceGroup().location]", + "dependsOn": [ + "[resourceId('Microsoft.Storage/storageAccounts', variables('StorageAccountName'))]", + "[resourceId('Microsoft.Web/serverfarms', variables('FunctionName'))]", + "[resourceId('Microsoft.Insights/components', variables('FunctionName'))]" + ], + "kind": "functionapp,linux", + "identity": { + "type": "SystemAssigned" + }, + "properties": { + "name": "[variables('FunctionName')]", + "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('FunctionName'))]", + "httpsOnly": true, + "clientAffinityEnabled": true, + "alwaysOn": true, + "reserved": true, + "siteConfig": { + "linuxFxVersion": "python|3.10" + } + }, + "resources": [ + { + "apiVersion": "2018-11-01", + "type": "config", + "name": "appsettings", + "dependsOn": [ + "[concat('Microsoft.Web/sites/', variables('FunctionName'))]" + ], + "properties": { + "FUNCTIONS_EXTENSION_VERSION": "~4", + "FUNCTIONS_WORKER_RUNTIME": "python", + "APPINSIGHTS_INSTRUMENTATIONKEY": "[reference(resourceId('Microsoft.insights/components', variables('FunctionName')), '2015-05-01').InstrumentationKey]", + "APPLICATIONINSIGHTS_CONNECTION_STRING": "[reference(resourceId('microsoft.insights/components', variables('FunctionName')), '2015-05-01').ConnectionString]", + "AzureWebJobsStorage": "[concat('DefaultEndpointsProtocol=https;AccountName=', toLower(variables('StorageAccountName')),';AccountKey=',listKeys(resourceId('Microsoft.Storage/storageAccounts', toLower(variables('StorageAccountName'))), '2019-06-01').keys[0].value, ';EndpointSuffix=core.windows.net')]", + "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING": "[concat('DefaultEndpointsProtocol=https;AccountName=', toLower(variables('StorageAccountName')),';AccountKey=', listKeys(resourceId('Microsoft.Storage/storageAccounts', toLower(variables('StorageAccountName'))), '2019-06-01').keys[0].value, ';EndpointSuffix=core.windows.net')]", + "WEBSITE_CONTENTSHARE": "[toLower(variables('FunctionName'))]", + "WEBSITE_RUN_FROM_PACKAGE": "https://raw.githubusercontent.com/Azure/Copilot-For-Security/main/Logic%20Apps/SecCopilot-UserReportedPhishing-FuncApp_parsing/FunctionApp.zip", + "SCM_DO_BUILD_DURING_DEPLOYMENT": true + + } + } + ] + }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "name": "[concat(variables('FunctionName'), '/web')]", + "location": "[resourceGroup().location]", + "dependsOn": [ + "[resourceId('Microsoft.Web/sites', variables('FunctionName'))]" + ], + "properties": { + "cors": { + "allowedOrigins": [ + "https://portal.azure.com" + ], + "supportCredentials": false + } + } + }, + { + "type": "Microsoft.Storage/storageAccounts/blobServices/containers", + "apiVersion": "2019-06-01", + "name": "[concat(variables('StorageAccountName'), '/default/azure-webjobs-hosts')]", + "dependsOn": [ + "[resourceId('Microsoft.Storage/storageAccounts/blobServices', variables('StorageAccountName'), 'default')]", + "[resourceId('Microsoft.Storage/storageAccounts', variables('StorageAccountName'))]" + ], + "properties": { + "publicAccess": "None" + } + }, + { + "type": "Microsoft.Storage/storageAccounts/blobServices/containers", + "apiVersion": "2019-06-01", + "name": "[concat(variables('StorageAccountName'), '/default/azure-webjobs-secrets')]", + "dependsOn": [ + "[resourceId('Microsoft.Storage/storageAccounts/blobServices', variables('StorageAccountName'), 'default')]", + "[resourceId('Microsoft.Storage/storageAccounts', variables('StorageAccountName'))]" + ], + "properties": { + "publicAccess": "None" + } + }, + { + "type": "Microsoft.Storage/storageAccounts/fileServices/shares", + "apiVersion": "2019-06-01", + "name": "[concat(variables('StorageAccountName'), '/default/', tolower(variables('StorageAccountName')))]", + "dependsOn": [ + "[resourceId('Microsoft.Storage/storageAccounts/fileServices', variables('StorageAccountName'), 'default')]", + "[resourceId('Microsoft.Storage/storageAccounts', variables('StorageAccountName'))]" + ], + "properties": { + "shareQuota": 5120 + } + } + ] +} \ No newline at end of file diff --git a/Logic Apps/SecCopilot-UserReportedPhishing-FuncApp_parsing/image-1.png b/Logic Apps/SecCopilot-UserReportedPhishing-FuncApp_parsing/image-1.png new file mode 100644 index 00000000..e62256f7 Binary files /dev/null and b/Logic Apps/SecCopilot-UserReportedPhishing-FuncApp_parsing/image-1.png differ diff --git a/Logic Apps/SecCopilot-UserReportedPhishing-FuncApp_parsing/image-3.png b/Logic Apps/SecCopilot-UserReportedPhishing-FuncApp_parsing/image-3.png new file mode 100644 index 00000000..c73b7242 Binary files /dev/null and b/Logic Apps/SecCopilot-UserReportedPhishing-FuncApp_parsing/image-3.png differ diff --git a/Logic Apps/SecCopilot-UserReportedPhishing-FuncApp_parsing/image.png b/Logic Apps/SecCopilot-UserReportedPhishing-FuncApp_parsing/image.png new file mode 100644 index 00000000..400034c3 Binary files /dev/null and b/Logic Apps/SecCopilot-UserReportedPhishing-FuncApp_parsing/image.png differ diff --git a/Logic Apps/SecCopilot-UserReportedPhishing-FuncApp_parsing/logicapp_azuredeploy.json b/Logic Apps/SecCopilot-UserReportedPhishing-FuncApp_parsing/logicapp_azuredeploy.json new file mode 100644 index 00000000..2141416c --- /dev/null +++ b/Logic Apps/SecCopilot-UserReportedPhishing-FuncApp_parsing/logicapp_azuredeploy.json @@ -0,0 +1,1058 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "LogicAppName": { + "type": "string" + }, + "FunctionAppRG": { + "type": "string" + }, + "FunctionAppName": { + "type": "string" + }, + "PhishingReportEmailAddress": { + "type": "string" + } + }, + + "variables": { + "Office365-1ConnectionName": "[concat('Office365-1-', parameters('LogicAppName'))]", + "Securitycopilot-1ConnectionName": "[concat('Securitycopilot-1-', parameters('LogicAppName'))]", + "FunctionName01": "parse_email", + "FunctionName02": "parse_pdf", + "FunctionName03": "normalize_json", + "FunctionName04": "json_to_html" + }, + "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" + } + }, + "triggers": { + "When_a_new_email_arrives_(V3)": { + "splitOn": "@triggerBody()?['value']", + "type": "ApiConnectionNotification", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['office365-1']['connectionId']" + } + }, + "fetch": { + "pathTemplate": { + "template": "/v3/Mail/OnNewEmail" + }, + "method": "get", + "queries": { + "importance": "Any", + "fetchOnlyWithAttachment": false, + "includeAttachments": true, + "folderPath": "Inbox" + } + }, + "subscribe": { + "body": { + "NotificationUrl": "@listCallbackUrl()" + }, + "pathTemplate": { + "template": "/GraphMailSubscriptionPoke/$subscriptions" + }, + "method": "post", + "queries": { + "importance": "Any", + "fetchOnlyWithAttachment": false, + "folderPath": "Inbox" + } + } + } + } + }, + "actions": { + "Check_attachments_on_MDTI": { + "actions": { + "Copilot_check_file_hashes": { + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['securitycopilot-1']['connectionId']" + } + }, + "method": "post", + "body": { + "PromptContent": "hash:@{body('Process_JSON')?['email_content']?['attachments'][0]?['attachment_sha256']}", + "SessionId": "@body('Summarize_Email_Intent')?['sessionId']", + "SkillName": "GetFileAnalysis" + }, + "path": "/process-prompt", + "retryPolicy": { + "type": "fixed", + "count": 12, + "interval": "PT5M" + } + } + } + }, + "runAfter": { + "Compose": [ + "Succeeded" + ] + }, + "else": { + "actions": {} + }, + "expression": { + "and": [ + { + "greater": [ + "@length(variables('attachments'))", + 0 + ] + } + ] + }, + "type": "If" + }, + "Check_if_domains_exist": { + "actions": { + "Copilot_check_MDTI_for_domain_reputation": { + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['securitycopilot-1']['connectionId']" + } + }, + "method": "post", + "body": { + "PromptContent": "Check the reputation for these domains @{body('Process_JSON')?['domains']}", + "SessionId": "@body('Summarize_Email_Intent')?['sessionId']", + "SkillName": "GetReputationsForIndicators" + }, + "path": "/process-prompt", + "retryPolicy": { + "type": "fixed", + "count": 12, + "interval": "PT5M" + } + } + }, + "Copilot_check_MDTI_for_url_reputation": { + "runAfter": { + "Copilot_check_MDTI_for_domain_reputation": [ + "Succeeded" + ] + }, + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['securitycopilot-1']['connectionId']" + } + }, + "method": "post", + "body": { + "PromptContent": "Check the reputation for these urls @{string(variables('urls'))}", + "SessionId": "@body('Summarize_Email_Intent')?['sessionId']", + "SkillName": "GetReputationsForIndicators" + }, + "path": "/process-prompt", + "retryPolicy": { + "type": "fixed", + "count": 12, + "interval": "PT5M" + } + } + } + }, + "runAfter": { + "Initialize_variable_domains": [ + "Succeeded" + ] + }, + "else": { + "actions": {} + }, + "expression": { + "and": [ + { + "greater": [ + "@length(body('Process_JSON')?['domains'])", + 0 + ] + } + ] + }, + "type": "If" + }, + "Export_email_(V2)": { + "runAfter": { + "Initialize_variable_message_id": [ + "Succeeded" + ] + }, + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['office365-1']['connectionId']" + } + }, + "method": "get", + "path": "/codeless/beta/me/messages/@{encodeURIComponent(triggerBody()?['id'])}/$value" + } + }, + "Analyze_email_body_and_attachment_data": { + "runAfter": { + "Initialize_variable": [ + "Succeeded" + ] + }, + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['securitycopilot-1']['connectionId']" + } + }, + "method": "post", + "body": { + "PromptContent": "/AskGpt\n### Task: Analyze the email for potential phishing indicators, focusing on the attachment's role and any actions it encourages. Output in JSON format.\n\n#### Pertinent Analysis Output from Previous Prompts:\n\"\"\"\nEmail intent: \n\n@{body('Summarize_Email_Intent')?['EvaluationResultContent']}\n\nAttachment data:\n\n@{body('Process_JSON')?['email_content']?['attachments']?[0]?['attachment_name']}\n@{body('Copilot_check_file_hashes')?['EvaluationResultContent']}\n\n\"\"\"\n\n### Task: Conduct a detailed analysis of the email attachment and its relationship to previously detected phishing indicators in the email body. Focus on the attachment’s alignment with the email's purpose and intent, especially looking for unusual characteristics that could indicate malicious intent.\n\nEmail Body Analysis Summary (from previous analysis):\n- Summary: The email body contains specific indicators of phishing intent, such as psychological manipulation, suspicious requests, or tactics encouraging action. Positive or neutral traits should not be included in the phishing indicators list.\n\n- Detected Indicators:\n - **Phishing Indicators**: Only include clear indicators of deception, focusing on psychological manipulation, suspicious requests, domain mismatches, urgency tactics, or unusual links/attachments.\n - **Positive Indicators (Optional)**: List any indicators of legitimacy if relevant, such as verified sender domains or expected file formats, but do not include these in phishing indicators.\n\n### Analysis Steps:\n\n1. **Email Body Context Recap**: Briefly summarize the key phishing indicators found in the email body to maintain context, emphasizing any manipulative tactics or suspicious requests encouraging user action.\n\n2. **Attachment Review**:\n - **Attachment Summary**: Describe the attachment’s content or file type (e.g., PDF invoice, Word document).\n - **Legitimacy and Intent Check**: Evaluate if the attachment logically supports the email’s purpose and intent. Consider if the attachment type, format, and file name are expected for legitimate communications from the sender.\n - **Potential Malicious Indicators**: Identify suspicious characteristics (e.g., executable file types, unexpected encryption, or file sizes) that suggest possible malicious intent.\n\n3. **Overall Phishing Likelihood Assessment**: Based on phishing indicators in the email body and attachment analysis, rate the phishing likelihood as 'High,' 'Medium,' 'Low,' or 'Inconclusive' if there’s insufficient information.\n\n### Output Format:\nProvide the output in JSON format as follows:\n\n{\n \"AttachmentReview\": {\n \"Findings\": \"Brief summary of attachment analysis or null if no findings.\",\n \"LegitimacyCheck\": \"true if the attachment logically aligns with the email’s intent, false if it does not, or null if no attachment.\"\n },\n \"EmailBodyAnalysis\": {\n \"EmailPurposeSummary\": \"One-sentence summary of the email's stated purpose.\",\n \"IntentSummary\": \"One-sentence summary of the email’s main intent, especially if it seeks user action.\",\n \"PhishingIndicators\": [\n \"List specific phishing indicators, focusing on manipulative tactics, suspicious requests, links, or attachment-related risks.\"\n ],\n \"PositiveIndicators\": [\n \"Optional: List any indicators of legitimacy here if relevant, such as verified sender domains or legitimate urgency.\"\n ],\n \"OverallPhishingLikelihood\": \"High, Medium, Low, or Inconclusive.\"\n }\n}\n\n### Example JSON Output:\n{\n \"AttachmentReview\": {\n \"Findings\": \"Attachment is labeled as an invoice but has a .exe file format, which is unusual.\",\n \"LegitimacyCheck\": false\n },\n \"EmailBodyAnalysis\": {\n \"EmailPurposeSummary\": \"Invoice notification with a call to action to view payment details.\",\n \"IntentSummary\": \"Encourages recipient to open a suspicious attachment and view payment instructions.\",\n \"PhishingIndicators\": [\n \"Urgency with payment warning.\",\n \"Suspicious file type in attachment.\",\n \"Request for sensitive payment details.\"\n ],\n \"PositiveIndicators\": [\n \"Recognized sender domain.\",\n \"Consistent company branding.\"\n ],\n \"OverallPhishingLikelihood\": \"High\"\n }\n}\n\n### Notes:\n- **Phishing Indicators** should reflect only suspicious elements directly linked to potential phishing.\n- **Positive Indicators** can be used to list legitimate traits if relevant.\n- The **Overall Phishing Likelihood** should primarily reflect the presence and severity of phishing indicators, with positive indicators reducing the risk assessment.", + "SessionId": "@body('Summarize_Email_Intent')?['sessionId']" + }, + "path": "/process-prompt", + "retryPolicy": { + "type": "fixed", + "count": 12, + "interval": "PT5M" + } + } + }, + "For_each_attachment": { + "foreach": "@triggerBody()?['attachments']", + "actions": { + "Check_if_binary_msg_eml_attachment": { + "actions": { + "Decode_Base64_ContentBytes": { + "type": "Compose", + "inputs": "@base64ToBinary(item()?['contentBytes'])" + }, + "Set_variable_raw_email_msg_binary_decoded": { + "runAfter": { + "Show_bytes": [ + "Succeeded" + ] + }, + "type": "SetVariable", + "inputs": { + "name": "raw_email", + "value": "@string(outputs('Show_bytes'))" + } + }, + "Show_bytes": { + "runAfter": { + "Decode_Base64_ContentBytes": [ + "Succeeded" + ] + }, + "type": "Compose", + "inputs": "@outputs('Decode_Base64_ContentBytes')" + } + }, + "runAfter": { + "Show_attachment": [ + "Succeeded" + ] + }, + "else": { + "actions": {} + }, + "expression": { + "or": [ + { + "equals": [ + "@outputs('Show_attachment')?['contentType']", + "message/rfc822" + ] + }, + { + "equals": [ + "@outputs('Show_attachment')?['contentType']", + "application/octet-stream" + ] + }, + { + "equals": [ + "@outputs('Show_attachment')?['contentType']", + "application/vnd.ms-outlook" + ] + } + ] + }, + "type": "If" + }, + "Show_attachment": { + "type": "Compose", + "inputs": "@item()" + } + }, + "runAfter": { + "Set_variable_raw_email": [ + "Succeeded" + ] + }, + "type": "Foreach" + }, + "Initialize_variable_domains": { + "runAfter": { + "Check_attachments_on_MDTI": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "domains", + "type": "array", + "value": [ + "@body('Process_JSON')?['domains']" + ] + } + ] + } + }, + "Initialize_variable_email_body": { + "runAfter": { + "Show_email_body_and_attachment_extracted_text_here": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "email_body", + "type": "string", + "value": "@outputs('Show_email_body_and_attachment_extracted_text_here')" + } + ] + } + }, + "Process_JSON": { + "runAfter": { + "Parse_Email": [ + "Succeeded" + ] + }, + "type": "ParseJson", + "inputs": { + "content": "@body('Parse_Email')", + "schema": { + "properties": { + "domains": { + "items": { + "type": "string" + }, + "type": "array" + }, + "email_content": { + "properties": { + "attachments": { + "type": "array" + }, + "body": { + "type": "string" + }, + "date": { + "type": "string" + }, + "dkim_result": { + "type": "string" + }, + "dmarc_result": { + "type": "string" + }, + "receiver": { + "type": "string" + }, + "reply_to": { + "type": "string" + }, + "return_path": { + "type": "string" + }, + "sender": { + "type": "string" + }, + "smtp": { + "properties": { + "delivered_to": { + "type": "string" + }, + "received": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "spf_result": { + "type": "string" + }, + "subject": { + "type": "string" + } + }, + "type": "object" + }, + "ip_addresses": { + "items": { + "type": "string" + }, + "type": "array" + }, + "urls": { + "type": "array" + } + }, + "type": "object" + } + } + }, + "Set_variable_raw_email": { + "runAfter": { + "Export_email_(V2)": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "raw_email", + "type": "string", + "value": "@{body('Export_email_(V2)')}" + } + ] + } + }, + "Initialize_variable_raw_urls": { + "runAfter": { + "If_pdf_attachment_the_extract_text": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "raw_urls", + "type": "array", + "value": "@body('Process_JSON')?['urls']" + } + ] + } + }, + "Initialize_variable_urls": { + "runAfter": { + "Initialize_variable_raw_urls": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "urls", + "type": "array", + "value": [] + } + ] + } + }, + "For_each_raw_url_truncate_if_too_long": { + "foreach": "@variables('raw_urls')", + "actions": { + "If_URL_length_greater_than_100": { + "actions": { + "Append_to_array_variable": { + "type": "AppendToArrayVariable", + "inputs": { + "name": "urls", + "value": "@concat(substring(string(item()), 0, 100), '...')" + } + } + }, + "else": { + "actions": { + "Append_to_array_variable_1": { + "type": "AppendToArrayVariable", + "inputs": { + "name": "urls", + "value": "@string(item())" + } + } + } + }, + "expression": { + "and": [ + { + "greater": [ + "@length(item())", + 100 + ] + } + ] + }, + "type": "If" + } + }, + "runAfter": { + "Initialize_variable_urls": [ + "Succeeded" + ] + }, + "type": "Foreach" + }, + "Initialize_variable_message_id": { + "runAfter": {}, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "message_id", + "type": "string", + "value": "@triggerBody()?['internetMessageId']" + } + ] + } + }, + "Parse_Email": { + "runAfter": { + "For_each_attachment": [ + "Succeeded" + ] + }, + "type": "Function", + "inputs": { + "body": "@variables('raw_email')", + "function": { + "id": "[concat('/subscriptions/', subscription().subscriptionId, '/resourceGroups/', parameters('FunctionAppRG'), '/providers/Microsoft.Web/sites/', parameters('FunctionAppName'), '/functions/', variables('FunctionName01'))]" + } + } + }, + "If_pdf_attachment_the_extract_text": { + "actions": { + "For_each_pdf_attachment": { + "foreach": "@variables('attachments')", + "actions": { + "Show_attachment_here": { + "type": "Compose", + "inputs": "@item()" + }, + "Check_if_valid_PDF": { + "runAfter": { + "Show_attachment_here": [ + "Succeeded" + ] + }, + "type": "Compose", + "inputs": "@if(or(equals(toLower(item()?['content_type']), 'application/pdf'),equals(toLower(item()?['content_type']), 'application/x-pdf'),equals(toLower(item()?['content_type']), 'application/octet-stream')),'true','false')" + }, + "Condition": { + "actions": { + "Process_Attachment_JSON": { + "type": "ParseJson", + "inputs": { + "content": "@outputs('Show_attachment_here')", + "schema": { + "type": "object", + "properties": { + "attachment_name": { + "type": "string" + }, + "attachment_sha256": { + "type": "string" + }, + "attachment_base64": { + "type": "string" + }, + "content_type": { + "type": "string" + } + } + } + } + }, + "Parse_PDF": { + "runAfter": { + "Process_Attachment_JSON": [ + "Succeeded" + ] + }, + "type": "Function", + "inputs": { + "body": "@body('Process_Attachment_JSON')?['attachment_base64']", + "function": { + "id": "[concat('/subscriptions/', subscription().subscriptionId, '/resourceGroups/', parameters('FunctionAppRG'), '/providers/Microsoft.Web/sites/', parameters('FunctionAppName'), '/functions/', variables('FunctionName02'))]" + } + } + }, + "Append_PDF_extracted_text_to_attachment_extracted_text_variable": { + "runAfter": { + "Parse_PDF": [ + "Succeeded" + ] + }, + "type": "AppendToStringVariable", + "inputs": { + "name": "attachment_extracted_text", + "value": "@body('Parse_PDF')" + } + } + }, + "runAfter": { + "Check_if_valid_PDF": [ + "Succeeded" + ] + }, + "else": { + "actions": {} + }, + "expression": { + "and": [ + { + "equals": [ + "@outputs('Check_if_valid_PDF')", + "true" + ] + } + ] + }, + "type": "If" + } + }, + "type": "Foreach" + } + }, + "runAfter": { + "Initialize_variable_attachments": [ + "Succeeded" + ] + }, + "else": { + "actions": {} + }, + "expression": { + "and": [ + { + "greater": [ + "@length(variables('attachments'))", + 0 + ] + }, + { + "or": [ + { + "equals": [ + "@toLower(variables('attachments')[0]?['content_type'])", + "application/pdf" + ] + }, + { + "equals": [ + "@toLower(variables('attachments')[0]?['content_type'])", + "application/x-pdf" + ] + }, + { + "equals": [ + "@toLower(variables('attachments')[0]?['content_type'])", + "application/octet-stream" + ] + } + ] + } + ] + }, + "type": "If" + }, + "Initialize_variable_attachment_extracted_text": { + "runAfter": { + "Process_JSON": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "attachment_extracted_text", + "type": "string", + "value": "\"\"" + } + ] + } + }, + "Show_email_body_and_attachment_extracted_text_here": { + "runAfter": { + "Show_URL_Array_here": [ + "Succeeded" + ] + }, + "type": "Compose", + "inputs": "@{concat(body('Process_JSON')?['email_content']?['body'], '\\n', variables('attachment_extracted_text'))}\"" + }, + "Analyze_domains_and_URLs": { + "runAfter": { + "Append_response1_to_array": [ + "Succeeded" + ] + }, + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['securitycopilot-1']['connectionId']" + } + }, + "method": "post", + "body": { + "PromptContent": "/AskGpt\n\n### Task: Conduct a thorough evaluation of domains and URLs in the email to assess for potential phishing intent. Break down your analysis step-by-step, focusing on alignment with email content and intent, and provide the output in JSON format.\n\n#### Pertinent Analysis Output from Previous Prompts:\n\"\"\"\n- **Email Intent Assessment**: @{body('Summarize_Email_Intent')?['EvaluationResultContent']}\n- **Email Subject**: @{body('Process_JSON')?['email_content']?['subject']}\n- **Domain Assessment**: @{body('Copilot_check_MDTI_for_domain_reputation')?['EvaluationResultContent']}\n- **URL Assessment**: @{body('Copilot_check_MDTI_for_url_reputation')?['EvaluationResultContent']}\n\"\"\"\n\n### Evaluation Steps:\n1. **Heuristic Analysis**: \n - Compare each domain to the brand or company referenced in the email. Flag any domain with significant deviations (e.g., unusual keywords, numbers, or unexpected TLDs) as suspicious.\n\n2. **Textual Consistency**:\n - Determine if the domain matches the email content. For well-known brands, the domain should include recognizable variations, if any (e.g., `fedex-secure.com` may be acceptable, but `fedex-delivery123.com` is suspicious).\n - Use Yes/No for `AlignedWithEmailIntent`, depending on the match between the domain and email subject.\n\n3. **URL Complexity**:\n - Examine the structure of each URL for legitimacy (e.g., `www.company.com` is straightforward and less suspicious).\n - Flag URLs with unusual elements like IP addresses, excessive subdomains, or complex query strings as suspicious.\n\n4. **Psychological and Social Engineering Cues**:\n - Identify emotional manipulation tactics, such as urgency or threats, which can indicate phishing intent.\n - Flag such domains as **not aligned** if they don’t match the brand yet include manipulative tactics (e.g., \"urgent action required\").\n\n### Expected JSON Output Format:\n\n{\n \"DomainEvaluation\": {\n \"DomainsFound\": [\n {\n \"Domain\": \"questionprov623407561664643060.com\",\n \"Reputation\": \"Suspicious\",\n \"AlignedWithEmailIntent\": \"No\"\n },\n {\n \"Domain\": \"exampledomain.com\",\n \"Reputation\": \"Safe\",\n \"AlignedWithEmailIntent\": \"Yes\"\n }\n ],\n \"OverallDomainAssessment\": \"A summary insight here based on session data, such as 'High Phishing Concern,' 'Moderate Risk,' or 'Benign. Provide evidence for your reasoning.'\"\n },\n \"URLEvaluation\": {\n \"URLsFound\": [\n {\n \"URL\": \"defanged URL\",\n \"Reputation\": \"Safe, Suspicious\",\n \"AlignedWithEmailIntent\": \"Yes\",\n \"Redirections\": \"None or final destination with number of hops\"\n }\n ],\n \"OverallUrlAssessment\": \"Summary insight based on the session data, like 'High Phishing Concern,' 'Moderate Risk,' or 'Benign. Provide evidence for your reasoning.'\"\n }\n}\n\n### Example for Alignment Evaluation:\n- **Email Intent**: \"The email claims to be from a shipping company about a pending delivery.\"\n - **Domain**: \"fedex.com\"\n - **AlignedWithEmailIntent**: `Yes`\n - **Domain**: \"questionprov623407561664643060.com\"\n - **AlignedWithEmailIntent**: `No`\n\n### Note for GPT Model:\n- If prior evaluations of domains and URLs revealed suspicious or malicious indicators, classify the `OverallDomainAssessment` and `OverallUrlAssessment` accordingly (e.g., \"High Phishing Concern\" if indicators were consistent with phishing).\n- If findings are ambiguous or non-threatening, choose a more moderate label like \"Moderate Risk\" or \"Benign.\"\n- Adhere strictly to the JSON format and provide concise Yes/No responses for `AlignedWithEmailIntent` based on the detailed analysis.\n", + "SessionId": "@body('Summarize_Email_Intent')?['sessionId']" + }, + "path": "/process-prompt", + "retryPolicy": { + "type": "fixed", + "count": 12, + "interval": "PT5M" + } + } + }, + "Final_Summary": { + "runAfter": { + "Append_response2_to_array": [ + "Succeeded" + ] + }, + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['securitycopilot-1']['connectionId']" + } + }, + "method": "post", + "body": { + "PromptContent": "/AskGpt\n\n### Task: Provide a final assessment of the email based on prior analyses of domains, URLs, and email content. Summarize key findings to determine if the email is benign, suspicious, or phishing, and output in JSON format.\n\n### Final Classification Criteria:\n- **Classification**: Assign one of **PHISHING**, **SUSPICIOUS**, or **BENIGN** based on all prior findings.\n- **Confidence Level**: Rate as \"High,\" \"Medium,\" or \"Low\" according to the strength of evidence gathered from previous analyses.\n- **Overall Assessment Summary**: Provide a concise rationale, highlighting significant indicators such as psychological manipulation, domain inconsistencies, URL irregularities, and any other relevant findings.\n\n### Expected JSON Output Format:\n\n{\n \"FinalEvaluation\": {\n \"Classification\": \"PHISHING\", \"SUSPICIOUS\", or \"BENIGN\",\n \"ConfidenceLevel\": \"High\", \"Medium\", or \"Low\",\n \"OverallAssessmentSummary\": \"Concise reasoning behind the classification, referencing key indicators from prior findings.\"\n }\n}\n\n### Example JSON Output:\n{\n \"FinalEvaluation\": {\n \"Classification\": \"PHISHING\",\n \"ConfidenceLevel\": \"High\",\n \"OverallAssessmentSummary\": \"Indicators include psychological manipulation, domain inconsistencies, and unusual URL structures. Collectively, these suggest phishing intent.\"\n }\n}\n", + "SessionId": "@body('Summarize_Email_Intent')?['sessionId']" + }, + "path": "/process-prompt", + "retryPolicy": { + "type": "fixed", + "count": 12, + "interval": "PT5M" + } + } + }, + "Initialize_variable_attachments": { + "runAfter": { + "Initialize_variable_attachment_extracted_text": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "attachments", + "type": "array", + "value": "@body('Process_JSON')?['email_content']?['attachments']" + } + ] + } + }, + "Initialize_variable": { + "runAfter": { + "Check_if_domains_exist": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "response_array", + "type": "array", + "value": [] + } + ] + } + }, + "Append_response1_to_array": { + "runAfter": { + "Analyze_email_body_and_attachment_data": [ + "Succeeded" + ] + }, + "type": "AppendToArrayVariable", + "inputs": { + "name": "response_array", + "value": "@body('Analyze_email_body_and_attachment_data')?['EvaluationResultContent']" + } + }, + "Append_response2_to_array": { + "runAfter": { + "Analyze_domains_and_URLs": [ + "Succeeded" + ] + }, + "type": "AppendToArrayVariable", + "inputs": { + "name": "response_array", + "value": "@body('Analyze_domains_and_URLs')?['EvaluationResultContent']" + } + }, + "Append_response3_to_array": { + "runAfter": { + "Final_Summary": [ + "Succeeded" + ] + }, + "type": "AppendToArrayVariable", + "inputs": { + "name": "response_array", + "value": "@body('Final_Summary')?['EvaluationResultContent']" + } + }, + "For_each": { + "foreach": "@variables('response_array')", + "actions": { + "Normalize_JSON": { + "type": "Function", + "inputs": { + "body": "@item()", + "function": { + "id": "[concat('/subscriptions/', subscription().subscriptionId, '/resourceGroups/', parameters('FunctionAppRG'), '/providers/Microsoft.Web/sites/', parameters('FunctionAppName'), '/functions/', variables('FunctionName03'))]" + } + } + }, + "Append_to_final_response_array_variable": { + "runAfter": { + "Normalize_JSON": [ + "Succeeded" + ] + }, + "type": "AppendToArrayVariable", + "inputs": { + "name": "final_response_array", + "value": "@body('Normalize_JSON')" + } + } + }, + "runAfter": { + "Initialize_final_json_array": [ + "Succeeded" + ] + }, + "type": "Foreach", + "runtimeConfiguration": { + "concurrency": { + "repetitions": 1 + } + } + }, + "Initialize_final_json_array": { + "runAfter": { + "Append_response3_to_array": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "final_response_array", + "type": "array", + "value": [] + } + ] + } + }, + "Show_final_array_here": { + "runAfter": { + "For_each": [ + "Succeeded" + ] + }, + "type": "Compose", + "inputs": "@variables('final_response_array')" + }, + "JSON_to_HTML_Report": { + "runAfter": { + "Show_final_array_here": [ + "Succeeded" + ] + }, + "type": "Function", + "inputs": { + "body": "@outputs('Show_final_array_here')", + "function": { + "id": "[concat('/subscriptions/', subscription().subscriptionId, '/resourceGroups/', parameters('FunctionAppRG'), '/providers/Microsoft.Web/sites/', parameters('FunctionAppName'), '/functions/', variables('FunctionName04'))]" + } + } + }, + "Show_HTML_report_here": { + "runAfter": { + "JSON_to_HTML_Report": [ + "Succeeded" + ] + }, + "type": "Compose", + "inputs": "@body('JSON_to_HTML_Report')" + }, + "Show_URL_Array_here": { + "runAfter": { + "For_each_raw_url_truncate_if_too_long": [ + "Succeeded" + ] + }, + "type": "Compose", + "inputs": "@variables('urls')" + }, + "Send_an_email_(V2)": { + "runAfter": { + "Show_HTML_report_here": [ + "Succeeded" + ] + }, + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['office365-1']['connectionId']" + } + }, + "method": "post", + "body": { + "To": "[parameters('PhishingReportEmailAddress')]", + "Subject": "Phishing Email Analysis", + "Body": "

@{outputs('Show_HTML_report_here')}


", + "Attachments": [ + { + "Name": "@concat(body('Process_JSON')?['email_content']?['subject'], '.eml')", + "ContentBytes": "@base64(variables('raw_email'))" + } + ], + "Importance": "Normal" + }, + "path": "/v2/Mail" + } + }, + "Summarize_Email_Intent": { + "runAfter": { + "Initialize_variable_email_body": [ + "Succeeded" + ] + }, + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['securitycopilot-1']['connectionId']" + } + }, + "method": "post", + "body": { + "PromptContent": "/AskGpt\n\n### Task: Conduct a detailed analysis of the email body to assess for phishing intent, focusing on the email's intent and any actions it encourages. Break down your analysis step-by-step, emphasizing any suspicious requests or manipulative tactics that could suggest phishing. Be mindful of legitimate transactional emails from known companies, especially where legitimate and suspicious elements may overlap.\n\nEmail Body:\n\"\"\"\n@{variables('email_body')}\n\"\"\"\nSender and Subject Information:\n\"\"\"\nSender: @{body('Process_JSON')?['email_content']?['sender']}\nReply-To: @{body('Process_JSON')?['email_content']?['reply_to']}\nSubject: @{body('Process_JSON')?['email_content']?['subject']}\n\"\"\"\n\n### Analysis Steps:\n\n1. **Intent and Action Encouraged**: Determine the email's primary intent. Is it prompting the recipient to take an action (e.g., click a link, download an attachment, or provide information)? If an action is requested, assess whether it’s consistent with expected interactions from a legitimate sender.\n\n2. **Sender Analysis**: Review the sender’s email address and domain for authenticity. Confirm if the domain matches the expected sender and evaluate whether it resembles known and trusted contacts or companies. Consider common tactics like domain impersonation or subtle misspellings.\n\n3. **Psychological Tactics**:\n - Identify if the email uses urgency, fear, or other manipulative tactics to encourage action. For well-known brands, recognize that some urgency (e.g., shipping notifications) may be legitimate.\n - Differentiate legitimate service-related urgency from manipulative tactics by assessing if the urgency aligns with the company’s usual communication style.\n\n4. **Requests for Sensitive Information**: Examine the content for direct or indirect requests for sensitive data, such as credentials, financial information, or personal identifiers. Legitimate transactional emails generally avoid unnecessary sensitive requests.\n\n5. **Link and Attachment Inspection**:\n - Carefully evaluate URLs, domains, and attachments in the email. For known brands, identify if tracking links (e.g., `click.e.usa.experian.com`) or subdomains align with the sender’s typical patterns.\n - Ensure that links and attachments match the sender’s domain or established tracking subdomains, and avoid flagging tracking links that adhere to known known patterns.\n\n6. **Consistency with Legitimate Brand Communication**: Assess if the email’s language, branding, and structure are consistent with the expected brand identity. While professional emails should be well-structured, legitimate transactional emails from known companies may vary in formatting based on system-generated content.\n\n### Additional Guidance for Known Brands:\n- **Tracking Links**: For recognized brands, identify if URLs are structured as common tracking links. Treat these as neutral if they follow expected patterns.\n- **Routine Service Notifications**: If the email's urgency or request aligns with typical service notifications (e.g., order shipment confirmations), do not classify it as suspicious based solely on urgency.\n\n### Output Format:\n- **Summary**: Provide a concise overview of the email's intent, based on the actions it encourages and other indicators.\n- **Detected Indicators**:\n - **PhishingIndicators**: Only list indicators of phishing here, focusing on suspicious requests, inconsistent branding, manipulative tactics, or unusual links/attachments.\n - **PositiveIndicators**: (Optional) List positive or neutral traits here, such as verified sender address, tracking links matching known patterns, or routine service notifications.\n- **Assessment**: Conclude with one of the following: 'Benign,' 'Suspicious,' or 'Phishing.'\n\n### Example:\nDetected Indicators:\n1. **PhishingIndicators**: None detected.\n2. **PositiveIndicators**: \n - Verified sender domain.\n - Routine service-related urgency.\n - Legitimate tracking link structure.\n\nAssessment: Benign\n" + }, + "path": "/process-prompt", + "retryPolicy": { + "type": "fixed", + "count": 12, + "interval": "PT5M" + } + } + }, + "Compose": { + "runAfter": { + "Summarize_Email_Intent": [ + "Succeeded" + ] + }, + "type": "Compose", + "inputs": "@actions('Summarize_Email_Intent').outputs.statusCode\n" + } + }, + "outputs": {} + }, + "parameters": { + "$connections": { + "value": { + "office365-1": { + "connectionId": "[resourceId('Microsoft.Web/connections', variables('Office365-1ConnectionName'))]", + "connectionName": "[variables('Office365-1ConnectionName')]", + "id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/Office365')]" + }, + "securitycopilot-1": { + "connectionId": "[resourceId('Microsoft.Web/connections', variables('Securitycopilot-1ConnectionName'))]", + "connectionName": "[variables('Securitycopilot-1ConnectionName')]", + "id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/Securitycopilot')]" + } + } + } + } + }, + "name": "[parameters('LogicAppName')]", + "type": "Microsoft.Logic/workflows", + "location": "[resourceGroup().location]", + "tags": {}, + "apiVersion": "2017-07-01", + "dependsOn": [ + "[resourceId('Microsoft.Web/connections', variables('Office365-1ConnectionName'))]", + "[resourceId('Microsoft.Web/connections', variables('Securitycopilot-1ConnectionName'))]" + ] + }, + { + "type": "Microsoft.Web/connections", + "apiVersion": "2016-06-01", + "name": "[variables('Office365-1ConnectionName')]", + "location": "[resourceGroup().location]", + "kind": "V1", + "properties": { + "displayName": "[variables('Office365-1ConnectionName')]", + "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('Securitycopilot-1ConnectionName')]", + "location": "[resourceGroup().location]", + "kind": "V1", + "properties": { + "displayName": "[variables('Securitycopilot-1ConnectionName')]", + "customParameterValues": {}, + "api": { + "id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/Securitycopilot')]" + } + } + } + ] +} 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 new file mode 100644 index 00000000..d4d5ec68 --- /dev/null +++ b/Logic Apps/SecCopilot-UserReportedPhishing/DefenderKqlPlugins_automation.yaml @@ -0,0 +1,41 @@ +Descriptor: + Name: DefenderKqlPlugins_automation + DisplayName: Defender Email KQL Automation + Description: Skills to query email logs and url clicks in M365 Advanced Hunting +SkillGroups: + - Format: KQL + Skills: + - Name: GetEmailsByEmailSubject + DisplayName: Get Emails by Subject + Description: Fetches the emails with the specified subject name + Inputs: + - Name: subject + Description: The subject of the email + Required: true + Settings: + Target: Defender + Template: |- + EmailEvents + | where Subject == '{{subject}}' + - Name: GetUrlClicksBySubjectAndSenderEmail + DisplayName: Get Url Clicks by Subject and Sender Email + Description: Fetches the URLs clicked in emails with the specified subject and sender email + Inputs: + - Name: subject + Description: The subject of the email + Required: true + - Name: senderEmail + Description: The sender of the email + Required: true + Settings: + Target: Defender + Template: |- + let email = EmailEvents + | where Subject == '{{subject}}' and SenderFromAddress == '{{senderEmail}}' + | project NetworkMessageId; + let urls = EmailUrlInfo + | join email on NetworkMessageId + | project Url; + UrlClickEvents + | join urls on Url + | summarize UsersWhoClicked = dcount(AccountUpn) \ No newline at end of file diff --git a/Logic Apps/SecCopilot-UserReportedPhishing/azuredeploy.json b/Logic Apps/SecCopilot-UserReportedPhishing/azuredeploy.json index d352a358..ddf4d2ca 100644 --- a/Logic Apps/SecCopilot-UserReportedPhishing/azuredeploy.json +++ b/Logic Apps/SecCopilot-UserReportedPhishing/azuredeploy.json @@ -3,16 +3,12 @@ "contentVersion": "1.0.0.0", "parameters": { "PlaybookName": { - "defaultValue": "Sec-Copilot-UserReportedPhishing", + "defaultValue": "Sec-Copilot-UserReportedPhishing-optimize", "type": "string" - }, - "EmailTo": { - "defaultValue": "", - "type": "String" } }, "variables": { - "MicrosoftSentinelConnectionName": "[concat('MicrosoftSentinel-', parameters('PlaybookName'))]", + "ConversionserviceConnectionName": "[concat('Conversionservice-', parameters('PlaybookName'))]", "Office365ConnectionName": "[concat('Office365-', parameters('PlaybookName'))]", "SecuritycopilotConnectionName": "[concat('Securitycopilot-', parameters('PlaybookName'))]" }, @@ -71,200 +67,106 @@ } }, "actions": { - "Condition": { - "actions": { - "Create_incident": { - "runAfter": { - "Send_an_email_(V2)_2": [ - "Succeeded" - ] - }, - "type": "ApiConnection", - "inputs": { - "body": { - "description": "@body('Create_HTML_table')", - "severity": "High", - "status": "New", - "title": "Copilot Investigation for user reported phishing- Phising certainty is @{decimal(variables('percentage number var'))}%" - }, - "host": { - "connection": { - "name": "@parameters('$connections')['azuresentinel']['connectionId']" - } - }, - "method": "put", - "path": "/Incidents/subscriptions/@{encodeURIComponent('ab48f397-fc82-4634-aa52-62dd91b3ebaa')}/resourceGroups/@{encodeURIComponent('Woodgrove-RG')}/workspaces/@{encodeURIComponent('Woodgrove-LogAnalyiticsWorkspace')}" - } - }, - "Send_an_email_(V2)_2": { - "runAfter": { - }, - "type": "ApiConnection", - "inputs": { - "body": { - "Body": "\u003cp\u003e@{body('Create_HTML_table')}\u003c/p\u003e", - "Importance": "High", - "Subject": "Copilot Phishing Report - Phishing Score is @{decimal(variables('percentage number var'))}%", - "To": "[parameters('EmailTo')]" - }, - "host": { - "connection": { - "name": "@parameters('$connections')['office365']['connectionId']" - } - }, - "method": "post", - "path": "/v2/Mail" - } - } - }, - "runAfter": { - "Create_HTML_table": [ - "Succeeded" - ] - }, - "else": { - "actions": { - "Send_an_email_(V2)": { - "runAfter": { - }, - "type": "ApiConnection", - "inputs": { - "body": { - "Body": "\u003cp\u003e@{body('Create_HTML_table')}\u003c/p\u003e", - "Importance": "Normal", - "Subject": "Copilot Phishing Report - Phishing Score is @{decimal(variables('percentage number var'))}", - "To": "[parameters('EmailTo')]" - }, - "host": { - "connection": { - "name": "@parameters('$connections')['office365']['connectionId']" - } - }, - "method": "post", - "path": "/v2/Mail" - } - } - } - }, - "expression": { - "and": [ - { - "greater": [ - "@decimal(variables('percentage number var'))", - 74 - ] - } - ] - }, - "type": "If" - }, - "Create_HTML_table": { - "runAfter": { - "Select": [ - "Succeeded" - ] - }, - "type": "Table", - "inputs": { - "format": "HTML", - "from": "@body('Select')" - } - }, "Domain_reputation_MDTI": { "runAfter": { - "_indicators_within_the_email": [ + "Triage_this_email": [ "Succeeded" ] }, "type": "ApiConnection", "inputs": { "body": { - "PromptContent": "Return the reputation for all the domain your shared", - "SessionId": "@body('_indicators_within_the_email')?['sessionId']" + "PromptContent": "Return the reputation for all the domains and IP addresses in the e-mail besides https://security.microsoft.com/userSubmissionsReportMessage, include the sender domain @{body('Parse_JSON')?['from']}(this should be the string that is coming after the @ sign)", + "SessionId": "@body('Triage_this_email')?['sessionId']", + "SkillName": "GetReputationsForIndicators" }, "host": { "connection": { - "name": "@parameters('$connections')['securitycopilot']['connectionId']" + "name": "@parameters('$connections')['securitycopilot_1']['connectionId']" } }, "method": "post", "path": "/process-prompt" } }, - "IP's_reputation_MDTI": { + "Html_to_text": { "runAfter": { - "Domain_reputation_MDTI": [ + "Parse_JSON": [ "Succeeded" ] }, "type": "ApiConnection", "inputs": { - "body": { - "PromptContent": "if you found IP address as indictors, please return the reputation for all the Ip's your shared.", - "SessionId": "@body('Domain_reputation_MDTI')?['sessionId']" - }, + "body": "\u003cp\u003e@{body('Parse_JSON')}\u003c/p\u003e", "host": { "connection": { - "name": "@parameters('$connections')['securitycopilot']['connectionId']" + "name": "@parameters('$connections')['conversionservice']['connectionId']" } }, "method": "post", - "path": "/process-prompt" + "path": "/html2text" } }, - "Initialize_variable": { + "KQL_Who_else_click_on_this_Email": { "runAfter": { - "Parse_JSON-percentage_number": [ + "KQL_sent_to_other_users": [ "Succeeded" ] }, - "type": "InitializeVariable", + "type": "ApiConnection", "inputs": { - "variables": [ - { - "name": "percentage number var", - "type": "string", - "value": "@body('Parse_JSON-percentage_number')?['Evaluation Result Content']" + "body": { + "PromptContent": "Based on the email title : @{body('Parse_JSON')?['subject']}, and the sender email address: @{body('Parse_JSON')?['from']}check if other users in the organization click on the link inside the email", + "SessionId": "@body('KQL_sent_to_other_users')?['sessionId']", + "SkillName": "GetUrlClicksBySubjectAndSenderEmail" + }, + "host": { + "connection": { + "name": "@parameters('$connections')['securitycopilot_1']['connectionId']" } - ] + }, + "method": "post", + "path": "/process-prompt" } }, - "KQL_Who_else_click_on_this_Email": { + "KQL_sent_to_other_users": { "runAfter": { - "KQL_sent_to_other_users": [ + "Look_up_relevant_articles": [ "Succeeded" ] }, "type": "ApiConnection", "inputs": { "body": { - "PromptContent": "/LookupDataFromDefender365Hunting Base on the email title, and the sender email address, check if other users in the organization click on the link inside the email? ignore the Url \"https://security.microsoft.com/userSubmissionsReportMessage\"", - "SessionId": "@body('KQL_sent_to_other_users')?['sessionId']" + "PromptContent": "Fetch emails with the same subject name, to see if it has been sent to others in my organization: use email subject:@{body('Parse_JSON')?['subject']}", + "SessionId": "@body('Look_up_relevant_articles')?['sessionId']", + "SkillName": "GetEmailsByEmailSubject" }, "host": { "connection": { - "name": "@parameters('$connections')['securitycopilot']['connectionId']" + "name": "@parameters('$connections')['securitycopilot_1']['connectionId']" } }, "method": "post", "path": "/process-prompt" } }, - "KQL_sent_to_other_users": { + "Look_up_relevant_articles": { "runAfter": { - "sender_and_return_path_email": [ + "Domain_reputation_MDTI": [ "Succeeded" ] }, "type": "ApiConnection", "inputs": { "body": { - "PromptContent": "/LookupDataFromDefender365Hunting Base on the email title check if this email sent to other users in my organization?", - "SessionId": "@body('sender_and_return_path_email')?['sessionId']" + "PromptContent": "Check if we have MDTI articles associated with any of the ip addresses, domains, or e-mail addresses", + "SessionId": "@body('Domain_reputation_MDTI')?['sessionId']", + "SkillName": "GetArticlesIndicators" }, "host": { "connection": { - "name": "@parameters('$connections')['securitycopilot']['connectionId']" + "name": "@parameters('$connections')['securitycopilot_1']['connectionId']" } }, "method": "post", @@ -332,86 +234,7 @@ } } }, - "Parse_JSON-percentage_number": { - "runAfter": { - "report_answer": [ - "Succeeded" - ] - }, - "type": "ParseJson", - "inputs": { - "content": "@body('percentage_number')", - "schema": { - "properties": { - "Evaluation Result Content": { - "type": "string" - }, - "Evaluation Result Type": { - "type": "string" - }, - "Next Prompt Suggestions": { - }, - "Prompt Content": { - "type": "string" - }, - "SessionId": { - "type": "string" - }, - "Skill Name": { - } - }, - "type": "object" - } - } - }, - "Parse_JSON_2": { - "runAfter": { - "Summaries_findings": [ - "Succeeded" - ] - }, - "type": "ParseJson", - "inputs": { - "content": "@body('Summaries_findings')", - "schema": { - "properties": { - "Evaluation Result Content": { - "type": "string" - }, - "Evaluation Result Type": { - "type": "string" - }, - "Next Prompt Suggestions": { - }, - "Prompt Content": { - "type": "string" - }, - "SessionId": { - "type": "string" - }, - "Skill Name": { - } - }, - "type": "object" - } - } - }, - "Select": { - "runAfter": { - "Initialize_variable": [ - "Succeeded" - ] - }, - "type": "Select", - "inputs": { - "from": "@variables('Report')", - "select": { - "Copilot Phishing Report": "@variables('Report')", - "Session Link": "https://securitycopilot.microsoft.com/sessions/@{body('Summaries_findings')?['sessionId']}" - } - } - }, - "Summaries_findings": { + "Summarize_findings": { "runAfter": { "KQL_Who_else_click_on_this_Email": [ "Succeeded" @@ -420,13 +243,13 @@ "type": "ApiConnection", "inputs": { "body": { - "PromptContent": "Base on the above email investigation, summarize this investigation with all the investigation steps that taken during this session share also supported evidence and let me know if this is a phishing email, also share certainty percentage: if the email is not suspicious give it 0-30% if it medium suspicious for phishing give it 30%-74% and if its high suspicious as phishing give it 75%-100%, allways include suspicious certainty percentage.", + "PromptContent": "Above was an investigation performed by a security analyst to determine if an e-mail was a phishing attempt. Summarize this investigation with all the steps taken, share and decide if this is a phishing email. Share certainty percentage: if the email is not suspicious give it 0-30% if it medium suspicious give it 30%-74% and if its highly suspicious give it 75%-100%, you **must** include suspicious certainty percentage.", "SessionId": "@body('KQL_Who_else_click_on_this_Email')?['sessionId']", "SkillName": "SummarizeData" }, "host": { "connection": { - "name": "@parameters('$connections')['securitycopilot']['connectionId']" + "name": "@parameters('$connections')['securitycopilot_1']['connectionId']" } }, "method": "post", @@ -435,119 +258,18 @@ }, "Triage_this_email": { "runAfter": { - "Parse_JSON": [ - "Succeeded" - ] - }, - "type": "ApiConnection", - "inputs": { - "body": { - "PromptContent": "/AskGPT Can you triage this email and point out what you find suspicious? @{body('Parse_JSON')?['body']} . Dont include in your investigation the above url https://security.microsoft.com/userSubmissionsReportMessage, in your triage Investigate the Message-ID for any inconsistencies or signs of spoofing." - }, - "host": { - "connection": { - "name": "@parameters('$connections')['securitycopilot']['connectionId']" - } - }, - "method": "post", - "path": "/process-prompt" - } - }, - "_indicators_within_the_email": { - "runAfter": { - "Triage_this_email": [ - "Succeeded" - ] - }, - "type": "ApiConnection", - "inputs": { - "body": { - "PromptContent": "Please list for me all the technical indicators within the email that should be investigated. don't include https://security.microsoft.com/userSubmissionsReportMessage as indicator", - "SessionId": "@body('Triage_this_email')?['sessionId']" - }, - "host": { - "connection": { - "name": "@parameters('$connections')['securitycopilot']['connectionId']" - } - }, - "method": "post", - "path": "/process-prompt" - } - }, - "percentage_number": { - "runAfter": { - "Parse_JSON_2": [ - "Succeeded" - ] - }, - "type": "ApiConnection", - "inputs": { - "body": { - "PromptContent": "Based on this investigation, share certainty percentage. only respond with the a number, only one token no more", - "SessionId": "@body('Summaries_findings')?['sessionId']" - }, - "host": { - "connection": { - "name": "@parameters('$connections')['securitycopilot']['connectionId']" - } - }, - "method": "post", - "path": "/process-prompt" - } - }, - "report_answer": { - "runAfter": { - "percentage_number": [ - "Succeeded" - ] - }, - "type": "InitializeVariable", - "inputs": { - "variables": [ - { - "name": "Report", - "type": "array", - "value": "@array(body('Parse_JSON_2')?['Evaluation Result Content'])" - } - ] - } - }, - "sender_and_return_path_email": { - "runAfter": { - "summarize_the_Article": [ - "Succeeded" - ] - }, - "type": "ApiConnection", - "inputs": { - "body": { - "PromptContent": "What about the sender and return path email? Are these associated with any intelligence articles?", - "SessionId": "@body('summarize_the_Article')?['sessionId']" - }, - "host": { - "connection": { - "name": "@parameters('$connections')['securitycopilot']['connectionId']" - } - }, - "method": "post", - "path": "/process-prompt" - } - }, - "summarize_the_Article": { - "runAfter": { - "IP's_reputation_MDTI": [ + "Html_to_text": [ "Succeeded" ] }, "type": "ApiConnection", "inputs": { "body": { - "PromptContent": "Check if we have MDTI articles the associated with this domain? if you found summarize them.", - "SessionId": "@body('Domain_reputation_MDTI')?['sessionId']" + "PromptContent": "/AskGPT Triage the following email and point out what you find suspicious?. Ignore this URL https://security.microsoft.com/userSubmissionsReportMessage. Investigate the Message-ID for any inconsistencies or signs of spoofing. Use inputs: email body:@{body('Html_to_text')} , sender emailL@{body('Parse_JSON')?['from']}, reciver email:@{body('Parse_JSON')?['toRecipients']}, messageId:@{body('Parse_JSON')?['internetMessageId']}, emailSubject:@{body('Parse_JSON')?['subject']},Email receivedDateTime:@{body('Parse_JSON')?['receivedDateTime']}, importance:@{body('Parse_JSON')?['importance']}" }, "host": { "connection": { - "name": "@parameters('$connections')['securitycopilot']['connectionId']" + "name": "@parameters('$connections')['securitycopilot_1']['connectionId']" } }, "method": "post", @@ -561,17 +283,17 @@ "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')]" + "conversionservice": { + "connectionId": "[resourceId('Microsoft.Web/connections', variables('ConversionserviceConnectionName'))]", + "connectionName": "[variables('ConversionserviceConnectionName')]", + "id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/Conversionservice')]" }, "office365": { "connectionId": "[resourceId('Microsoft.Web/connections', variables('Office365ConnectionName'))]", "connectionName": "[variables('Office365ConnectionName')]", "id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/Office365')]" }, - "securitycopilot": { + "securitycopilot_1": { "connectionId": "[resourceId('Microsoft.Web/connections', variables('SecuritycopilotConnectionName'))]", "connectionName": "[variables('SecuritycopilotConnectionName')]", "id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/Securitycopilot')]" @@ -584,13 +306,13 @@ "type": "Microsoft.Logic/workflows", "location": "[resourceGroup().location]", "tags": { - "Created By": "Yaniv Shasha", - "CreatedDate": "9/25/2023 7:38:44 PM", - "CreatorUPN": "yanivsh@woodgrove.ms" + "Created By": "Abbas Bengtsson", + "CreatedDate": "5/17/2024 4:19:09 PM", + "CreatorUPN": "abbe67@woodgrove.ms" }, "apiVersion": "2017-07-01", "dependsOn": [ - "[resourceId('Microsoft.Web/connections', variables('MicrosoftSentinelConnectionName'))]", + "[resourceId('Microsoft.Web/connections', variables('ConversionserviceConnectionName'))]", "[resourceId('Microsoft.Web/connections', variables('Office365ConnectionName'))]", "[resourceId('Microsoft.Web/connections', variables('SecuritycopilotConnectionName'))]" ] @@ -598,15 +320,15 @@ { "type": "Microsoft.Web/connections", "apiVersion": "2016-06-01", - "name": "[variables('MicrosoftSentinelConnectionName')]", + "name": "[variables('ConversionserviceConnectionName')]", "location": "[resourceGroup().location]", "kind": "V1", "properties": { - "displayName": "[variables('MicrosoftSentinelConnectionName')]", + "displayName": "[variables('ConversionserviceConnectionName')]", "customParameterValues": { }, "api": { - "id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/Azuresentinel')]" + "id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/Conversionservice')]" } } }, diff --git a/Logic Apps/SecCopilot-UserReportedPhishing/images/conditations.jpg b/Logic Apps/SecCopilot-UserReportedPhishing/images/conditations.jpg deleted file mode 100644 index e21bab9f..00000000 Binary files a/Logic Apps/SecCopilot-UserReportedPhishing/images/conditations.jpg and /dev/null differ diff --git a/Logic Apps/SecCopilot-UserReportedPhishing/images/email_results.jpg b/Logic Apps/SecCopilot-UserReportedPhishing/images/email_results.jpg deleted file mode 100644 index 9682c3e9..00000000 Binary files a/Logic Apps/SecCopilot-UserReportedPhishing/images/email_results.jpg and /dev/null differ diff --git a/Logic Apps/SecCopilot-UserReportedPhishing/images/enable_skill.png b/Logic Apps/SecCopilot-UserReportedPhishing/images/enable_skill.png new file mode 100644 index 00000000..f755887c Binary files /dev/null and b/Logic Apps/SecCopilot-UserReportedPhishing/images/enable_skill.png differ diff --git a/Logic Apps/SecCopilot-UserReportedPhishing/images/full_logic_app.jpg b/Logic Apps/SecCopilot-UserReportedPhishing/images/full_logic_app.jpg deleted file mode 100644 index e60f3bfe..00000000 Binary files a/Logic Apps/SecCopilot-UserReportedPhishing/images/full_logic_app.jpg and /dev/null differ diff --git a/Logic Apps/SecCopilot-UserReportedPhishing/images/full_view.png b/Logic Apps/SecCopilot-UserReportedPhishing/images/full_view.png new file mode 100644 index 00000000..2b6f9bc9 Binary files /dev/null and b/Logic Apps/SecCopilot-UserReportedPhishing/images/full_view.png differ diff --git a/Logic Apps/SecCopilot-UserReportedPhishing/images/sentinel incident.jpg b/Logic Apps/SecCopilot-UserReportedPhishing/images/sentinel incident.jpg deleted file mode 100644 index fdbebc4b..00000000 Binary files a/Logic Apps/SecCopilot-UserReportedPhishing/images/sentinel incident.jpg and /dev/null differ diff --git a/Logic Apps/SecCopilot-UserReportedPhishing/images/upload01.png b/Logic Apps/SecCopilot-UserReportedPhishing/images/upload01.png new file mode 100644 index 00000000..5805b8d4 Binary files /dev/null and b/Logic Apps/SecCopilot-UserReportedPhishing/images/upload01.png differ diff --git a/Logic Apps/SecCopilot-UserReportedPhishing/readme.md b/Logic Apps/SecCopilot-UserReportedPhishing/readme.md index 716b98f1..aedd6e8b 100644 --- a/Logic Apps/SecCopilot-UserReportedPhishing/readme.md +++ b/Logic Apps/SecCopilot-UserReportedPhishing/readme.md @@ -2,7 +2,7 @@ Author: Yaniv Shasha -### As of this moment, please deploy this logic app in West-US-2 region + By initiating the deployment process, you will set up an Azure Logic App integrated with Security Copilot Actions.
This setup activates when a user reports a phishing attempt in Outlook.
@@ -18,19 +18,18 @@ Additionally, if the phishing probability exceeds 75%, a Sentinel Incident will • The user deploying this Logic app must possess a Contributor Role.
• Activation of the Security Copilot license on your tenant is required.
• The user authenticated in the Copilot logic app action should have Security Admin permissions and the role of a Microsoft Sentinel contributor, as this is necessary for creating incident comments.
+• Upload and enable custom KQL plugin, this need to be on the same user profile that running automation.
- -
+ -
+ - + +

- -
@@ -39,23 +38,12 @@ Additionally, if the phishing probability exceeds 75%, a Sentinel Incident will To deploy the above logic app, you need to
• Press on the Deploy option, select your subscription and the resource group (select the same tenant that Security Copilot is enabled)
- - - - -### Full Logic App view - - + + -### Phishing condition - - - -### Notification Email Send - -### Sentinel Incident +### Full Logic App view - + \ No newline at end of file diff --git a/Logic Apps/SecurityCopilot-SOCshift-reporting-transfer/azuredeploy.json b/Logic Apps/SecurityCopilot-SOCshift-reporting-transfer/azuredeploy.json index 9b842144..6cf67cdc 100644 --- a/Logic Apps/SecurityCopilot-SOCshift-reporting-transfer/azuredeploy.json +++ b/Logic Apps/SecurityCopilot-SOCshift-reporting-transfer/azuredeploy.json @@ -5,12 +5,13 @@ "PlaybookName": { "defaultValue": "SecurityCopilot-SOCshift-reporting-transfer", "type": "string" - } - , - "EmailTo": { - "defaultValue": "", - "type": "String" - } + }, + "email": { + "type": "string", + "metadata": { + "description": "Enter value for email" + } + } }, "variables": { "Office365ConnectionName": "[concat('Office365-', parameters('PlaybookName'))]", @@ -29,17 +30,21 @@ "defaultValue": { }, "type": "Object" + }, + "email": { + "defaultValue": "[parameters('email')]", + "type": "string" } }, "triggers": { "Recurrence": { "recurrence": { - "frequency": "day", - "interval": 1 + "frequency": "day", + "interval": 1 }, "evaluatedRecurrence": { - "frequency": "day", - "interval": 1 + "frequency": "day", + "interval": 1 }, "type": "Recurrence" } @@ -47,14 +52,14 @@ "actions": { "Counter_Int": { "runAfter": { - "Parse_JSON_2": [ + "Get_number_of_incidents_for_the_counter": [ "Succeeded" ] }, "type": "SetVariable", "inputs": { "name": "counter", - "value": "@int(body('Parse_JSON_2')?['Evaluation Result Content'])" + "value": "@int(body('Get_number_of_incidents_for_the_counter')?['EvaluationResultContent'])" } }, "Get_number_of_incidents_for_the_counter": { @@ -66,12 +71,12 @@ "type": "ApiConnection", "inputs": { "body": { - "PromptContent": "count the number of incidents you menationed above, , this will be used in an automation workflow that only accepts a single string. Provide the incident count in a response with no text before or after", + "PromptContent": "/AskGPT count the number of incidents you menationed above, , this will be used in an automation workflow that only accepts a single string. Provide the incident count in a response with no text before or after", "SessionId": "@body('Run_Copilot_for_Security_prompt')?['sessionId']" }, "host": { "connection": { - "name": "@parameters('$connections')['securitycopilot']['connectionId']" + "name": "@parameters('$connections')['securitycopilot_1']['connectionId']" } }, "method": "post", @@ -80,14 +85,14 @@ }, "Incidents_split": { "runAfter": { - "Parse_JSON": [ + "Run_Copilot_for_Security_prompt": [ "Succeeded" ] }, "type": "SetVariable", "inputs": { "name": "incidents", - "value": "@split(body('Parse_JSON')?['Evaluation Result Content'], ',')" + "value": "@split(body('Run_Copilot_for_Security_prompt')?['EvaluationResultContent'], ',')" } }, "Initialize_variable": { @@ -99,77 +104,11 @@ { "name": "Soc Email address", "type": "string", - "value": "[parameters('EmailTo')]" + "value": "@parameters('email')" } ] } }, - "Parse_JSON": { - "runAfter": { - "Run_Copilot_for_Security_prompt": [ - "Succeeded" - ] - }, - "type": "ParseJson", - "inputs": { - "content": "@body('Run_Copilot_for_Security_prompt')", - "schema": { - "properties": { - "Evaluation Result Content": { - "type": "string" - }, - "Evaluation Result Type": { - "type": "string" - }, - "Prompt Content": { - "type": "string" - }, - "SessionId": { - "type": "string" - }, - "Skill Name": { - }, - "Skill Sources": { - "type": "array" - } - }, - "type": "object" - } - } - }, - "Parse_JSON_2": { - "runAfter": { - "Get_number_of_incidents_for_the_counter": [ - "Succeeded" - ] - }, - "type": "ParseJson", - "inputs": { - "content": "@body('Get_number_of_incidents_for_the_counter')", - "schema": { - "properties": { - "Evaluation Result Content": { - "type": "string" - }, - "Evaluation Result Type": { - "type": "string" - }, - "Prompt Content": { - "type": "string" - }, - "SessionId": { - "type": "string" - }, - "Skill Name": { - }, - "Skill Sources": { - "type": "array" - } - }, - "type": "object" - } - } - }, "Report_variable": { "runAfter": { "counter_variable": [ @@ -195,11 +134,12 @@ "type": "ApiConnection", "inputs": { "body": { - "PromptContent": "im a soc analyst leader and our SOC is in the phase for changing a shift, can you summarize all the defender incident that opened in the last 24 hr and they are in status in progress, from the above response only take the incident number, this will be used in an automation workflow that only accepts a single string. Provide the incident number in a response with no text before or after" + "PromptContent": "im a soc analyst leader and our SOC is in the phase for changing a shift, can you summarize all the defender incident that opened in the last 24 hr and they are in status in progress, from the above response only take the incident number, this will be used in an automation workflow that only accepts a single string. Provide the incident number in a response with no text before or after", + "SkillName": "GetDefenderIncidents" }, "host": { "connection": { - "name": "@parameters('$connections')['securitycopilot']['connectionId']" + "name": "@parameters('$connections')['securitycopilot_1']['connectionId']" } }, "method": "post", @@ -222,7 +162,7 @@ }, "host": { "connection": { - "name": "@parameters('$connections')['office365']['connectionId']" + "name": "@parameters('$connections')['office365_1']['connectionId']" } }, "method": "post", @@ -236,14 +176,14 @@ "actions": { "Append_to_string_variable": { "runAfter": { - "Parse_JSON_3": [ + "generate_the_report": [ "Succeeded" ] }, "type": "AppendToStringVariable", "inputs": { "name": "Report", - "value": "@{body('Parse_JSON_3')?['Evaluation Result Content']} " + "value": " @{body('generate_the_report')?['EvaluationResultContent']}" } }, "Increment_variable": { @@ -258,50 +198,18 @@ "value": 1 } }, - "Parse_JSON_3": { - "runAfter": { - "generate_the_report": [ - "Succeeded" - ] - }, - "type": "ParseJson", - "inputs": { - "content": "@body('generate_the_report')", - "schema": { - "properties": { - "Evaluation Result Content": { - "type": "string" - }, - "Evaluation Result Type": { - "type": "string" - }, - "Prompt Content": { - "type": "string" - }, - "SessionId": { - "type": "string" - }, - "Skill Name": { - }, - "Skill Sources": { - "type": "array" - } - }, - "type": "object" - } - } - }, "generate_the_report": { "runAfter": { }, "type": "ApiConnection", "inputs": { "body": { - "PromptContent": "/GetDefenderIncidentReport @{items('For_each')} The results from this response will be used in an automation workflow that only accepts only html structure and tags, \nProvide the report as html tags only, add the incident id as h2 tag, format the incident details as table, include in this table a direct link to defender portal in the above format: https://security.microsoft.com/incidents/\u003cincidentid\u003e.\nFinally use html CSS that will include our organization look and feel that is on color green, add this colors as all the titles and use the color black as the reset of the content." + "PromptContent": "@{items('For_each')} The results from this response will be used in an automation workflow that only accepts only html structure and tags, \nProvide the report as html tags only, add the incident id as h2 tag, format the incident details as table, include in this table a direct link to defender portal in the above format: https://security.microsoft.com/incidents/\u003cincidentid\u003e.\nFinally use html CSS that will include our organization look and feel that is on color green, add this colors as all the titles and use the color black as the reset of the content.", + "SkillName": "GetDefenderIncidentReport" }, "host": { "connection": { - "name": "@parameters('$connections')['securitycopilot']['connectionId']" + "name": "@parameters('$connections')['securitycopilot_1']['connectionId']" } }, "method": "post", @@ -385,12 +293,12 @@ "parameters": { "$connections": { "value": { - "office365": { + "office365_1": { "connectionId": "[resourceId('Microsoft.Web/connections', variables('Office365ConnectionName'))]", "connectionName": "[variables('Office365ConnectionName')]", "id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/Office365')]" }, - "securitycopilot": { + "securitycopilot_1": { "connectionId": "[resourceId('Microsoft.Web/connections', variables('SecuritycopilotConnectionName'))]", "connectionName": "[variables('SecuritycopilotConnectionName')]", "id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/Securitycopilot')]" @@ -403,9 +311,9 @@ "type": "Microsoft.Logic/workflows", "location": "[resourceGroup().location]", "tags": { - "Created By": "Yaniv Shasha", + "Created By": "Yaniv", "CreatedDate": "1/29/2024 10:04:47 PM", - "CreatorUPN": "yanivsh@woodgrove.ms" + "CreatorUPN": "" }, "apiVersion": "2017-07-01", "dependsOn": [ diff --git a/Logic Apps/SecurityCopilot-SOCshift-reporting-transfer/readme.md b/Logic Apps/SecurityCopilot-SOCshift-reporting-transfer/readme.md index 96e5aa84..fe6dc4b2 100644 --- a/Logic Apps/SecurityCopilot-SOCshift-reporting-transfer/readme.md +++ b/Logic Apps/SecurityCopilot-SOCshift-reporting-transfer/readme.md @@ -29,7 +29,7 @@ Prior to beginning the installation process, it's crucial to confirm that you ha To deploy the above logic app, you need to
• Press on the Deploy option, select your subscription and the resource group (select the same tenant that Security Copilot is enabled)
-
+
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/ThreatBulletinCopilot/Threatbulletin.png b/Logic Apps/ThreatBulletinCopilot/Threatbulletin.png index a7a2cda9..39f51721 100644 Binary files a/Logic Apps/ThreatBulletinCopilot/Threatbulletin.png and b/Logic Apps/ThreatBulletinCopilot/Threatbulletin.png differ diff --git a/Logic Apps/ThreatBulletinCopilot/azuredeploy.json b/Logic Apps/ThreatBulletinCopilot/azuredeploy.json index 159b4784..64c68ffa 100644 --- a/Logic Apps/ThreatBulletinCopilot/azuredeploy.json +++ b/Logic Apps/ThreatBulletinCopilot/azuredeploy.json @@ -1,256 +1,228 @@ { - "$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" + "$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": "ThreatBulletinCopilot", +"type": "string" +}, +"email": { +"type": "String", +"metadata": { + "description": "Enter value for email" +} +} +}, +"variables": { +"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" }, - "author": { - "name": "" + "email": { + "type": "String", + "defaultValue": "[parameters('email')]" } }, - "parameters": { - "PlaybookName": { - "defaultValue": "ThreatBulletinCopilot", - "type": "string" - }, - "email": { - "type": "string", - "metadata": { - "description": "Enter value for email" - } + "triggers": { + "Recurrence": { + "recurrence": { + "frequency": "Day", + "interval": 1 + }, + "evaluatedRecurrence": { + "frequency": "Day", + "interval": 1 + }, + "type": "Recurrence" } }, - "variables": { - "Office365ConnectionName": "[concat('Office365-', parameters('PlaybookName'))]", - "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" - }, - "email": { - "defaultValue": "[parameters('email')]", - "type": "string" - } - }, - "triggers": { - "Recurrence": { - "recurrence": { - "frequency": "Day", - "interval": 1 - }, - "evaluatedRecurrence": { - "frequency": "Day", - "interval": 1 - }, - "type": "Recurrence" - } - }, - "actions": { - "Compose": { - "runAfter": { - "Parse_JSON": [ - "Succeeded" - ] - }, - "type": "Compose", - "inputs": "@body('Parse_JSON')?['Evaluation Result Content']" - }, - "Compose_2": { - "runAfter": { - "Compose": [ - "Succeeded" - ] - }, - "type": "Compose", - "inputs": "@decodeUriComponent(replace(encodeUriComponent(outputs('Compose')), '%0A', '%3Cbr%3E'))" - }, - "Get_the_latest_threat_articles_over_the_last_24_hours": { - "runAfter": { - }, - "type": "ApiConnection", - "inputs": { - "body": { - "PromptContent": "Share the latest Threat articles published in the last 24 hours in a table format" - }, - "host": { - "connection": { - "name": "@parameters('$connections')['securitycopilot']['connectionId']" - } - }, - "method": "post", - "path": "/process-prompt" - } - }, - "Parse_JSON": { - "runAfter": { - "Summarize_into_a_threat_bulletin": [ - "Succeeded" - ] - }, - "type": "ParseJson", - "inputs": { - "content": "@body('Summarize_into_a_threat_bulletin')", - "schema": { - "properties": { - "Evaluation Result Content": { - "type": "string" - }, - "Evaluation Result Type": { - "type": "string" - }, - "Prompt Content": { - "type": "string" - }, - "SessionId": { - "type": "string" - }, - "Skill Name": { - }, - "Skill Sources": { - "type": "array" - } - }, - "type": "object" - } - } - }, - "Send_an_email_(V2)": { - "runAfter": { - "Compose_2": [ - "Succeeded" - ] - }, - "type": "ApiConnection", - "inputs": { - "body": { - "Body": "\u003cp\u003e@{outputs('Compose_2')}\u003c/p\u003e", - "Importance": "Normal", - "Subject": "Daily Threat Bulletin", - "To": "@parameters('email')" - }, - "host": { - "connection": { - "name": "@parameters('$connections')['office365']['connectionId']" - } - }, - "method": "post", - "path": "/v2/Mail" - } - }, - "Summarize_into_a_threat_bulletin": { - "runAfter": { - "Get_the_latest_threat_articles_over_the_last_24_hours": [ - "Succeeded" - ] - }, - "type": "ApiConnection", - "inputs": { - "body": { - "PromptContent": "Summarize the following articles and consolidate them in a Threat Bulletin format Named Threat bulletin with the current date . In the bulletin ensure to capture all details including the title summary , link , Created date , also highlight the action items the Team should prioritize in relation to these article", - "SessionId": "@body('Get_the_latest_threat_articles_over_the_last_24_hours')?['sessionId']" - }, - "host": { - "connection": { - "name": "@parameters('$connections')['securitycopilot']['connectionId']" - } - }, - "method": "post", - "path": "/process-prompt" - } - } - }, - "outputs": { + "actions": { + "Convert_Response_into_HTML_Format_for_Proper_Output": { + "runAfter": { + "Summarize_into_a_threat_bulletin": [ + "Succeeded" + ] + }, + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['securitycopilot_1']['connectionId']" } }, - "parameters": { - "$connections": { - "value": { - "office365": { - "connectionId": "[resourceId('Microsoft.Web/connections', variables('Office365ConnectionName'))]", - "connectionName": "[variables('Office365ConnectionName')]", - "id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/Office365')]" - }, - "securitycopilot": { - "connectionId": "[resourceId('Microsoft.Web/connections', variables('SecuritycopilotConnectionName'))]", - "connectionName": "[variables('SecuritycopilotConnectionName')]", - "id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/Securitycopilot')]" - } - } - } + "method": "post", + "body": { + "PromptContent": "/AskGpt Provide the previous response in optimized HTML format ", + "SessionId": "@body('Summarize_into_a_threat_bulletin')?['sessionId']" + }, + "path": "/process-prompt" + } + }, + "Get_the_latest_threat_articles_over_the_last_24_hours": { + "runAfter": {}, + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['securitycopilot_1']['connectionId']" } }, - "name": "[parameters('PlaybookName')]", - "type": "Microsoft.Logic/workflows", - "location": "[resourceGroup().location]", - "tags": { - "Created By": "Sean Wasonga", - "CreatedDate": "", - "CreatorUPN": "Sean Wasonga", - "hidden-SentinelTemplateName": "ThreatBulletinCopilot", - "hidden-SentinelTemplateVersion": "1.0" + "method": "post", + "body": { + "PromptContent": "Share the latest Threat articles published in the last 24 hours in a table format", + "SkillName": "FindThreatIntelligence" }, - "identity": { - "type": "SystemAssigned" - }, - "apiVersion": "2017-07-01", - "dependsOn": [ - "[resourceId('Microsoft.Web/connections', variables('Office365ConnectionName'))]", - "[resourceId('Microsoft.Web/connections', variables('SecuritycopilotConnectionName'))]" + "path": "/process-prompt" + } + }, + "Send_an_email_(V2)": { + "runAfter": { + "Add_Copilot_Logo": [ + "Succeeded" ] }, - { - "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": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['office365_1']['connectionId']" } - } + }, + "method": "post", + "body": { + "To": "@parameters('email')", + "Subject": "Daily Threat Bulletin - Microsoft Copilot for Security", + "Body": "

@{outputs('Add_Copilot_Logo')}
@{body('Convert_Response_into_HTML_Format_for_Proper_Output')?['EvaluationResultContent']}

", + "Importance": "Normal" + }, + "path": "/v2/Mail" + } + }, + "Summarize_into_a_threat_bulletin": { + "runAfter": { + "Get_the_latest_threat_articles_over_the_last_24_hours": [ + "Succeeded" + ] }, - { - "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": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['securitycopilot_1']['connectionId']" } - } + }, + "method": "post", + "body": { + "PromptContent": "/AskGPT Summarize the following articles and consolidate them in a Threat Bulletin format Named Microsoft Copilot for Security -Threat bulletin with the current date . In the bulletin , ensure to capture all details including the title summary , link , Created date , also highlight the action items the Team should prioritize in relation to these article. Include a sign off Generated by Micrososoft Copilot for Security at the end", + "SessionId": "@body('Get_the_latest_threat_articles_over_the_last_24_hours')?['sessionId']" + }, + "path": "/process-prompt" } - ] + }, + "Add_Copilot_Logo": { + "runAfter": { + "Convert_Response_into_HTML_Format_for_Proper_Output": [ + "Succeeded" + ] + }, + "type": "Compose", + "inputs": "\"Copilot" + } +}, +"outputs": {} +}, +"parameters": { +"$connections": { +"value": { + "securitycopilot_1": { + "connectionId": "[resourceId('Microsoft.Web/connections', variables('SecuritycopilotConnectionName'))]", + "connectionName": "[variables('SecuritycopilotConnectionName')]", + "id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/Securitycopilot')]" + }, + "office365_1": { + "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": { +"Created By": "Sean Wasonga", +"CreatedDate": "", +"CreatorUPN": "Sean Wasonga", +"hidden-SentinelTemplateName": "ThreatBulletinCopilot", +"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/ThreatBulletinCopilot/readme.md b/Logic Apps/ThreatBulletinCopilot/readme.md index 84bf1e61..ccaf8929 100644 --- a/Logic Apps/ThreatBulletinCopilot/readme.md +++ b/Logic Apps/ThreatBulletinCopilot/readme.md @@ -1,4 +1,4 @@ -# Copilot-For-Security-ThreatBulletin +# Security Copilot-ThreatBulletin Author: Sean Wasonga ## Overview This playbook serves as a cornerstone for Security Copilot, facilitating integration with Microsoft Defender Threat Intelligence (MDTI) to swiftly provide the latest threat bulletins for threat articles. By consolidating this connection, users can efficiently access comprehensive threat article information. Over time, this foundational playbook may expand to incorporate additional capabilities. @@ -13,7 +13,7 @@ Prior to beginning the installation process, it's crucial to confirm that you ha ## Deployment - + @@ -26,7 +26,7 @@ Prior to beginning the installation process, it's crucial to confirm that you ha 3. Proceed to run the Logic app and Prepare for the results Logic App View -![Threat Bulletin Logic App](https://github.com/KwachSean/SecurityCopilot/raw/main/playbooks/ThreatBulletinCopilot/threatbulletinlogicapp.png) +![Threat Bulletin Logic App](https://github.com/Azure/Copilot-For-Security/blob/main/Logic%20Apps/ThreatBulletinCopilot/threatbulletinlogicapp.png) Results of the Logic APP -![Threat Bulletin Image](https://github.com/KwachSean/SecurityCopilot/raw/main/playbooks/ThreatBulletinCopilot/Threatbulletin.png) +![Threat Bulletin Image](https://github.com/Azure/Copilot-For-Security/blob/main/Logic%20Apps/ThreatBulletinCopilot/Threatbulletin.png) diff --git a/Logic Apps/ThreatBulletinCopilot/threatbulletinlogicapp.png b/Logic Apps/ThreatBulletinCopilot/threatbulletinlogicapp.png index f99be2d4..0e2091a3 100644 Binary files a/Logic Apps/ThreatBulletinCopilot/threatbulletinlogicapp.png and b/Logic Apps/ThreatBulletinCopilot/threatbulletinlogicapp.png differ diff --git a/Logic Apps/ThreatactorCopilot/azuredeploy.json b/Logic Apps/ThreatactorCopilot/azuredeploy.json index e1d02a06..5ec4fe95 100644 --- a/Logic Apps/ThreatactorCopilot/azuredeploy.json +++ b/Logic Apps/ThreatactorCopilot/azuredeploy.json @@ -23,7 +23,7 @@ }, "parameters": { "PlaybookName": { - "defaultValue": "ThreatactorCopilot", + "defaultValue": "ThreatactorPlaybook", "type": "string" }, "email": { @@ -128,6 +128,162 @@ }, "type": "Scope" }, + "Copilot_for_Security_prompts": { + "actions": { + "CVES_on_threat_actor": { + "runAfter": { + "Check_for_Mitre_attack_techniques_with_Threat_Actor": [ + "Succeeded" + ] + }, + "type": "ApiConnection", + "inputs": { + "body": { + "PromptContent": "If there are any CVES references in regards to the threat Actor please share them in a table format", + "SessionId": "@body('Check_for_Mitre_attack_techniques_with_Threat_Actor')?['sessionId']", + "SkillName": "GetCvesByIdsDti" + }, + "host": { + "connection": { + "name": "@parameters('$connections')['securitycopilot_1']['connectionId']" + } + }, + "method": "post", + "path": "/process-prompt" + } + }, + "Check_for_Mitre_attack_techniques_with_Threat_Actor": { + "runAfter": { + "Check_for_Threat_articles_on_threat_actor_profile": [ + "Succeeded" + ] + }, + "type": "ApiConnection", + "inputs": { + "body": { + "PromptContent": "Based on the above can you share a list of Mitre attack techniques referencing the Threat Actor", + "SessionId": "@body('Check_for_Threat_articles_on_threat_actor_profile')?['sessionId']", + "SkillName": "FindThreatIntelligence" + }, + "host": { + "connection": { + "name": "@parameters('$connections')['securitycopilot_1']['connectionId']" + } + }, + "method": "post", + "path": "/process-prompt" + } + }, + "Check_for_Threat_articles_on_threat_actor_profile": { + "runAfter": { + "Summary_of_threat_actor": [ + "Succeeded" + ] + }, + "type": "ApiConnection", + "inputs": { + "body": { + "PromptContent": "Based on the above can you share the latest articles referencing the threat actor and when it was published in a table", + "SessionId": "@body('Summary_of_threat_actor')?['sessionId']", + "SkillName": "FindThreatIntelligence" + }, + "host": { + "connection": { + "name": "@parameters('$connections')['securitycopilot_1']['connectionId']" + } + }, + "method": "post", + "path": "/process-prompt" + } + }, + "Hunt_my_environment_against_CVE": { + "runAfter": { + "CVES_on_threat_actor": [ + "Succeeded" + ] + }, + "type": "ApiConnection", + "inputs": { + "body": { + "PromptContent": "Hunt across my defender environment and determine if these CVEs have exploited my devices", + "SessionId": "@body('CVES_on_threat_actor')?['sessionId']", + "SkillName": "NL2KQLDefender" + }, + "host": { + "connection": { + "name": "@parameters('$connections')['securitycopilot_1']['connectionId']" + } + }, + "method": "post", + "path": "/process-prompt" + } + }, + "Recommendations_against_Threat_actor": { + "runAfter": { + "Hunt_my_environment_against_CVE": [ + "Succeeded" + ] + }, + "type": "ApiConnection", + "inputs": { + "body": { + "PromptContent": "Based on your understanding , what recommendations can you provide to protect my environment from the following threat Actor", + "SessionId": "@body('Hunt_my_environment_against_CVE')?['sessionId']", + "SkillName": "FindThreatIntelligence" + }, + "host": { + "connection": { + "name": "@parameters('$connections')['securitycopilot_1']['connectionId']" + } + }, + "method": "post", + "path": "/process-prompt" + } + }, + "Summarize_investigation": { + "runAfter": { + "Recommendations_against_Threat_actor": [ + "Succeeded" + ] + }, + "type": "ApiConnection", + "inputs": { + "body": { + "PromptContent": "/AskGpt Can you create a report , capturing Each and every step in the investigation above ,Make sure to capture the CVEs and their References , the Articles and their references as well , the report is intended for a technical audience and should encapsulate actions that need to be taken in regards to this investigation", + "SessionId": "@body('Recommendations_against_Threat_actor')?['sessionId']" + }, + "host": { + "connection": { + "name": "@parameters('$connections')['securitycopilot_1']['connectionId']" + } + }, + "method": "post", + "path": "/process-prompt" + } + }, + "Summary_of_threat_actor": { + "runAfter": { + }, + "type": "ApiConnection", + "inputs": { + "body": { + "PromptContent": "Share with me a summary of threat actor Hazel Sandstorm", + "SkillName": "FindThreatIntelligence" + }, + "host": { + "connection": { + "name": "@parameters('$connections')['securitycopilot_1']['connectionId']" + } + }, + "method": "post", + "path": "/process-prompt" + } + } + }, + "runAfter": { + }, + "type": "Scope" + }, "Parse_Mitre": { "actions": { "Compose_5": { @@ -238,7 +394,7 @@ } }, "runAfter": { - "Security_Copilot_Prompts": [ + "Copilot_for_Security_prompts": [ "Succeeded" ] }, @@ -360,156 +516,6 @@ }, "type": "Scope" }, - "Security_Copilot_Prompts": { - "actions": { - "CVES_on_threat_actor": { - "runAfter": { - "Check_for_Mitre_attack_techniques_with_Threat_Actor": [ - "Succeeded" - ] - }, - "type": "ApiConnection", - "inputs": { - "body": { - "PromptContent": "If there are any CVES references in regards to the threat Actor please share them in a table format", - "SessionId": "@body('Check_for_Mitre_attack_techniques_with_Threat_Actor')?['sessionId']" - }, - "host": { - "connection": { - "name": "@parameters('$connections')['securitycopilot']['connectionId']" - } - }, - "method": "post", - "path": "/process-prompt" - } - }, - "Check_for_Mitre_attack_techniques_with_Threat_Actor": { - "runAfter": { - "Check_for_Threat_articles_on_threat_actor_profile": [ - "Succeeded" - ] - }, - "type": "ApiConnection", - "inputs": { - "body": { - "PromptContent": "Based on the above can you share a list of Mitre attack techniques referencing the Threat Actor", - "SessionId": "@body('Check_for_Threat_articles_on_threat_actor_profile')?['sessionId']" - }, - "host": { - "connection": { - "name": "@parameters('$connections')['securitycopilot']['connectionId']" - } - }, - "method": "post", - "path": "/process-prompt" - } - }, - "Check_for_Threat_articles_on_threat_actor_profile": { - "runAfter": { - "Summary_of_threat_actor": [ - "Succeeded" - ] - }, - "type": "ApiConnection", - "inputs": { - "body": { - "PromptContent": "Based on the above can you share the latest articles referencing the threat actor and when it was published in a table", - "SessionId": "@body('Summary_of_threat_actor')?['sessionId']" - }, - "host": { - "connection": { - "name": "@parameters('$connections')['securitycopilot']['connectionId']" - } - }, - "method": "post", - "path": "/process-prompt" - } - }, - "Hunt_my_environment_against_CVE": { - "runAfter": { - "CVES_on_threat_actor": [ - "Succeeded" - ] - }, - "type": "ApiConnection", - "inputs": { - "body": { - "PromptContent": "/LookupDataFromDefender365Hunting hunt across my defender environment and determine if these CVEs have exploited my devices", - "SessionId": "@body('CVES_on_threat_actor')?['sessionId']" - }, - "host": { - "connection": { - "name": "@parameters('$connections')['securitycopilot']['connectionId']" - } - }, - "method": "post", - "path": "/process-prompt" - } - }, - "Recommendations_against_Threat_actor": { - "runAfter": { - "Hunt_my_environment_against_CVE": [ - "Succeeded" - ] - }, - "type": "ApiConnection", - "inputs": { - "body": { - "PromptContent": "Based on your understanding , what recommendations can you provide to protect my environment from the following threat Actor", - "SessionId": "@body('Hunt_my_environment_against_CVE')?['sessionId']" - }, - "host": { - "connection": { - "name": "@parameters('$connections')['securitycopilot']['connectionId']" - } - }, - "method": "post", - "path": "/process-prompt" - } - }, - "Summarize_investigation": { - "runAfter": { - "Recommendations_against_Threat_actor": [ - "Succeeded" - ] - }, - "type": "ApiConnection", - "inputs": { - "body": { - "PromptContent": "Can you create a report , capturing Each and every step in the investigation above ,Make sure to capture the CVEs and their References , the Articles and their references as well , the report is intended for a technical audience and should encapsulate actions that need to be taken in regards to this investigation", - "SessionId": "@body('Recommendations_against_Threat_actor')?['sessionId']" - }, - "host": { - "connection": { - "name": "@parameters('$connections')['securitycopilot']['connectionId']" - } - }, - "method": "post", - "path": "/process-prompt" - } - }, - "Summary_of_threat_actor": { - "runAfter": { - }, - "type": "ApiConnection", - "inputs": { - "body": { - "PromptContent": "Share with me a summary of threat actor APT 28" - }, - "host": { - "connection": { - "name": "@parameters('$connections')['securitycopilot']['connectionId']" - } - }, - "method": "post", - "path": "/process-prompt" - } - } - }, - "runAfter": { - }, - "type": "Scope" - }, "Send_an_email_(V2)": { "runAfter": { "Summarize_the_report": [ @@ -521,12 +527,12 @@ "body": { "Body": "\u003cp\u003e@{outputs('Compose_4')}\u003c/p\u003e", "Importance": "Normal", - "Subject": "Threat Actor Profle Analysis - APT28", + "Subject": "Threat Actor Profle Analysis ", "To": "@parameters('email')" }, "host": { "connection": { - "name": "@parameters('$connections')['office365_1']['connectionId']" + "name": "@parameters('$connections')['office365']['connectionId']" } }, "method": "post", @@ -656,12 +662,12 @@ "parameters": { "$connections": { "value": { - "office365_1": { + "office365": { "connectionId": "[resourceId('Microsoft.Web/connections', variables('Office365ConnectionName'))]", "connectionName": "[variables('Office365ConnectionName')]", "id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/Office365')]" }, - "securitycopilot": { + "securitycopilot_1": { "connectionId": "[resourceId('Microsoft.Web/connections', variables('SecuritycopilotConnectionName'))]", "connectionName": "[variables('SecuritycopilotConnectionName')]", "id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/Securitycopilot')]" @@ -675,7 +681,7 @@ "location": "[resourceGroup().location]", "tags": { "Created By": "Sean Wesonga", - "hidden-SentinelTemplateName": "ThreatactorCopilot", + "hidden-SentinelTemplateName": "", "hidden-SentinelTemplateVersion": "1.0" }, "identity": { diff --git a/Logic Apps/ThreatactorCopilot/readme.md b/Logic Apps/ThreatactorCopilot/readme.md index 3a397fb1..837e4b33 100644 --- a/Logic Apps/ThreatactorCopilot/readme.md +++ b/Logic Apps/ThreatactorCopilot/readme.md @@ -1,4 +1,4 @@ -# Copilot-For-Security-ThreatActor +# Security Copilot-ThreatActor Author: Sean Wasonga ## Overview This playbook emphasizes the capability to conduct thorough threat actor profiling, search for associated threat articles, CVEs, and perform comprehensive hunting across your environment. By leveraging Security Copilot's integration with Microsoft Defender Threat Intelligence (MDTI), users can efficiently gather intelligence on threat actors, associated articles, and CVEs, enhancing their understanding of potential threats. Additionally, the playbook facilitates proactive investigation by summarizing the findings from hunting activities, empowering users to strengthen their security posture. As this foundational playbook evolves, it aims to further enhance threat intelligence gathering and investigation capabilities. @@ -6,16 +6,16 @@ This playbook emphasizes the capability to conduct thorough threat actor profili ## 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 toaccess M365 Defender environment -• The user authenticated within the CoPilot logic app action and has permission to send emails. +- 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 toaccess M365 Defender environment +- The user authenticated within the CoPilot logic app action and has permission to send emails. Define the email you want the daily logic app to be sent too ## Deployment - + 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/IngestSecurityCopilotAuditlogs/README.md b/Monitoring/IngestSecurityCopilotAuditlogs/README.md new file mode 100644 index 00000000..58821495 --- /dev/null +++ b/Monitoring/IngestSecurityCopilotAuditlogs/README.md @@ -0,0 +1,36 @@ +# Ingest Microsoft Security Copilot Audit logs and Deploy Audit Workbook +Technical community blog : [Monitor User Activities and System Events with Security Copilot](https://techcommunity.microsoft.com/blog/securitycopilotblog/monitor-user-activities-and-system-events-with-security-copilot-and-microsoft-se/4303368) + +Auditing is a critical feature in any security solution, providing visibility into user activities and system events, detect usage and activities anomalies, and ensure compliance with regulatory requirements. By sending Copilot for Security audit logs into your cloud native SIEM - Microsoft Sentinel, users can gain deeper insights into their Security Copilot usage and take proactive measures to mitigate risks + +# Prerequisites +### Enable the audit log capability in Security Copilot +During the first run experience, a Security Administrator is given the option of opting into allowing Microsoft Purview to access, process, copy and store admin actions, user actions, and Copilot responses. For more information, see Get started with Security Copilot. + +Security Administrators can also access this option through the Owner settings page. + +Use the following steps to update the audit log settings: + +1. Sign in to Microsoft Security Copilot (https://securitycopilot.microsoft.com). +2. Select the home menu icon. +3. Navigate to the Owner settings > Logging audit data in Microsoft Purview. +![Security Copilot](./images/cfs_setting_1.png)
+ +### Enable Microsoft Defender for Cloud Apps logs via Microsoft Defender XDR Data connector +To integrate with Microsoft Defender XDR make sure you have: +1. Sentinel Log Analytics Workspace: read and write permissions. +2. Connector Access Control: the user applying changes to the connector must be a member of the Microsoft Entra ID associated with the tenant that the workspace belongs to. +3. Tenant Permissions: 'Global Administrator' or 'Security Administrator' on the workspace's tenant. +4. License: M365 E5, M365 A5 or any other Microsoft Defender XDR eligible license. + +![Microsoft Defender XDR](./images/sentinel_XDR.png)
+ + +### Deploy Analytical Rules +[![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%2FIngestSecurityCopilotAuditlogs%2Fazuredeploy_analytics.json) +![image](https://github.com/user-attachments/assets/c0def0de-9fe0-41e8-9ec4-69866094fa55) + + +### Deploy Workbook +[![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%2FIngestSecurityCopilotAuditlogs%2Fazuredeploy_workbook.json) +![image](https://github.com/user-attachments/assets/ea8f42f4-afb2-4bb2-9f9f-d9c4b44d0a31) diff --git a/Monitoring/IngestSecurityCopilotAuditlogs/azuredeploy_analytics.json b/Monitoring/IngestSecurityCopilotAuditlogs/azuredeploy_analytics.json new file mode 100644 index 00000000..3fec1ebb --- /dev/null +++ b/Monitoring/IngestSecurityCopilotAuditlogs/azuredeploy_analytics.json @@ -0,0 +1,298 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "LogAnalyticsWorkspaceName": { + "type": "string", + "metadata": { + "description": "Log Analytics Workspace Name" + } + } + }, + "resources": [ + { + "id": "[concat(resourceId('Microsoft.OperationalInsights/workspaces/providers', parameters('LogAnalyticsWorkspaceName'), 'Microsoft.SecurityInsights'),'/alertRules/4807b4a5-1354-4c4a-b8b2-4beb1a07c37e')]", + "name": "[concat(parameters('LogAnalyticsWorkspaceName'),'/Microsoft.SecurityInsights/4807b4a5-1354-4c4a-b8b2-4beb1a07c37e')]", + "type": "Microsoft.OperationalInsights/workspaces/providers/alertRules", + "kind": "Scheduled", + "apiVersion": "2023-12-01-preview", + "properties": { + "displayName": "Security Copilot - TI map IP entity to Security Copilot Audit Prompts", + "description": "Identifies a match in SecurityCopilot Audit Logs prompts activity from any IP IOC from TI", + "severity": "Medium", + "enabled": false, + "query": "let dt_lookBack = 1h;\r\nlet ioc_lookBack = 14d;\r\nThreatIntelligenceIndicator\r\n| where TimeGenerated >= ago(ioc_lookBack) and ExpirationDateTime > now()\r\n| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId\r\n| where Active == true\r\n// Picking up only IOC's that contain the entities we want\r\n| where isnotempty(NetworkIP) or isnotempty(EmailSourceIpAddress) or isnotempty(NetworkDestinationIP) or isnotempty(NetworkSourceIP)\r\n// As there is potentially more than 1 indicator type for matching IP, taking NetworkIP first, then others if that is empty.\r\n// Taking the first non-empty value based on potential IOC match availability\r\n| extend TI_ipEntity = iff(isnotempty(NetworkIP), NetworkIP, NetworkDestinationIP)\r\n| extend TI_ipEntity = iff(isempty(TI_ipEntity) and isnotempty(NetworkSourceIP), NetworkSourceIP, TI_ipEntity)\r\n| extend TI_ipEntity = iff(isempty(TI_ipEntity) and isnotempty(EmailSourceIpAddress), EmailSourceIpAddress, TI_ipEntity)\r\n//Exclude local addresses, using the ipv4_is_private operator\r\n| where ipv4_is_private(TI_ipEntity) == false and TI_ipEntity !startswith \"fe80\" and TI_ipEntity !startswith \"::\" and TI_ipEntity !startswith \"127.\"\r\n// using innerunique to keep perf fast and result set low, we only need one match to indicate potential malicious activity that needs to be investigated\r\n| join kind=innerunique (\r\n CloudAppEvents\r\n| where parse_json(RawEventData)[\"AppIdentity\"] == 'Copilot.Security.SecurityCopilot'\r\n| where parse_json(RawEventData)[\"Workload\"] == 'Copilot'\r\n| extend RecordType = RawEventData.RecordType\r\n| where RecordType == 261\r\n| where TimeGenerated >= ago(dt_lookBack)\r\n| extend Operation = RawEventData.Operation\r\n| extend ClientIP = tostring(RawEventData.ClientIP)\r\n| project TimeGenerated, Operation , sessionoid = tostring(parse_json(tostring(RawEventData.CopilotEventData)).CorrelationId), EvaluationId = tostring(parse_json(tostring(parse_json(tostring(RawEventData.CopilotEventData)).Messages))[0].Id), ISPrompt = tostring(parse_json(tostring(parse_json(tostring(RawEventData.CopilotEventData)).Messages))[0].isPrompt), UserId = AccountDisplayName, RecordType, ClientIP, AccountDisplayName, AppHost = tostring(parse_json(tostring(RawEventData.CopilotEventData)).AppHost)\r\n| where isnotempty(ClientIP)\r\n| extend CFS_TimeGenerated = TimeGenerated\r\n)\r\non $left.TI_ipEntity == $right.ClientIP\r\n| where CFS_TimeGenerated < ExpirationDateTime\r\n| summarize CFS_TimeGenerated = arg_max(CFS_TimeGenerated, *) by IndicatorId, ClientIP\r\n| project CFS_TimeGenerated, Description, ActivityGroupNames, IndicatorId, ThreatType, Url, ExpirationDateTime, ConfidenceScore, TI_ipEntity, ClientIP, NetworkIP, NetworkDestinationIP, NetworkSourceIP, UserId,EmailSourceIpAddress\r\n| extend timestamp = CFS_TimeGenerated, AccountCustomEntity = UserId, IPCustomEntity = ClientIP", + "queryFrequency": "PT1H", + "queryPeriod": "P14D", + "triggerOperator": "GreaterThan", + "triggerThreshold": 0, + "suppressionDuration": "PT5H", + "suppressionEnabled": false, + "startTimeUtc": null, + "tactics": [ + "Impact" + ], + "techniques": [], + "subTechniques": [], + "alertRuleTemplateName": null, + "incidentConfiguration": { + "createIncident": true, + "groupingConfiguration": { + "enabled": false, + "reopenClosedIncident": false, + "lookbackDuration": "PT5H", + "matchingMethod": "AllEntities", + "groupByEntities": [], + "groupByAlertDetails": [], + "groupByCustomDetails": [] + } + }, + "eventGroupingSettings": { + "aggregationKind": "SingleAlert" + }, + "alertDetailsOverride": null, + "customDetails": null, + "entityMappings": [ + { + "entityType": "Account", + "fieldMappings": [ + { + "identifier": "FullName", + "columnName": "AccountCustomEntity" + } + ] + }, + { + "entityType": "IP", + "fieldMappings": [ + { + "identifier": "Address", + "columnName": "IPCustomEntity" + } + ] + } + ], + "sentinelEntitiesMappings": null, + "templateVersion": null + } + }, + { + "id": "[concat(resourceId('Microsoft.OperationalInsights/workspaces/providers', parameters('LogAnalyticsWorkspaceName'), 'Microsoft.SecurityInsights'),'/alertRules/ea3b0624-539a-440c-b20d-687daac975a7')]", + "name": "[concat(parameters('LogAnalyticsWorkspaceName'),'/Microsoft.SecurityInsights/ea3b0624-539a-440c-b20d-687daac975a7')]", + "type": "Microsoft.OperationalInsights/workspaces/providers/alertRules", + "kind": "Scheduled", + "apiVersion": "2023-12-01-preview", + "properties": { + "displayName": "Security Copilot - Anomalous Operations by Security Copilot for User", + "description": "Detect Anomalous audit operations involving actions such as \"DisableCopilotPlugin\" , \"DeleteFile\" , \"UpdatePluginSettings\" , or \"DeleteCopilotPromptBook\"", + "severity": "Medium", + "enabled": true, + "query": "CloudAppEvents\r\n| where parse_json(RawEventData)[\"AppIdentity\"] == 'Copilot.Security.SecurityCopilot'\r\n| where parse_json(RawEventData)[\"Workload\"] == 'Copilot'\r\n| extend ClientIP = tostring(RawEventData.ClientIP)\r\n| where isnotempty( AccountDisplayName) and isnotempty( ClientIP)\r\n| extend Operation = RawEventData.Operation\r\n| where Operation contains \"DisableCopilotPlugin\" or Operation contains \"DeleteFile\" or Operation contains \"UpdatePluginSettings\" or Operation contains \"DeleteCopilotPromptBook\"\r\n| project TimeGenerated, AccountDisplayName, ClientIP\r\n| evaluate basket()\r\n| join kind=leftouter (CloudAppEvents\r\n| extend Operation = RawEventData.Operation\r\n| extend ClientIP = tostring(RawEventData.ClientIP)\r\n| where Operation contains \"DisableCopilotPlugin\" or Operation contains \"DeleteFile\" or Operation contains \"UpdatePluginSettings\" or Operation contains \"DeleteCopilotPromptBook\"\r\n| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by AccountDisplayName, ClientIP) on AccountDisplayName,ClientIP\r\n| extend timestamp = StartTime, AccountCustomEntity = AccountDisplayName, IPCustomEntity = ClientIP , ClientIP\r\n| project-away AccountDisplayName1, ClientIP1\r\n| extend timestamp = StartTime, AccountCustomEntity = AccountDisplayName, IPCustomEntity = ClientIP", + "queryFrequency": "PT5H", + "queryPeriod": "PT5H", + "triggerOperator": "GreaterThan", + "triggerThreshold": 0, + "suppressionDuration": "PT5H", + "suppressionEnabled": false, + "startTimeUtc": null, + "tactics": [ + "Execution" + ], + "techniques": [ + "T0863", + "T1651" + ], + "subTechniques": [], + "alertRuleTemplateName": null, + "incidentConfiguration": { + "createIncident": true, + "groupingConfiguration": { + "enabled": false, + "reopenClosedIncident": false, + "lookbackDuration": "PT5H", + "matchingMethod": "AllEntities", + "groupByEntities": [], + "groupByAlertDetails": [], + "groupByCustomDetails": [] + } + }, + "eventGroupingSettings": { + "aggregationKind": "SingleAlert" + }, + "alertDetailsOverride": null, + "customDetails": null, + "entityMappings": [ + { + "entityType": "Account", + "fieldMappings": [ + { + "identifier": "Name", + "columnName": "AccountCustomEntity" + } + ] + }, + { + "entityType": "IP", + "fieldMappings": [ + { + "identifier": "Address", + "columnName": "ClientIP" + } + ] + } + ], + "sentinelEntitiesMappings": null, + "templateVersion": null + } + }, + { + "id": "[concat(resourceId('Microsoft.OperationalInsights/workspaces/providers', parameters('LogAnalyticsWorkspaceName'), 'Microsoft.SecurityInsights'),'/alertRules/dd3244af-ee41-4cf6-885c-a50d426d27fe')]", + "name": "[concat(parameters('LogAnalyticsWorkspaceName'),'/Microsoft.SecurityInsights/dd3244af-ee41-4cf6-885c-a50d426d27fe')]", + "type": "Microsoft.OperationalInsights/workspaces/providers/alertRules", + "kind": "Scheduled", + "apiVersion": "2023-12-01-preview", + "properties": { + "displayName": "Security Copilot - Anomalous sign-in activity by Security Copilot user", + "description": "This rule detects anomalous user log on and resource access associated with usage of Copilot for Security where any of these operations have been executed: DeleteCopilotPromptBook,DisableCopilotPlugin,DeleteFile or EnableCopilotPlugin", + "severity": "Medium", + "enabled": false, + "query": "BehaviorAnalytics\r\n| where ActionType =~ \"Sign-in\"\r\n| where ActivityInsights.FirstTimeConnectionFromCountryObservedInTenant == True or ActivityInsights.CountryUncommonlyConnectedFromAmongPeers == True or ActivityInsights.ResourceUncommonlyAccessedAmongPeers == True \r\n| join kind=inner (\r\n SigninLogs\r\n )\r\n on $left.SourceRecordId == $right._ItemId\r\n| extend\r\n UserPrincipalName = iff(UserPrincipalName has \"#EXT#\", replace_string(tostring(split(UserPrincipalName, \"#\")[0]), \"_\", \"@\"), UserPrincipalName),\r\n UserName = iff(UserName has \"#EXT#\", replace_string(tostring(split(UserPrincipalName, \"#\")[0]), \"_\", \"@\"), UserName)\r\n| join CloudAppEvents on $left.UserPrincipalName == $right.AccountDisplayName\r\n| where parse_json(RawEventData)[\"AppIdentity\"] == 'Copilot.Security.SecurityCopilot'\r\n| where parse_json(RawEventData)[\"Workload\"] == 'Copilot'\r\n| extend Operation = RawEventData.Operation\r\n| extend BlastRadius_ = tostring(UsersInsights.BlastRadius)\r\n| where Operation in (\"DeleteCopilotPromptBook\",\"DisableCopilotPlugin\",\"DeleteFile\",\"EnableCopilotPlugin\")\r\n| project\r\n TimeGenerated,\r\n AccountDisplayName,\r\n UserPrincipalName,\r\n UsersInsights,\r\n ActivityType,\r\n ActionType,\r\n [\"Evidence\"]=ActivityInsights,\r\n ResourceDisplayName,\r\n AppDisplayName,\r\n SourceIPAddress,\r\n SourceIPLocation,\r\n SourceDevice,\r\n DevicesInsights,\r\n InvestigationPriority,\r\n BlastRadius_\r\n | extend\r\n Name=split(UserPrincipalName, \"@\")[0],\r\n UPNSuffix=split(UserPrincipalName, \"@\")[1]", + "queryFrequency": "PT5H", + "queryPeriod": "PT5H", + "triggerOperator": "GreaterThan", + "triggerThreshold": 0, + "suppressionDuration": "PT5H", + "suppressionEnabled": false, + "startTimeUtc": null, + "tactics": [ + "InitialAccess" + ], + "techniques": [ + "T1078" + ], + "subTechniques": [], + "alertRuleTemplateName": null, + "incidentConfiguration": { + "createIncident": true, + "groupingConfiguration": { + "enabled": false, + "reopenClosedIncident": false, + "lookbackDuration": "PT5H", + "matchingMethod": "AllEntities", + "groupByEntities": [], + "groupByAlertDetails": [], + "groupByCustomDetails": [] + } + }, + "eventGroupingSettings": { + "aggregationKind": "SingleAlert" + }, + "alertDetailsOverride": null, + "customDetails": null, + "entityMappings": [ + { + "entityType": "Account", + "fieldMappings": [ + { + "identifier": "Name", + "columnName": "AccountDisplayName" + } + ] + }, + { + "entityType": "CloudApplication", + "fieldMappings": [ + { + "identifier": "Name", + "columnName": "AppDisplayName" + } + ] + }, + { + "entityType": "IP", + "fieldMappings": [ + { + "identifier": "Address", + "columnName": "SourceIPAddress" + } + ] + } + ], + "sentinelEntitiesMappings": null, + "templateVersion": null + } + }, + { + "id": "[concat(resourceId('Microsoft.OperationalInsights/workspaces/providers', parameters('LogAnalyticsWorkspaceName'), 'Microsoft.SecurityInsights'),'/alertRules/5e5fc31d-d9e0-4fad-b659-b183b0643aac')]", + "name": "[concat(parameters('LogAnalyticsWorkspaceName'),'/Microsoft.SecurityInsights/5e5fc31d-d9e0-4fad-b659-b183b0643aac')]", + "type": "Microsoft.OperationalInsights/workspaces/providers/alertRules", + "kind": "Scheduled", + "apiVersion": "2023-12-01-preview", + "properties": { + "displayName": "Security Copilot-Audit logging settings changes", + "description": "This rule detects changes to logging settings", + "severity": "Medium", + "enabled": true, + "query": "CloudAppEvents\r\n| where parse_json(RawEventData)[\"AppIdentity\"] == 'Copilot.Security.SecurityCopilot'\r\n| where parse_json(RawEventData)[\"Workload\"] == 'Copilot'\r\n| where RawEventData.Operation == \"UpdateCopilotSettings\"\r\n| extend NewSetting = case(tostring(parse_json(tostring(parse_json(tostring(RawEventData.CopilotSettingsEventData)).Resource))[0].NewValue) == \"False\", \"Off\", tostring(parse_json(tostring(parse_json(tostring(RawEventData.CopilotSettingsEventData)).Resource))[0].NewValue) == \"True\", \"On\", tostring(parse_json(tostring(parse_json(tostring(RawEventData.CopilotSettingsEventData)).Resource))[0].NewValue))\r\n| extend PreviousSetting = case(tostring(parse_json(tostring(parse_json(tostring(RawEventData.CopilotSettingsEventData)).Resource))[0].OriginalValue) == \"False\", \"Off\", tostring(parse_json(tostring(parse_json(tostring(RawEventData.CopilotSettingsEventData)).Resource))[0].OriginalValue) == \"True\", \"On\", tostring(parse_json(tostring(parse_json(tostring(RawEventData.CopilotSettingsEventData)).Resource))[0].OriginalValue))\r\n| extend SettingChanged = tostring(parse_json(tostring(parse_json(tostring(RawEventData.CopilotSettingsEventData)).Resource))[0].Property)\r\n| project TimeGenerated, User= AccountDisplayName, Operation=RawEventData.Operation, PreviousSetting, NewSetting, SettingChanged, ClientIP = tostring(RawEventData.ClientIP)", + "queryFrequency": "P1D", + "queryPeriod": "P14D", + "triggerOperator": "GreaterThan", + "triggerThreshold": 0, + "suppressionDuration": "PT5H", + "suppressionEnabled": false, + "startTimeUtc": null, + "tactics": [ + "DefenseEvasion" + ], + "techniques": [], + "subTechniques": [], + "alertRuleTemplateName": null, + "incidentConfiguration": { + "createIncident": true, + "groupingConfiguration": { + "enabled": false, + "reopenClosedIncident": false, + "lookbackDuration": "PT5H", + "matchingMethod": "AllEntities", + "groupByEntities": [], + "groupByAlertDetails": [], + "groupByCustomDetails": [] + } + }, + "eventGroupingSettings": { + "aggregationKind": "SingleAlert" + }, + "alertDetailsOverride": { + "alertDynamicProperties": [] + }, + "customDetails": { + "Operation": "Operation", + "PreviousSetting": "PreviousSetting", + "NewSetting": "NewSetting" + }, + "entityMappings": [ + { + "entityType": "IP", + "fieldMappings": [ + { + "identifier": "Address", + "columnName": "ClientIP" + } + ] + }, + { + "entityType": "Account", + "fieldMappings": [ + { + "identifier": "AadUserId", + "columnName": "User" + } + ] + } + ], + "sentinelEntitiesMappings": null, + "templateVersion": null + } + } + ] +} \ No newline at end of file diff --git a/Monitoring/IngestSecurityCopilotAuditlogs/azuredeploy_workbook.json b/Monitoring/IngestSecurityCopilotAuditlogs/azuredeploy_workbook.json new file mode 100644 index 00000000..7dd43557 --- /dev/null +++ b/Monitoring/IngestSecurityCopilotAuditlogs/azuredeploy_workbook.json @@ -0,0 +1,40 @@ +{ + "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "WorkbookDisplayName": { + "type": "string", + "defaultValue": "Security Copilot Audit", + "metadata": { + "description": "Name for the workbook that is used in the Gallery or Saved List. This name must be unique within a resource group." + } + }, + "LogAnalyticsWorkspaceName": { + "type": "string", + "metadata": { + "description": "Log Analytics Workspace Name" + } + } + }, + "variables": { + "WorkbookSourceId": "[concat('/subscriptions/', subscription().subscriptionId, '/resourceGroups/', resourceGroup().name, '/providers/Microsoft.OperationalInsights/workspaces/', parameters('LogAnalyticsWorkspaceName'))]" + }, + "resources": [ + { + "name": "fcfe6645-a34b-4168-b42a-df2e1d0deceb", + "type": "microsoft.insights/workbooks", + "location": "[resourceGroup().location]", + "apiVersion": "2022-04-01", + "dependsOn": [], + "kind": "shared", + "properties": { + "displayName": "[parameters('WorkbookDisplayName')]", + "serializedData": "{\"version\":\"Notebook/1.0\",\"items\":[{\"type\":1,\"content\":{\"json\":\"# [Microsoft Security Copilot Audit Workbook](https://learn.microsoft.com/en-us/copilot/security/microsoft-security-copilot)\\n---\\n\\nMicrosoft Security Copilot is a generative AI-powered security solution designed to enhance the efficiency and capabilities of security professionals\\n\\nIt supports end-to-end scenarios such as incident response, threat hunting, intelligence gathering, and posture management\\nBy integrating with products like Microsoft Defender XDR, Microsoft Sentinel, and Microsoft Intune, as well as third-party services like ServiceNow, Security Copilot leverages security-specific plugins, organizational data, authoritative sources, and global threat intelligence\\n\\nThis enables security professionals to gain wider visibility into threats, prioritize response efforts, and streamline decision-making\\n\\nCopilot for Security provides actionable guidance for incident response, translating complex security alerts into concise summaries and offering step-by-step directions for triage, investigation, containment, and remediation.\"},\"name\":\"text - 2\"},{\"type\":11,\"content\":{\"version\":\"LinkItem/1.0\",\"style\":\"tabs\",\"links\":[{\"id\":\"f16d570f-12c1-48f2-94fa-7e114263a291\",\"cellValue\":\"Nav\",\"linkTarget\":\"parameter\",\"linkLabel\":\"Security Copilot Audit\",\"subTarget\":\"audit\",\"preText\":\"Copilot for Security Audit Data\",\"style\":\"link\"},{\"id\":\"ab2c8e5c-1a0f-4041-ab18-c9b387ecf33b\",\"cellValue\":\"Nav\",\"linkTarget\":\"parameter\",\"linkLabel\":\"Security Copilot Sign in Data\",\"subTarget\":\"Signin\",\"style\":\"link\"},{\"id\":\"03e3f1de-2a0f-4f14-ad2f-cba53365c4b3\",\"cellValue\":\"Nav\",\"linkTarget\":\"parameter\",\"linkLabel\":\"Security Copilot SCU Events\",\"subTarget\":\"SCU\",\"style\":\"link\"}]},\"name\":\"links - 2\",\"styleSettings\":{\"padding\":\"0\",\"showBorder\":true}},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"items\":[{\"type\":9,\"content\":{\"version\":\"KqlParameterItem/1.0\",\"crossComponentResources\":[\"value::selected\"],\"parameters\":[{\"id\":\"41bb3efb-b37d-47d6-851a-64929f841597\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"DefaultSubscription_Internal\",\"type\":1,\"isRequired\":true,\"query\":\"where type =~ 'microsoft.operationalinsights/workspaces'\\r\\n| take 1\\r\\n| project subscriptionId\",\"crossComponentResources\":[\"value::selected\"],\"isHiddenWhenLocked\":true,\"queryType\":1,\"resourceType\":\"microsoft.resourcegraph/resources\"},{\"id\":\"e3388fc6-e10b-4a86-bdc1-22677adcb351\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"TimeRange\",\"type\":4,\"typeSettings\":{\"selectableValues\":[{\"durationMs\":300000},{\"durationMs\":900000},{\"durationMs\":1800000},{\"durationMs\":3600000},{\"durationMs\":14400000},{\"durationMs\":43200000},{\"durationMs\":86400000},{\"durationMs\":172800000},{\"durationMs\":259200000},{\"durationMs\":604800000},{\"durationMs\":1209600000},{\"durationMs\":2419200000},{\"durationMs\":2592000000},{\"durationMs\":5184000000},{\"durationMs\":7776000000}]},\"timeContext\":{\"durationMs\":86400000},\"value\":{\"durationMs\":2419200000}},{\"id\":\"eede023b-bfca-4112-accb-440efb5709ab\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"Subscription\",\"type\":6,\"query\":\"summarize by subscriptionId\\r\\n| project value = strcat(\\\"/subscriptions/\\\", subscriptionId), label = subscriptionId, selected = iff(subscriptionId =~ '{DefaultSubscription_Internal}', true, false)\\r\\n\",\"crossComponentResources\":[\"value::selected\"],\"typeSettings\":{\"additionalResourceOptions\":[],\"showDefault\":false},\"queryType\":1,\"resourceType\":\"microsoft.resourcegraph/resources\"},{\"id\":\"b68ed14e-d0e2-41b2-9444-b38c88394beb\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"Workspace\",\"type\":5,\"query\":\"where type =~ 'microsoft.operationalinsights/workspaces'\\r\\n| project id\",\"crossComponentResources\":[\"{Subscription}\"],\"typeSettings\":{\"resourceTypeFilter\":{\"microsoft.operationalinsights/workspaces\":true},\"additionalResourceOptions\":[]},\"queryType\":1,\"resourceType\":\"microsoft.resourcegraph/resources\",\"value\":\"variables('WorkbookSourceId')\"},{\"id\":\"1ba9eace-73f1-4958-a0a7-11bdb68195b9\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"resourceGroup\",\"type\":1,\"query\":\"resources\\r\\n| where type =~ 'microsoft.operationalinsights/workspaces'\\r\\n| where id == \\\"{Workspace}\\\"\\r\\n| project resourceGroup\",\"crossComponentResources\":[\"value::selected\"],\"isHiddenWhenLocked\":true,\"queryType\":1,\"resourceType\":\"microsoft.resourcegraph/resources\"}],\"style\":\"pills\",\"queryType\":1,\"resourceType\":\"microsoft.resourcegraph/resources\"},\"name\":\"parameters - 11 - Copy\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let IP_Data = \\r\\n external_data(network:string,geoname_id:long,continent_code:string,continent_name:string ,country_iso_code:string, country_name:string,is_anonymous_proxy:bool,is_satellite_provider:bool)\\r\\n [@\\\"https://raw.githubusercontent.com/datasets/geoip2-ipv4/master/data/geoip2-ipv4.csv\\\"]\\r\\n with (ignoreFirstRecord=true, format=\\\"csv\\\");\\r\\nIdentityLogonEvents\\r\\n| where AdditionalFields.[\\\"ARG.CLOUD_SERVICE\\\"] == \\\"Security Copilot\\\"\\r\\n| extend IPaddresses=tostring(IPAddress)\\r\\n| where isnotempty(IPaddresses) \\r\\n| evaluate ipv4_lookup(IP_Data, IPaddresses, network)\\r\\n| summarize interactioncount = count() by IPAddress, country_name\\r\\n\",\"size\":2,\"title\":\"Succesfull Sign ins By Location\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{Workspace}\"],\"visualization\":\"map\",\"mapSettings\":{\"locInfo\":\"CountryRegion\",\"locInfoColumn\":\"country_name\",\"sizeSettings\":\"interactioncount\",\"sizeAggregation\":\"Sum\",\"legendMetric\":\"interactioncount\",\"legendAggregation\":\"Sum\",\"itemColorSettings\":{\"nodeColorField\":\"interactioncount\",\"colorAggregation\":\"Sum\",\"type\":\"heatmap\",\"heatmapPalette\":\"greenRed\"}}},\"customWidth\":\"50\",\"name\":\"query - 3\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let IP_Data = \\r\\n external_data(network:string,geoname_id:long,continent_code:string,continent_name:string ,country_iso_code:string, country_name:string,is_anonymous_proxy:bool,is_satellite_provider:bool)\\r\\n [@\\\"https://raw.githubusercontent.com/datasets/geoip2-ipv4/master/data/geoip2-ipv4.csv\\\"]\\r\\n with (ignoreFirstRecord=true, format=\\\"csv\\\");\\r\\nBehaviorAnalytics\\r\\n| where ActivityInsights.App == \\\"Security Copilot\\\"\\r\\n| where ActivityInsights.Resource == \\\"Security Copilot\\\"\\r\\n| where ActivityType == \\\"FailedLogOn\\\"\\r\\n| extend IPaddresses=tostring(SourceIPAddress)\\r\\n| where isnotempty(IPaddresses) \\r\\n| evaluate ipv4_lookup(IP_Data, IPaddresses, network)\\r\\n| summarize interactioncount = count() by SourceIPAddress, country_name\\r\\n\",\"size\":2,\"title\":\"Failed Sign ins by Location \",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{Workspace}\"],\"visualization\":\"map\",\"mapSettings\":{\"locInfo\":\"CountryRegion\",\"locInfoColumn\":\"country_name\",\"sizeSettings\":\"interactioncount\",\"sizeAggregation\":\"Sum\",\"legendMetric\":\"interactioncount\",\"legendAggregation\":\"Sum\",\"itemColorSettings\":{\"nodeColorField\":\"interactioncount\",\"colorAggregation\":\"Sum\",\"type\":\"heatmap\",\"heatmapPalette\":\"greenRed\"}}},\"customWidth\":\"50\",\"name\":\"query - 5\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"IdentityLogonEvents\\r\\n| where TimeGenerated >= ago(24h)\\r\\n| where AdditionalFields.[\\\"ARG.CLOUD_SERVICE\\\"] == \\\"Security Copilot\\\"\\r\\n| extend User = AdditionalFields.[\\\"ACTOR.ALIAS\\\"]\\r\\n| project AccountDomain, User, ActionType, AccountUpn, IPAddress, Location, ISP, OSPlatform, DeviceType\",\"size\":0,\"title\":\"Successfull Sign ins for Security Copilot\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{Workspace}\"]},\"customWidth\":\"100\",\"name\":\"query - 0\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"//Failed signins to the CfS service exposing user, reason, and other necessary information.\\r\\n\\r\\nBehaviorAnalytics\\r\\n| where TimeGenerated >= ago(7d)\\r\\n| where ActivityInsights.App == \\\"Security Copilot\\\"\\r\\n| where ActivityInsights.Resource == \\\"Security Copilot\\\"\\r\\n| where ActivityType == \\\"FailedLogOn\\\"\\r\\n| project UserName, UserPrincipalName, ActionType, EventSource, SourceIPAddress, SourceIPLocation\",\"size\":0,\"title\":\"Failed Sign ins for Security Copilot\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"ActionType\",\"formatter\":18,\"formatOptions\":{\"thresholdsOptions\":\"colors\",\"thresholdsGrid\":[{\"operator\":\"contains\",\"thresholdValue\":\"User did not pass the MFA challenge\",\"representation\":\"redBright\",\"text\":\"{0}{1}\"},{\"operator\":\"contains\",\"thresholdValue\":\"\\t Other\",\"representation\":\"gray\",\"text\":\"{0}{1}\"},{\"operator\":\"contains\",\"thresholdValue\":\"Invalid username or password \",\"representation\":\"orange\",\"text\":\"{0}{1}\"},{\"operator\":\"contains\",\"thresholdValue\":\"Flow token expired - Authentication Failed\",\"representation\":\"blue\",\"text\":\"{0}{1}\"},{\"operator\":\"contains\",\"thresholdValue\":\"Device Authentication Required\",\"representation\":\"yellow\",\"text\":\"{0}{1}\"},{\"operator\":\"Default\",\"thresholdValue\":null,\"representation\":\"gray\",\"text\":\"{0}{1}\"}]}}]}},\"customWidth\":\"100\",\"name\":\"query - 2\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"BehaviorAnalytics\\r\\n| where TimeGenerated >= ago(7d)\\r\\n| where ActivityInsights.App == \\\"Security Copilot\\\"\\r\\n| where ActivityInsights.Resource == \\\"Security Copilot\\\"\\r\\n| where ActivityType == \\\"FailedLogOn\\\"\\r\\n| summarize Failedlogin = count() by ActionType\\r\\n\",\"size\":0,\"title\":\"Failed Sign ins By Reason\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{Workspace}\"],\"visualization\":\"piechart\"},\"customWidth\":\"30\",\"name\":\"query - 4\"}]},\"conditionalVisibility\":{\"parameterName\":\"Nav\",\"comparison\":\"isEqualTo\",\"value\":\"Signin\"},\"name\":\"group - 3\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"items\":[{\"type\":9,\"content\":{\"version\":\"KqlParameterItem/1.0\",\"crossComponentResources\":[\"value::selected\"],\"parameters\":[{\"id\":\"061dd12a-4223-4b86-8d66-51dd276c35ae\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"TimeRange\",\"type\":4,\"typeSettings\":{\"selectableValues\":[{\"durationMs\":300000},{\"durationMs\":900000},{\"durationMs\":1800000},{\"durationMs\":3600000},{\"durationMs\":14400000},{\"durationMs\":43200000},{\"durationMs\":86400000},{\"durationMs\":172800000},{\"durationMs\":259200000},{\"durationMs\":604800000},{\"durationMs\":1209600000},{\"durationMs\":2419200000},{\"durationMs\":2592000000},{\"durationMs\":5184000000},{\"durationMs\":7776000000}]},\"timeContext\":{\"durationMs\":86400000},\"value\":{\"durationMs\":7776000000}},{\"id\":\"0e871995-794b-4969-a964-4d4aeaa29e9b\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"DefaultSubscription_Internal\",\"type\":1,\"isRequired\":true,\"query\":\"where type =~ 'microsoft.operationalinsights/workspaces'\\r\\n| take 1\\r\\n| project subscriptionId\",\"crossComponentResources\":[\"value::selected\"],\"isHiddenWhenLocked\":true,\"queryType\":1,\"resourceType\":\"microsoft.resourcegraph/resources\"},{\"id\":\"88276ecc-5d60-47cd-acfc-e1e61c4e3545\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"Subscription\",\"type\":6,\"query\":\"summarize by subscriptionId\\r\\n| project value = strcat(\\\"/subscriptions/\\\", subscriptionId), label = subscriptionId, selected = iff(subscriptionId =~ '{DefaultSubscription_Internal}', true, false)\",\"crossComponentResources\":[\"value::selected\"],\"typeSettings\":{\"additionalResourceOptions\":[],\"showDefault\":false},\"queryType\":1,\"resourceType\":\"microsoft.resourcegraph/resources\"},{\"id\":\"ca3cd047-6606-44e1-87a6-117bf68ab98e\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"Workspace\",\"type\":5,\"query\":\"where type =~ 'microsoft.operationalinsights/workspaces'\\r\\n| project id\",\"crossComponentResources\":[\"{Subscription}\"],\"typeSettings\":{\"resourceTypeFilter\":{\"microsoft.operationalinsights/workspaces\":true},\"additionalResourceOptions\":[]},\"queryType\":1,\"resourceType\":\"microsoft.resourcegraph/resources\",\"value\":null},{\"id\":\"81626c63-e609-4a86-9d65-c57d01c8307e\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"resourceGroup\",\"type\":1,\"query\":\"resources\\r\\n| where type =~ 'microsoft.operationalinsights/workspaces'\\r\\n| where id == \\\"{Workspace}\\\"\\r\\n| project resourceGroup\",\"crossComponentResources\":[\"value::selected\"],\"isHiddenWhenLocked\":true,\"queryType\":1,\"resourceType\":\"microsoft.resourcegraph/resources\"}],\"style\":\"pills\",\"queryType\":1,\"resourceType\":\"microsoft.resourcegraph/resources\"},\"name\":\"parameters - 11\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"CloudAppEvents\\r\\n| where parse_json(RawEventData)[\\\"AppIdentity\\\"] == 'Copilot.Security.SecurityCopilot'\\r\\n| where parse_json(RawEventData)[\\\"Workload\\\"] == 'Copilot'\\r\\n| distinct AccountDisplayName\\r\\n| count\",\"size\":3,\"title\":\"Total number of users for Security Copilot\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{Workspace}\"],\"visualization\":\"tiles\",\"tileSettings\":{\"titleContent\":{\"columnMatch\":\"Count\",\"formatter\":8,\"formatOptions\":{\"palette\":\"orangeDark\"},\"numberFormat\":{\"unit\":0,\"options\":{\"style\":\"decimal\"}}},\"showBorder\":true}},\"customWidth\":\"16\",\"name\":\"query - 3\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"CloudAppEvents\\r\\n| where parse_json(RawEventData)[\\\"AppIdentity\\\"] == 'Copilot.Security.SecurityCopilot'\\r\\n| where parse_json(RawEventData)[\\\"Workload\\\"] == 'Copilot'\\r\\n| where RawEventData.RecordType == 261\\r\\n| count \",\"size\":4,\"title\":\"Total No: Prompts\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{Workspace}\"],\"visualization\":\"tiles\",\"tileSettings\":{\"titleContent\":{\"columnMatch\":\"Count\",\"formatter\":8,\"formatOptions\":{\"palette\":\"orangeDark\"},\"numberFormat\":{\"unit\":0,\"options\":{\"style\":\"decimal\"}}},\"showBorder\":true}},\"customWidth\":\"16\",\"name\":\"query - 3 - Copy\",\"styleSettings\":{\"maxWidth\":\"20\"}},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"CloudAppEvents\\r\\n| where parse_json(RawEventData)[\\\"AppIdentity\\\"] == 'Copilot.Security.SecurityCopilot'\\r\\n| where parse_json(RawEventData)[\\\"Workload\\\"] == 'Copilot'\\r\\n| where RawEventData.RecordType == 325\\r\\n| where ActionType == \\\"UploadFile\\\"\\r\\n| count\",\"size\":4,\"title\":\"File Uploads\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{Workspace}\"],\"visualization\":\"tiles\",\"tileSettings\":{\"titleContent\":{\"columnMatch\":\"Count\",\"formatter\":8,\"formatOptions\":{\"palette\":\"orangeDark\"},\"numberFormat\":{\"unit\":0,\"options\":{\"style\":\"decimal\"}}},\"showBorder\":true}},\"customWidth\":\"17\",\"name\":\"query - 3 - Copy - Copy\",\"styleSettings\":{\"maxWidth\":\"20\"}},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"CloudAppEvents\\r\\n| where parse_json(RawEventData)[\\\"AppIdentity\\\"] == 'Copilot.Security.SecurityCopilot'\\r\\n| where parse_json(RawEventData)[\\\"Workload\\\"] == 'Copilot'\\r\\n| where ActionType == \\\"DisableCopilotPlugin\\\"\\r\\n| count \",\"size\":4,\"title\":\"Disabled Security Copilot Plugins\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{Workspace}\"],\"visualization\":\"tiles\",\"tileSettings\":{\"titleContent\":{\"columnMatch\":\"Count\",\"formatter\":8,\"formatOptions\":{\"palette\":\"orangeDark\"},\"numberFormat\":{\"unit\":0,\"options\":{\"style\":\"decimal\"}}},\"showBorder\":true}},\"customWidth\":\"17\",\"name\":\"query - 3 - Copy - Copy - Copy\",\"styleSettings\":{\"maxWidth\":\"20\"}},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"SecurityAlert\\r\\n| where DisplayName == \\\"Security Copilot - Anomalous Operations by Copilot for Security User\\\" \\r\\n| where DisplayName == \\\"Security Copilot - Anomalous sign-in activity by Copilot for Security user\\\"\\r\\n| where DisplayName == \\\"Security Copilot - TI map IP entity to Prompts\\\"\\r\\n| where DisplayName == \\\"Security Copilot-Audit logging settings changes\\\"\\r\\n| count \",\"size\":4,\"title\":\"Security Copilot Detections\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{Workspace}\"],\"visualization\":\"tiles\",\"tileSettings\":{\"titleContent\":{\"columnMatch\":\"Count\",\"formatter\":8,\"formatOptions\":{\"palette\":\"orangeDark\"},\"numberFormat\":{\"unit\":0,\"options\":{\"style\":\"decimal\"}}},\"showBorder\":true}},\"customWidth\":\"17\",\"name\":\"query - 3 - Copy - Copy - Copy - Copy - Copy\",\"styleSettings\":{\"maxWidth\":\"20\"}},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"CloudAppEvents\\r\\n| where parse_json(RawEventData)[\\\"AppIdentity\\\"] == 'Copilot.Security.SecurityCopilot'\\r\\n| where parse_json(RawEventData)[\\\"Workload\\\"] == 'Copilot'\\r\\n| where ActionType == \\\"UpdateCopilotSettings\\\"\\r\\n| count\",\"size\":4,\"title\":\"Changed Security Copilot Settings\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{Workspace}\"],\"visualization\":\"tiles\",\"tileSettings\":{\"titleContent\":{\"columnMatch\":\"Count\",\"formatter\":8,\"formatOptions\":{\"palette\":\"orangeDark\"},\"numberFormat\":{\"unit\":0,\"options\":{\"style\":\"decimal\"}}},\"showBorder\":true}},\"customWidth\":\"17\",\"name\":\"query - 3 - Copy - Copy - Copy - Copy\",\"styleSettings\":{\"maxWidth\":\"20\"}},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"CloudAppEvents\\r\\n| where parse_json(RawEventData)[\\\"AppIdentity\\\"] == 'Copilot.Security.SecurityCopilot'\\r\\n| where parse_json(RawEventData)[\\\"Workload\\\"] == 'Copilot'\\r\\n| where RawEventData.RecordType == 261\\r\\n| extend AppHost = tostring(parse_json(tostring(RawEventData.CopilotEventData)).AppHost)\\r\\n| where AppHost !contains \\\"test\\\"\\r\\n| summarize count() by AppHost\",\"size\":0,\"title\":\"Security Copilot Prompts Per Experience\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{Workspace}\"],\"visualization\":\"piechart\",\"graphSettings\":{\"type\":0,\"topContent\":{\"columnMatch\":\"CopilotEventData_AppHost_s\",\"formatter\":1},\"centerContent\":{\"columnMatch\":\"count_\",\"formatter\":1,\"numberFormat\":{\"unit\":17,\"options\":{\"maximumSignificantDigits\":3,\"maximumFractionDigits\":2}}}},\"chartSettings\":{\"showLegend\":true}},\"customWidth\":\"40\",\"name\":\"query - 15\",\"styleSettings\":{\"showBorder\":true}},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"CloudAppEvents\\r\\n| where parse_json(RawEventData)[\\\"AppIdentity\\\"] == 'Copilot.Security.SecurityCopilot'\\r\\n| where parse_json(RawEventData)[\\\"Workload\\\"] == 'Copilot'\\r\\n| where RawEventData.RecordType == 261\\r\\n| extend AppHost = tostring(parse_json(tostring(RawEventData.CopilotEventData)).AppHost)\\r\\n| where AppHost !contains \\\"test\\\" \\r\\n| summarize CountPerAppHost = count() by bin(TimeGenerated, 1d), AppHost\\r\\n| join kind=leftouter ( CloudAppEvents\\r\\n| where RawEventData.RecordType == 261\\r\\n| where parse_json(RawEventData)[\\\"AppIdentity\\\"] == 'Copilot.Security.SecurityCopilot'\\r\\n| where parse_json(RawEventData)[\\\"Workload\\\"] == 'Copilot'\\r\\n| extend AppHost = tostring(parse_json(tostring(RawEventData.CopilotEventData)).AppHost)\\r\\n| where AppHost !contains \\\"test\\\" \\r\\n| summarize TotalCount = count() by bin(TimeGenerated, 1d)\\r\\n) on TimeGenerated\\r\\n| project TimeGenerated, AppHost, CountPerAppHost, TotalCount\",\"size\":0,\"title\":\"Prompts over time \",\"color\":\"turquoise\",\"timeContextFromParameter\":\"TimeRange\",\"timeBrushParameterName\":\"TimeBrush\",\"exportFieldName\":\"CreatedTime\",\"exportParameterName\":\"TimePicker\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{Workspace}\"],\"visualization\":\"categoricalbar\",\"graphSettings\":{\"type\":0,\"topContent\":{\"columnMatch\":\"UserKey_s\",\"formatter\":1},\"centerContent\":{\"columnMatch\":\"promptCount\",\"formatter\":1,\"numberFormat\":{\"unit\":17,\"options\":{\"maximumSignificantDigits\":3,\"maximumFractionDigits\":2}}}},\"chartSettings\":{\"xAxis\":\"TimeGenerated\",\"yAxis\":[\"CountPerAppHost\"],\"group\":\"AppHost\",\"createOtherGroup\":null,\"seriesLabelSettings\":[{\"seriesName\":\"count_\",\"label\":\"Count of prompts\"},{\"seriesName\":\"Copilot in Intune\",\"label\":\"Copilot in Intune\"},{\"seriesName\":\"Copilot in Defender\",\"label\":\"Copilot in Defender\"},{\"seriesName\":\"Copilot in Microsoft Purview\",\"label\":\"Copilot in Microsoft Purview\"},{\"seriesName\":\"Security Copilot standalone\",\"label\":\"Security Copilot standalone\"},{\"seriesName\":\"Copilot in Azure Firewall\",\"label\":\"Copilot in Azure Firewall\"},{\"seriesName\":\"Copilot in Microsoft Entra\",\"label\":\"Copilot in Microsoft Entra\"},{\"seriesName\":\"Copilot in Defender External Attack Surface Management (EASM)\",\"label\":\"Copilot in Defender External Attack Surface Management (EASM)\"}],\"ySettings\":{\"label\":\"Sum\"}}},\"customWidth\":\"60\",\"name\":\"Prompts over time \",\"styleSettings\":{\"showBorder\":true}},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"CloudAppEvents\\r\\n| where parse_json(RawEventData)[\\\"AppIdentity\\\"] == 'Copilot.Security.SecurityCopilot'\\r\\n| where parse_json(RawEventData)[\\\"Workload\\\"] == 'Copilot'\\r\\n| where RawEventData.RecordType == 261\\r\\n| extend AppHost = tostring(parse_json(tostring(RawEventData.CopilotEventData)).AppHost)\\r\\n| where AppHost !contains \\\"test\\\" and AppHost !contains \\\"unknown\\\"\\r\\n| summarize interactioncount = count() by AppHost\",\"size\":0,\"title\":\"Security Copilot Prompts per Action\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{Workspace}\"],\"visualization\":\"piechart\",\"chartSettings\":{\"showLegend\":true}},\"customWidth\":\"40\",\"name\":\"query - 4\",\"styleSettings\":{\"showBorder\":true}},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"CloudAppEvents\\r\\n| where parse_json(RawEventData)[\\\"AppIdentity\\\"] == 'Copilot.Security.SecurityCopilot'\\r\\n| where parse_json(RawEventData)[\\\"Workload\\\"] == 'Copilot'\\r\\n| where RawEventData.RecordType == 261\\r\\n| extend AppHost = tostring(parse_json(tostring(RawEventData.CopilotEventData)).AppHost)\\r\\n| where AppHost !contains \\\"test\\\"\\r\\n| summarize CopilotInDefender = countif(AppHost == \\\"Copilot in Defender\\\"),\\r\\nAutomation = countif(AppHost == \\\"Logic App\\\"), \\r\\nStandalone = countif(AppHost == \\\"Security Copilot standalone\\\"), \\r\\nCopilotInMicrosoftPurview = countif(AppHost == \\\"Sopilot in Microsoft Purview\\\"), \\r\\nCopilotinMicrosoftEntra = countif(AppHost == \\\"Copilot in Microsoft Entra\\\"),\\r\\nCopilotInIntune = countif(AppHost == \\\"Copilot in Intune\\\"), \\r\\nEASM = countif(AppHost == \\\"Copilot in Defender External Attack Surface Management (EASM)\\\"), \\r\\nCopilotInAzureFirewall = countif(AppHost == \\\"Copilot in Azure Firewall\\\"),\\r\\nTotalPrompts = count() by AccountDisplayName\\r\\n| sort by TotalPrompts\",\"size\":0,\"title\":\"Top Users Prompts\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{Workspace}\"],\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"Automation\",\"formatter\":4,\"formatOptions\":{\"palette\":\"blue\",\"aggregation\":\"Count\"}},{\"columnMatch\":\"Standalone\",\"formatter\":4,\"formatOptions\":{\"palette\":\"green\",\"aggregation\":\"Count\"}},{\"columnMatch\":\"CopilotInMicrosoftPurview\",\"formatter\":4,\"formatOptions\":{\"palette\":\"purple\"}},{\"columnMatch\":\"CopilotinMicrosoftEntra\",\"formatter\":4,\"formatOptions\":{\"palette\":\"orangeDark\",\"aggregation\":\"Count\"}},{\"columnMatch\":\"CopilotInIntune\",\"formatter\":4,\"formatOptions\":{\"palette\":\"magenta\",\"aggregation\":\"Count\"}},{\"columnMatch\":\"EASM\",\"formatter\":4,\"formatOptions\":{\"palette\":\"yellow\",\"aggregation\":\"Count\"}},{\"columnMatch\":\"CopilotInAzureFirewall\",\"formatter\":4,\"formatOptions\":{\"palette\":\"brown\",\"aggregation\":\"Count\"}}],\"filter\":true,\"labelSettings\":[{\"columnId\":\"CopilotInDefender\",\"label\":\"DefenderXDR\"},{\"columnId\":\"CopilotInMicrosoftPurview\",\"label\":\"Purview\"},{\"columnId\":\"CopilotinMicrosoftEntra\",\"label\":\"Entra\"},{\"columnId\":\"CopilotInIntune\",\"label\":\"Intune\"},{\"columnId\":\"CopilotInAzureFirewall\",\"label\":\"AZFW\"}]},\"tileSettings\":{\"showBorder\":false}},\"customWidth\":\"60\",\"name\":\"query - 2\",\"styleSettings\":{\"showBorder\":true}},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let IP_Data = \\n external_data(network:string,geoname_id:long,continent_code:string,continent_name:string ,country_iso_code:string, country_name:string,is_anonymous_proxy:bool,is_satellite_provider:bool)\\n [@\\\"https://raw.githubusercontent.com/datasets/geoip2-ipv4/master/data/geoip2-ipv4.csv\\\"]\\n with (ignoreFirstRecord=true, format=\\\"csv\\\");\\nCloudAppEvents\\n| where parse_json(RawEventData)[\\\"AppIdentity\\\"] == 'Copilot.Security.SecurityCopilot'\\n| where parse_json(RawEventData)[\\\"Workload\\\"] == 'Copilot'\\n| extend ClientIP = tostring(RawEventData.ClientIP)\\n| extend IPaddresses=tostring(ClientIP)\\n| where isnotempty(IPaddresses) \\n| evaluate ipv4_lookup(IP_Data, IPaddresses, network)\\n| summarize interactioncount = count() by ClientIP, country_name\",\"size\":0,\"title\":\"Security Copilot Interactions by Location\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{Workspace}\"],\"visualization\":\"map\",\"mapSettings\":{\"locInfo\":\"CountryRegion\",\"locInfoColumn\":\"country_name\",\"latitude\":\"_TableName\",\"longitude\":\"_TableName\",\"sizeSettings\":\"interactioncount\",\"sizeAggregation\":\"Sum\",\"maxSize\":100,\"opacity\":0.5,\"legendMetric\":\"interactioncount\",\"legendAggregation\":\"Count\",\"itemColorSettings\":null}},\"customWidth\":\"40\",\"name\":\"query - 2\",\"styleSettings\":{\"padding\":\"0\",\"showBorder\":true}},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"CloudAppEvents\\r\\n| where parse_json(RawEventData)[\\\"AppIdentity\\\"] == 'Copilot.Security.SecurityCopilot'\\r\\n| where parse_json(RawEventData)[\\\"Workload\\\"] == 'Copilot'\\r\\n| where RawEventData.RecordType in (\\\"320\\\", \\\"321\\\", \\\"322\\\")\\r\\n| project TimeGenerated, AccountDisplayName, Operation = ActionType , ClientIP = RawEventData.ClientIP, CopilotSettingsEventData_Resource = tostring(parse_json(tostring(parse_json(tostring(RawEventData.CopilotSettingsEventData)).Resource))[0].Property)\\r\\n| sort by TimeGenerated\\r\\n| take 50\",\"size\":0,\"title\":\"Security Copilot - Promptbook Interactions\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{Workspace}\"],\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"Operation\",\"formatter\":18,\"formatOptions\":{\"thresholdsOptions\":\"colors\",\"thresholdsGrid\":[{\"operator\":\"contains\",\"thresholdValue\":\"Create\",\"representation\":\"green\",\"text\":\"{0}{1}\"},{\"operator\":\"contains\",\"thresholdValue\":\"Delete\",\"representation\":\"red\",\"text\":\"{0}{1}\"},{\"operator\":\"contains\",\"thresholdValue\":\"Update\",\"representation\":\"orange\",\"text\":\"{0}{1}\"},{\"operator\":\"Default\",\"thresholdValue\":null,\"representation\":\"lightBlue\",\"text\":\"{0}{1}\"}]}},{\"columnMatch\":\"ClientIP\",\"formatter\":5}]}},\"customWidth\":\"60\",\"name\":\"query - 9\",\"styleSettings\":{\"showBorder\":true}},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"CloudAppEvents\\r\\n| where parse_json(RawEventData)[\\\"AppIdentity\\\"] == 'Copilot.Security.SecurityCopilot'\\r\\n| where parse_json(RawEventData)[\\\"Workload\\\"] == 'Copilot'\\r\\n| where RawEventData.RecordType == 313\\r\\n| extend Operation = tostring(RawEventData.Operation)\\r\\n| where Operation contains \\\"Enable\\\"\\r\\n| where parse_json(tostring(parse_json(tostring(RawEventData.CopilotSettingsEventData)).Resource))[0].Property <> \\\"FileUploads\\\"\\r\\n| extend PluginsName = tostring(parse_json(tostring(parse_json(tostring(RawEventData.CopilotSettingsEventData)).Resource))[0].Property)\\r\\n| where parse_json(tostring(parse_json(tostring(RawEventData.CopilotSettingsEventData)).Resource))[0].NewValue == \\\"Enabled\\\"\\r\\n| where PluginsName !contains \\\"ApiValidatorDefangUrlSkillsetUser\\\"\\r\\n| mv-expand todynamic(PluginsName)\\r\\n| project TimeGenerated, AccountDisplayName, PluginsName\\r\\n| sort by TimeGenerated\",\"size\":0,\"title\":\"Enable Plugin Opertion\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{Workspace}\"],\"gridSettings\":{\"labelSettings\":[{\"columnId\":\"PluginsName\",\"label\":\"Plugin_Name\"}]}},\"customWidth\":\"40\",\"name\":\"query - 12\",\"styleSettings\":{\"showBorder\":true}},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"CloudAppEvents\\r\\n| where parse_json(RawEventData)[\\\"AppIdentity\\\"] == 'Copilot.Security.SecurityCopilot'\\r\\n| where parse_json(RawEventData)[\\\"Workload\\\"] == 'Copilot'\\r\\n| where RawEventData.RecordType == 314\\r\\n| extend Operation = tostring(RawEventData.Operation)\\r\\n| where parse_json(tostring(parse_json(tostring(RawEventData.CopilotSettingsEventData)).Resource))[0].NewValue == \\\"Disabled\\\"\\r\\n| where parse_json(tostring(parse_json(tostring(RawEventData.CopilotSettingsEventData)).Resource))[0].Property <> \\\"FileUploads\\\"\\r\\n| extend PluginsName = tostring(parse_json(tostring(parse_json(tostring(RawEventData.CopilotSettingsEventData)).Resource))[0].Property)\\r\\n| where PluginsName !contains \\\"ApiValidatorDefangUrlSkillsetUser\\\"\\r\\n| mv-expand todynamic(PluginsName)\\r\\n| project TimeGenerated, AccountDisplayName, PluginsName\\r\\n| sort by TimeGenerated\",\"size\":0,\"title\":\"Disable Plugin\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{Workspace}\"],\"gridSettings\":{\"labelSettings\":[{\"columnId\":\"PluginsName\",\"label\":\"PluginName\"}]}},\"customWidth\":\"60\",\"name\":\"query - 13\",\"styleSettings\":{\"showBorder\":true}},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"CloudAppEvents\\r\\n| where parse_json(RawEventData)[\\\"AppIdentity\\\"] == 'Copilot.Security.SecurityCopilot'\\r\\n| where parse_json(RawEventData)[\\\"Workload\\\"] == 'Copilot'\\r\\n| where RawEventData.RecordType == 325\\r\\n| extend Operation = tostring(RawEventData.Operation)\\r\\n| where Operation <> \\\"DeleteFile\\\"\\r\\n| extend Property = tostring(parse_json(tostring(parse_json(tostring(RawEventData.CopilotSettingsEventData)).Resource))[0].Property)\\r\\n| where Property != \\\"Skillsets.ApiValidatorDefangUrlSkillsetTenant\\\" and Property != \\\"Skillsets.ApiValidatorDefangUrlSkillsetUser\\\"\\r\\n| extend NewValue = tostring(parse_json(tostring(parse_json(tostring(RawEventData.CopilotSettingsEventData)).Resource))[0].NewValue)\\r\\n| extend Property1 = substring(Property, 9) \\r\\n| extend Enable = NewValue\\r\\n| extend SettingLevel = case( Property1 contains \\\"AllowAuditLogging\\\", \\\"TenantLevel\\\", Property1 contains \\\"tenant\\\" , \\\"TenantLevel\\\", Property1 contains \\\"allowO365DataCollection\\\", \\\"TenantLevel\\\", Property1 contains \\\"User\\\", \\\"UserLevel\\\", \\\"Unknown\\\"\\r\\n)\\r\\n| project TimeGenerated, AccountDisplayName, Property1 ,SettingLevel, NewValue\\r\\n| sort by TimeGenerated, SettingLevel asc \",\"size\":0,\"title\":\"Change Setting Opertion\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{Workspace}\"],\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"SettingLevel\",\"formatter\":18,\"formatOptions\":{\"thresholdsOptions\":\"colors\",\"thresholdsGrid\":[{\"operator\":\"contains\",\"thresholdValue\":\"TenantLevel\",\"representation\":\"yellow\",\"text\":\"{0}{1}\"},{\"operator\":\"Default\",\"thresholdValue\":null,\"representation\":\"gray\",\"text\":\"{0}{1}\"}]}},{\"columnMatch\":\"NewValue\",\"formatter\":18,\"formatOptions\":{\"thresholdsOptions\":\"colors\",\"thresholdsGrid\":[{\"operator\":\"contains\",\"thresholdValue\":\"True\",\"representation\":\"green\",\"text\":\"{0}{1}\"},{\"operator\":\"contains\",\"thresholdValue\":\"False\",\"representation\":\"redBright\",\"text\":\"{0}{1}\"},{\"operator\":\"Default\",\"thresholdValue\":null,\"representation\":null,\"text\":\"{0}{1}\"}]}},{\"columnMatch\":\"Action\",\"formatter\":18,\"formatOptions\":{\"thresholdsOptions\":\"colors\",\"thresholdsGrid\":[{\"operator\":\"contains\",\"thresholdValue\":\"True\",\"representation\":\"green\",\"text\":\"{0}{1}\"},{\"operator\":\"contains\",\"thresholdValue\":\"False\",\"representation\":\"redBright\",\"text\":\"{0}{1}\"},{\"operator\":\"Default\",\"thresholdValue\":null,\"representation\":\"lightBlue\",\"text\":\"{0}{1}\"}]}}],\"labelSettings\":[{\"columnId\":\"Property1\",\"label\":\"Action\"},{\"columnId\":\"NewValue\",\"label\":\"Value\"}]}},\"name\":\"query - 14\",\"styleSettings\":{\"showBorder\":true}}]},\"conditionalVisibility\":{\"parameterName\":\"Nav\",\"comparison\":\"isEqualTo\",\"value\":\"audit\"},\"name\":\"group - 12\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"items\":[{\"type\":9,\"content\":{\"version\":\"KqlParameterItem/1.0\",\"crossComponentResources\":[\"variables('WorkbookSourceId')\"],\"parameters\":[{\"id\":\"59e79699-280b-4339-8cd2-55624b203cac\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"TimeRange\",\"type\":4,\"typeSettings\":{\"selectableValues\":[{\"durationMs\":300000},{\"durationMs\":900000},{\"durationMs\":1800000},{\"durationMs\":3600000},{\"durationMs\":14400000},{\"durationMs\":43200000},{\"durationMs\":86400000},{\"durationMs\":172800000},{\"durationMs\":259200000},{\"durationMs\":604800000},{\"durationMs\":1209600000},{\"durationMs\":2419200000},{\"durationMs\":2592000000},{\"durationMs\":5184000000},{\"durationMs\":7776000000}]},\"timeContext\":{\"durationMs\":86400000}},{\"id\":\"fefebdf8-9122-4dc4-ae76-5817d7e3bace\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"DefaultSubscription_Internal\",\"type\":1,\"isRequired\":true,\"query\":\"where type =~ 'microsoft.operationalinsights/workspaces'\\r\\n| take 1\\r\\n| project subscriptionId\",\"crossComponentResources\":[\"value::selected\"],\"isHiddenWhenLocked\":true,\"timeContext\":{\"durationMs\":86400000},\"queryType\":1,\"resourceType\":\"microsoft.resourcegraph/resources\"},{\"id\":\"6cbd900c-7049-484c-a0eb-d23c1ac5ff38\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"Subscription\",\"type\":6,\"query\":\"summarize by subscriptionId\\r\\n| project value = strcat(\\\"/subscriptions/\\\", subscriptionId), label = subscriptionId, selected = iff(subscriptionId =~ '{DefaultSubscription_Internal}', true, false)\",\"crossComponentResources\":[\"value::selected\"],\"typeSettings\":{\"additionalResourceOptions\":[]},\"timeContext\":{\"durationMs\":86400000},\"queryType\":1,\"resourceType\":\"microsoft.resourcegraph/resources\"},{\"id\":\"37403463-ff86-47c0-8d04-3f432d0413a3\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"Workspace\",\"type\":5,\"query\":\"where type =~ 'microsoft.operationalinsights/workspaces'\\r\\n| project id\",\"crossComponentResources\":[\"{Subscription}\"],\"typeSettings\":{\"additionalResourceOptions\":[]},\"timeContext\":{\"durationMs\":86400000},\"queryType\":1,\"resourceType\":\"microsoft.resourcegraph/resources\",\"value\":null},{\"id\":\"8ac8842a-fc4e-44fd-9542-72baf58224f1\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"resourceGroup\",\"type\":1,\"query\":\"resources\\r\\n| where type =~ 'microsoft.operationalinsights/workspaces'\\r\\n| where id == \\\"{Workspace}\\\"\\r\\n| project resourceGroup\",\"crossComponentResources\":[\"value::selected\"],\"isHiddenWhenLocked\":true,\"timeContext\":{\"durationMs\":86400000},\"queryType\":1,\"resourceType\":\"microsoft.resourcegraph/resources\"}],\"style\":\"pills\",\"queryType\":0,\"resourceType\":\"microsoft.resourcegraph/resources\"},\"name\":\"parameters - 3\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"arg(\\\"\\\").resourcechanges\\r\\n| extend timestamp = todatetime(properties[\\\"changeAttributes\\\"][\\\"timestamp\\\"])\\r\\n| extend changes = properties[\\\"changes\\\"]\\r\\n| extend ResourceId = tostring(properties[\\\"targetResourceId\\\"])\\r\\n| extend CorrelationId = tostring(properties[\\\"changeAttributes\\\"][\\\"correlationId\\\"]) \\r\\n| extend changeType = tostring(properties.changeType)\\r\\n| where changeType == \\\"Update\\\"\\r\\n| where changes contains \\\"numberOfUnits\\\"\\r\\n| extend newValue = tostring(parse_json(tostring(changes.[\\\"properties.numberOfUnits\\\"])).newValue)\\r\\n| extend previousValue = tostring(parse_json(tostring(changes.[\\\"properties.numberOfUnits\\\"])).previousValue)\\r\\n| extend changedBy = tostring(parse_json(tostring(properties.changeAttributes)).changedBy)\\r\\n| sort by timestamp\\r\\n| take 1\\r\\n| project toint(newValue)\",\"size\":4,\"title\":\"Number Of SCU's\",\"timeContext\":{\"durationMs\":2592000000},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{Workspace}\"],\"visualization\":\"tiles\",\"tileSettings\":{\"titleContent\":{\"columnMatch\":\"newValue\",\"numberFormat\":{\"unit\":17,\"options\":{\"style\":\"decimal\"}}},\"showBorder\":true}},\"customWidth\":\"30\",\"name\":\"query - 0\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"arg(\\\"\\\").resourcechanges\\r\\n| extend timestamp = todatetime(properties[\\\"changeAttributes\\\"][\\\"timestamp\\\"])\\r\\n| where timestamp > ago(60d)\\r\\n| extend changes = properties[\\\"changes\\\"]\\r\\n| extend ResourceId = tostring(properties[\\\"targetResourceId\\\"])\\r\\n| extend CorrelationId = tostring(properties[\\\"changeAttributes\\\"][\\\"correlationId\\\"]) \\r\\n| extend changeType = tostring(properties.changeType)\\r\\n| where changeType == \\\"Update\\\"\\r\\n| where changes contains \\\"numberOfUnits\\\"\\r\\n| extend newValue = tostring(parse_json(tostring(changes.[\\\"properties.numberOfUnits\\\"])).newValue)\\r\\n| extend previousValue = tostring(parse_json(tostring(changes.[\\\"properties.numberOfUnits\\\"])).previousValue)\\r\\n| extend changedBy = tostring(parse_json(tostring(properties.changeAttributes)).changedBy)\\r\\n| project timestamp, previousValue, newValue , changedBy\",\"size\":1,\"title\":\"SCU Changes\",\"timeContext\":{\"durationMs\":86400000},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{Workspace}\"],\"visualization\":\"table\",\"graphSettings\":{\"type\":0}},\"customWidth\":\"70\",\"name\":\"query - 1\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"AzureActivity\\r\\n| where ResourceProviderValue contains \\\"copilot\\\"\",\"size\":0,\"title\":\"SCU capacity Activities\",\"timeContext\":{\"durationMs\":86400000},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{Workspace}\"]},\"name\":\"query - 2\"}]},\"conditionalVisibility\":{\"parameterName\":\"Nav\",\"comparison\":\"isEqualTo\",\"value\":\"SCU\"},\"name\":\"group - 4\"}],\"isLocked\":false,\"fallbackResourceIds\":[\"variables('WorkbookSourceId')\"],\"fromTemplateId\":\"sentinel-UserWorkbook\"}", + "version": "1.0", + "sourceId": "[concat(resourceGroup().id, '/providers/Microsoft.OperationalInsights/workspaces/',parameters('LogAnalyticsWorkspaceName'))]", + "category": "sentinel", + "etag": "*" + } + } + ] + } \ No newline at end of file diff --git a/Monitoring/IngestSecurityCopilotAuditlogs/images/cfs_setting_1.png b/Monitoring/IngestSecurityCopilotAuditlogs/images/cfs_setting_1.png new file mode 100644 index 00000000..b4423907 Binary files /dev/null and b/Monitoring/IngestSecurityCopilotAuditlogs/images/cfs_setting_1.png differ diff --git a/Monitoring/IngestSecurityCopilotAuditlogs/images/sentinel_XDR.png b/Monitoring/IngestSecurityCopilotAuditlogs/images/sentinel_XDR.png new file mode 100644 index 00000000..b5bd3813 Binary files /dev/null and b/Monitoring/IngestSecurityCopilotAuditlogs/images/sentinel_XDR.png differ diff --git a/Monitoring/KQL query to see who enabled CFS/WhoEnabledCFS.kql b/Monitoring/KQL query to see who enabled CFS/WhoEnabledCFS.kql new file mode 100644 index 00000000..9d6591bd --- /dev/null +++ b/Monitoring/KQL query to see who enabled CFS/WhoEnabledCFS.kql @@ -0,0 +1,9 @@ +AzureActivity +| where TimeGenerated >= ago(30d) +| where ResourceProviderValue == "MICROSOFT.SECURITYCOPILOT" +| extend resourceName = tostring(parse_json(Properties).resource) +| extend resourceGroupName = tostring(parse_json(Properties).resourceGroup) +| extend resourceActivity = tostring(parse_json(Properties).message) +| where resourceActivity == "Microsoft.SecurityCopilot/capacities/write" +| project Caller, resourceName, resourceGroupName, resourceActivity, TimeGenerated + diff --git a/Monitoring/KQL to see who deleted CFS/WhoDeletedCFS.kql b/Monitoring/KQL to see who deleted CFS/WhoDeletedCFS.kql new file mode 100644 index 00000000..23f77ae5 --- /dev/null +++ b/Monitoring/KQL to see who deleted CFS/WhoDeletedCFS.kql @@ -0,0 +1,8 @@ +AzureActivity +| where TimeGenerated >= ago(30d) +| where ResourceProviderValue == "MICROSOFT.SECURITYCOPILOT" +| extend resourceName = tostring(parse_json(Properties).resource) +| extend resourceGroupName = tostring(parse_json(Properties).resourceGroup) +| extend resourceActivity = tostring(parse_json(Properties).message) +| where resourceActivity == "Microsoft.SecurityCopilot/capacities/delete" +| project Caller, resourceName, resourceGroupName, resourceActivity, TimeGenerated diff --git a/Monitoring/KQL to see who deleted CFS/readme.me b/Monitoring/KQL to see who deleted CFS/readme.me new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/Monitoring/KQL to see who deleted CFS/readme.me @@ -0,0 +1 @@ + diff --git a/Monitoring/readme.md b/Monitoring/readme.md new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/Monitoring/readme.md @@ -0,0 +1 @@ + diff --git a/Plugins/Community Based Plugins/AbuseIPDB/API-GetAbuseIPDBIP.yaml b/Plugins/Community Based Plugins/AbuseIPDB/API-GetAbuseIPDBIP.yaml deleted file mode 100644 index f8619244..00000000 --- a/Plugins/Community Based Plugins/AbuseIPDB/API-GetAbuseIPDBIP.yaml +++ /dev/null @@ -1,17 +0,0 @@ -Descriptor: - Name: Get AbuseIPDB IP Address Information - DisplayName: Get AbuseIPDB IP Address Information - Description: Get detailed IP Address reputation information from AbuseIPDB - SupportedAuthTypes: - - ApiKey - Authorization: - Type: APIKey - Key: Key - Location: Header - AuthScheme: '' - -SkillGroups: - - Format: API - Settings: - OpenApiSpecUrl: https://raw.githubusercontent.com/Azure/Copilot-For-Security/main/Plugins/Community%20Based%20Plugins/AbuseIPDB/API-GetAbuseIPDBIP.yaml - diff --git a/Plugins/Community Based Plugins/AbuseIPDB/OpenAPI/API-GetAbuseIPDBIP-OAPI.yaml b/Plugins/Community Based Plugins/AbuseIPDB/OpenAPI/API-GetAbuseIPDBIP-OAPI.yaml deleted file mode 100644 index 44e32cbb..00000000 --- a/Plugins/Community Based Plugins/AbuseIPDB/OpenAPI/API-GetAbuseIPDBIP-OAPI.yaml +++ /dev/null @@ -1,41 +0,0 @@ -openapi: 3.0.0 - -info: - title: Get AbuseIPDB IP Address Information - description: Get detailed IP Address reputation information from AbuseIPDB - #Examples - #Can you summarize the AbuseIPDB information for the IP 205.210.31.142? - #Can you output a table of the last 25 reported port scan's for the IP 205.210.31.142 coming from AbuseIPDB Reports? - #Can you output a list of all of the port numbers that users reported port scans from AbuseIPDB for IP 205.210.31.142. - version: "v1" - -servers: - - url: https://api.abuseipdb.com/api/v2 - -paths: - /reports: - get: - operationId: getAbuseIPDBInformation - summary: Lookup IP Address reputation information from AbuseIPDB - parameters: - - in: query - name: ipAddress - schema: - type: string - required: true - description: The ip address to lookup - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/getAbuseIPDBInformationResponse" - -components: - schemas: - getAbuseIPDBInformationResponse: - type: object - properties: - data: - type: object diff --git a/Plugins/Community Based Plugins/AbuseIPDB/README.md b/Plugins/Community Based Plugins/AbuseIPDB/README.md deleted file mode 100644 index 420bd1b4..00000000 --- a/Plugins/Community Based Plugins/AbuseIPDB/README.md +++ /dev/null @@ -1,31 +0,0 @@ -# Copilot for Security Custom Plugin - API - Get Abuse IPDB IP Information - -#### Copilot for Security Plugin to gather reputation and abuse reports for an IP Address using AbuseIPDB API's - -### Pre-requisites - -* [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) -* [API Key](https://www.abuseipdb.com/api.html) from AbuseIPDB.com - -### Instructions -#### Upload the Custom Plugin - -1. Obtain the file [API-GetAbuseIPDBIP.yaml](https://github.com/Azure/Copilot-For-Security/blob/main/Plugins/Community%20Based%20Plugins/AbuseIPDB/API-GetAbuseIPDBIP.yaml) from this directory. -2. [Upload the custom plugin](https://learn.microsoft.com/en-us/security-copilot/manage-plugins?tabs=securitycopilotplugin#add-custom-plugins) and verify it's activated. -3. After plugin upload put in your [AbuseIPDB API Key](https://learn.microsoft.com/en-us/security-copilot/plugin_api#configure-authentication-1). - -![CopilotForSecurity](https://learn.microsoft.com/en-us/security-copilot/media/add-plugin-button.png) - -### Plugin Utilization - -Here's some sample queries you can utilize to trigger this plugin - -* Can you summarize the AbuseIPDB information for the IP 205.210.31.142? -* Can you output a table of the last 25 reported port scan's for the IP 205.210.31.142 coming from AbuseIPDB Reports? -* Can you output a list of all of the port numbers that users reported port scans from AbuseIPDB for IP 205.210.31.142? -
- -
- - diff --git a/Plugins/Community Based Plugins/Copilot For Security Output To Email/Readme.md b/Plugins/Community Based Plugins/Copilot For Security Output To Email/Readme.md index 8032837d..aa6307db 100644 --- a/Plugins/Community Based Plugins/Copilot For Security Output To Email/Readme.md +++ b/Plugins/Community Based Plugins/Copilot For Security Output To Email/Readme.md @@ -1,11 +1,11 @@ -![Copilot for Security Overview](https://github.com/Azure/Copilot-For-Security/blob/main/Images/ic_fluent_copilot_64_64%402x.png) -# Microsoft Copilot for Security Custom Plugins - Send Copilot Output to Email +![Security Copilot Overview](https://github.com/Azure/Copilot-For-Security/blob/main/Images/ic_fluent_copilot_64_64%402x.png) +# Microsoft Security Copilot Custom Plugins - Send Copilot Output to Email # Send Copilot Output to Email Plugin ## Overview -The "Send Copilot Output to Email" plugin is designed to seamlessly integrate with Microsoft Copilot for Security. Its primary purpose is to take an output generated by Copilot and send it to a designated email address. This plugin enhances the flexibility of Copilot by allowing users to receive important security alerts and notifications directly in their email inbox. +The "Send Copilot Output to Email" plugin is designed to seamlessly integrate with Microsoft Security Copilot. Its primary purpose is to take an output generated by Copilot and send it to a designated email address. This plugin enhances the flexibility of Copilot by allowing users to receive important security alerts and notifications directly in their email inbox. ## Solution Components 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 new file mode 100644 index 00000000..41e50f5d --- /dev/null +++ b/Plugins/Community Based Plugins/Copilot Logins/Readme.md @@ -0,0 +1,49 @@ +# Security Copilot Login Activities + +## 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. + +--- + +## TYPE AND REQUIREMENTS +**TYPE**: KQL (Sentinel)
+**SOURCE**: _Signinlogs_ table
+**REQUIREMENTS**: Log Analytics Workspace with Sentinel enabled + +--- + +## 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 +
+ +
+ + +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/Country Info/KQL_Plugin_country_info.yaml b/Plugins/Community Based Plugins/Country Info/KQL_Plugin_country_info.yaml new file mode 100644 index 00000000..18aa9afb --- /dev/null +++ b/Plugins/Community Based Plugins/Country Info/KQL_Plugin_country_info.yaml @@ -0,0 +1,19 @@ +Descriptor: + Name: CountryInfo + DisplayName: Country Codes + Description: Country information including country code and language and region and TLD and currency + +SkillGroups: + - Format: KQL + Skills: + - Name: CountryInfo + DisplayName: Country Codes + Description: Country information including country code and language and region and TLD and currency + Settings: + Target: Sentinel + TenantId: + SubscriptionId: + ResourceGroupName: + WorkspaceName: + Template: |- + let CountryInfo = externaldata (FIFA:string,Dial:string,ISO31661Alpha3:string,MARC:string,independence:string,ISO31661numeric:string,GAUL:string,FIPS:string,WMO:string,ISO31661Alpha2:string,ITU:string,IOC:string,DS:string,UNTERMSpanishFormal:string,GlobalCode:string,IntermediateRegionCode:string,officialnamefr:string,UNTERMFrenchShort:string,ISO4217currencyname:string,DevelopedDevelopingCountries:string,UNTERMRussianFormal:string,UNTERMEnglishShort:string,ISO4217currencyalphabeticcode:string,SmallIslandDevelopingStates:string,UNTERMSpanishShort:string,ISO4217currencynumericcode:string,UNTERMChineseFormal:string,UNTERMFrenchFormal:string,UNTERMRussianShort:string,M49:string,SubregionCode:string,RegionCode:string,officialnamear:string,ISO4217currencyminorunit:string,UNTERMArabicFormal:string,UNTERMChineseShort:string,LandLockedDevelopingCountries:string,IntermediateRegionName:string,officialnamees:string,UNTERMEnglishFormal:string,officialnamecn:string,officialnameen:string,SO4217currencycountryname:string,LeastDevelopedCountries:string,RegionName:string,UNTERMArabicShort:string,SubregionName:string,officialnameru:string,GlobalName:string,Capital:string,Continent:string,TLD:string,Languages:string,GeonameID:string,CLDRdisplayname:string,EDGAR:string) [@"https://raw.githubusercontent.com/datasets/country-codes/master/data/country-codes.csv"] with (ignoreFirstRecord=true, format="csv"); CountryInfo diff --git a/Plugins/Community Based Plugins/Country Info/Readme.md b/Plugins/Community Based Plugins/Country Info/Readme.md new file mode 100644 index 00000000..2eed4e94 --- /dev/null +++ b/Plugins/Community Based Plugins/Country Info/Readme.md @@ -0,0 +1,20 @@ +# Security Copilot Plugin: Country Info + +### **This plugin retrieves Country information using 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_country_info.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: Country Info](https://rodtrent.substack.com/p/copilot-for-security-plugin-country) diff --git a/Plugins/Community Based Plugins/DefenderDailyOperations/readme.md b/Plugins/Community Based Plugins/DefenderDailyOperations/readme.md deleted file mode 100644 index 95a71a54..00000000 --- a/Plugins/Community Based Plugins/DefenderDailyOperations/readme.md +++ /dev/null @@ -1,19 +0,0 @@ -# Copilot for Security Custom Plugin - Defender Daily Operations - -This plugin aids in comprehending the recommended approach of integrating multiple related skills into a single plugin. The provided code sample demonstrates the combination of two skills to retrieve information from few defender tables. You can further expand upon this example to extract additional details from custom tables etc. - -### Pre-requisites - -* [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) -* [Microsoft Sentinel Workspace](https://learn.microsoft.com/en-us/azure/sentinel/quickstart-onboard) created. -* Skills and Parameters for KQL Plugin - GetDefenderDevices (devicehealthstate & exposurelevel), GetLatestEmailsByRecipient (email) - -### Instructions -#### Upload the Custom Plugin - -1. Obtain the file [KQL_DefenderDailyOperationsSample.yaml](https://github.com/Azure/Copilot-For-Security/tree/main/Plugins/Community%20Based%20Plugins/DefenderDailyOperations) from this directory. -2. Upload the custom plugin - - - diff --git a/Plugins/Community Based Plugins/EchoTrail Insights Plugin/EchoTrail.yaml b/Plugins/Community Based Plugins/EchoTrail Insights Plugin/EchoTrail.yaml new file mode 100644 index 00000000..d9d5ee41 --- /dev/null +++ b/Plugins/Community Based Plugins/EchoTrail Insights Plugin/EchoTrail.yaml @@ -0,0 +1,16 @@ +Descriptor: + Name: EchoTrail Insights reports + DisplayName: EchoTrail Insights reports + Description: Skills for looking up reports using EchoTrail to search for an exe or a hash and check the prevalence of a file in Windows environments. Also gives a rank- prevalence - the most common filepath and an overview along with some security information + SupportedAuthTypes: + - ApiKey + Authorization: + Type: APIKey + Key: x-apikey + Location: Header + AuthScheme: '' + +SkillGroups: + - Format: API + Settings: + OpenApiSpecUrl: https://raw.githubusercontent.com/hesaad/MSFTCopilot4Security/main/EchoTrailSkillgroups.yaml \ No newline at end of file diff --git a/Plugins/Community Based Plugins/EchoTrail Insights Plugin/Readme.md b/Plugins/Community Based Plugins/EchoTrail Insights Plugin/Readme.md new file mode 100644 index 00000000..337ecfd7 --- /dev/null +++ b/Plugins/Community Based Plugins/EchoTrail Insights Plugin/Readme.md @@ -0,0 +1,33 @@ + +### EchoTrail Insights reports Custom Plugin + +Welcome to the EchoTrail Insights reports custom plugin! Here, you'll discover a Skills for looking up reports using EchoTrail to search for an exe or a hash and check the prevalence of a file in Windows environments. Also gives a rank- prevalence - the most common filepath and an overview along with some security information. + +#### Features: + +- **EchoTrailInsightsFullSummary:** +Get a full summary of a requested filename or hash, The name or hash of an endpoint process to lookup. Must be a Windows filename with extension or a SHA256 hash of a Windows process or an MD5 hash of a Windows process. +- **EchoTrailInsightsFieldSearch:** +Get one particular field from the results of a process search. Useful for efficiently fetching data when you only need one field, The name or hash of an endpoint process to lookup. Must be a Windows filename with extension or a SHA256 hash of a Windows process or an MD5 hash of a Windows process. +- **EchoTrailInsightsSubSearch:** +Perform a search within a list of results from a process search. For example, searching for a particular port in the network table of the cmd.exe process. + +**How to Use the Custom Plugin** + +1. Add the custom plugin in Microsoft Security Copilot + +![Echo1](https://github.com/Azure/Copilot-For-Security/blob/main/Images/Logicapp%20images/Echo1.jpg) + +2. Add in the API value for the plugin + +![Echo1](https://github.com/Azure/Copilot-For-Security/blob/main/Images/Logicapp%20images/Echo2.jpg) + + +3. Get visibility of the Skills asscociated with the Custom Plugin + +![Echo1](https://github.com/Azure/Copilot-For-Security/blob/main/Images/Logicapp%20images/Echo3.jpg) + + +4. Try the Echo trail insight Skill plugin by providing a hash ( should be a windows filename with extenstion or a sha256 fo a windows process or an MD5 has of a windows process) + +![Echo1](https://github.com/Azure/Copilot-For-Security/blob/main/Images/Logicapp%20images/Echo4.jpg) diff --git a/Plugins/Community Based Plugins/GitHub Advanced Security Custom Plugin Scenarios/GitHubAdvancedSecurity-CustomPlugins/GitHubAdvancedSecurityPlugin-Org-Manifest.yaml b/Plugins/Community Based Plugins/GitHub Advanced Security Custom Plugin Scenarios/GitHubAdvancedSecurity-CustomPlugins/GitHubAdvancedSecurityPlugin-Org-Manifest.yaml new file mode 100644 index 00000000..bc11c24a --- /dev/null +++ b/Plugins/Community Based Plugins/GitHub Advanced Security Custom Plugin Scenarios/GitHubAdvancedSecurity-CustomPlugins/GitHubAdvancedSecurityPlugin-Org-Manifest.yaml @@ -0,0 +1,28 @@ +Descriptor: + Name: GitHub Advanced Security for GitHub Organizations + DisplayName: GitHub Advanced Security for GitHub Organizations (Community release) + Description: Skills to retrieve code scanning, secret scanning and dependabot results (alerts) executed by GitHub Advanced Security (GhAS) + on all the repositories of a specified GitHub organization + + Settings: + - Name: GitHubOrganizationUrl + Label: Git Hub API URL for Organizations + Description: The URL of the GitHub API for accessing your Organization. Specify the name of your Organization. + HintText: "https://api.github.com/orgs/MyOrganizationName" + DefaultValue: "https://api.github.com/orgs/" + SettingType: String + Required: true + + SupportedAuthTypes: + - ApiKey + Authorization: + Type: APIKey + Key: Authorization + Location: Header + AuthScheme: 'Bearer' + +SkillGroups: + - Format: API + Settings: + OpenApiSpecUrl: https://raw.githubusercontent.com/Azure/Copilot-For-Security/main/Plugins/Community%20Based%20Plugins/GitHub%20Advanced%20Security%20Custom%20Plugin%20Scenarios/GitHubAdvancedSecurity-CustomPlugins/GitHubAdvancedSecurityPlugin-Org-Specification.yaml + EndpointUrlSettingName: GitHubOrganizationUrl diff --git a/Plugins/Community Based Plugins/GitHub Advanced Security Custom Plugin Scenarios/GitHubAdvancedSecurity-CustomPlugins/GitHubAdvancedSecurityPlugin-Org-Specification.yaml b/Plugins/Community Based Plugins/GitHub Advanced Security Custom Plugin Scenarios/GitHubAdvancedSecurity-CustomPlugins/GitHubAdvancedSecurityPlugin-Org-Specification.yaml new file mode 100644 index 00000000..f6a4c2a1 --- /dev/null +++ b/Plugins/Community Based Plugins/GitHub Advanced Security Custom Plugin Scenarios/GitHubAdvancedSecurity-CustomPlugins/GitHubAdvancedSecurityPlugin-Org-Specification.yaml @@ -0,0 +1,73 @@ +openapi: 3.0.0 + +info: + title: GitHub Advanced Security for GitHub Organizations + description: Skills to retrieve Code Scanning, Dependabot Scanning and Secret Scanning alerts for the default branch of + all the repositiories (repo) of the specified Organization in GitHub. + These scannings are executed by GitHub Advanced Security (GhAS). + version: "0.0.1" + +paths: + /code-scanning/alerts: + get: + operationId: ListCodeScanningAlertsForAnOrganization + summary: Lists the alerts produced as result of the code scanning executed by GitHub Advanced Security (GhAS) + Each alert is related to a specific code vulnerability found in one or more files in the repos of the specified Organization. + In each alert listed in the response, the field named "tool" contains the name of the code scanning tool that raised the alert. + The field "most_recent_instance" contains the location (path, start line & column, end line & column) of the most recent instance of this alert in the default branch. + The field "rule" contains the logic that found the code vulnerability and triggered the alert; the most relevant attributes for this rule are its "description" and its "severity". + parameters: + - in: header + name: User-Agent + schema: + type: string + required: true + description: User-Agent (You can write any text here. This parameter is added as a temporary workaround to make the call succeed) + responses: + 200: + description: Successful authentication. + 401: + description: Unsuccessful authentication. + /dependabot/alerts: + get: + operationId: ListDependabotAlertsForAnOrganization + summary: Lists the alerts produced as result of the dependabot scanning executed by GitHub Advanced Security (GhAS). + Each alert is related to a specific software vulnerability existing in one of the software components found as dependencies in the solution in the repo. + In each alert listed in the response, the fields named "name" and "ecosystem", under the node "security_vulnerability/package", describe the name of the + software component found as dependency to which the alert is related. + The field "value" with type "CVE" under "identifiers" gives the CVE name of the vulnerability found in the affected software component. + The field named "references" include the links to all relevant official resources describing the vulnerability. + The fields named "vulnerable_version_range" and "first_patched_version" describe the versions that are vulnerable and the one where the vulnerability has been removed. + parameters: + - in: header + name: User-Agent + schema: + type: string + required: true + description: User-Agent (You can write any text here. This parameter is added as a temporary workaround to make the call succeed) + responses: + 200: + description: Successful authentication. + 401: + description: Unsuccessful authentication. + /secret-scanning/alerts: + get: + operationId: ListSecretScanningAlertsForAnOrganization + summary: Lists the alerts produced as result of the secret scanning executed by GitHub Advanced Security (GhAS). + Each alert is related to a specific "secret" (text that should not be written in clear and stored in GitHub) found in one of the files in the repos of the specified Organization. + In each alert listed in the response, the field named "secret_type_display_name" include the display name of the type of secret found in the code. + The field named "secret" include the content of the secret found. + The filed "login" in the element "push_protection_bypassed_by" is the login name of the user who introduced that secret in the code. + Use the GetSecretScanningAlertLocations skill to get the location (path URL) of the file containing the secret. + parameters: + - in: header + name: User-Agent + schema: + type: string + required: true + description: User-Agent (You can write any text here. This parameter is added as a temporary workaround to make the call succeed) + responses: + 200: + description: Successful authentication. + 401: + description: Unsuccessful authentication. diff --git a/Plugins/Community Based Plugins/GitHub Advanced Security Custom Plugin Scenarios/GitHubAdvancedSecurity-CustomPlugins/GitHubAdvancedSecurityPlugin-Repo-Manifest.yaml b/Plugins/Community Based Plugins/GitHub Advanced Security Custom Plugin Scenarios/GitHubAdvancedSecurity-CustomPlugins/GitHubAdvancedSecurityPlugin-Repo-Manifest.yaml new file mode 100644 index 00000000..579fe1b9 --- /dev/null +++ b/Plugins/Community Based Plugins/GitHub Advanced Security Custom Plugin Scenarios/GitHubAdvancedSecurity-CustomPlugins/GitHubAdvancedSecurityPlugin-Repo-Manifest.yaml @@ -0,0 +1,37 @@ +Descriptor: + Name: GitHub Advanced Security for GitHub Repositories + DisplayName: GitHub Advanced Security for GitHub Repositories (Community release) + Description: Skills to retrieve code scanning, secret scanning and dependabot results (alerts) executed by GitHub Advanced Security (GhAS) + on the specified GitHub repository + + Settings: + - Name: GitHubOrganizationUrlForRepos + Label: Git Hub Organization API URL for Repository + Description: The URL of the GitHub API for accessing the Repositories of your Organization. Specify the name of your Organization. + HintText: "https://api.github.com/repos/MyOrganizationName" + DefaultValue: "https://api.github.com/repos/" + SettingType: String + Required: true + + SupportedAuthTypes: + - ApiKey + Authorization: + Type: APIKey + Key: Authorization + Location: Header + AuthScheme: 'Bearer' + +SkillGroups: + - Format: API + Settings: + OpenApiSpecUrl: https://raw.githubusercontent.com/Azure/Copilot-For-Security/main/Plugins/Community%20Based%20Plugins/GitHub%20Advanced%20Security%20Custom%20Plugin%20Scenarios/GitHubAdvancedSecurity-CustomPlugins/GitHubAdvancedSecurityPlugin-Repo-CodeScanning-Specification.yaml + EndpointUrlSettingName: GitHubOrganizationUrlForRepos + - Format: API + Settings: + OpenApiSpecUrl: https://raw.githubusercontent.com/Azure/Copilot-For-Security/main/Plugins/Community%20Based%20Plugins/GitHub%20Advanced%20Security%20Custom%20Plugin%20Scenarios/GitHubAdvancedSecurity-CustomPlugins/GitHubAdvancedSecurityPlugin-Repo-SecretScanning-Specification.yaml + EndpointUrlSettingName: GitHubOrganizationUrlForRepos + - Format: API + Settings: + OpenApiSpecUrl: https://raw.githubusercontent.com/Azure/Copilot-For-Security/main/Plugins/Community%20Based%20Plugins/GitHub%20Advanced%20Security%20Custom%20Plugin%20Scenarios/GitHubAdvancedSecurity-CustomPlugins/GitHubAdvancedSecurityPlugin-Repo-DependabotScanning-Specification.yaml + EndpointUrlSettingName: GitHubOrganizationUrlForRepos + diff --git a/Plugins/Community Based Plugins/GitHub Advanced Security Custom Plugin Scenarios/GitHubAdvancedSecurity-CustomPlugins/readme.md b/Plugins/Community Based Plugins/GitHub Advanced Security Custom Plugin Scenarios/GitHubAdvancedSecurity-CustomPlugins/readme.md new file mode 100644 index 00000000..481c267a --- /dev/null +++ b/Plugins/Community Based Plugins/GitHub Advanced Security Custom Plugin Scenarios/GitHubAdvancedSecurity-CustomPlugins/readme.md @@ -0,0 +1,22 @@ +**GitHub Advanced Security (GhAS) Custom Plugins** + +GitHub Advanced Security (GhAS) executes code scanning (Static Application Security Testing, SAST, via CodeQL), dependabot analysis (Software Composition Analysis, SCA) and secret scanning on the files in the repos of GitHub users and organizations. +This folder contains 2 plugins for Security Copilot: +- The "GitHub Advanced Security for GitHub Organizations" plugin has the skills to list all the alerts resulting from GhAS analysis on all the repos of an organization. It calls the https://api.github.com/orgs/Org-Name API in GitHub. +- The "GitHub Advanced Security for GitHub Repositories" plugin has the skills to list all the alerts resulting from GhAS analysis on a specific repo of an organization. It has also the skills to get the details of each alert. It allows also to read the CodeQL configuration on a specific repo. It calls the https://api.github.com/repos/Org-Name API in GitHub. + +Instructions +- In GitHub, create a Personal access token of type "classic". Assign to it at least the scope of "repo" / "security_events" +- Download the 2 files *Manifest.yaml from this folder +- Create the 2 custom plugins in Security Copilot, one after the other, by uploading these 2 manifest files +- After each upload, configure each of the 2 custom plugins by setting, on each of them, the 2 requested parameters: the name of your organization in GitHub (write it at the end of the proposed URL) and the personal access token (key) created in GitHub +- You can now use the skills available in these 2 plugins. + +For detailed step-by-step guidance and demos, please refer to https://www.linkedin.com/pulse/copilot-security-custom-plugin-github-advanced-stefano-pescosolido-vrcpf/?trackingId=asq05nvu75FGTlUOJYBw6w%3D%3D + +Note: +- In this initial first preview release, all skills require to specify in input a "User-Agent" parameter. This is a temporary workaround for ensuring that Security Copilot writes this parameter in the header of its calls to the GitHub APIs (without that header, the APIs return access denied). **In that parameter you can set any value** (the value of that parameter is not relevant). Typically you have to write set its value only once in a session: in the following prompts Copilot assumes that its value remains the same. + +Initial release author: +Stefano Pescosolido +https://linkedin.com/in/stefanopescosolido diff --git a/Plugins/Community Based Plugins/GitHub Advanced Security Custom Plugin Scenarios/GitHubAdvancedSecurityForAzureDevOps-CustomPlugin/readme.md b/Plugins/Community Based Plugins/GitHub Advanced Security Custom Plugin Scenarios/GitHubAdvancedSecurityForAzureDevOps-CustomPlugin/readme.md new file mode 100644 index 00000000..c9a9d2c6 --- /dev/null +++ b/Plugins/Community Based Plugins/GitHub Advanced Security Custom Plugin Scenarios/GitHubAdvancedSecurityForAzureDevOps-CustomPlugin/readme.md @@ -0,0 +1,17 @@ +**GitHub Advanced Security for Azure DevOps (GAzDO) Custom Plugin** + +GitHub Advanced Security for Azure DevOps (GAzDO) executes code scanning (Static Application Security Testing, SAST, via CodeQL), dependabot analysis (Software Composition Analysis, SCA) and secret scanning on the files in the repos of Azure DevOps organizations. +This folder contains a plugin for Security Copilot that queries the "List" and "Get" API exposed by Azure DevOps and related to Advanced Security (GAzDO). + +Instructions +- In Azure DevOps, create a Personal Access Token (PAT). Assign to it at least the scope of "Read" in "Advanced Security" +- Generate the Base64 version of the string "Basic:". For example, use this PowerShell command: + [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes("Basic:")) +- Download the file *Manifest.yaml from this folder +- Create the custom plugins in Security Copilot by uploading this manifest file +- After the upload, configure the custom plugins by setting the 2 requested parameters: the name of your organization in Azure DevOps (write it at the end of the proposed URL) and the Base64 version of your Personal Access Token, created as described above. +- You can now use the skills available in this plugin. + +Initial release author: +Stefano Pescosolido +https://linkedin.com/in/stefanopescosolido diff --git a/Plugins/Community Based Plugins/HaveIBeenPwned/OpenAPI/hibp-openapi.yaml b/Plugins/Community Based Plugins/HaveIBeenPwned/OpenAPI/hibp-openapi.yaml new file mode 100644 index 00000000..ebaee1b6 --- /dev/null +++ b/Plugins/Community Based Plugins/HaveIBeenPwned/OpenAPI/hibp-openapi.yaml @@ -0,0 +1,53 @@ +openapi: 3.0.0 +info: + title: HaveIBeenPwned + version: 1.0.0 +servers: + - url: https://haveibeenpwned.com/api/v3 +paths: + /breachedaccount/{email}: + get: + operationId: HaveIBeenPwned + summary: This path checks if a email address is part of any data breaches from HaveIBeenPwned. + parameters: + - in: path + name: email + schema: + type: string + required: true + - name: User-Agent + in: header + schema: + type: string + default: Default User-Agent + responses: + '200': + description: Successful response + content: + application/json: {} + '400': + description: The account does not comply with an acceptable format + content: + application/json: {} + '401': + description: API Key is missing or invalid + content: + application/json: {} + '403': + description: Unauthorized + content: + application/json: {} + '404': + description: User Not Found in any data breaches + content: + application/json: {} + '429': + description: Rate limit exceeded + content: + application/json: {} + '503': + description: Service unavailable + content: + application/json: {} + default: + description: default \ No newline at end of file diff --git a/Plugins/Community Based Plugins/HaveIBeenPwned/README.md b/Plugins/Community Based Plugins/HaveIBeenPwned/README.md new file mode 100644 index 00000000..002ba4b9 --- /dev/null +++ b/Plugins/Community Based Plugins/HaveIBeenPwned/README.md @@ -0,0 +1,29 @@ +# Security Copilot Custom Plugin - API - Get User Account Breach Information from Have I Been Pwned + +#### Security Copilot Plugin to gather list of breaches for a user account using Have I Been Pwned + +### 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) +* [API Key](https://haveibeenpwned.com/API/Key) from Have I Been Pwned + +### Instructions +#### Upload the Custom Plugin + +1. Obtain the file [hibp-manifest.yaml](https://github.com/Azure/Copilot-For-Security/blob/main/Plugins/Community%20Based%20Plugins/HaveIBeenPwned/hibp-manifest.yaml) from this directory. +2. [Upload the custom plugin](https://learn.microsoft.com/en-us/security-copilot/manage-plugins?tabs=securitycopilotplugin#add-custom-plugins) and verify it's activated. +3. After plugin upload put in your [Have I Been Pwned API Key](https://learn.microsoft.com/en-us/security-copilot/plugin_api#configure-authentication-1). + +![CopilotForSecurity](https://learn.microsoft.com/en-us/security-copilot/media/add-plugin-button.png) + +### Plugin Utilization + +Here's some sample queries you can utilize to trigger this plugin +Note: you can use the keyword 'Have I Been Pwned' or 'HIBP' in your queries + +* Check if the user test@email.com is part of any data breaches from Have I Been Pwned +* Give me the list of breaches for the user test@email.com from Have I Been Pwned as bullet points +* Summarize the list of data breaches for the user test@email.com from Have I Been Pwned +
+ diff --git a/Plugins/Community Based Plugins/HaveIBeenPwned/hibp-manifest.yaml b/Plugins/Community Based Plugins/HaveIBeenPwned/hibp-manifest.yaml new file mode 100644 index 00000000..2592a850 --- /dev/null +++ b/Plugins/Community Based Plugins/HaveIBeenPwned/hibp-manifest.yaml @@ -0,0 +1,16 @@ +Descriptor: + Name: HaveIBeenPwned + DisplayName: HaveIBeenPwned + Description: This tool checks if an email address has been found in any of the data breaches in Have I Been Pwned (HIBP). + SupportedAuthTypes: + - ApiKey + Authorization: + Type: APIKey + Key: hibp-api-key + Location: Header + AuthScheme: '' + +SkillGroups: + - Format: API + Settings: + OpenApiSpecUrl: https://raw.githubusercontent.com/Azure/Copilot-For-Security/main/Plugins/Community%20Based%20Plugins/HaveIBeenPwned/OpenAPI/hibp-openapi.yaml \ No newline at end of file diff --git a/Plugins/Community Based Plugins/IBM/IBM_OpenAPI.yaml b/Plugins/Community Based Plugins/IBM/IBM_OpenAPI.yaml new file mode 100644 index 00000000..ac390a37 --- /dev/null +++ b/Plugins/Community Based Plugins/IBM/IBM_OpenAPI.yaml @@ -0,0 +1,156 @@ +openapi: 3.0.0 +info: + title: IBM X-Force Exchange API + version: 2.0 +servers: + - url: https://api.xforce.ibmcloud.com/api +paths: + /reports/: + get: + operationId: Get Threat Intelligence Reports IBM + summary: Get premium threat intelligence reports based on a given report type + description: | + This endpoint allows you to view the get Threat Intelligence reports by report type. + Possible report types are threatanalysis, osintadvisory, malware, industry, and threatgroup. + #ExamplePrompts Tell me about recent IBM threat intelligence reports + #ExamplePrompts Show me IBM threat intelligence reports + #ExamplePrompts What are the recent X-Force threat intelligence reports? + #ExamplePrompts Show me X-Force threat intelligence reports + parameters: + - name: reportType + in: path + description: The type of Threat Intelligence report. Possible report types are threatanalysis, osintadvisory, malware, industry, and threatgroup. + required: true + schema: + type: string + example: "threatanalysis" + - name: added_after + in: query + description: To get reports created after specified date, For example, 2022-10-12. If not specified, the query will return the newest IPs. + required: false + schema: + type: string + example: "2022-10-12" + - name: added_before + in: query + description: To get reports created before specified date, For example, 2022-10-12. If not specified, the query will return the newest IPs. + required: false + schema: + type: string + example: "2022-10-12" + - name: limit + in: query + description: The number of returned Malware Analysis Reports, default value is 200. The limit must not be larger than 200. + required: false + schema: + type: integer + example: 201 + - name: skip + in: query + description: The startingpoint to retrieve entries, default value is 0 + required: false + schema: + type: integer + example: 0 + responses: + "200": + description: Successful response + content: + application/json + "403": + description: Access denied + "404": + description: Not found + + /threat_groups: + get: + operationId: Get Threat Group Profiles IBM + summary: Get a List of Threat Group Profiles. + description: | + Return a list of Threat Group Profiles based on an integer value of how many profiles you wish to see. + #ExamplePrompts Show me the most recent IBM Threat Group Profiles + #ExamplePrompts Show me the most recent X-Force threat group profiles + #ExamplePrompts What are the most recent X-Force threat group profiles? + #ExamplePrompts What are the IBM threat group profiles? + #ExamplePrompts Show me the top 20 IBM threat group profiles. + parameters: + - name: limit + in: query + description: The number of returned Threat Group Profiles, default value is 200. The limit must not be larger than 200. + required: true + schema: + type: integer + example: 20 + - name: skip + in: query + description: The startingpoint to retrieve entries, default value is 0 + required: false + schema: + type: integer + example: 0 + responses: + "200": + description: Successful response + content: + application/json + "403": + description: Access denied + "404": + description: Not found + + /resolve/{input}: + get: + operationId: Get Dns Records IBM + summary: Verify a domain name, IP address, or URL against IBM X-Force DNS records + description: | + This endpoint allows you to view the DNS information for a domain name, IP address, or URL. + #ExamplePrompts Show me the IBM DNS records for www.bing.com + #ExamplePrompts What are the IBM X-Force DNS records for https://www.bing.com/search?q=microsoft&form=QBLH&sp=-1&lq=0&pq=microsof&sc=11-8&qs=n&sk=&cvid=166E76706804404BAA7390129EE88004&ghsh=0&ghacc=0&ghpl=? + #ExamplePrompts What is the IBM DNS record for www.https://www.microsoft.com/? + #ExamplePrompts Are there IBM DNS records for www.https://www.google.com? + parameters: + - name: input + in: path + description: The domain name, IP Address, or URL to check for DNS information. + required: true + schema: + type: string + example: "www.bing.com" + responses: + "200": + description: Successful response + content: + application/json + "403": + description: Access denied + "404": + description: Not found + + + /whois/{host}: + get: + operationId: Get WHOIS IBM + summary: Returns the WHOIS information for an IP, URL, or domain. + description: | + This endpoint allows you to view WHOIS for a domain name, IP address, or URL. + #ExamplePrompts Show me the IBM whois records for www.bing.com + #ExamplePrompts What is the IBM WHOIS for https://www.bing.com/search?q=microsoft&form=QBLH&sp=-1&lq=0&pq=microsof&sc=11-8&qs=n&sk=&cvid=166E76706804404BAA7390129EE88004&ghsh=0&ghacc=0&ghpl=? + #ExamplePrompts IBM WHOIS www.https://www.microsoft.com/? + #ExamplePrompts Is there a WHOIS for www.https://www.google.com? + parameters: + - name: host + in: path + description: The domain name,IP Address, or URL to check for WHOIS information. + required: true + schema: + type: string + example: "www.bing.com" + responses: + "200": + description: Successful response + content: + application/json + "403": + description: Access denied + "404": + description: Not found \ No newline at end of file diff --git a/Plugins/Community Based Plugins/IBM/images/ibm_insert.png b/Plugins/Community Based Plugins/IBM/images/ibm_insert.png new file mode 100644 index 00000000..703944ee Binary files /dev/null and b/Plugins/Community Based Plugins/IBM/images/ibm_insert.png differ diff --git a/Plugins/Community Based Plugins/IBM/images/ibm_select.png b/Plugins/Community Based Plugins/IBM/images/ibm_select.png new file mode 100644 index 00000000..3454f87e Binary files /dev/null and b/Plugins/Community Based Plugins/IBM/images/ibm_select.png differ diff --git a/Plugins/Community Based Plugins/IBM/images/ibm_settings.png b/Plugins/Community Based Plugins/IBM/images/ibm_settings.png new file mode 100644 index 00000000..815e983a Binary files /dev/null and b/Plugins/Community Based Plugins/IBM/images/ibm_settings.png differ diff --git a/Plugins/Community Based Plugins/IBM/manifest.yaml b/Plugins/Community Based Plugins/IBM/manifest.yaml new file mode 100644 index 00000000..c825ff84 --- /dev/null +++ b/Plugins/Community Based Plugins/IBM/manifest.yaml @@ -0,0 +1,22 @@ +Descriptor: + Name: IBM + DisplayName: IBM X-Force Exchange (Preview) + DescriptionDisplay: Threat Intelligence Profiles, Threat Group Profiles, and DNS Profiles + Description: | + Use this skillset to call the IBM X-Force Exchange API: + - What it does: Retrieves Threat Intelligence Profiles, Threat Group Profiles, and DNS Profiles from the IBM X-Force Exchange platform. + - To what categories does it belong: DNS Profile, Threat Intelligence, Threat Groups. + IBM X-Force's API presents the below functions: + - Get Threat Intelligence Reports IBM: Get premium threat intelligence reports based on a given report type. + - Get Threat Group Profiles IBM: Get a List of Threat Group Profiles. + - Get Dns Records IBM: Verify a domain name, IP address, or URL against IBM X-Force DNS records + - Get WHOIS IBM: Returns the WHOIS information for an IP, URL, or domain. + SupportedAuthTypes: + - Basic + Category: other + Icon: https://www.ibm.com/brand/experience-guides/developer/b1db1ae501d522a1a4b49613fe07c9f1/01_8-bar-positive.svg #TODO: Use IBM X-Force Exchange icon + +SkillGroups: + - Format: API + Settings: + OpenApiSpecUrl: https://medeinaplugins.blob.core.windows.net/gpt-plugins/ibm-xforce-web-api.yaml diff --git a/Plugins/Community Based Plugins/IBM/pluginVersion.json b/Plugins/Community Based Plugins/IBM/pluginVersion.json new file mode 100644 index 00000000..5e6a6823 --- /dev/null +++ b/Plugins/Community Based Plugins/IBM/pluginVersion.json @@ -0,0 +1,22 @@ +{ + "Name": "IBM", + "Skills":[ + { + "Name": "Get Threat Intelligence Reports IBM", + "Path": "/reports" + }, + { + "Name": "Get Threat Group Profiles IBM", + "Path": "/threat_groups" + }, + { + "Name": "Get Dns Records IBM", + "Path": "/resolve/{input}" + }, + { + "Name": "Get WHOIS IBM", + "Path": "/whois/{host}" + } + ], + "Version" :"1.0.0" +} \ No newline at end of file diff --git a/Plugins/Community Based Plugins/IBM/readme.md b/Plugins/Community Based Plugins/IBM/readme.md new file mode 100644 index 00000000..6f2b552d --- /dev/null +++ b/Plugins/Community Based Plugins/IBM/readme.md @@ -0,0 +1,99 @@ +# # IBM X-Force Thread Intelligence +**Publisher: Microsoft** + +Product Information: +https://www.ibm.com/products/xforce-threat-intelligence + +## Overview +IBM X-Force is an online platform designed to help you understand and respond to security threats. It's a cloud service that lets you access, distribute, and take action on information about potential security risks. With this platform, you can quickly find out about new security threats around the world, gather information that you can use to protect yourself, talk to experts for advice, and work with other people who are also trying to stay safe. IBM X-Force Exchange is powered by both human expertise and automated processes, and it uses IBM X-Force's extensive resources to help users get ahead of threats that are just starting to appear. + +## Invoking the Plugin and Skills + +1. A window entitled, Manage plugins, will open. Scroll down, until you see the IBM X-Force plugin. +settings + + +2. Click on the Setup button for the IBM X-Force plugin highlighted in the image above, a new model will appear. +settings + + +3. Enter the IBM X-Force API key and password in the Value input box and click on the Save button. Once saved, you would see an acknowledgement. +settings + + +4. Use the IBM X-Force Skillset + 1. Make sure the IBM X-Force Plugin is enabled, verify by navigating to the Manage plugins tab and making sure the toggle is active. + 2. Run a sample prompt such as `how me the IBM whois for www.bing.com.` + + + +## Skills & Prompts + +1. Get Threat Intelligence Reports IBM: Get premium threat intelligence reports based on a given report type + - Example Prompt(s): + - Tell me about recent IBM threat intelligence report + - Show me IBM X-Force threat intelligence reports + - Inputs: + - reportType + - Description: The type of Threat Intelligence report. Possible report types are threatanalysis, osintadvisory, malware, industry, and threatgroup. + - Required: true + - Type: string + - Example: "threatanalysis" + - added_after + - Description: To get reports created after specified date, For example, 2022-10-12. If not specified, the query will return the newest IPs. + - Required: false + - Type: string + - Example: "2022-10-12" + - added_before + - Description: To get reports created before specified date, For example, 2022-10-12. If not specified, the query will return the newest IPs. + - Required: false + - Type: string + - Example: "2022-10-12" + - limit + - Description: The number of returned Malware Analysis Reports, default value is 200. The limit must not be larger than 200. + - Required: false + - Type: integer + - Example: 201 + - skip + - Description: The startingpoint to retrieve entries, default value is 0 + - Required: false + - Type: integer + - Example: 10 +2. Get Threat Group Profiles IBM: Get a List of Threat Group Profiles. + - Example Prompt(s): + - What are the most recent X-Force threat group profiles? + - What are the IBM threat group profiles? + - Show me the top 20 IBM threat group profiles. + - Inputs: + - limit + - Description: The number of returned Malware Analysis Reports, default value is 200. The limit must not be larger than 200. + - Required: false + - Type: integer + - Example: 201 + - skip + - Description: The startingpoint to retrieve entries, default value is 0 + - Required: false + - Type: integer + - Example: 10 +3. Get Dns Records IBM: Verify a domain name, IP address, or URL against IBM X-Force DNS records. + - Example Prompt(s): + - Show me the IBM DNS records for www.bing.com + - What are the IBM X-Force DNS records for https://www.bing.com/search?q=microsoft&form=QBLH&sp=-1&lq=0&pq=microsof&sc=11-8&qs=n&sk=&cvid=166E76706804404BAA7390129EE88004&ghsh=0&ghacc=0&ghpl=? + - What is the IBM DNS record for www.https://www.microsoft.com/? + - Inputs: + - input + - Description: The domain name, IP Address, or URL to check for DNS information. + - Required: true + - Type: string + - Example: "www.bing.com" +4. Get WHOIS IBM: Returns the WHOIS information for an IP, URL, or domain. + - Example Prompt(s): + - Show me the IBM whois records for www.bing.com + - What is the IBM WHOIS for https://www.bing.com/search?q=microsoft&form=QBLH&sp=-1&lq=0&pq=microsof&sc=11-8&qs=n&sk=&cvid=166E76706804404BAA7390129EE88004&ghsh=0&ghacc=0&ghpl=? + - IBM WHOIS www.https://www.microsoft.com/? + - Inputs: + - host + - Description: The domain name,IP Address, or URL to check for WHOIS information. + - Required: true + - Rype: string + - Example: "www.bing.com" \ No newline at end of file diff --git a/Plugins/Community Based Plugins/IP-API/IP-API.yaml b/Plugins/Community Based Plugins/IP-API/IP-API.yaml new file mode 100644 index 00000000..d75a965d --- /dev/null +++ b/Plugins/Community Based Plugins/IP-API/IP-API.yaml @@ -0,0 +1,10 @@ +Descriptor: + Name: IP-API + DisplayName: IP-API + Description: IP-API IP Address Lookup + Icon: https://github.com/rod-trent/Copilot-for-Security/blob/main/Plugins/IP-API/IPAPI.png?raw=true + +SkillGroups: + - Format: API + Settings: + OpenApiSpecUrl: https://raw.githubusercontent.com/rod-trent/Copilot-for-Security/main/Plugins/IP-API/IP-API.txt diff --git a/Plugins/Community Based Plugins/IP-API/Readme.md b/Plugins/Community Based Plugins/IP-API/Readme.md new file mode 100644 index 00000000..361d4eba --- /dev/null +++ b/Plugins/Community Based Plugins/IP-API/Readme.md @@ -0,0 +1,17 @@ +# Copilot for Security Plugin: IP-API + +### **This plugin allows you to submit an IP address to IP-API.com and retrieve a wealth of information about the IP address.** + +### Pre-requisites + +- [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 IP-API.yaml from this directory. + 1. Upload the custom plugin + +For more information, see: Copilot for Security Plugin: [Using IP-API with Copilot for Security](https://rodtrent.substack.com/p/using-ip-api-with-copilot-for-security) diff --git a/Plugins/Community Based Plugins/IPGeoLocation/OpenAPI/ipgl-openapi.yaml b/Plugins/Community Based Plugins/IPGeoLocation/OpenAPI/ipgl-openapi.yaml new file mode 100644 index 00000000..78d0c102 --- /dev/null +++ b/Plugins/Community Based Plugins/IPGeoLocation/OpenAPI/ipgl-openapi.yaml @@ -0,0 +1,31 @@ +openapi: 3.0.0 +info: + title: IPGeoLocation + version: 1.0.0 +servers: +- url: https://api.ipgeolocation.io +paths: + "/ipgeo/": + get: + operationId: IPGeoLocation + tags: + - default + summary: This path checks an IP address' location and ISP. + parameters: + - name: ip + in: query + schema: + type: string + required: true + responses: + '200': + description: Successful response + content: + application/json: {} + '401': + description: Unauthorized + content: + application/json: {} + default: + description: default +components: {} \ No newline at end of file diff --git a/Plugins/Community Based Plugins/IPGeoLocation/README.md b/Plugins/Community Based Plugins/IPGeoLocation/README.md new file mode 100644 index 00000000..cc4ca130 --- /dev/null +++ b/Plugins/Community Based Plugins/IPGeoLocation/README.md @@ -0,0 +1,30 @@ +# Copilot for Security Custom Plugin - API - Get IP Information from IP Geo Location + +#### Copilot for Security Plugin to gather location and ISP (Organisation) for an IP Address using IP Geo Location + +### Pre-requisites + +* [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) +* [API Key](https://app.ipgeolocation.io/signup) from IP Geo Location + +### Instructions +#### Upload the Custom Plugin + +1. Obtain the file [ipgl-manifest.yaml](https://github.com/Azure/Copilot-For-Security/blob/main/Plugins/Community%20Based%20Plugins/IPGeoLocation/ipgl-manifest.yaml) from this directory. +2. [Upload the custom plugin](https://learn.microsoft.com/en-us/security-copilot/manage-plugins?tabs=securitycopilotplugin#add-custom-plugins) and verify it's activated. +3. After plugin upload put in your [IP Geo Location API Key](https://learn.microsoft.com/en-us/security-copilot/plugin_api#configure-authentication-1). + +![CopilotForSecurity](https://learn.microsoft.com/en-us/security-copilot/media/add-plugin-button.png) + +### Plugin Utilization + +Here's some sample queries you can utilize to trigger this plugin + +* Summarize the information for the IP 1.1.1.1 from IP Geo Location +* Summarize the information for the IP 4.4.4.4 from IP Geo Location as bullet points +* Give me the location, ISP and reputation score of the IP address 8.8.8.8 from IP Geo Location in form of a table +
+ +
+ \ No newline at end of file diff --git a/Plugins/Community Based Plugins/IPGeoLocation/ipgl-manifest.yaml b/Plugins/Community Based Plugins/IPGeoLocation/ipgl-manifest.yaml new file mode 100644 index 00000000..26cd7cd7 --- /dev/null +++ b/Plugins/Community Based Plugins/IPGeoLocation/ipgl-manifest.yaml @@ -0,0 +1,16 @@ +Descriptor: + Name: IP Geo Location + DisplayName: IP Geo Location + Description: This tool checks an IP address' location and ISP using IP Geo Location. + SupportedAuthTypes: + - ApiKey + Authorization: + Type: APIKey + Key: apiKey + Location: QueryParams + AuthScheme: '' + +SkillGroups: + - Format: API + Settings: + OpenApiSpecUrl: https://raw.githubusercontent.com/Azure/Copilot-For-Security/main/Plugins/Community%20Based%20Plugins/IPGeoLocation/OpenAPI/ipgl-openapi.yaml \ No newline at end of file diff --git a/Plugins/Community Based Plugins/IPQualityScore/OpenAPI/ipqs-openapi.yaml b/Plugins/Community Based Plugins/IPQualityScore/OpenAPI/ipqs-openapi.yaml new file mode 100644 index 00000000..45c88edf --- /dev/null +++ b/Plugins/Community Based Plugins/IPQualityScore/OpenAPI/ipqs-openapi.yaml @@ -0,0 +1,52 @@ +openapi: 3.0.0 +info: + title: IPQualityScore + version: 1.0.0 +servers: + - url: https://ipqualityscore.com +paths: + "/api/json/ip/": + get: + tags: + - default + operationId: IPQualityScore + summary: This path checks an IP address' location, ISP, Type, Reputation Score and Abuse Velocity. + parameters: + - name: ip + in: query + schema: + type: string + required: true + - name: strictness + in: query + schema: + type: integer + example: 0 + - name: allow_public_access_points + in: query + schema: + type: boolean + example: true + - name: fast + in: query + schema: + type: boolean + example: true + - name: lighter_penalties + in: query + schema: + type: boolean + example: true + - name: mobile + in: query + schema: + type: boolean + example: true + responses: + "200": + description: Successful response + content: + application/json: {} + default: + description: default +components: {} \ No newline at end of file diff --git a/Plugins/Community Based Plugins/IPQualityScore/README.md b/Plugins/Community Based Plugins/IPQualityScore/README.md new file mode 100644 index 00000000..d6ebbb58 --- /dev/null +++ b/Plugins/Community Based Plugins/IPQualityScore/README.md @@ -0,0 +1,30 @@ +# Security Copilot Custom Plugin - API - Get IP Information from IP Quality Score + +#### Security Copilot Plugin to gather type, location, reputation score, and abuse velocity for an IP Address using IP Quality Score + +### 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) +* [API Key](https://www.ipqualityscore.com/proxy-vpn-tor-detection-service) from IP Quality Score + +### Instructions +#### Upload the Custom Plugin + +1. Obtain the file [ipqs-manifest.yaml](https://github.com/Azure/Copilot-For-Security/blob/main/Plugins/Community%20Based%20Plugins/IPQualityScore/ipqs-manifest.yaml) from this directory. +2. [Upload the custom plugin](https://learn.microsoft.com/en-us/security-copilot/manage-plugins?tabs=securitycopilotplugin#add-custom-plugins) and verify it's activated. +3. After plugin upload put in your [IPQS API Key](https://learn.microsoft.com/en-us/security-copilot/plugin_api#configure-authentication-1). + +![CopilotForSecurity](https://learn.microsoft.com/en-us/security-copilot/media/add-plugin-button.png) + +### Plugin Utilization + +Here's some sample queries you can utilize to trigger this plugin + +* Summarize the information for the IP 1.1.1.1 from IPQS +* Summarize the information for the IP 4.4.4.4 from IPQS as bullet points +* Give me the location, ISP and reputation score of the IP address 8.8.8.8 from IPQS in form of a table +
+ +
+ diff --git a/Plugins/Community Based Plugins/IPQualityScore/ipqs-manifest.yaml b/Plugins/Community Based Plugins/IPQualityScore/ipqs-manifest.yaml new file mode 100644 index 00000000..fda5e490 --- /dev/null +++ b/Plugins/Community Based Plugins/IPQualityScore/ipqs-manifest.yaml @@ -0,0 +1,16 @@ +Descriptor: + Name: IPQS + DisplayName: IP Quality Score + Description: This tool checks an IP address' location, ISP, reputation etc. using IP Quality Score + SupportedAuthTypes: + - ApiKey + Authorization: + Type: APIKey + Key: IPQS-KEY + Location: Header + AuthScheme: '' + +SkillGroups: + - Format: API + Settings: + OpenApiSpecUrl: https://raw.githubusercontent.com/Azure/Copilot-For-Security/main/Plugins/Community%20Based%20Plugins/IPQualityScore/OpenAPI/ipqs-openapi.yaml \ No newline at end of file diff --git a/Plugins/Community Based Plugins/Identity/IdentitySecurityAnalyst.yml b/Plugins/Community Based Plugins/Identity/IdentitySecurityAnalyst.yml new file mode 100644 index 00000000..eb324c34 --- /dev/null +++ b/Plugins/Community Based Plugins/Identity/IdentitySecurityAnalyst.yml @@ -0,0 +1,209 @@ +Descriptor: + Name: IdentitySecurityAnalyst + DisplayName: Identity Security Analyst plugin + Description: > + Identity Security skills to query Identity events for detection and forensics hunting across User Risk Assessment, + Sign-in Monitoring, Admin Activity Monitoring, Application Usage Monitoring, Privileged Identity Management, Access Review + +SkillGroups: + - Format: KQL + Skills: + - Name: IdentityGetUserRiskAssesment + DisplayName: Get User Risk Assessment + Description: > + Fetches the user risk levels based on their activities. This could include sign-in attempts from unfamiliar locations, + repeated failed sign-in attempts, or other suspicious behavior. + ExamplePrompt: + - "Get all risky users" + - "Fetch risky users" + - "Run users risk assessment" + - "List all risky users" + Settings: + Target: Defender + Template: |- + // Query to fetch user risk events with sign-in details + let RiskyEvents = AADUserRiskEvents + | where TimeGenerated > ago(1d) + | project UserPrincipalName, RiskLevel, RiskEventType, TimeGenerated; + let SignInAttempts = SigninLogs + | where TimeGenerated > ago(1d) // Same time filter + | summarize AttemptCount = count(), FailedAttempts = sumif(1, ResultType != 0) by UserPrincipalName, Location = tostring(LocationDetails), IPAddress, TimeGenerated; + RiskyEvents + | join kind=inner (SignInAttempts) on UserPrincipalName + | where RiskLevel != "None" // Filter out non-risky users + | project UserPrincipalName, RiskLevel, RiskEventType, AttemptCount, FailedAttempts, Location, IPAddress, TimeGenerated + | order by RiskLevel desc, TimeGenerated desc + | take 1000 + + - Name: IdentityGetSignInMonitoring + DisplayName: Get Users Sign-in Activities + Description: > + Fetches all user sign-in activities. This includes successful sign-ins, failed attempts, and the location and + device used for sign-in. Any unusual sign-in activity could be a sign of a potential security threat. + ExamplePrompt: + - "Get all sign-in activities" + - "Fetch all users sign-in activities" + - "List all users sign-in monitoring" + Settings: + Target: Defender + Template: |- + SigninLogs + | where TimeGenerated > ago(1d) // Filter for the last 1 day, adjust as necessary + | project + UserPrincipalName, + SignInStatus = iff(ResultType == 0, "Success", "Failure"), + FailureReason = iff(ResultType != 0, ResultDescription, "N/A"), + TimeGenerated, + Location = tostring(LocationDetails), + Device = tostring(DeviceDetail), + IPAddress, + ClientAppUsed, + OperatingSystem = tostring(DeviceDetail.operatingSystem), + Browser = tostring(DeviceDetail.browser) + | order by TimeGenerated desc + | take 1000 + + - Name: IdentityGetAdminActivityMonitoring + DisplayName: Get Admin Activities Monitoring + Description: > + Fetches Admin Activity Monitoring logs. Admin accounts have high-level access and can be a prime target for attackers. + Monitor all admin activities, especially those involving changes to security settings, user privileges, or access controls. + ExamplePrompt: + - "Get all admin activities" + - "Fetch all admin activities" + - "List all admin monitoring events" + Settings: + Target: Defender + Template: |- + AuditLogs + | where TimeGenerated > ago(1d) // Filter for the last 1 days; adjust as needed + | where OperationName in ( + "Add member to role", + "Remove member from role", + "Update user", + "Add application", + "Update application", + "Delete application", + "Add group", + "Update group", + "Delete group", + "Add directory role", + "Update directory role", + "Delete directory role", + "Update conditional access policy", + "Delete conditional access policy", + "Add conditional access policy", + "Update user privileges", + "Update access controls" + ) + | extend AdminPrincipalName = tostring(InitiatedBy.user.userPrincipalName) + | project + TimeGenerated, + OperationName, + AdminPrincipalName, + InitiatedByDetails = tostring(InitiatedBy.user), + TargetResources = tostring(TargetResources[0].displayName), + PrincipalNameFull = tostring(TargetResources[0].userPrincipalName), + TargetType = tostring(TargetResources[0].type), + ActivityDetails = tostring(ActivityDisplayName), + Result + | order by TimeGenerated desc + | take 1000 + + - Name: IdentityGetApplicationUsageMonitoring + DisplayName: Get Application Usage Monitoring + Description: > + Fetches Application Usage Monitoring logs to keep an eye on the usage of applications within your organization. + Unusual application activity, such as a high number of downloads or an increase in usage outside of normal business hours, + could indicate a potential security issue. + ExamplePrompt: + - "Get all applications usage" + - "Fetch all applications activities" + - "List all applications usage monitoring" + Settings: + Target: Defender + Template: |- + SigninLogs + | where TimeGenerated > ago(1d) // Filter for the last 1 days; adjust as needed + | where AppDisplayName != "" // Only consider records with application usage + | summarize + SignInCount = count(), + UniqueUsers = dcount(UserPrincipalName), + Downloads = sumif(1, ClientAppUsed == "Browser" or ClientAppUsed == "Mobile apps and desktop clients") + by AppDisplayName, bin(TimeGenerated, 1d) // Aggregate data daily + | project + TimeGenerated, + AppDisplayName, + SignInCount, + UniqueUsers, + Downloads + | order by TimeGenerated desc + | take 1000 + + - Name: IdentityPIMMonitoring + DisplayName: Get Privileged Identity Management (PIM) Monitoring + Description: > + Fetches Privileged Identity Management logs to monitor the lifecycle of privileged identities within your organization. + This includes the creation, modification, and deletion of privileged accounts. + ExamplePrompt: + - "Get all PIM activities" + - "Fetch all Privileged identities activities" + - "List all Privileged identity management monitoring events" + Settings: + Target: Defender + Template: |- + AuditLogs + | where TimeGenerated > ago(1d) // Filter for the last 1 days; adjust as needed + | where Category contains "PIM" or Category contains "PrivilegedIdentity" or InitiatedBy.app.displayName contains "MS-PIM" // Filter for Privileged Identity Management related activities + | where OperationName in ( + "Add member to role", + "Remove member from role", + "Update role", + "Add role assignment", + "Remove role assignment", + "Activate role", + "Deactivate role" + ) + | extend AdminPrincipalName = tostring(InitiatedBy.user.userPrincipalName) + | project + TimeGenerated, + OperationName, + AdminPrincipalName, + InitdName = tostring(InitiatedBy.app.displayName), + InitiPrinName = tostring(InitiatedBy.app.servicePrincipalName), + InitiPrinID = tostring(InitiatedBy.app.servicePrincipalId), + TargetUser = tostring(TargetResources[0].userPrincipalName), + RoleName = tostring(TargetResources[0].displayName), + ActivityDetails = tostring(ActivityDisplayName), + Result + | order by TimeGenerated desc + | take 1000 + + - Name: IdentityAccessReviewMonitoring + DisplayName: Get Access Review Monitoring + Description: > + Fetches Access Review logs to regularly review user access to various resources within your organization. + This can help ensure that users only have access to the resources they need for their job functions, + reducing the risk of insider threats. + ExamplePrompt: + - "Get all access review activities" + - "Fetch all access review activities" + - "List all access review monitoring events" + Settings: + Target: Defender + Template: |- + AuditLogs + | where TimeGenerated > ago(1d) // Filter for the last 1 days; adjust as needed + | where OperationName startswith "Access Review" or Category contains "Access Review" // Filter for Access Review related activities + | project + TimeGenerated, + OperationName, + InitiatedBy = tostring(InitiatedBy.user.userPrincipalName), + TargetUser = tostring(TargetResources[0].userPrincipalName), + TargetDisplayName = tostring(TargetResources[0].displayName), + TargetResourceType = tostring(TargetResources[0].type), + ActivityDetails = tostring(ActivityDisplayName), + Result, + CorrelationId + | order by TimeGenerated desc + | take 1000 \ No newline at end of file diff --git a/Plugins/Community Based Plugins/Identity/readme.md b/Plugins/Community Based Plugins/Identity/readme.md new file mode 100644 index 00000000..83165902 --- /dev/null +++ b/Plugins/Community Based Plugins/Identity/readme.md @@ -0,0 +1,23 @@ +# Custom Identity Security Analyst plugin for Security Copilot + +The custom Identity Security skills to query Identity events for detection and forensics hunting across User Risk Assessment, Sign-in Monitoring, Admin Activity Monitoring, Application Usage Monitoring, Privileged Identity Management, Access Review: + +## Key Features + +### User Risk Assessment +Fetches the user risk levels based on their activities. This could include sign-in attempts from unfamiliar locations, repeated failed sign-in attempts, or other suspicious behavior. + +### Users Sign-in activities +Fetches all user sign-in activities. This includes successful sign-ins, failed attempts, and the location and device used for sign-in. Any unusual sign-in activity could be a sign of a potential security threat. + +### Admin activities monitoring +Fetches Admin Activity Monitoring logs. Admin accounts have high-level access and can be a prime target for attackers. Monitor all admin activities, especially those involving changes to security settings, user privileges, or access controls. + +### Applications usage monitoring +Fetches Application Usage Monitoring logs to keep an eye on the usage of applications within your organization. Unusual application activity, such as a high number of downloads or an increase in usage outside of normal business hours, could indicate a potential security issue. + +### Privileged Identity Management (PIM) monitoring +Fetches Privileged Identity Management logs to monitor the lifecycle of privileged identities within your organization. This includes the creation, modification, and deletion of privileged accounts. + +### Access Review monitoring +Fetches Access Review logs to regularly review user access to various resources within your organization. This can help ensure that users only have access to the resources they need for their job functions, reducing the risk of insider threats. diff --git a/Plugins/Community Based Plugins/Images/hibp-example-screenshot.png b/Plugins/Community Based Plugins/Images/hibp-example-screenshot.png new file mode 100644 index 00000000..e8f6c1a7 Binary files /dev/null and b/Plugins/Community Based Plugins/Images/hibp-example-screenshot.png differ diff --git a/Plugins/Community Based Plugins/Images/ipgl-example-screenshot-1.png b/Plugins/Community Based Plugins/Images/ipgl-example-screenshot-1.png new file mode 100644 index 00000000..45e41990 Binary files /dev/null and b/Plugins/Community Based Plugins/Images/ipgl-example-screenshot-1.png differ diff --git a/Plugins/Community Based Plugins/Images/ipgl-example-screenshot-2.png b/Plugins/Community Based Plugins/Images/ipgl-example-screenshot-2.png new file mode 100644 index 00000000..1b3f2c79 Binary files /dev/null and b/Plugins/Community Based Plugins/Images/ipgl-example-screenshot-2.png differ diff --git a/Plugins/Community Based Plugins/Images/ipqs-example-screenshot-1.png b/Plugins/Community Based Plugins/Images/ipqs-example-screenshot-1.png new file mode 100644 index 00000000..7b88b3e5 Binary files /dev/null and b/Plugins/Community Based Plugins/Images/ipqs-example-screenshot-1.png differ diff --git a/Plugins/Community Based Plugins/Images/ipqs-example-screenshot-2.png b/Plugins/Community Based Plugins/Images/ipqs-example-screenshot-2.png new file mode 100644 index 00000000..2a5110f7 Binary files /dev/null and b/Plugins/Community Based Plugins/Images/ipqs-example-screenshot-2.png differ diff --git a/Plugins/Community Based Plugins/DefenderDailyOperations/KQL_DefenderDailyOperationsSample.yaml b/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/DefenderDailyOperations/Manifest_DefenderDailyOperations.yaml similarity index 97% rename from Plugins/Community Based Plugins/DefenderDailyOperations/KQL_DefenderDailyOperationsSample.yaml rename to Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/DefenderDailyOperations/Manifest_DefenderDailyOperations.yaml index cc99a3c8..9161810f 100644 --- a/Plugins/Community Based Plugins/DefenderDailyOperations/KQL_DefenderDailyOperationsSample.yaml +++ b/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/DefenderDailyOperations/Manifest_DefenderDailyOperations.yaml @@ -37,4 +37,4 @@ SkillGroups: EmailEvents | where RecipientEmailAddress =~ '{{email}}' | project Timestamp, NetworkMessageId, SenderFromAddress, SenderDisplayName, Subject, DeliveryLocation - | top 100 by Timestamp desc + | top 10 by Timestamp desc diff --git a/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/DefenderDailyOperations/readme.md b/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/DefenderDailyOperations/readme.md new file mode 100644 index 00000000..8173eccd --- /dev/null +++ b/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/DefenderDailyOperations/readme.md @@ -0,0 +1,61 @@ +# Defender Daily Operations + +## DESCRIPTION +This plugin aids in comprehending the recommended approach of integrating multiple related skills into a single plugin. The provided code sample demonstrates the combination of two skills to retrieve information from few defender tables. You can further expand upon this example to extract additional details from custom tables etc. + +--- + +## TYPE AND REQUIREMENTS +**TYPE**: KQL (Defender)
+**SOURCE**: _DeviceInfo_ and _EmailEvents_ tables
+**REQUIREMENTS**: access to Defender XDR + +--- + +## SKILLS + + + + + + + + + + + + + + + + + + + +
SkillNameDescriptionParameters
GetDefenderDevicesGet the top 10 devices from defender based on device state, exposure for the last 8 hours +
    +
  • + devicehealthstate
    (Active, Misconfigured, Inactive) +
  • +
  • exposurelevel
  • (Low, Medium, High) +
+
GetLatestEmailsByRecipientFetches the latest emails received by the user with the specified email address +
    +
  • email
  • +
+
+ + +--- + +## SAMPLE PROMPTS + +- `« Fetch the latest emails received by user email »` +- `« Show Defender Devices with Active health state with an High exposure level » ` +--- + +## SCREENSHOTS +
+ + +
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/ALERTENRICHMENT.md b/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/ALERTENRICHMENT.md new file mode 100644 index 00000000..a35691a8 --- /dev/null +++ b/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/ALERTENRICHMENT.md @@ -0,0 +1,15 @@ +# Enrichment Plugin Suite +**Author: Craig Freyman** + +## AlertEnrichment Plugin +This suite of alert enrichment skills is designed to enhance security investigations by providing detailed insights into alert data. These tools allow analysts to identify and analyze alerts based on titles and retrieve evidence associated with specific alerts for effective threat investigation. + +### AlertTitleSearch +Search for alerts in the Defender database that contain a specified keyword in their title. Returns key alert details, including time generated, alert ID, title, category, and severity, to assist in quickly identifying relevant alerts. + +Example Prompt: Search for alerts containing "DLP policy" in their titles over the past 7 days. + +### AlertEvidenceSearch +Retrieve detailed evidence associated with a specific alert using its `AlertId`. Provides insights into entity type, associated URLs, account information, email metadata, file details, and other contextual information to assist in investigations. + +Example Prompt: Retrieve evidence for alert ID "dl3e6607b8-8ff6-7180-d400-08dcf2ab98ff". diff --git a/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/AlertEnrichment.yaml b/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/AlertEnrichment.yaml new file mode 100644 index 00000000..2508743f --- /dev/null +++ b/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/AlertEnrichment.yaml @@ -0,0 +1,67 @@ +Descriptor: + Name: Alert Enrichment Skills + DisplayName: Alert Enrichment Skills + DescriptionForModel: |- + A collection of KQL-based skills to enrich security alert investigations by: + - Searching alert titles for specified keywords (e.g., "DLP policy") and returning key details, including `TimeGenerated`, `AlertId`, `Title`, `Category`, and `Severity`. + - Retrieving evidence associated with specific alerts, such as: + - Entity details: `EntityType`, `Title`, and `EvidenceRole`. + - URL and email metadata: `RemoteUrl`, `AccountUpn`, `NetworkMessageId`, and `EmailSubject`. + - File-related information: `FileName`, `FolderPath`, `SHA1`, `SHA256`, `FileSize`, and `ProcessCommandLine`. + - Contextual data: `Categories`, `AttackTechniques`, `ServiceSource`, `DetectionSource`, `AdditionalFields`, `Severity`, and `Type`. + - Highlighting `RemoteUrl` fields to identify potentially suspicious public domains for further investigation. + This skill set provides detailed alert context, enabling users to prioritize alerts and perform thorough investigations efficiently. + Description: A comprehensive set of KQL-based skills designed to enhance alert analysis by searching alert titles for specific keywords, retrieving detailed evidence, and providing contextual information to support effective security investigations. + +SkillGroups: + - Format: KQL + Skills: + - Name: AlertTitleSearch + DisplayName: Search Alert Title + DescriptionForModel: |- + Performs a KQL query to search the `AlertInfo` table for alerts containing a specified keyword in their title. Filters results by a specified time range (defaulting to the last 7 days) and returns the following details: + - `TimeGenerated`: Timestamp of the alert. + - `AlertId`: Unique identifier for the alert. + - `Title`: Title of the alert. + - `Category`: Categorization of the alert. + - `Severity`: Severity level of the alert. + Designed for identifying and analyzing alerts that match specific title keywords, helping to prioritize investigations. + Description: Search for alerts in the Defender database that contain a specified keyword in their title. Returns key alert details, including time generated, alert ID, title, category, and severity, to assist in quickly identifying relevant alerts. + Inputs: + - Name: StringToSearch + Description: A string from the Alert Title. Example 'DLP policy' + Required: true + - Name: TimeRange + Description: The number of days to look back. Example 7d + Required: false + Settings: + Target: Defender + Template: |- + AlertInfo + | where Title contains "{{StringToSearch}}" + | where TimeGenerated >= ago(7d) + | project TimeGenerated, AlertId, Title, Category, Severity + + - Name: AlertEvidenceSearch + DisplayName: Search AlertId for Evidence + DescriptionForModel: |- + Performs a KQL query on the `AlertEvidence` table to retrieve evidence related to a specified `AlertId`. Returns comprehensive details, including: + - `EntityType`: The type of entity involved in the alert. + - `RemoteUrl`: Associated URLs, highlighting those containing public domains for investigation. + - `AccountUpn`: User account information related to the alert. + - `NetworkMessageId` and `EmailSubject`: Metadata for email-based alerts. + - `Title`: Evidence title summarizing the entity. + - `Categories`, `AttackTechniques`, `ServiceSource`, `DetectionSource`: Contextual data about the alert. + - `EvidenceRole`, `FileName`, `FolderPath`, `SHA1`, `SHA256`, `FileSize`, `ProcessCommandLine`: Key file-related details. + - `AdditionalFields`, `Severity`, `Type`: Additional information to aid in the analysis. + Designed to focus on associated URLs (`RemoteUrl`) and metadata to identify suspicious domains, accounts, or files for prioritizing and directing further investigation. + Description: Retrieve detailed evidence associated with a specific alert using its AlertId. Provides insights into entity type, associated URLs, account information, email metadata, file details, and other contextual information to assist in investigations. + Inputs: + - Name: AlertId + Description: The specific AlertId to search for evidence. Example dl3e6607b8-8ff6-7180-d400-08dcf2ab98ff + Required: true + Settings: + Target: Defender + Template: |- + AlertEvidence + | where AlertId == "{{AlertId}}" 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 new file mode 100644 index 00000000..6d097328 --- /dev/null +++ b/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/DEVICEENRICHMENT.md @@ -0,0 +1,95 @@ +# Enrichment Plugin Suite +**Author: Craig Freyman** + +## DeviceEnrichment Plugin +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. + +### DeviceExposureLevel +Summarize device exposure levels in the environment by identifying the total number of high-risk devices and high-risk internet-facing devices. Helps prioritize remediation efforts for critical assets. + +Example Prompt: Summarize exposure levels for all devices in the network. + +### DeviceLogonSearch +Retrieve a detailed log of interactive and remote device logon events for a specified account on a specific date. Includes device ID, device name, logon timestamps, and account name to aid in tracking user activity and identifying associated devices. + +Example Prompt: Retrieve logon events for jsmith on 2024-09-13. + +### DeviceTimeline +Retrieve and analyze a detailed timeline of device events and security alerts surrounding a specific timestamp for a given device. Includes file actions, network connections, process activity, and related alerts to investigate suspicious behavior and anomalies. + +Example Prompt: Build a timeline of device events for laptop-win10v around 2024-08-30T01:10:39.501000. + +### DeviceCVESearch +Retrieve a list of software vulnerabilities (CVEs) affecting a specified device, filtered by severity level (Critical, High, Medium, or Low). Provides insights into software vendors, vulnerability impacts, and the device's overall security posture to help prioritize remediation efforts. + +Example Prompt: List CVEs for some-host-name with severity level High. + +### CVEHostSearch +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 new file mode 100644 index 00000000..ca869f2f --- /dev/null +++ b/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/DeviceEnrichment.yaml @@ -0,0 +1,1149 @@ +Descriptor: + Name: Device Enrichment Skills + DisplayName: Device Enrichment Skills + Description: A set of KQL-based skills designed to enrich device-related data in Microsoft Defender and Sentinel. These skills provide insights into device vulnerabilities, logon events, exposure levels, and event timelines, empowering security analysts to monitor, investigate, and prioritize device-level security risks. + DescriptionForModel: |- + A collection of KQL-based skills that interact with Microsoft Defender and Sentinel to enhance device-related data analysis. These skills include: + - Summarizing device exposure levels to identify high-risk and internet-facing devices. + - Retrieving detailed logon events for specified accounts and devices. + - Constructing event timelines around specific timestamps to analyze suspicious device activities. + - Identifying vulnerabilities (CVEs) affecting devices and assessing their impact. + - Listing devices affected by specific CVEs to prioritize remediation efforts. + This skillset enables comprehensive device-level security investigations, helping analysts understand vulnerabilities, logon behavior, and event patterns to mitigate risks effectively. Use these skills for detailed insights into device security posture and incident response. + SupportedAuthTypes: + - None + + Settings: + + - 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 + + - Name: WorkspaceName + Label: WorkspaceName + 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. + HintText: yourresourcegroup + SettingType: String + Required: true + +SkillGroups: + - Format: KQL + Skills: + - Name: DeviceExposureLevel + DisplayName: Summarize Device Exposure Levels + DescriptionForModel: |- + Performs a KQL query on the `DeviceInfo` table to evaluate the exposure posture of devices by calculating two key metrics: + - **Total Number of High-Risk Devices**: The count of devices classified with a "High" exposure level. + - **High Exposure Internet-Facing Devices**: The count of high-risk devices that are accessible from the internet. + Designed to provide insights into the security posture of devices, this skill helps prioritize remediation actions by highlighting critical and exposed devices, enabling GPT to suggest further steps to reduce risk. + Description: Summarize device exposure levels in the environment by identifying the total number of high-risk devices and high-risk internet-facing devices. Helps prioritize remediation efforts for critical assets. + Settings: + Target: Defender + Template: |- + // Count the number of devices with a High Exposure Level + let highRiskDevices = + DeviceInfo + | where ExposureLevel == "High" + | summarize HighRiskDeviceCount = count(); + // Count the number of high exposure level devices that are internet-facing + let highExposureInternetFacingDevices = + DeviceInfo + | where ExposureLevel == "High" and IsInternetFacing == true + | summarize HighExposureInternetFacingCount = count(); + // Combine both summaries in a single output + union highRiskDevices, highExposureInternetFacingDevices + + - Name: DeviceLogonSearch + DisplayName: Device Logon & Owner + DescriptionForModel: |- + Performs a KQL query on the `DeviceLogonEvents` table to retrieve interactive and remote logon activities for a specified account and date. Key details include: + - `Timestamp`: Logon event time. + - `DeviceId`: Unique identifier of the device. + - `DeviceName`: Name of the device. + - `AccountName`: Account name used for the logon. + - `LogonType`: Type of logon (e.g., Interactive, Remote). + This skill enables security analysts to track and investigate logon events for specific accounts, identify devices associated with the account, and review activity timelines for further analysis. + Description: Retrieve a detailed log of interactive and remote device logon events for a specified account on a specific date. Includes device ID, device name, logon timestamps, and account name to aid in tracking user activity and identifying associated devices. + Inputs: + - Name: AccountName + Description: The account name, NOT the UPN. Example jsmith + Required: true + - Name: event_date + Description: The date to retrieve logon events for, formatted as YYYY-MM-DD. Example 2024-09-13 + Required: true + Settings: + Target: Defender + Template: |- + // This query retrieves detailed device logon events for a specified account on a given date. + // It focuses on interactive and remote logon types, providing details like device IDs and names. + let userDate = "{{event_date}}"; + let start = strcat(userDate, "T00:00:00"); + let stop = strcat(userDate, "T23:59:59"); + let startDatetime = todatetime(start); + let stopDatetime = todatetime(stop); + DeviceLogonEvents + | where AccountName == "{{AccountName}}" + and Timestamp >= startDatetime + and Timestamp < stopDatetime + and LogonType in ("Interactive", "Remote") // Includes only interactive logons (2: Interactive, 10: RemoteInteractive) + | project Timestamp, DeviceId, DeviceName, AccountName, LogonType + | order by Timestamp desc + + - Name: DeviceTimeline + DisplayName: Device Timeline + DescriptionForModel: |- + Performs a KQL query to construct a timeline of device events and security alerts for a specified device, centered around a user-provided timestamp. Captures events within a defined time window (e.g., 15 seconds before and after) to identify suspicious behavior, anomalies, and patterns. + Key features include: + - Events: File actions, network connections, process activity, and logon events. + - Alerts: Related security alerts and evidence for additional context. + - Time categorization: Events are classified as occurring "Before," "Pivot," or "After" the specified timestamp. + This skill enables security analysts to investigate device behavior comprehensively, focusing on potential malicious actions or correlated events, and provides actionable insights for remediation or further inquiry. + Description: Retrieve and analyze a detailed timeline of device events and security alerts surrounding a specific timestamp for a given device. Includes file actions, network connections, process activity, and related alerts to investigate suspicious behavior and anomalies. + Inputs: + - Name: device_name + Description: The name of the device for which the timeline is being constructed. Example vnevado-win10v + Required: true + - Name: event_timestamp + Description: The timestamp to build the timeline around, formatted in ISO 8601. Example 2024-08-30T01:10:39.501000 + Required: true + - Name: time_window + Description: The time window to search. Example 30s for 30 seconds. + Default: 15s + Required: false + Settings: + Target: Sentinel + TenantId: "{{TenantId}}" + SubscriptionId: "{{SubscriptionId}}" + ResourceGroupName: "{{ResourceGroupName}}" + WorkspaceName: "{{WorkspaceName}}" + Template: |- + // This query retrieves and analyzes a timeline of device events and security alerts for a specific device, + // centered around a user-provided timestamp. + // The query focuses on a user specified time window (X seconds before and after the provided timestamp) + // to understand events before, during, and after a key moment. + // Results include key information like file actions, network connections, and process activity. + // The GPT model should analyze the data to identify suspicious patterns or anomalies, + // focusing on potential malicious behaviors or correlating events. + + let deviceName = "{{device_name}}"; // The name of the device to filter the timeline + let timelineWindow = {{time_window}}; // Default of 15 seconds before and after the provided timestamp + let pivotTimestampInput = datetime('{{event_timestamp}}'); // Timestamp provided by the user + let pivotTimestamp = pivotTimestampInput; + // Define the start and end times of the time window to capture relevant events + let startTime = pivotTimestamp - timelineWindow; + let endTime = pivotTimestamp + timelineWindow; + // Fetch and combine relevant device events: file events, network events, logon events, and process events. + // The union of these event types is filtered by the device name and the defined time window. + // Exclude network signature inspection events as they are less relevant for the analysis. + let deviceEvents = DeviceEvents + | union DeviceFileEvents, DeviceNetworkEvents, DeviceLogonEvents, DeviceProcessEvents + | where DeviceName contains deviceName // Filter events by the specified device name + | where Timestamp between (startTime .. endTime) // Capture only events within the time window + | where ActionType != "NetworkSignatureInspected" // Exclude irrelevant network signature inspection events + // Classify events as "Before", "Pivot", or "After" based on their relationship to the pivot timestamp + | extend TimeRelation = case( + Timestamp < pivotTimestamp, "Before", + Timestamp == pivotTimestamp, "Pivot", + Timestamp > pivotTimestamp, "After", + "Unknown") // Default case for any unexpected values + // Project relevant event details for analysis, including timestamp, action type, filenames, and network information + | project Timestamp, ActionType, FileName, FolderPath, ProcessCommandLine, RemoteIP, RemotePort, AccountName, + InitiatingProcessFileName, InitiatingProcessCommandLine, AlertId = "", Title = "", Category = "", AttackTechniques = "", TimeRelation; + // Fetch alert evidence for the specified device within the same time window. + // Alert evidence provides additional context about alerts triggered around the device. + deviceEvents + | distinct Timestamp, ActionType, FileName, FolderPath, ProcessCommandLine, RemoteIP, RemotePort, AccountName, + InitiatingProcessFileName, InitiatingProcessCommandLine + | order by Timestamp asc + + - Name: DeviceCVESearch + DisplayName: Device CVE Search + DescriptionForModel: |- + 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. + Description: Retrieve a list of software vulnerabilities (CVEs) affecting a specified device, filtered by severity level (Critical, High, Medium, or Low). Provides insights into software vendors, vulnerability impacts, and the device's overall security posture to help prioritize remediation efforts. + Inputs: + - Name: device_name + Description: Device to search on + Required: true + - Name: severity_level + Description: Critical, High, Medium, or Low + Required: true + Settings: + Target: Defender + Template: |- + // Retrieve vulnerabilities affecting the specified device and severity level + DeviceTvmSoftwareVulnerabilities + | where DeviceName == "{{device_name}}" + | where VulnerabilitySeverityLevel contains "{{severity_level}}" + | 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 + DescriptionForModel: |- + Performs a KQL query to identify devices impacted by a specific CVE identifier. For each affected device, it: + 1. **Analyzes Impact**: Examines how the vulnerability affects the device, considering the type of software involved, its role in the system, and potential impacts if the vulnerability is exploited. + 2. **Summarizes Security Posture**: Evaluates the overall security risk by analyzing the number of affected devices, their importance within the network, and the severity of the vulnerability. + This skill provides actionable insights to prioritize remediation, mitigate security risks, and strengthen the overall security posture of the environment. + Description: 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. + Inputs: + - Name: cve_id + Description: CVE identifier to search for (e.g., CVE-2023-12345) + Required: true + Settings: + Target: Defender + Template: |- + // Retrieve devices affected by the specified CVE + DeviceTvmSoftwareVulnerabilities + | where CveId == "{{cve_id}}" + | project DeviceName, SoftwareVendor, SoftwareName, SoftwareVersion, VulnerabilitySeverityLevel, RecommendedSecurityUpdate + // For each device: + // 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 new file mode 100644 index 00000000..06f42cfe --- /dev/null +++ b/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/EMAILENRICHMENT.md @@ -0,0 +1,31 @@ +# Enrichment Plugin Suite +**Author: Craig Freyman** + +## EmailEnrichment Plugin +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. + +### DomainEmailSearch +Search for emails in the `EmailEvents` table containing a specified domain name. Provides key details, including the time, sender, recipient, subject, body, and attachment count, to support threat analysis and investigations. + +Example Prompt: Identify emails containing the domain "example.com" in the past 7 days. + +### RecipientEmailSearch +Search for emails in the `EmailEvents` table sent to a specific recipient. Provides key details, including the time, sender, recipient, subject, body, and attachment count, to support threat investigations. + +Example Prompt: Find all emails sent to user@example.com in the past 7 days. + +### SenderEmailSearch +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 new file mode 100644 index 00000000..a71379e6 --- /dev/null +++ b/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/EmailEnrichment.yaml @@ -0,0 +1,160 @@ +Descriptor: + Name: Email Enrichment Skills + DisplayName: Email Enrichment Skills + DescriptionForModel: |- + - A set of KQL-based skills to enhance email analysis by: + - Searching the `EmailEvents` table for specific domain names, recipients, or senders to identify relevant email activities. + - Retrieving essential details about emails, 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. + - `Body`: Content of the email. + - `AttachmentCount`: Number of attachments in the email. + - Allowing deeper investigation into potential email-related incidents by filtering based on specific domains, recipients, or senders. + - Identifying trends and patterns in email traffic to assist in compliance checks, threat detection, and incident response. + - Providing comprehensive context on email events to support security investigations and remediation efforts. + Description: A set of skills to enrich email analysis by searching the `EmailEvents` table for specific domains, recipients, or senders, and retrieving key email details for effective investigations. + +SkillGroups: + - Format: KQL + Skills: + - Name: DomainEmailSearch + DisplayName: Search Emails by Domain + DescriptionForModel: |- + Performs a KQL query on the `EmailEvents` table to identify emails associated with a specified domain. 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. + - `Body`: Content of the email. + - `AttachmentCount`: Number of attachments in the email. + This skill is designed to assist security analysts in investigating email activities related to specific domains, identifying potential threats, and understanding communication patterns for targeted investigations. + Description: Search for emails in the `EmailEvents` table containing a specified domain name. Provides key details, including the time, sender, recipient, subject, body, and attachment count, to support threat analysis and investigations. + Inputs: + - Name: DomainName + Description: The domain name to search for in the `EmailEvents` table. Example 'example.com' + Required: true + - Name: TimeRange + Description: The number of days to look back. Example 7d + Required: false + Settings: + Target: Defender + Template: |- + EmailEvents + | where RecipientEmailAddress contains "{{DomainName}}" + | where TimeGenerated >= ago(7d) + | project TimeGenerated, SenderFromAddress, RecipientEmailAddress, Subject, NetworkMessageId + + - Name: RecipientEmailSearch + DisplayName: Search Emails by Recipient + DescriptionForModel: |- + Performs a KQL query on the `EmailEvents` table to identify emails sent to a specified recipient. 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. + - `Body`: Content of the email. + - `AttachmentCount`: Number of attachments in the email. + This skill helps security analysts investigate email activities related to specific recipients, supporting threat analysis and investigations. + Description: Search for emails in the `EmailEvents` table containing a specified recipient. Provides key details, including the time, sender, recipient, subject, body, and attachment count. + Inputs: + - Name: RecipientAddress + Description: The recipient email address to search for in the `EmailEvents` table. Example 'user@example.com' + Required: true + - Name: TimeRange + Description: The number of days to look back. Example 7d + Required: false + Settings: + Target: Defender + Template: |- + EmailEvents + | where RecipientEmailAddress == "{{RecipientAddress}}" + | where TimeGenerated >= ago(7d) + | project TimeGenerated, SenderFromAddress, RecipientEmailAddress, Subject, NetworkMessageId, AttachmentCount + + - Name: SenderEmailSearch + DisplayName: Search Emails by Sender + DescriptionForModel: |- + Performs a KQL query on the `EmailEvents` table to identify emails sent from a specified sender. 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. + - `Body`: Content of the email. + - `AttachmentCount`: Number of attachments in the email. + This skill helps security analysts investigate email activities originating from specific senders, aiding in threat detection and analysis. + Description: Search for emails in the `EmailEvents` table sent by a specified sender. Provides key details, including the time, sender, recipient, subject, body, and attachment count. + Inputs: + - Name: SenderAddress + Description: The sender email address to search for in the `EmailEvents` table. Example 'admin@example.com' + Required: true + - Name: TimeRange + Description: The number of days to look back. Example 7d + Required: false + Settings: + Target: Defender + Template: |- + EmailEvents + | 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/FILEENRICHMENT.md b/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/FILEENRICHMENT.md new file mode 100644 index 00000000..39dc872e --- /dev/null +++ b/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/FILEENRICHMENT.md @@ -0,0 +1,10 @@ +# Enrichment Plugin Suite +**Author: Craig Freyman** + +## FileEnrichment Plugin +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. + +### SearchFilesWritten +Search for files written on a specific device based on a partial file name string provided by the user. Analyze potential security threats by identifying suspicious files and providing guidance for further investigation or remediation. + +Example Prompt: Search for files containing "chrome" on the device computer.domain.local. diff --git a/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/FileEnrichment.yaml b/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/FileEnrichment.yaml new file mode 100644 index 00000000..fafb0383 --- /dev/null +++ b/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/FileEnrichment.yaml @@ -0,0 +1,47 @@ +Descriptor: + Name: File Enrichment Skills + DisplayName: File Enrichment Skills + DescriptionForModel: |- + - A set of skills providing enrichment data on files across devices. Capabilities include: + - Searching for filenames on specific devices based on user-provided criteria. + - Assisting in identifying potential security threats by locating and analyzing files across the network. + - Guiding users in remediation steps if suspicious files are identified. + Description: A set of skills for locating and analyzing files on devices to assist in identifying and addressing security threats. + +SkillGroups: + - Format: KQL + Skills: + - Name: SearchFilesWritten + DisplayName: Search Written Files on Device + DescriptionForModel: |- + Performs a KQL query on the `DeviceFileEvents` table to search for file events on a specified device where the file name or related attributes contain a user-provided partial string (e.g., "chrome"). Key details include: + - **File Matching**: Matches files by name, folder path, initiating process name, or command line. + - **Device Context**: Filters results to focus on a specific device if provided. + - **Event Details**: Provides information such as the file name, device name, folder path, process details, and file hashes (`SHA1`, `SHA256`). + Designed to identify potentially malicious or suspicious files written on devices. GPT can analyze findings to determine security risks and recommend remediation actions, such as quarantining files, investigating their origin, or monitoring for further compromise. Summarizes results to support threat detection and incident response. + Description: Search for files written on a specific device based on a partial file name string provided by the user. Analyze potential security threats by identifying suspicious files and providing guidance for further investigation or remediation. + Inputs: + - Name: software + Description: Partial string supplied by the user to search for a file name, e.g., "chrome" + Required: true + - Name: device_name + Description: The name of the device to focus the search on, e.g., "computer.domain.local" + Default: "" + Required: false + Settings: + Target: Defender + Template: |- + // This query searches for file events on a specific device where the file name matches the user-provided string. + // The user provides a partial file name, which is matched against written files on the network. + let search_string = "{{software}}"; + DeviceFileEvents + | where DeviceName contains "{{device_name}}" + | where FileName contains search_string + or InitiatingProcessFileName contains search_string + or InitiatingProcessCommandLine contains search_string + or FolderPath contains search_string + // Filter the results to focus on events from the specific device provided by the user + | project Timestamp, FileName, DeviceName, ActionType, FolderPath, InitiatingProcessFileName, InitiatingProcessCommandLine, SHA1, SHA256 + | order by Timestamp asc + + diff --git a/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/INCIDENTENRICHMENT.md b/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/INCIDENTENRICHMENT.md new file mode 100644 index 00000000..e9db3c5b --- /dev/null +++ b/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/INCIDENTENRICHMENT.md @@ -0,0 +1,15 @@ +# Enrichment Plugin Suite +**Author: Craig Freyman** + +## IncidentEnrichment Plugin +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. + +### AlertTitleToIncidentCorrelation +Retrieve alerts associated with a specified incident ID and provide an overview of alert titles. Suggest search refinement by alert name if more than 10 alerts are found, enabling efficient correlation and targeted investigation. + +Example Prompt: Correlate alert titles with incident ID "1337" to refine search results. + +### IncidentAlertRetrieval +Retrieve all alerts associated with a specific incident ID. Provides comprehensive details, including timestamp, unique alert ID, alert name, and severity level, to support in-depth incident investigations. + +Example Prompt: Retrieve all alerts for incident ID "2807" to review related activity. 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 new file mode 100644 index 00000000..d5c79564 --- /dev/null +++ b/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/IncidentEnrichment.yaml @@ -0,0 +1,115 @@ +Descriptor: + Name: Incident Enrichment Skills + DisplayName: Incident Enrichment Skills + DescriptionForModel: |- + - A set of KQL-based skills designed to enhance incident investigations by: + - Correlating and retrieving alerts associated with a specific incident using the `IncidentId`. + - Enabling targeted searches through alert titles to refine results and focus investigations. + - Retrieving all alerts tied to an incident for comprehensive review, including key details like timestamps, alert names, IDs, and severity levels. + - Providing actionable insights to support efficient incident analysis and response. + Description: A set of KQL-based skills for correlating and retrieving alerts linked to specific incidents, offering detailed insights to enhance focused investigations and response efforts. + + SupportedAuthTypes: + - 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. + HintText: yoursubscriptionname + SettingType: String + Required: true + + - Name: WorkspaceName + Label: WorkspaceName + 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. + HintText: yourresourcegroup + SettingType: String + Required: true + +SkillGroups: + - Format: KQL + Skills: + - Name: AlertTitleToIncidentCorrelation + DisplayName: Alert Title Search to Incident Correlation + DescriptionForModel: |- + Performs a KQL query on the `SecurityAlert` table to correlate alerts with a specified `IncidentId`. Key features include: + - **Alert Association**: Retrieves alerts linked to the incident ID and filters by alert title if specified. + - **Alert Summary**: Counts the total number of alerts and lists up to 10 `AlertNames` for an overview. + - **Search Refinement**: Recommends refining the search by specifying an `AlertName` if more than 10 alerts are found. + This skill enables users to efficiently correlate alerts with incidents, identify patterns, and refine investigations for deeper insights into potential threats. Provides actionable recommendations to focus the search on relevant alerts. + Description: Retrieve alerts associated with a specified incident ID and provide an overview of alert titles. Suggest search refinement by alert name if more than 10 alerts are found, enabling efficient correlation and targeted investigation. + Inputs: + - Name: IncidentId + Description: The unique identifier of the incident to search for associated alerts. Example "1337" + Required: true + - Name: AlertName + Description: A specific alert name to refine the search. Example "Suspicious Microsoft Defender AntiVirus exclusion" + Default: "" + Required: false + Settings: + Target: Sentinel + TenantId: "{{TenantId}}" + SubscriptionName: "{{SubscriptionName}}" + ResourceGroupName: "{{ResourceGroupName}}" + WorkspaceName: "{{WorkspaceName}}" + Template: |- + let recordCount = toscalar( + SecurityAlert + | extend ExtendedPropertiesJson = parse_json(ExtendedProperties) + | extend EntitiesJson = parse_json(Entities) + | where ExtendedPropertiesJson.IncidentId == "{{IncidentId}}" + | where isempty("{{AlertName}}") or AlertName contains "{{AlertName}}" + | summarize count() + ); + SecurityAlert + | extend ExtendedPropertiesJson = parse_json(ExtendedProperties) + | extend EntitiesJson = parse_json(Entities) + | where ExtendedPropertiesJson.IncidentId == "{{IncidentId}}" + | where isempty("{{AlertName}}") or AlertName contains "{{AlertName}}" + | project AlertName + | limit iff(recordCount > 10, 10, recordCount) + | summarize Alerts = make_list(AlertName) + | project DistinctAlertTitles = Alerts, Count = recordCount + | extend Recommendation = iff(Count > 10, "Consider refining the search using the AlertName variable.", "") + + - Name: IncidentAlertRetrieval + DisplayName: Retrieve Alerts for an Incident + DescriptionForModel: |- + Performs a KQL query on the `SecurityAlert` table to retrieve all alerts linked to a specified `IncidentId`. Key details include: + - **`TimeGenerated`**: Timestamp indicating when the alert was generated. + - **`AlertId`**: Unique identifier for each alert. + - **`AlertName`**: Name of the alert. + - **`Severity`**: Severity level of the alert. + This skill enables a complete review of all alerts associated with an incident, helping security analysts understand the scope and context of the incident for effective analysis and response. + Description: Retrieve all alerts associated with a specific incident ID. Provides comprehensive details, including timestamp, unique alert ID, alert name, and severity level, to support in-depth incident investigations. + Inputs: + - Name: IncidentId + Description: The unique identifier of the incident to retrieve all associated alerts. Example "2807" + Required: true + Settings: + Target: Sentinel + TenantId: "{{TenantId}}" + SubscriptionName: "{{SubscriptionName}}" + ResourceGroupName: "{{ResourceGroupName}}" + WorkspaceName: "{{WorkspaceName}}" + Template: |- + SecurityAlert + | extend ExtendedPropertiesJson = parse_json(ExtendedProperties) + | extend EntitiesJson = parse_json(Entities) + | where ExtendedPropertiesJson.IncidentId == "{{IncidentId}}" + | distinct TimeGenerated, SystemAlertId, AlertName, AlertSeverity 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 new file mode 100644 index 00000000..306da4c1 --- /dev/null +++ b/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/README.md @@ -0,0 +1,49 @@ +![Copilot for Security Overview](https://github.com/Azure/Copilot-For-Security/blob/main/Images/ic_fluent_copilot_64_64%402x.png) + +# Copilot for Security Enrichment Plugin Suite + +**Author: Craig Freyman** + +## Overview +This plugin suite enables users to enrich various entities in security operations & investigations. Detailed README's are available for each enrichment type. + +### 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. + +### 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. + +### 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. + +### 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. + +### 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. + +### 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. + +## **Pre-requisites** +1. Access to Microsoft Defender and Sentinel environments. + +2. Ensure proper permissions are assigned to query relevant tables (e.g., `SigninLogs`, `EmailEvents`, `DeviceFileEvents`). + +## 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. + diff --git a/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/URLENRICHMENT.md b/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/URLENRICHMENT.md new file mode 100644 index 00000000..e419402c --- /dev/null +++ b/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/URLENRICHMENT.md @@ -0,0 +1,20 @@ +# Enrichment Plugin Suite +**Author: Craig Freyman** + +## URLEnrichment Plugin +This suite of URL enrichment skills is 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. + +### WhoClickedUrl +Identify users who clicked on a specific URL from an email on a given date. Provides details such as the time of the click, action type, user information, and any related network or threat data to assist in investigating potential security breaches. + +Example Prompt: Identify users who clicked on https://example.com in the past 30 days. + +### UrlClickCount +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. + +Example Prompt: Analyze click frequency for https://example.com over the past 30 days. + +### UrlCorrelation +Correlate clicks on a specific URL with security alerts, email events, and network events over the past 30 days. Provides a comprehensive risk overview, including event times, users, related alerts, and associated incident IDs. + +Example Prompt: Correlate URL activity for https://example.com with related security events in the last 30 days. 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 new file mode 100644 index 00000000..12f2ee3a --- /dev/null +++ b/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/URLEnrichment.yaml @@ -0,0 +1,137 @@ +Descriptor: + Name: URL Enrichment Skills + DisplayName: URL Enrichment Skills + DescriptionForModel: |- + A set of KQL-based skills designed to enhance URL analysis within a network. These skills help security analysts: + - Identify users who clicked a specific URL, focusing on Safe Links from email, Microsoft Teams, and Office 365 apps. + - Analyze the frequency and recency of URL clicks to assess usage patterns and potential threats. + - Correlate URL click events with other security-related data, including alerts, email events, and network activity, to provide a comprehensive risk overview. + - Highlight related incidents and provide actionable insights for investigations. + Description: 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. + SupportedAuthTypes: + - None + +SkillGroups: + - Format: KQL + Skills: + - Name: WhoClickedUrl + DisplayName: URL & User Correlation + DescriptionForModel: |- + Performs a KQL query on the `UrlClickEvents` table to identify user activity associated with a specified URL. Key features include: + - **User Identification**: Correlates the URL click to the user's account (UPN). + - **Event Context**: Captures details such as the time of the click, action type, and email metadata (e.g., `NetworkMessageId` and `Workload`). + - **Threat Indicators**: Includes information about potential threats, such as `ThreatTypes` and `UrlChain`. + - **Focus on SafeLinks**: Targets clicks originating from email messages processed through SafeLinks. + This skill helps trace user activity related to specific URLs, enabling security analysts to investigate potential breaches, monitor suspicious behavior, and take appropriate remediation actions. + Description: Identify users who clicked on a specific URL from an email on a given date. Provides details such as the time of the click, action type, user information, and any related network or threat data to assist in investigating potential security breaches. + Inputs: + - Name: url + Description: The URL to search for user clicks. Example https://example.com + Required: true + + Settings: + Target: Defender + Template: |- + // This query identifies users who clicked on the specified URL in the last 30 days. + // It retrieves user activity, including the time of click and additional context like network or threat details. + let urlToSearch = "{{url}}"; + UrlClickEvents + | where Timestamp >= ago(30d) + | where Url has urlToSearch + // Uncomment this for customers + //| where IsClickedThrough == true + | 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. + - **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. + Inputs: + - Name: url + Description: The URL to search for click counts. Example https://example.com + Required: true + Settings: + Target: Defender + Template: |- + // This query provides the number of times a specific URL was clicked in the past 30 days. + // It also retrieves the first and last time the URL was clicked to help identify patterns in access. + UrlClickEvents + | where Timestamp >= ago(30d) and Url has "{{url}}" + // Uncomment this for only results that a user clicked through to original URL + //| where IsClickedThrough == true + | summarize Count = count(), FirstClicked = min(Timestamp), LastClicked = max(Timestamp) + + - Name: UrlCorrelation + DisplayName: URL Correlation to Other Security Events + DescriptionForModel: |- + 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**: + - `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). + - `AlertId`: Identifier for associated security alerts. + - `IncidentId`: Related incident IDs for cross-referencing. + - **Incident Highlighting**: Notes any associated incidents for prioritization and investigation. + This skill provides a comprehensive view of activity related to a URL, enabling security analysts to assess risks, trace potential threats, and correlate user activity with security events for an in-depth investigation. + Description: Correlate clicks on a specific URL with security alerts, email events, and network events over the past 30 days. Provides a comprehensive risk overview, including event times, users, related alerts, and associated incident IDs. + Inputs: + - Name: url + Description: The URL to search for correlations with other events. Example https://example.com + Required: true + Settings: + Target: Defender + Template: |- + let timeframe = 30d; + let targetUrl = "{{url}}"; + // Find all events where the URL was clicked within the specified timeframe + let urlClicks = UrlClickEvents + | where Timestamp >= ago(timeframe) + | where Url has targetUrl + | project Timestamp, AccountUpn, Url, EventType = "UrlClickEvent", AlertId = "", IncidentId = ""; + + // Find security alerts where the URL is mentioned in the RemoteUrl field + let alertsContainingUrl = AlertEvidence + | where Timestamp >= ago(timeframe) + | where tostring(RemoteUrl) has targetUrl + | join kind=leftouter ( + SecurityAlert + | extend ExtendedPropertiesJson = parse_json(ExtendedProperties) + | project VendorOriginalId, IncidentId = tostring(ExtendedPropertiesJson.IncidentId) + ) on $left.AlertId == $right.VendorOriginalId + | 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 + | where Timestamp >= ago(timeframe) + | where Url has targetUrl + | project NetworkMessageId; + + let emailEvents = EmailEvents + | where Timestamp >= ago(timeframe) + | where NetworkMessageId in (emailUrlInfo) + | 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 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 Timestamp desc + // Return only distinct rows to avoid duplicates + | 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, 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 new file mode 100644 index 00000000..8ad23517 --- /dev/null +++ b/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/USERENRICHMENT.md @@ -0,0 +1,94 @@ +# Enrichment Plugin Suite +**Author: Craig Freyman** + +## User Enrichment Plugin +This suite of enrichment skills is designed to enhance security investigations by providing detailed insights into user activities, such as login behavior, password changes, MFA modifications, location analysis, 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. + +--- + +### 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. + +--- + +### 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. + +--- + +### 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. + +--- + +### 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. + +--- + +### FailedUserSignInSpecificDay +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. + +--- + +### 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. + +**Example Prompt**: Fetch enhanced user risk details for mscott@paper.com. + +--- + +### ImpossibleTravel +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. + +--- 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 new file mode 100644 index 00000000..a941e497 --- /dev/null +++ b/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/UserEnrichment.yaml @@ -0,0 +1,659 @@ +Descriptor: + Name: User Enrichment Skills + DisplayName: User Enrichment Skills + DescriptionForModel: A comprehensive set of skills designed to enhance security investigations by providing detailed insights into user activities. This plugin family focuses on analyzing sign-in behavior, detecting anomalies, tracking password changes, monitoring multi-factor authentication modifications, and verifying user location information. By leveraging these skills, security teams can identify potential threats, ensure compliance with security policies, and protect organizational assets from unauthorized access. + Description: A set of tools for analyzing user activity and enhancing security investigations, including login behavior, password changes, MFA modifications, and location verification. + SupportedAuthTypes: + - None + +SkillGroups: + - Format: KQL + Skills: + - Name: UnusualSigninActivity + DisplayName: Unusual Sign-in Activity + DescriptionForModel: |- + Performs a KQL query on the `SigninLogs` table to analyze the last 30 days of sign-in activity for a specified user. Key features include: + - **Anomaly Detection**: Identifies unusual login patterns such as: + - High failure rates compared to successful sign-ins. + - Sign-ins from unfamiliar or high-risk locations. + - Sudden changes in devices, applications, or user agents. + - **Detailed Insights**: Provides data on: + - `IPAddress`: Location of the sign-in. + - `DeviceDetail.deviceId`: Device used for the sign-in. + - `UserAgent`: Client used for the session. + - `ClientAppUsed` and `AppDisplayName`: Applications accessed. + - **Summarization**: Includes the first observed sign-in, last observed sign-in, and counts of successful vs. failed attempts. + - **Actionable Recommendations**: Suggests next steps such as password reset, MFA review, or security investigation for anomalies, while confirming consistent activity for normal patterns. + This skill enables security analysts to detect and respond to potential sign-in threats effectively. + Description: 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. + Inputs: + - 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: |- + // This query checks the last 30 days of sign-in activity for a specified user. + // 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 + + - Name: PasswordChangeCount + DisplayName: Password Change Count + DescriptionForModel: |- + Performs a KQL query on the `AuditLogs` table to track password changes for a specified user within the last 7 days. Key features include: + - **Password Change Detection**: Identifies the number of password changes and their timing. + - **Anomaly Detection**: Highlights unusual patterns such as: + - Multiple password changes within a short timeframe. + - Password changes following failed sign-in attempts. + - **Detailed Insights**: Provides a daily summary of password changes, including: + - `TimeGenerated`: Timestamp of the change. + - `PasswordChangeCount`: Number of changes per day. + - `userPrincipalName`: User associated with the changes. + - **Actionable Recommendations**: Suggests security actions, such as MFA enforcement or account review, if anomalies are detected. Confirms consistent behavior if no anomalies are found. + This skill assists security analysts in detecting and responding to potential account threats effectively. + Description: 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 and providing actionable recommendations. + Inputs: + - Name: upn + Description: User principal name, e.g., mscott@woodgrove.ms + Required: true + Settings: + Target: Defender + Template: |- + // This query tracks password changes for a specific user over the last seven days. + // It highlights suspicious behavior, such as multiple password changes in a short timeframe or changes following failed login attempts. + let TimeFrame = ago(7d); + let upn = "{{upn}}"; // Replace with the specific users upn + AuditLogs + | where TimeGenerated > TimeFrame + | where OperationName == "Change user password" + | extend TargetResourcesJson = parse_json(tostring(TargetResources)) + | mv-expand TargetResource = TargetResourcesJson + | extend userPrincipalName = tostring(TargetResource.userPrincipalName) + | where userPrincipalName == upn + | summarize PasswordChangeCount = count() by bin(TimeGenerated, 1d), userPrincipalName + | project TimeGenerated, PasswordChangeCount, userPrincipalName + | order by TimeGenerated desc + + - Name: RecentMFAChanges + DisplayName: Recent MFA Changes + DescriptionForModel: |- + Performs a KQL query on the `CloudAppEvents` table to investigate recent changes to MFA settings for a specified user. Key features include: + - **MFA Method Tracking**: + - Detects methods added, removed, or modified. + - Identifies changes to default authentication methods. + - **Detailed Insights**: + - `Timestamp`: Time of the change. + - `Actor`: User or system initiating the change. + - `Target`: User principal name (UPN) affected. + - `Action`: Description of the change (e.g., "Method Added," "Method Removed," or "Default Method Changed"). + - `OldValue` and `NewValue`: Details of the previous and updated MFA methods. + - **Anomaly Detection**: + - Highlights suspicious changes that may indicate unauthorized access or account compromise. + - Prompts recommendations such as re-authentication, MFA enforcement, or further security review. + This skill enables security analysts to monitor and investigate MFA changes, ensuring account security and identifying potential threats. + Description: Track recent MFA changes for a specific user over the past 7 days, identifying added, removed, or modified authentication methods. Provides detailed insights into changes, highlights anomalies, and recommends security actions if unusual activity is detected. + Inputs: + - Name: upn + Description: User principal name, e.g., mscott@woodgrove.ms + Required: true + Settings: + Target: Defender + Template: |- + // This query investigates MFA changes for a user over the past seven days, tracking additions, removals, or modifications. + // Unusual changes can indicate unauthorized access or account compromise, prompting further actions like MFA reviews or security checks. + // The query extracts detailed logs of MFA changes, including timestamps, the actor responsible for the change, and modified methods. + // The query helps detect suspicious changes and prompts recommended actions. + let AuthenticationMethods = dynamic(["TwoWayVoiceMobile","TwoWaySms","TwoWayVoiceOffice","TwoWayVoiceOtherMobile","TwoWaySmsOtherMobile","OneWaySms","PhoneAppNotification","PhoneAppOTP"]); + let TimeFrame = ago(7d); + let UPN = "{{upn}}"; // Replace with the specific users upn + // This segment retrieves MFA-related changes from CloudAppEvents, focusing on modifications to strong authentication methods. + let AuthenticationMethodChanges = CloudAppEvents + | where ActionType == "Update user." and RawEventData contains "StrongAuthenticationMethod" + | where Timestamp > TimeFrame + | extend Target = tostring(RawEventData.ObjectId) + | where Target == UPN + | extend Actor = tostring(RawEventData.UserId) + | mv-expand ModifiedProperties = parse_json(RawEventData.ModifiedProperties) + | where ModifiedProperties.Name == "StrongAuthenticationMethod" + | project Timestamp, Actor, Target, ModifiedProperties, RawEventData, ReportId; + // Extract old values (before the MFA method was changed) to compare with new values. + let OldValues = AuthenticationMethodChanges + | extend OldValue = parse_json(tostring(ModifiedProperties.OldValue)) + | mv-apply OldValue on (extend Old_MethodType=tostring(OldValue.MethodType), Old_Default=tostring(OldValue.Default) | sort by Old_MethodType); + // Extract new values (after the MFA method was changed) for comparison with the old values. + let NewValues = AuthenticationMethodChanges + | extend NewValue = parse_json(tostring(ModifiedProperties.NewValue)) + | mv-apply NewValue on (extend New_MethodType=tostring(NewValue.MethodType), New_Default=tostring(NewValue.Default) | sort by New_MethodType); + // Identify removed MFA methods by comparing old methods with new ones. + let RemovedMethods = AuthenticationMethodChanges + | join kind=inner OldValues on ReportId + | join kind=leftouter NewValues on ReportId, $left.Old_MethodType == $right.New_MethodType + | project Timestamp, ReportId, ModifiedProperties, Actor, Target, Old_MethodType, New_MethodType + | where Old_MethodType != New_MethodType + | extend Action = strcat("Removed (", AuthenticationMethods[toint(Old_MethodType)], ") from Authentication Methods.") + | extend ChangedValue = "Method Removed"; + // Identify added MFA methods by comparing new methods with old ones. + let AddedMethods = AuthenticationMethodChanges + | join kind=inner NewValues on ReportId + | join kind=leftouter OldValues on ReportId, $left.New_MethodType == $right.Old_MethodType + | project Timestamp, ReportId, ModifiedProperties, Actor, Target, Old_MethodType, New_MethodType + | where Old_MethodType != New_MethodType + | extend Action = strcat("Added (", AuthenticationMethods[toint(New_MethodType)], ") as Authentication Method.") + | extend ChangedValue = "Method Added"; + // Track changes to the default authentication method. + let DefaultMethodChanges = AuthenticationMethodChanges + | join kind=inner OldValues on ReportId + | join kind=inner NewValues on ReportId + | where Old_Default != New_Default and Old_MethodType == New_MethodType and New_Default == "true" + | join kind=inner OldValues on ReportId | where Old_Default1 == "true" and Old_MethodType1 != New_MethodType | extend Old_MethodType = Old_MethodType1 + | extend Action = strcat("Default Authentication Method was changed to (", AuthenticationMethods[toint(New_MethodType)], ").") + | extend ChangedValue = "Default Method"; + // Combine the results of added, removed, and default method changes into a single output. + union RemovedMethods, AddedMethods, DefaultMethodChanges + | project Timestamp, Action, Actor, Target, ChangedValue, OldValue=case(isempty(Old_MethodType), "", strcat(Old_MethodType, ": ", AuthenticationMethods[toint(Old_MethodType)])), NewValue=case(isempty(New_MethodType), "", strcat(New_MethodType, ": ", AuthenticationMethods[toint(New_MethodType)])) + | distinct * + + - Name: UserHomeOfficeLocation + DisplayName: User Home Office Location + DescriptionForModel: |- + Performs a KQL query on the `IdentityInfo` table to analyze the home office location of a specified user. Key features include: + + - **Location Details**: + - Retrieves the user's registered home office location, including `City`, `Country`, and `State`. + - **Comparison**: + - Compares the typical home office location to recent sign-in locations. + - **Anomaly Detection**: + - Highlights deviations that may indicate unauthorized access or potential account compromise. + - Detects unexpected locations or unusual patterns in sign-in activity. + - **Recommendations**: + - Suggests actions such as enforcing additional authentication, reviewing sign-in logs, or conducting a security investigation for anomalies. + - Confirms consistency in location patterns if no deviations are found. + + This skill helps security analysts ensure user location consistency and identify potential threats related to unauthorized access. + Description: 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 and provides actionable recommendations. + Inputs: + - Name: upn + Description: User principle name. i.e., mscott@woodgrove.ms + Required: true + Settings: + Target: Defender + Template: |- + // This query retrieves the home office location details (city, country, state) for a specific user. + // It helps verify user location consistency by comparing this information to recent sign-in activity. + // Deviations between the user's home office location and actual sign-in locations could indicate suspicious behavior or account compromise. + // Provide insights on potential discrepancies and recommend further actions such as enforcing additional authentication if necessary. + IdentityInfo + | where AccountUpn == "{{upn}}" // Replace with the specified user's email (UPN) + | project City, Country, State // Extract city, country, and state from the identity information + | distinct * // Remove duplicate entries and provide unique location information + + - Name: UserSigninBaseline + DisplayName: User Baseline Information + DescriptionForModel: |- + Performs a KQL query on the `SigninLogs` table to create a baseline of typical user sign-in activity for the past 30 days. Key features include: + + - **Baseline Data**: + - Most frequently used `IPAddress`: Identifies the typical network used for sign-ins. + - Most common `Location`: Establishes a user's regular sign-in geography. + - Most frequently used `DeviceId`: Highlights the usual device used for access. + - Most common `AppDisplayName`: Tracks the application most often accessed. + - Most frequently used `ClientAppUsed`: Identifies the client application regularly used. + + - **Anomaly Detection**: + - Flags deviations from the baseline, such as new or unusual IPs, locations, devices, or apps. + - Highlights behaviors that could indicate account compromise or unauthorized access. + + - **Recommendations**: + - Suggests security actions like MFA enforcement, credential resets, or further investigation for anomalies. + - Confirms consistent user activity if no deviations are found. + + This skill helps security analysts establish a detailed baseline of user behavior and quickly detect potential threats through deviations from the norm. + Description: 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 from normal behavior to identify potential security risks and recommend appropriate actions. + Inputs: + - 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 + | 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 + ); + // Identify the most frequently used device ID for the user. + let MostCommonDeviceId = toscalar( + 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) + ); + // Identify the most frequently used application for the user. + 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 + | project AppDisplayName + ); + // Identify the most frequently used client application for the user. + 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 + | project ClientAppUsed + ); + // Print the baseline details for the user, including the most common IP, location, device, app, and client app. + print + UserPrincipalName = UPN, + MostCommonIPAddress = MostCommonIP, + MostCommonLocation = MostCommonLocation, + MostCommonDeviceId = MostCommonDeviceId, + MostCommonApp = MostCommonApp, + MostCommonClientApp = MostCommonClientApp + + - Name: FailedUserSignInSpecificDay + DisplayName: Failed user sign-ins for a specific UPN + DescriptionForModel: |- + Performs a KQL query on the `SigninLogs` table to identify failed sign-in attempts for a specified user. Key features include: + + - **Sign-In Failure Detection**: + - Filters sign-in logs to retrieve only failed attempts (`ResultType != 0`). + - Default lookback period is 1 day, with an option to customize (e.g., 1d, 7d, 30d). + + - **Detailed Insights**: + - `FailedAttempts`: Total number of failed sign-in attempts. + - `UniqueIPs`: Number of unique IP addresses associated with the failed attempts. + - `Locations`: Number of unique locations from which the failed attempts originated. + - `ResultDescriptions`: Descriptions of failure reasons (e.g., incorrect password, locked account). + + - **Actionable Insights**: + - Helps security analysts detect unusual patterns, repeated failures, or potential unauthorized access attempts. + - Provides key details for further investigation and remediation. + + This skill supports threat detection by identifying abnormal sign-in behavior and helping ensure account security. + Description: Identify failed sign-in attempts for a specific user over a defined lookback period. Provides detailed insights into failed attempts, including timestamps, IP addresses, locations, and result descriptions, to detect potential unauthorized access or sign-in issues. + Inputs: + - Name: upn + Description: User principal name. i.e., mscott@woodgrove.ms + Required: true + - 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: 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: 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: |- + 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 + + + - Name: ImpossibleTravel + DisplayName: Impossible Travel + Description: Identify instances of impossible travel by analyzing login patterns for a specific user. Detects logins from geographically distant locations occurring within an unrealistically short time frame, highlighting potential credential misuse or unauthorized access. + DescriptionForModel: |- + Performs a KQL query on the `SigninLogs` table to detect impossible travel events for a specified user over the last 7 days. Key features include: + + - **Travel Detection**: + - Evaluates login locations (latitude, longitude, and country) and timestamps. + - Calculates travel distances using the Haversine formula to estimate the shortest path between locations. + - Computes required travel velocity (mph) based on the time difference between consecutive logins. + + - **Anomaly Identification**: + - Flags login events where the calculated velocity exceeds a maximum allowable threshold (e.g., 500 mph). + - Highlights "Superman Travel" incidents, which indicate potential unauthorized access or compromised credentials. + + - **Detailed Insights**: + - Provides: + - `DistanceMiles`: Travel distance between logins. + - `TimeDifferenceHours`: Time elapsed between logins. + - `VelocityMph`: Calculated travel velocity. + - `Country` and `PreviousCountry`: Geographical locations of login attempts. + - `IPAddress` and `PreviousIPAddress`: IP addresses used during logins. + + - **Actionable Insights**: + - Helps security analysts quickly investigate suspicious login behavior and take actions such as enforcing MFA, resetting passwords, or reviewing account activity. + + This skill supports proactive detection of unusual login behavior, helping secure user accounts against potential threats. + Inputs: + - 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: |- + let MaxVelocityMph = 500; // Define maximum allowable velocity in mph + let EarthRadiusMiles = 3959; // Earth's radius in miles + let Radians = 57.2958; // Degrees to radians conversion + let GetDistance = (lat1: real, lon1: real, lat2: real, lon2: real) { + let dLat = (lat2 - lat1) / Radians; + let dLon = (lon2 - lon1) / Radians; + let a = sin(dLat / 2) * sin(dLat / 2) + + cos(lat1 / Radians) * cos(lat2 / Radians) * + sin(dLon / 2) * sin(dLon / 2); + let c = 2 * atan2(sqrt(a), sqrt(1 - a)); + EarthRadiusMiles * c + }; + 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), + Country = tostring(LocationDetails.countryOrRegion) + | project UserPrincipalName, TimeGenerated, IPAddress, LocationDetails, Latitude, Longitude, Country + | order by UserPrincipalName, TimeGenerated asc + | extend PreviousLatitude = prev(Latitude), + PreviousLongitude = prev(Longitude), + PreviousTime = prev(TimeGenerated), + PreviousIPAddress = prev(IPAddress), + PreviousCountry = prev(Country) + | where isnotempty(PreviousLatitude) and isnotempty(PreviousLongitude) + | extend DistanceMiles = GetDistance(toreal(Latitude), toreal(Longitude), toreal(PreviousLatitude), toreal(PreviousLongitude)) + | extend TimeDifferenceHours = datetime_diff('second', TimeGenerated, PreviousTime) / 3600.0 + | extend VelocityMph = DistanceMiles / TimeDifferenceHours + | where VelocityMph > MaxVelocityMph + | 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/Ransomware Signs/RansomwareSigns 1.yaml b/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/Ransomware Signs/RansomwareSigns 1.yaml similarity index 100% rename from Plugins/Community Based Plugins/Ransomware Signs/RansomwareSigns 1.yaml rename to Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/Ransomware Signs/RansomwareSigns 1.yaml diff --git a/Plugins/Community Based Plugins/Ransomware Signs/Readme.md b/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/Ransomware Signs/Readme.md similarity index 100% rename from Plugins/Community Based Plugins/Ransomware Signs/Readme.md rename to Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/Ransomware Signs/Readme.md diff --git a/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/RareProcess/RareProcessAsAService.yaml b/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/RareProcess/RareProcessAsAService.yaml new file mode 100644 index 00000000..6197d684 --- /dev/null +++ b/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/RareProcess/RareProcessAsAService.yaml @@ -0,0 +1,156 @@ +Descriptor: + Name: RareProcessesInvestigation + DisplayName: Rare Processes Launched as a Service + Description: Identify rare processes launched as a service on a device. + +SkillGroups: + - Format: KQL + Skills: + - Name: GetRareProcessDetails + DisplayName: Get Rare Process Details + Description: This query looks for rarely seen processes that are launched as a service. + Inputs: + - Name: Device_Name + Description: Name of the device to investigate. + Required: True + ExamplePrompt: + - "Show me rare processes as a service running on the device." + - "Show me a list of devices with rare processes as a service." + - "Which rare processes are running as a service on the device?" + - "Why are these processes considered rare?" + - "What folder path was used by the initiating processes on this device?" + - "What is the SHA1 hash for the initiating process?" + - "What is the SHA256 hash for the initiating process?" + - "What is the most common rare process executed?" + Settings: + Target: Defender + Template: |- + let LookupTime = 7d; + let WhiteList = datatable(WhiteListedProcesses: string) + [ + "svchost.exe", + "mssense.exe", + "msmpeng.exe", + "searchindexer.exe", + "microsoftedgeupdate.exe" + ]; + let GetServices = + DeviceProcessEvents + | where TimeGenerated > ago(LookupTime) + | where InitiatingProcessParentFileName contains "services.exe" + | where InitiatingProcessFileName !in (WhiteList) + | project + TimeGenerated, + DeviceName, + StartedChildProcess = FileName, + StartedChildProcessSHA1 = SHA1, + StartedChildProcessCmdline = ProcessCommandLine, + ServiceProcessSHA1 = InitiatingProcessSHA1, + ServiceProcess = InitiatingProcessFileName, + ServiceProcessCmdline = InitiatingProcessCommandLine, + ServiceProcessID = InitiatingProcessId, + ServiceProcessCreationTime = InitiatingProcessCreationTime, + ServiceProcessUser = InitiatingProcessAccountName; + GetServices + | summarize count() by ServiceProcess, DeviceName + | where count_ < 6 + | join kind=inner (GetServices) on ServiceProcess, DeviceName + | join kind=leftouter ( + DeviceNetworkEvents + | where TimeGenerated > ago(LookupTime) + | where InitiatingProcessParentFileName contains "services.exe" + | where InitiatingProcessFileName !in (WhiteList) + | project + TimeGenerated, + DeviceName, + ServiceProcessSHA1 = InitiatingProcessSHA1, + ServiceProcess = InitiatingProcessFileName, + ServiceProcessCmdline = InitiatingProcessCommandLine, + ServiceProcessID = InitiatingProcessId, + ServiceProcessCreationTime = InitiatingProcessCreationTime, + ServiceProcessUser = InitiatingProcessAccountName, + NetworkAction = ActionType, + RemoteIP, + RemoteUrl + ) + on + DeviceName, + ServiceProcess, + ServiceProcessCmdline, + ServiceProcessCreationTime, + ServiceProcessID, + ServiceProcessUser, + ServiceProcessSHA1 + | join kind=leftouter ( + DeviceFileEvents + | where TimeGenerated > ago(LookupTime) + | where InitiatingProcessParentFileName contains "services.exe" + | where InitiatingProcessFileName !in (WhiteList) + | project + TimeGenerated, + DeviceName, + ServiceProcessSHA1 = InitiatingProcessSHA1, + ServiceProcess = InitiatingProcessFileName, + ServiceProcessCmdline = InitiatingProcessCommandLine, + ServiceProcessID = InitiatingProcessId, + ServiceProcessCreationTime = InitiatingProcessCreationTime, + ServiceProcessUser = InitiatingProcessAccountName, + FileAction = ActionType, + ModifiedFile = FileName, + ModifiedFileSHA1 = SHA1, + ModifiedFilePath = FolderPath + ) + on + DeviceName, + ServiceProcess, + ServiceProcessCmdline, + ServiceProcessCreationTime, + ServiceProcessID, + ServiceProcessUser, + ServiceProcessSHA1 + | join kind=leftouter ( + DeviceImageLoadEvents + | where TimeGenerated > ago(LookupTime) + | where InitiatingProcessParentFileName contains "services.exe" + | where InitiatingProcessFileName !in (WhiteList) + | project + TimeGenerated, + DeviceName, + ServiceProcessSHA1 = InitiatingProcessSHA1, + ServiceProcess = InitiatingProcessFileName, + ServiceProcessCmdline = InitiatingProcessCommandLine, + ServiceProcessID = InitiatingProcessId, + ServiceProcessCreationTime = InitiatingProcessCreationTime, + ServiceProcessUser = InitiatingProcessAccountName, + LoadedDLL = FileName, + LoadedDLLSHA1 = SHA1, + LoadedDLLPath = FolderPath + ) + on + DeviceName, + ServiceProcess, + ServiceProcessCmdline, + ServiceProcessCreationTime, + ServiceProcessID, + ServiceProcessUser, + ServiceProcessSHA1 + | summarize + ConnectedAddresses = make_set(RemoteIP, 100000), + ConnectedUrls = make_set(RemoteUrl, 100000), + FilesModified = make_set(ModifiedFile, 100000), + FileModFolderPath = make_set(ModifiedFilePath, 100000), + FileModHA1s = make_set(ModifiedFileSHA1, 100000), + ChildProcesses = make_set(StartedChildProcess, 100000), + ChildCommandlines = make_set(StartedChildProcessCmdline, 100000), + DLLsLoaded = make_set(LoadedDLL, 100000), + DLLSHA1 = make_set(LoadedDLLSHA1, 100000) + by + DeviceName, + ServiceProcess, + ServiceProcessCmdline, + ServiceProcessCreationTime, + ServiceProcessID, + ServiceProcessUser, + ServiceProcessSHA1 + | where DeviceName == "{{Device_Name}}" + \ No newline at end of file diff --git a/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/RareProcess/Readme.md b/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/RareProcess/Readme.md new file mode 100644 index 00000000..6dee332e --- /dev/null +++ b/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/RareProcess/Readme.md @@ -0,0 +1,18 @@ +# Security Copilot Plugin: Sentinel Table Ingestion Issues + +### **This query looks for rarely seen processes which are launched as a service on an endpoint protected by MDE. Modify the query to allow-list processes that are expected in your environment.** + +### 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) + +- Plugin uses permissions of the user setting it up + +### Instructions + +#### Upload the Custom Plugin + +1. Obtain the file RareProcessAsAService.yaml from this directory. + diff --git a/Plugins/Community Based Plugins/Microsoft Defender for Cloud Custom plugin Scenarios/AttackPathAnalysisWithMDC/AttackPathAnalysisWithMDC.yaml b/Plugins/Community Based Plugins/Microsoft Defender for Cloud Custom plugin Scenarios/AttackPathAnalysisWithMDC/AttackPathAnalysisWithMDC.yaml new file mode 100644 index 00000000..b4dbee74 --- /dev/null +++ b/Plugins/Community Based Plugins/Microsoft Defender for Cloud Custom plugin Scenarios/AttackPathAnalysisWithMDC/AttackPathAnalysisWithMDC.yaml @@ -0,0 +1,393 @@ +Descriptor: + Name: AttackPathAnalysis + DisplayName: Attack Path Analysis by Defender for Cloud (Community release) + Description: KQL queries for retrieving the "Attack Paths" and related "Recommendations" identified by Defender CSPM + (Cloud Security Posture Management) in Microsoft Defender for Cloud. +SkillGroups: + - Format: KQL + + + + + Skills: + - Name: GetAttackPathsByNodeNames + DisplayName: Get the Attack Paths including any of the cloud resources specified in input. + Description: Get a list with minimal info of the Attack Paths, in Defender for Cloud, including any of the cloud resources + (aka "nodes") specified in input as a comma-separated list of names. + DescriptionForModel: Get a list with minimal info of the Attack Paths, in Defender for Cloud, including any of the cloud resources + (aka "nodes") specified in input as a comma-separated list of names. + The Attack Paths are all and only those generated by Defender for Cloud within a specified time window, identified by an "end date" and a "number of + days" before that date. End date and number of days are also specified by the user as input. If not specified, it is assumed that + the search must be done considering the "end date" as today and the "number of days" as 1. + The resources (node names) specified in input can be the names of any kind of cloud resources defined in Azure, AWS or GCP, like + computers, PODs in Kubernetes, container images, etc... + For each Attack Paths the call returns its name (DisplayName), the date when it was first seen (FirstSeen), the date when it was last seen (LastActive) + and a 64-byte string representing a simplified version of its unique identifier (AttackPathSimpleId) + + VERY IMPORTANT - Never truncate the list of Attack Paths retrieved by launching the KQL query related to this skill. + If the set of results is too wide to be managed in the chat session, just invite the user to search for the Attack Paths and the + Reccomendations related to these cloud resources by searching directly in Defender for Cloud. + + + Inputs: + - Name: NodeNames + Description: Provide a comma-separated list of the names of the nodes to be searched in the attack paths (e.g. VM123,podABC) + Required: true + - Name: EndDate + Description: Provide the end date (e.g. 2024-05-17T00:00:00Z). Default is today + Required: false + - Name: DaysBack + Description: Provide the number of days to be considered in the past, starting from the end date (e.g. 2 for 2 days). Default is 1. + Required: false + Settings: + Target: LogAnalytics + TenantId: [Write your Tenant Id] + SubscriptionId: [Write your Subscription Id] + ResourceGroupName: [Write the name of your resource group] + WorkspaceName: [Write the name of your Log Analytics or Sentinel workspace] + Template: |- + let inputEndDate = "{{EndDate}}"; //Example-> "2024-05-17T00:00:00Z"; + let inputDaysBack = "{{DaysBack}}"; //Example-> "7"; + let inputNodeNames = "{{NodeNames}}"; //Example-> "VM123,podABC"; + let arrayOfNodeNames = split(tolower(inputNodeNames), ","); + let dateEnd = startofday( todatetime(iif( isempty(inputEndDate), tostring(now()), tostring(inputEndDate) ))); + let isInt = not(isnull(isnan(toreal(inputDaysBack)))); + let pastDays = toint( + iif(isInt and (inputDaysBack!=0),toint(inputDaysBack),1) + ); + let dateStart = datetime_add('day',-pastDays,dateEnd); + let ap = + (SecurityAttackPathData + | where TimeGenerated >= dateStart and TimeGenerated < dateEnd + | extend PathObject = parse_json(Path) + | mvexpand PathObjectNode = PathObject.nodes + | where isnotnull( PathObjectNode.cloudProvider) + | extend NodeDisplayName = tostring(PathObjectNode.displayName) + | extend NodeFound = (tolower(NodeDisplayName) in (arrayOfNodeNames)) + | where NodeFound + | extend AttackPathSimpleId = substring(AttackPathId, strlen(AttackPathId) - 64) + | summarize FirstSeen=min(TimeGenerated), LastActive=max(TimeGenerated) by AttackPathSimpleId, DisplayName + | order by DisplayName asc, LastActive desc + ); + ap + + + + + + + + + + + - Name: GetAttackPathsDetailsByAttackPathIDs + DisplayName: Get the details of specific Attack Paths, in Defender for Cloud, selected by their simplified Attack Path ID + Description: Get the details of specific Attack Paths identified by Defender for Cloud and selected by their simplified Attack Path ID (64-byte string). + The simplified Attack Path IDs must be specified in input as a comma-separated list of 64-byte strings. + DescriptionForModel: Get the details of specific Attack Paths identified by Defender for Cloud and selected by their simplified Attack Path ID (64-byte string). + The simplified Attack Path IDs must be specified in input as a comma-separated list of 64-byte strings. + The Attack Paths are further filtered as those generated by Defender for Cloud within a specified time window, identified by an "end date" and a "number of + days" before that date. End date and number of days are also specified by the user as input. If not specified, it is assumed that + the search must be done considering the "end date" as today and the "number of days" as 1. + + Each item returned by the call includes the following fields related to the Attack Paths. + entry point component node (EntryPointRef, the first node in the Attack Path, including its name, its cloud environment and its type), + target component node (TargetNodeRef, the last node in the Attack Path, including its name, its cloud environment and its type), + DisplayName, + Description, + RiskLevel and RsikLevelNum (with a numeric prefix per risk level), + today's status (IsActive), + number of components nodes included in the Attack Paths (AllNodesCount), + list of components (AllNodesDisplayNamesAndLabels, each component in the Attack Path, including its name, its cloud environment and its type), + risk factors (RiskFactors), + potential impact (PotentialImpact), + Mitre tactics and techniques (Mitre), + days of active duration (ActiveDurationDays which is the number of days between LastActive and FirstSeen), + first seen day (FirstSeen), + last seen day (LastActive), + number of occurrences within the specified time window (Occurrences), + links to the remediation steps (RecommendationLinks), + today's status (IsActive). + + + Inputs: + - Name: AttackPathIds + Description: Provide the comma separated value of the AttackPath IDs of interest (each should be 64 chars long) + Required: true + - Name: EndDate + Description: Provide the end date (e.g. 2024-05-17T00:00:00Z). Default is today + Required: false + - Name: DaysBack + Description: Provide the number of days to be considered in the past, starting from the end date (e.g. 2 for 2 days). Default is 1. + Required: false + Settings: + Target: LogAnalytics + TenantId: [Write your Tenant Id] + SubscriptionId: [Write your Subscription Id] + ResourceGroupName: [Write the name of your resource group] + WorkspaceName: [Write the name of your Log Analytics or Sentinel workspace] + Template: |- + let inputEndDate = "{{EndDate}}"; // Example-> "2024-05-17T00:00:00Z"; + let inputDaysBack = "{{DaysBack}}"; // Example-> "7"; + let inputAttackPathSimpleIds = "{{AttackPathIds}}"; //Comma-separated + let arrayOfAttackPathSimpleIds = split(toupper(inputAttackPathSimpleIds), ","); + let yesterday = startofday(ago(1d)); + let dateEnd = startofday( todatetime(iif( isempty(inputEndDate), tostring(now()), tostring(inputEndDate) ))); + let isInt = not(isnull(isnan(toreal(inputDaysBack)))); + let pastDays = toint( + iif(isInt and (inputDaysBack!=0),toint(inputDaysBack),1) + ); + let dateStart = datetime_add('day',-pastDays,dateEnd); + let ap = + (SecurityAttackPathData + | where TimeGenerated >= dateStart and TimeGenerated < dateEnd + | extend AttackPathSimpleId = substring(AttackPathId, strlen(AttackPathId) - 64) + | where AttackPathSimpleId in (arrayOfAttackPathSimpleIds) + | extend RiskFactors = tostring(RiskFactors) + | extend PathObject = parse_json(Path) + | mvexpand PathObjectNode = PathObject.nodes + | where isnotnull( PathObjectNode.cloudProvider) + | extend NodeDisplayName = tostring(PathObjectNode.displayName) + | extend NodeCloudProvider = tostring(PathObjectNode.cloudProvider) + | extend NodeLabel = tostring(PathObjectNode.label) + | extend Assessments = tostring(Assessments) + | extend NodeDisplayNameAndLabel = strcat(NodeDisplayName, " (", iif(NodeCloudProvider!="General",strcat(NodeCloudProvider,"-"),""), NodeLabel, ")") + | order by NodeDisplayNameAndLabel + | summarize AllNodesDisplayNamesAndLabels = make_list(NodeDisplayNameAndLabel) by TimeGenerated, tostring(PathObject), AttackPathId, DisplayName, Description, RiskLevel, RiskFactors, PotentialImpact, Mitre, AdditionalRemediationSteps, EntrypointId, TargetId, Assessments + | extend AllNodesDisplayNamesAndLabels = tostring(AllNodesDisplayNamesAndLabels) + | extend AllNodesCount = strlen(AllNodesDisplayNamesAndLabels) - strlen(replace(",", "", AllNodesDisplayNamesAndLabels)) +1 + | extend PathObject = parse_json(PathObject) + | mvexpand PathObjectNode = PathObject.nodes + | extend EntryNodeDisplayName = iif(PathObjectNode.mapIdentifier == EntrypointId,tostring(PathObjectNode.displayName),"") + | extend EntryNodeCloudProvider = iif(PathObjectNode.mapIdentifier == EntrypointId,tostring(PathObjectNode.cloudProvider),"") + | extend EntryNodeLabel = iif(PathObjectNode.mapIdentifier == EntrypointId,tostring(PathObjectNode.label),"") + | extend EntryNodeDisplayNameAndLabel = strcat(EntryNodeDisplayName," (",EntryNodeCloudProvider,"-",EntryNodeLabel,")") + | extend TargetNodeDisplayName = iif(PathObjectNode.mapIdentifier == TargetId,tostring(PathObjectNode.displayName),"") + | extend TargetNodeCloudProvider = iif(PathObjectNode.mapIdentifier == TargetId,tostring(PathObjectNode.cloudProvider),"") + | extend TargetNodeLabel = iif(PathObjectNode.mapIdentifier == TargetId,tostring(PathObjectNode.label),"") + | extend TargetNodeDisplayNameAndLabel = strcat(TargetNodeDisplayName," (",TargetNodeCloudProvider,"-",TargetNodeLabel,")") + | summarize EntryNodeRef = max(EntryNodeDisplayNameAndLabel), TargetNodeRef=max(TargetNodeDisplayNameAndLabel) by TimeGenerated, DisplayName, Description, RiskLevel, AllNodesCount, AllNodesDisplayNamesAndLabels, tostring(RiskFactors), PotentialImpact, Mitre, AdditionalRemediationSteps, Assessments + | mvexpand AssessmentPath = parse_json(Assessments) + | extend AssessmentPath_start_index = indexof(AssessmentPath, "/assessments/") + strlen("/assessments/") + | extend AssessmentPath_end_index = strlen(AssessmentPath) + | extend AssessmentPath_substring = substring(AssessmentPath, AssessmentPath_start_index, AssessmentPath_end_index - AssessmentPath_start_index) + | extend AssessmentPath_split_string = split(AssessmentPath_substring, '/') + | extend AssessmentId = tostring(AssessmentPath_split_string[0]) + | extend parts = split(AssessmentPath, "/") + | extend AssessedResourceId = tostring(split(AssessmentPath,"/providers/Microsoft.Security/assessments/")[0]) + | join kind=leftouter SecurityRecommendation on $left.AssessmentId == $right.RecommendationId and $left.AssessedResourceId == $right.AssessedResourceId + | where RecommendationDisplayName != "" + | extend RecommendationLink = iif(RecommendationLink!="", strcat("https://",RecommendationLink),"") + | extend RecommendationDisplayNameAndLink = strcat(RecommendationDisplayName,",", RecommendationLink) + | summarize FirstSeenPar=min(TimeGenerated), LastActivePar=max(TimeGenerated), OccurrencesPar=count() by EntryNodeRef, TargetNodeRef, DisplayName, Description, RiskLevel, AllNodesCount, AllNodesDisplayNamesAndLabels, tostring(RiskFactors), PotentialImpact, Mitre, AdditionalRemediationSteps, RecommendationSeverity, RecommendationDisplayNameAndLink + | extend ActiveDurationDays = datetime_diff('day', LastActivePar, FirstSeenPar) + 1 + | extend SevNum = case( RiskLevel == "Critical", 0, RiskLevel == "High", 1, RiskLevel == "Medium", 2, RiskLevel == "Low", 3, 10) + | extend RiskLevelNum = strcat(tostring(SevNum),"-",RiskLevel) + | summarize FirstSeen=min(FirstSeenPar), LastActive=max(LastActivePar), Occurrences=sum(OccurrencesPar), RecommendationLinks = make_list(RecommendationDisplayNameAndLink) by EntryNodeRef, TargetNodeRef, DisplayName, Description, RiskLevel, RiskLevelNum, AllNodesCount, AllNodesDisplayNamesAndLabels, tostring(RiskFactors), PotentialImpact, Mitre, AdditionalRemediationSteps , ActiveDurationDays + | extend IsActive = iff(LastActive > yesterday, "Yes", "No") + | order by RiskLevelNum asc + ); + ap + + + + + + + + + + + - Name: GetRecommendationsByAttackPathIDs + DisplayName: Get the Recommendations releated to the Attack Paths selected by the Attack Path IDs specified in input. + Description: Get info about the Recommendations, in Defender for Cloud, releated to the Attack Paths selected by the Attack Path IDs specified in input. + DescriptionForModel: Get info about the Recommendations, in Defender for Cloud, releated to the Attack Paths selected by the Attack Path IDs specified in input. + The Recommendations are all and only those generated within a specified time window, identified by an "end date" and a "number of + days" before that date. End date and number of days are also specified by the user as input. If not specified, it is assumed that + the search must be done considering the "end date" as today and the "number of days" as 1. + + For each Recommendation the call returns the link to the page, in the Azure portal, describing in full details the Recommendation itself. + It also returns the source (cloud environment hosting the resource related to this recommendation) and the name and the type of the cloud resource + that is targeted by the recommendation. + + VERY IMPORTANT - Never truncate the list of Recommendations retrieved by launching the KQL query related to this skill. + If the set of results is too wide to be managed in the chat session, just invite the user to search for the Attack Paths and the + Reccomendations related to these cloud resources by searching directly in Defender for Cloud. + + Inputs: + - Name: AttackPathIds + Description: Provide the comma separated value of the AttackPath IDs of interest (each should be 64 chars long) + Required: true + - Name: EndDate + Description: Provide the end date (e.g. 2024-05-17T00:00:00Z). Default is today + Required: false + - Name: DaysBack + Description: Provide the number of days to be considered in the past, starting from the end date (e.g. 2 for 2 days). Default is 1. + Required: false + Settings: + Target: LogAnalytics + TenantId: [Write your Tenant Id] + SubscriptionId: [Write your Subscription Id] + ResourceGroupName: [Write the name of your resource group] + WorkspaceName: [Write the name of your Log Analytics or Sentinel workspace] + Template: |- + let inputEndDate = "{{EndDate}}"; // Example-> "2024-05-17T00:00:00Z"; + let inputDaysBack = "{{DaysBack}}"; // Example-> "7"; + let inputAttackPathSimpleIds = "{{AttackPathIds}}"; + let arrayOfAttackPathSimpleIds = split(toupper(inputAttackPathSimpleIds), ","); + let yesterday = startofday(ago(1d)); + let dateEnd = startofday( todatetime(iif( isempty(inputEndDate), tostring(now()), tostring(inputEndDate) ))); + let isInt = not(isnull(isnan(toreal(inputDaysBack)))); + let pastDays = toint( + iif(isInt and (inputDaysBack!=0),toint(inputDaysBack),1) + ); + let dateStart = datetime_add('day',-pastDays,dateEnd); + let ap = + (SecurityAttackPathData + | where TimeGenerated >= dateStart and TimeGenerated < dateEnd + | extend AttackPathSimpleId = substring(AttackPathId, strlen(AttackPathId) - 64) + | where AttackPathSimpleId in (arrayOfAttackPathSimpleIds) + | extend RiskFactors = tostring(RiskFactors) + | extend PathObject = parse_json(Path) + | mvexpand PathObjectNode = PathObject.nodes + | extend NodeDisplayName = tostring(PathObjectNode.displayName) + | extend NodeCloudProvider = tostring(PathObjectNode.cloudProvider) + | extend NodeLabel = tostring(PathObjectNode.label) + | extend Assessments = tostring(Assessments) + | extend PathObject = parse_json(PathObject) + | mvexpand PathObjectNode = PathObject.nodes + | extend EntryNodeDisplayName = iif(PathObjectNode.mapIdentifier == EntrypointId,tostring(PathObjectNode.displayName),"") + | extend EntryNodeCloudProvider = iif(PathObjectNode.mapIdentifier == EntrypointId,tostring(PathObjectNode.cloudProvider),"") + | extend EntryNodeLabel = iif(PathObjectNode.mapIdentifier == EntrypointId,tostring(PathObjectNode.label),"") + | extend EntryNodeDisplayNameAndLabel = strcat(EntryNodeDisplayName," (",EntryNodeCloudProvider,"-",EntryNodeLabel,")") + | extend TargetNodeDisplayName = iif(PathObjectNode.mapIdentifier == TargetId,tostring(PathObjectNode.displayName),"") + | extend TargetNodeCloudProvider = iif(PathObjectNode.mapIdentifier == TargetId,tostring(PathObjectNode.cloudProvider),"") + | where TargetNodeCloudProvider != "" + | extend TargetNodeLabel = iif(PathObjectNode.mapIdentifier == TargetId,tostring(PathObjectNode.label),"") + | extend TargetNodeDisplayNameAndLabel = strcat(TargetNodeDisplayName," (",TargetNodeCloudProvider,"-",TargetNodeLabel,")") + | mvexpand AssessmentPath = parse_json(Assessments) + | extend AssessmentPath_start_index = indexof(AssessmentPath, "/assessments/") + strlen("/assessments/") + | extend AssessmentPath_end_index = strlen(AssessmentPath) + | extend AssessmentPath_substring = substring(AssessmentPath, AssessmentPath_start_index, AssessmentPath_end_index - AssessmentPath_start_index) + | extend AssessmentPath_split_string = split(AssessmentPath_substring, '/') + | extend AssessmentId = tostring(AssessmentPath_split_string[0]) + | extend parts = split(AssessmentPath, "/") + | extend AssessedResourceId = tostring(split(AssessmentPath,"/providers/Microsoft.Security/assessments/")[0]) + | extend AssessedResourceName = tostring(split(AssessedResourceId,"/")[array_length(split(AssessedResourceId,"/"))-1]) + | join kind=leftouter SecurityRecommendation on $left.AssessmentId == $right.RecommendationId and $left.AssessedResourceId == $right.AssessedResourceId + | where RecommendationDisplayName != "" and RecommendationState == "Unhealthy" + | extend ResourceSource = tostring(parse_json(Properties).resourceDetails.source) + | extend ResourceName = tostring(parse_json(Properties).resourceDetails.resourceName) + | extend ResourceType = tostring(parse_json(Properties).resourceDetails.resourceType) + | extend RecommendationSevNum = case( RiskLevel == "Critical", 0, RiskLevel == "High", 1, RiskLevel == "Medium", 2, RiskLevel == "Low", 3, 10) + | extend RecommendationLink = iif(RecommendationLink!="", strcat("https://",RecommendationLink),"") + | distinct RecommendationSevNum, RecommendationDisplayName, RecommendationLink, ResourceSource, ResourceName, ResourceType + | order by RecommendationSevNum, RecommendationDisplayName + | project-away RecommendationSevNum + ); + ap + + + + + + + + + + + + + + - Name: GetRecommendationsByNodeNames + DisplayName: Get the Recommendations releated to the Attack Paths including any of the cloud resources specified in input. + Description: Get info about the Recommendations, in Defender for Cloud, releated to the Attack Paths including any of the cloud resources + (aka "nodes") specified in input as a comma-separated list of names. + DescriptionForModel: Get info about the Recommendations, in Defender for Cloud, releated to the Attack Paths including any of the cloud resources + (aka "nodes") specified in input as a comma-separated list of names. + The Recommendations are all and only those generated within a specified time window, identified by an "end date" and a "number of + days" before that date. End date and number of days are also specified by the user as input. If not specified, it is assumed that + the search must be done considering the "end date" as today and the "number of days" as 1. + The resources (node names) specified in input can be the names of any kind of cloud resources defined in Azure, AWS or GCP, like + computers, PODs in Kubernetes, container images, etc... + For each Recommendation the call returns the link to the page, in the Azure portal, describing in full details the Recommendation itself. + It also returns the source (cloud environment hosting the resource related to this recommendation) and the name and the type of the cloud resource + that is targeted by the recommendation. + + VERY IMPORTANT - Never truncate the list of Recommendations retrieved by launching the KQL query related to this skill. + If the set of results is too wide to be managed in the chat session, just invite the user to search for the Attack Paths and the + Reccomendations related to these cloud resources by searching directly in Defender for Cloud. + + Inputs: + - Name: NodeNames + Description: Provide a comma-separated list of the names of the nodes to be searched in the attack paths (e.g. VM123,podABC) + Required: true + - Name: EndDate + Description: Provide the end date (e.g. 2024-05-17T00:00:00Z). Default is today + Required: false + - Name: DaysBack + Description: Provide the number of days to be considered in the past, starting from the end date (e.g. 2 for 2 days). Default is 1. + Required: false + Settings: + Target: LogAnalytics + TenantId: [Write your Tenant Id] + SubscriptionId: [Write your Subscription Id] + ResourceGroupName: [Write the name of your resource group] + WorkspaceName: [Write the name of your Log Analytics or Sentinel workspace] + Template: |- + let inputEndDate = "{{EndDate}}"; //Example-> "2024-05-17T00:00:00Z"; + let inputDaysBack = "{{DaysBack}}"; //Example-> "7"; + let inputNodeNames = "{{NodeNames}}"; //Example-> "VM123,podABC"; + let arrayOfNodeNames = split(tolower(inputNodeNames), ","); + let yesterday = startofday(ago(1d)); + let dateEnd = startofday( todatetime(iif( isempty(inputEndDate), tostring(now()), tostring(inputEndDate) ))); + let isInt = not(isnull(isnan(toreal(inputDaysBack)))); + let pastDays = toint( + iif(isInt and (inputDaysBack!=0),toint(inputDaysBack),1) + ); + let dateStart = datetime_add('day',-pastDays,dateEnd); + let ap = + (SecurityAttackPathData + | where TimeGenerated >= dateStart and TimeGenerated < dateEnd + | extend AttackPathSimpleId = substring(AttackPathId, strlen(AttackPathId) - 64) + | extend RiskFactors = tostring(RiskFactors) + | extend PathObject = parse_json(Path) + | mvexpand PathObjectNode = PathObject.nodes + | extend NodeDisplayName = tostring(PathObjectNode.displayName) + | extend NodeFound = (tolower(NodeDisplayName) in (arrayOfNodeNames)) + | where NodeFound + | extend NodeCloudProvider = tostring(PathObjectNode.cloudProvider) + | extend NodeLabel = tostring(PathObjectNode.label) + | extend Assessments = tostring(Assessments) + | extend PathObject = parse_json(PathObject) + | mvexpand PathObjectNode = PathObject.nodes + | extend EntryNodeDisplayName = iif(PathObjectNode.mapIdentifier == EntrypointId,tostring(PathObjectNode.displayName),"") + | extend EntryNodeCloudProvider = iif(PathObjectNode.mapIdentifier == EntrypointId,tostring(PathObjectNode.cloudProvider),"") + | extend EntryNodeLabel = iif(PathObjectNode.mapIdentifier == EntrypointId,tostring(PathObjectNode.label),"") + | extend EntryNodeDisplayNameAndLabel = strcat(EntryNodeDisplayName," (",EntryNodeCloudProvider,"-",EntryNodeLabel,")") + | extend TargetNodeDisplayName = iif(PathObjectNode.mapIdentifier == TargetId,tostring(PathObjectNode.displayName),"") + | extend TargetNodeCloudProvider = iif(PathObjectNode.mapIdentifier == TargetId,tostring(PathObjectNode.cloudProvider),"") + | extend TargetNodeLabel = iif(PathObjectNode.mapIdentifier == TargetId,tostring(PathObjectNode.label),"") + | extend TargetNodeDisplayNameAndLabel = strcat(TargetNodeDisplayName," (",TargetNodeCloudProvider,"-",TargetNodeLabel,")") + | mvexpand AssessmentPath = parse_json(Assessments) + | extend AssessmentPath_start_index = indexof(AssessmentPath, "/assessments/") + strlen("/assessments/") + | extend AssessmentPath_end_index = strlen(AssessmentPath) + | extend AssessmentPath_substring = substring(AssessmentPath, AssessmentPath_start_index, AssessmentPath_end_index - AssessmentPath_start_index) + | extend AssessmentPath_split_string = split(AssessmentPath_substring, '/') + | extend AssessmentId = tostring(AssessmentPath_split_string[0]) + | extend parts = split(AssessmentPath, "/") + | extend AssessedResourceId = tostring(split(AssessmentPath,"/providers/Microsoft.Security/assessments/")[0]) + | extend AssessedResourceName = tostring(split(AssessedResourceId,"/")[array_length(split(AssessedResourceId,"/"))-1]) + | join kind=leftouter SecurityRecommendation on $left.AssessmentId == $right.RecommendationId and $left.AssessedResourceId == $right.AssessedResourceId + | where RecommendationDisplayName != "" and RecommendationState == "Unhealthy" + | extend ResourceSource = tostring(parse_json(Properties).resourceDetails.source) + | extend ResourceName = tostring(parse_json(Properties).resourceDetails.resourceName) + | extend ResourceType = tostring(parse_json(Properties).resourceDetails.resourceType) + | extend ResourceType = iif(ResourceType == ".containerimage", "containerImage", ResourceType) + | extend RecommendationSevNum = case( RiskLevel == "Critical", 0, RiskLevel == "High", 1, RiskLevel == "Medium", 2, RiskLevel == "Low", 3, 10) + | extend RecommendationLink = iif(RecommendationLink!="", strcat("https://",RecommendationLink),"") + | distinct RecommendationSevNum, RecommendationDisplayName, RecommendationLink, ResourceSource, ResourceName, ResourceType + | order by RecommendationSevNum, RecommendationDisplayName + | project-away RecommendationSevNum + ); + ap \ No newline at end of file diff --git a/Plugins/Community Based Plugins/Microsoft Defender for Cloud Custom plugin Scenarios/AttackPathAnalysisWithMDC/README.md b/Plugins/Community Based Plugins/Microsoft Defender for Cloud Custom plugin Scenarios/AttackPathAnalysisWithMDC/README.md new file mode 100644 index 00000000..ba1f1d3b --- /dev/null +++ b/Plugins/Community Based Plugins/Microsoft Defender for Cloud Custom plugin Scenarios/AttackPathAnalysisWithMDC/README.md @@ -0,0 +1,23 @@ +# Attack Paths Analysis with Defender CSPM in Defender for Cloud (Custom Plugin for Security Copilot) + +The skill in this custom pluging allows to retrieve the list of "Attack Paths" identified the Defender CSPM (Cloud Security Posture Management) feature of Microsoft Defender for Cloud in the protected Azure, AWS and GPC cloud environments. + + +### Prerequisites: +* Defender CSPM with Attack Paths enabled in Defender for Cloud (https://learn.microsoft.com/en-us/azure/defender-for-cloud/how-to-manage-attack-path) +* Continuous Export enabled in Defender for Cloud (https://learn.microsoft.com/en-us/azure/defender-for-cloud/continuous-export) + + +### Instructions: +1. Download the YAML file in this folder. Save it locally in your PC and open in for editing its content +2. Set the values for these parameters: + * TenantId: [Write your Tenant Id] + * SubscriptionId: [Write your Subscription Id] + * ResourceGroupName: [Write the name of your resource group] + * WorkspaceName: [Write the name of your Log Analytics or Sentinel workspace] +3. Add the plugin by uploading the YAML in Security Copilot (https://learn.microsoft.com/en-us/copilot/security/manage-plugins?tabs=securitycopilotplugin) + + +### Additional references: +* [Operationalizing Attack Path Insights](https://techcommunity.microsoft.com/t5/microsoft-defender-for-cloud/operationalizing-attack-path-insights/ba-p/4121643), by Giulio Astori +* [Sharing my first experiences on how to build custom KQL plugins for Security Copilot](https://www.linkedin.com/feed/update/urn:li:activity:7199175292278505472/), my blog post on how this plugin was designed and developed diff --git a/Plugins/Community Based Plugins/Microsoft Defender for Cloud Custom plugin Scenarios/DevSecOpsCustomPlugInWithMDC/GetLastDevSecOpsAlerts.yaml b/Plugins/Community Based Plugins/Microsoft Defender for Cloud Custom plugin Scenarios/DevSecOpsCustomPlugInWithMDC/GetLastDevSecOpsAlerts.yaml new file mode 100644 index 00000000..f698ed8b --- /dev/null +++ b/Plugins/Community Based Plugins/Microsoft Defender for Cloud Custom plugin Scenarios/DevSecOpsCustomPlugInWithMDC/GetLastDevSecOpsAlerts.yaml @@ -0,0 +1,147 @@ +Descriptor: + Name: DevSecOps + DisplayName: DevSecOps by Defender for Cloud + Description: KQL queries for retrieving the results of the Code Scanning (CodeQL), Dependency Scanning (Dependabot), Secret Scanning + and Infrastructure as Code (IaC) scanning made by GitHub Advanced Security (GhAS) in GitHub and by GitHub Advanced Security for Azure DevOps (GAzDO) + in Azure DevOps, as collected by the Defender Cloud Security Posture Management (CSPM) component of Microsoft Defender for Cloud (MDC). + These KQL queries allow to track the changes of these results over time. For example, it is possible to extract only the evidences (alerts) that + were added in the last X days. +SkillGroups: + - Format: KQL + Skills: + - Name: GetNewDevSecOpsAlertsInTheLastTimespan + DisplayName: Get the new alerts for DevSecOps in the last timespan + Description: Get the the new DevSecOps results that were added, during the specified past timespan, in the recommendations in Defender for Cloud related to DevOps Security. + These results are the alerts produced by the Code Scanning (CodeQL), Dependency Scanning (Dependabot), Secret Scanning and Infrastructure as Code (IaC) + scanning made by GitHub Advanced Security (GhAS) in GitHub and by GitHub Advanced Security for Azure DevOps (GAzDO) in Azure DevOps. + Each of these recommendations includes these fields + AssessedResourceType (AdoRepo for Azure DevOps repository, GitHubRepo for GitHub repository), + RepositoryUri (URL of the repo in Azure DevOps or GitHub), + Category (Code for Code Scanning with CodeQL, dependency for dependency scanning, secrets for secrets scanning and infrastruture as code for IaC scanning), + RecommendationSeverity, + RecommendationState, + RecommendationName, + AssessedResourceId, + Description, + CodeQLRuleName (name of the CodeQL rule that raised the alert - Only for Code scanning alerts), + CodeQLRuleDescription (description of the CodeQL rule that raised the alert - Only for Code scanning alerts), + CodeQLRuleSeverity (severity of the CodeQL rule that raised the alert - Only for Code scanning alerts), + CodeQLFile (file identified by the CodeQL rule that raised the alert - Only for Code scanning alerts and secrets scanning alerts), + CodeQLLine (line in the file identified by the CodeQL rule or by the secret scanning that raised the alert - Only for Code scanning alerts and secrets scanning alerts), + IaCFile (file identified by the Infrastructure as Code [IaC] rule that raised the alert - Only for Code IaC alerts), + State, + Tool_name (name of the tool that raised the alert), + DepSecurityAdvisoryCVE (CVE for the dependency - only for dependency scanning), + DepSecurityAdvisoryCVSS (CVSS of the CVE for the dependency - only for dependency scanning), + DepSecurityAdvisoryLink (Link for the CVE for the dependency - only for dependency scanning), + DepSecurityVulnPkgType (Type of package of the dependency with the CVE - only for dependency scanning), + DepSecurityVulnPkgName (Name of package of the dependency with the CVE - only for dependency scanning), + DepSecurityVulnVerRange (Versions of package of the dependency with the CVE - only for dependency scanning) + While DevOps Security in Defender for Cloud supports also the code scanning results in devops environments based on GitLab Enterprise, this KQL + query at this time does not support retriving results coming from that specific source. + Inputs: + - Name: TimespanBack + Description: Provide the time interval in the past, starting now, within which new alarms should be searched for. (e.g. 1d for 1 day, 4h for 4 hours) + Required: true + Settings: + Target: LogAnalytics + TenantId: [Write your Tenant Id] + SubscriptionId: [Write your Subscription Id] + ResourceGroupName: [Write the name of your resource group] + WorkspaceName: [Write the name of your Log Analytics or Sentinel workspace] + Template: |- + let timeInt = {{TimespanBack}}; + let res = ( + SecurityNestedRecommendation + | extend AssessedResourceType = tostring(parse_json(AdditionalData).Data.AssessedResourceType) + | where AssessedResourceType =~ "adoRepo" or AssessedResourceType =~ "GitHubRepo" + | extend CodeQLRuleName = iif(Category=='Dependency','',iif(AssessedResourceType=="GitHubRepo",tostring(parse_json(AdditionalData).Data.Rule_name),tostring(parse_json(AdditionalData).Data.RuleId))) + | extend CodeQLRuleDescription = iif(AssessedResourceType=="GitHubRepo",tostring(parse_json(AdditionalData).Data.Rule_description),tostring(parse_json(AdditionalData).Data.RuleDescription)) + | extend CodeQLRuleSeverity = iif(AssessedResourceType=="GitHubRepo",tostring(parse_json(AdditionalData).Data.Rule_severity),tostring(parse_json(AdditionalData).Data.Severity)) + | extend State = tostring(parse_json(AdditionalData).Data.State) + | extend CodeQLFile = iif(AssessedResourceType=="GitHubRepo",tostring(parse_json(AdditionalData).Data.Path),tostring(parse_json(AdditionalData).Data.File)) + | extend CodeQLLine = iif(AssessedResourceType=="GitHubRepo",tostring(parse_json(AdditionalData).Data.Line),tostring(parse_json(AdditionalData).Data.Line)) + | extend IaCFile = iif(AssessedResourceType=="GitHubRepo",tostring(parse_json(AdditionalData).Data.Path),tostring(parse_json(AdditionalData).Data.Path)) + | extend Tool_name = iif(AssessedResourceType=="GitHubRepo",tostring(parse_json(AdditionalData).Data.Tool_name),tostring(parse_json(AdditionalData).Data.ToolName)) + | extend RepositoryUri = iif(AssessedResourceType=="GitHubRepo",tostring(parse_json(AdditionalData).Data.RepositoryUri),iif(tostring(parse_json(AdditionalData).Data.RepositoryUri)!="",tostring(parse_json(AdditionalData).Data.RepositoryUri),tostring(parse_json(AdditionalData).Data.ProjectUri))) + | extend DepSecurityAdvisoryCVE = iif(AssessedResourceType=="GitHubRepo",tostring(parse_json(AdditionalData).Data.SecurityAdvisoryCVE),iif(Category=='Dependency','?','')) + | extend DepSecurityAdvisoryCVSS = iif(AssessedResourceType=="GitHubRepo",tostring(parse_json(AdditionalData).Data.securityAdvisory__cvss__score),iif(Category=='Dependency','?','')) + | extend DepSecurityAdvisoryLink = iif(AssessedResourceType=="GitHubRepo",tostring(parse_json(AdditionalData).Data.securityAdvisory__permalink),iif(Category=='Dependency','?','')) + | extend DepSecurityVulnPkgType = iif(AssessedResourceType=="GitHubRepo",tostring(parse_json(AdditionalData).Data.securityVulnerability__package__ecosystem),iif(Category=='Dependency','?','')) + | extend DepSecurityVulnPkgName = iif(AssessedResourceType=="GitHubRepo",tostring(parse_json(AdditionalData).Data.securityVulnerability__package__name),iif(Category=='Dependency','?','')) + | extend DepSecurityVulnVerRange = iif(AssessedResourceType=="GitHubRepo",tostring(parse_json(AdditionalData).Data.securityVulnerability__vulnerableVersionRange),iif(Category=='Dependency','?','')) + | project TimeGenerated, SubAssessmentTimeGeneration, AssessedResourceType, RepositoryUri, Category, RecommendationSeverity, RecommendationState, RecommendationName, AssessedResourceId, Description, CodeQLRuleName, CodeQLRuleDescription, CodeQLRuleSeverity, CodeQLFile, CodeQLLine, IaCFile, State, Tool_name, DepSecurityAdvisoryCVE, DepSecurityAdvisoryCVSS, DepSecurityAdvisoryLink, DepSecurityVulnPkgType, DepSecurityVulnPkgName, DepSecurityVulnVerRange + ); + let lastRes = res | where SubAssessmentTimeGeneration >= ago(timeInt); + let prevRes = res | where SubAssessmentTimeGeneration < ago(timeInt); + lastRes + | join kind=anti prevRes on AssessedResourceType, RepositoryUri, Category, RecommendationSeverity, RecommendationState, RecommendationName, AssessedResourceId, Description, CodeQLRuleName, CodeQLRuleDescription, CodeQLRuleSeverity, CodeQLFile, CodeQLLine, IaCFile, State, Tool_name, DepSecurityAdvisoryCVE, DepSecurityAdvisoryCVSS, DepSecurityAdvisoryLink, DepSecurityVulnPkgType, DepSecurityVulnPkgName, DepSecurityVulnVerRange + - Name: GetDevSecOpsAlertsInTheLastTimespan + DisplayName: Get the unique alerts for DevSecOps in the last timespan. + Description: Get the the DevSecOps unique results that were added, during the specified past timespan, in the recommendations in Defender for Cloud related to DevOps Security. + These results are the alerts produced by the Code Scanning (CodeQL), Dependency Scanning (Dependabot), Secret Scanning and Infrastructure as Code (IaC) + scanning made by GitHub Advanced Security (GhAS) in GitHub and by GitHub Advanced Security for Azure DevOps (GAzDO) in Azure DevOps. + Each of these recommendations includes these fields + AssessedResourceType (AdoRepo for Azure DevOps repository, GitHubRepo for GitHub repository), + RepositoryUri (URL of the repo in Azure DevOps or GitHub), + Category (Code for Code Scanning with CodeQL, dependency for dependency scanning, secrets for secrets scanning and infrastruture as code for IaC scanning), + RecommendationSeverity, + RecommendationState, + RecommendationName, + AssessedResourceId, + Description, + CodeQLRuleName (name of the CodeQL rule that raised the alert - Only for Code scanning alerts), + CodeQLRuleDescription (description of the CodeQL rule that raised the alert - Only for Code scanning alerts), + CodeQLRuleSeverity (severity of the CodeQL rule that raised the alert - Only for Code scanning alerts), + CodeQLFile (file identified by the CodeQL rule that raised the alert - Only for Code scanning alerts and secrets scanning alerts), + CodeQLLine (line in the file identified by the CodeQL rule or by the secret scanning that raised the alert - Only for Code scanning alerts and secrets scanning alerts), + IaCFile (file identified by the Infrastructure as Code [IaC] rule that raised the alert - Only for Code IaC alerts), + State, + Tool_name (name of the tool that raised the alert), + DepSecurityAdvisoryCVE (CVE for the dependency - only for dependency scanning), + DepSecurityAdvisoryCVSS (CVSS of the CVE for the dependency - only for dependency scanning), + DepSecurityAdvisoryLink (Link for the CVE for the dependency - only for dependency scanning), + DepSecurityVulnPkgType (Type of package of the dependency with the CVE - only for dependency scanning), + DepSecurityVulnPkgName (Name of package of the dependency with the CVE - only for dependency scanning), + DepSecurityVulnVerRange (Versions of package of the dependency with the CVE - only for dependency scanning), + FirstOccurrence (First occurrence of the unique alerts with all the above fields), + LastOccurrence (Last occurrence of the unique alerts with all the above fields), + NumberOfOccurrences (Number of occurrence of the unique alerts with all the above fields) + While DevOps Security in Defender for Cloud supports also the code scanning results in devops environments based on GitLab Enterprise, this KQL + query at this time does not support retriving results coming from that specific source. + Inputs: + - Name: TimespanBack + Description: Provide the time interval in the past, starting now, within which new alarms should be searched for. (e.g. 1d for 1 day, 4h for 4 hours) + Required: true + Settings: + Target: LogAnalytics + TenantId: [Write your Tenant Id] + SubscriptionId: [Write your Subscription Id] + ResourceGroupName: [Write the name of your resource group] + WorkspaceName: [Write the name of your Log Analytics or Sentinel workspace] + Template: |- + let timeInt = {{TimespanBack}}; + let res = ( + SecurityNestedRecommendation + | where SubAssessmentTimeGeneration >= ago(timeInt) + | extend AssessedResourceType = tostring(parse_json(AdditionalData).Data.AssessedResourceType) + | where AssessedResourceType =~ "adoRepo" or AssessedResourceType =~ "GitHubRepo" + | extend CodeQLRuleName = iif(Category=='Dependency','',iif(AssessedResourceType=="GitHubRepo",tostring(parse_json(AdditionalData).Data.Rule_name),tostring(parse_json(AdditionalData).Data.RuleId))) + | extend CodeQLRuleDescription = iif(AssessedResourceType=="GitHubRepo",tostring(parse_json(AdditionalData).Data.Rule_description),tostring(parse_json(AdditionalData).Data.RuleDescription)) + | extend CodeQLRuleSeverity = iif(AssessedResourceType=="GitHubRepo",tostring(parse_json(AdditionalData).Data.Rule_severity),tostring(parse_json(AdditionalData).Data.Severity)) + | extend State = tostring(parse_json(AdditionalData).Data.State) + | extend CodeQLFile = iif(AssessedResourceType=="GitHubRepo",tostring(parse_json(AdditionalData).Data.Path),tostring(parse_json(AdditionalData).Data.File)) + | extend CodeQLLine = iif(AssessedResourceType=="GitHubRepo",tostring(parse_json(AdditionalData).Data.Line),tostring(parse_json(AdditionalData).Data.Line)) + | extend IaCFile = iif(AssessedResourceType=="GitHubRepo",tostring(parse_json(AdditionalData).Data.Path),tostring(parse_json(AdditionalData).Data.Path)) + | extend Tool_name = iif(AssessedResourceType=="GitHubRepo",tostring(parse_json(AdditionalData).Data.Tool_name),tostring(parse_json(AdditionalData).Data.ToolName)) + | extend RepositoryUri = iif(AssessedResourceType=="GitHubRepo",tostring(parse_json(AdditionalData).Data.RepositoryUri),iif(tostring(parse_json(AdditionalData).Data.RepositoryUri)!="",tostring(parse_json(AdditionalData).Data.RepositoryUri),tostring(parse_json(AdditionalData).Data.ProjectUri))) + | extend DepSecurityAdvisoryCVE = iif(AssessedResourceType=="GitHubRepo",tostring(parse_json(AdditionalData).Data.SecurityAdvisoryCVE),iif(Category=='Dependency','?','')) + | extend DepSecurityAdvisoryCVSS = iif(AssessedResourceType=="GitHubRepo",tostring(parse_json(AdditionalData).Data.securityAdvisory__cvss__score),iif(Category=='Dependency','?','')) + | extend DepSecurityAdvisoryLink = iif(AssessedResourceType=="GitHubRepo",tostring(parse_json(AdditionalData).Data.securityAdvisory__permalink),iif(Category=='Dependency','?','')) + | extend DepSecurityVulnPkgType = iif(AssessedResourceType=="GitHubRepo",tostring(parse_json(AdditionalData).Data.securityVulnerability__package__ecosystem),iif(Category=='Dependency','?','')) + | extend DepSecurityVulnPkgName = iif(AssessedResourceType=="GitHubRepo",tostring(parse_json(AdditionalData).Data.securityVulnerability__package__name),iif(Category=='Dependency','?','')) + | extend DepSecurityVulnVerRange = iif(AssessedResourceType=="GitHubRepo",tostring(parse_json(AdditionalData).Data.securityVulnerability__vulnerableVersionRange),iif(Category=='Dependency','?','')) + | project TimeGenerated, SubAssessmentTimeGeneration, AssessedResourceType, RepositoryUri, Category, RecommendationSeverity, RecommendationState, RecommendationName, AssessedResourceId, Description, CodeQLRuleName, CodeQLRuleDescription, CodeQLRuleSeverity, CodeQLFile, CodeQLLine, IaCFile, State, Tool_name, DepSecurityAdvisoryCVE, DepSecurityAdvisoryCVSS, DepSecurityAdvisoryLink, DepSecurityVulnPkgType, DepSecurityVulnPkgName, DepSecurityVulnVerRange + ); + res + | summarize FirstOccurrence = min(SubAssessmentTimeGeneration), LastOccurrence = max(SubAssessmentTimeGeneration), NumberOfOccurrences=count() by AssessedResourceType, RepositoryUri, Category, RecommendationSeverity, RecommendationState, RecommendationName, AssessedResourceId, Description, CodeQLRuleName, CodeQLRuleDescription, CodeQLRuleSeverity, CodeQLFile, CodeQLLine, IaCFile, State, Tool_name, DepSecurityAdvisoryCVE, DepSecurityAdvisoryCVSS, DepSecurityAdvisoryLink, DepSecurityVulnPkgType, DepSecurityVulnPkgName, DepSecurityVulnVerRange \ No newline at end of file diff --git a/Plugins/Community Based Plugins/Microsoft Defender for Cloud Custom plugin Scenarios/DevSecOpsCustomPlugInWithMDC/README.md b/Plugins/Community Based Plugins/Microsoft Defender for Cloud Custom plugin Scenarios/DevSecOpsCustomPlugInWithMDC/README.md new file mode 100644 index 00000000..ae6cc2f3 --- /dev/null +++ b/Plugins/Community Based Plugins/Microsoft Defender for Cloud Custom plugin Scenarios/DevSecOpsCustomPlugInWithMDC/README.md @@ -0,0 +1,24 @@ +# Security Copilot Custom Plugin - KQL - DevSecOps with Defender for Cloud (based on GhAS and GAzDO) + +This custom plugins contains two skills: +* The first skill (GetNewDevSecOpsAlertsInTheLastTimespan) returns the list of new alerts related to DevSecOps and generated within a user-defined past period, relative to the current timestamp. The user must specificy the timespan to consider in the past (e.g. 1d for 1 day, 4h for 4 hours, etc...). Only new alerts are shown: if an alert is repeated but was already fired with the same identical information in a period of time before the considered timespan, then this alert is not returned. +* The second skill (GetDevSecOpsAlertsInTheLastTimespan) returns the list of all the unique alerts related to DevSecOps and generated within a user-defined past period, relative to the current timestamp. The user must specificy the timespan to consider in the past (e.g. 1d for 1 day, 4h for 4 hours, etc...). All alerts are considered but they are listed only once, considering all their details (name of the alert, target resources, ect...). Additionally, for each of these unique alerts, the query returns the timestamp (within the considered timespan) of the first and last occurrence as well as the number of occurrences. + +### Prerequisites: +* DevOps Security enabled in Defender for Cloud (https://learn.microsoft.com/en-us/azure/defender-for-cloud/devops-support) +* Continuous Export enabled in Defender for Cloud (https://learn.microsoft.com/en-us/azure/defender-for-cloud/continuous-export) + +### Notes: +* The DevSecOps alerts returned by these skills are produced by Code Scanning (SAST by CodeQL), Dependency Scanning (SCA), Secrets Scanning and Infrastructure as Code (IaC) Scanning. Code Scanning, Dependency Scanning and Secrets Scanning are made available by GitHub Advanced Security (GhAS) in GitHub and GitHub Advanced Security for Azure DevOps (GAzDO) in Azure DevOps. Infrastructure as Code (IaC) Scanning is made available by enabling the DevOps Security capability of Defender for Cloud (Defender CSPM) in GitHub or Azure DevOps. +* DevOps Security in Defender for Cloud supports also GitLab Enterprise environments but the KQL in this plugin doesn't support this source + + +### Instructions: +1. Download the YAML file in this folder. Save it locally in your PC and open in for editing its content +2. Set the values for these parameters: + * TenantId: [Write your Tenant Id] + * SubscriptionId: [Write your Subscription Id] + * ResourceGroupName: [Write the name of your resource group] + * WorkspaceName: [Write the name of your Log Analytics or Sentinel workspace] + Note: these parameters should be set in both skills defined in the YAML file. +3. Add the plugin by uploading the customized YAML in Security Copilot (https://learn.microsoft.com/en-us/copilot/security/manage-plugins?tabs=securitycopilotplugin) 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 new file mode 100644 index 00000000..de1d5613 --- /dev/null +++ b/Plugins/Community Based Plugins/Microsoft Security Experts/Microsoft Defender Experts Plugin/Dexplugin.yaml @@ -0,0 +1,919 @@ +--- +Descriptor: + Name: MicrosoftDefenderExpertsPlugin + DisplayName: Microsoft Defender Experts Security Copilot Plugin + Description: Leverage detections built by the Microsoft Defender Experts team in identifying threat actor activity. For more information on Microsoft Defender Experts, go here https://www.microsoft.com/en-us/security/business/services/microsoft-defender-experts-hunting?msockid=3eb9717c7a346743120665a77b05660a + Icon: https://raw.githubusercontent.com/KwachSean/SecurityCopilot/main/DEXlogo_resized_icon.png + +SkillGroups: + - Format: KQL + Skills: + + - Name: DEX-SuspiciousUseOfAzureHound + DisplayName: Suspicious Use of AzureHound + Description: Detects potential suspicious usage of AzureHound on devices. + Inputs: + - Name: days + Description: Look back x amount of days, for example 10, 20, 30. + Required: true + Settings: + Target: Defender + Template: |- + DeviceProcessEvents + | 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. + 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 RemoteUrl in ("login.microsoftonline.com", "graph.microsoft.com") + | where InitiatingProcessCommandLine has_any ("list", "-u", "-p", "--u", "--p", "--app", "--secret", "-app", "-secret", "--cert", "-cert", "-j", "--j") + | where InitiatingProcessFolderPath !contains "Microsoft" + | summarize dcount(RemoteUrl) by bin(Timestamp, 1m), InitiatingProcessFileName, DeviceId + | where dcount_RemoteUrl == 2 + + - Name: DEX-RoadreconAndRoadtxActivity + DisplayName: ROADrecon and ROADtx Activity + 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. + Required: true + Settings: + Target: Defender + Template: |- + DeviceProcessEvents + | where TimeGenerated > ago({{days}}d) + | where InitiatingProcessFileName has_any ("powershell", "cmd.exe", "pwsh.exe") + | where FileName has_any ("roadrecon.exe", "roadtx.exe") + | where ProcessCommandLine has_any ("auth", "gather", "plugin", "dump", "gettokens", "interactiveauth", "listaliases", "findscope", "device", "prt", "prtauth", "keepassauth", "browserprtauth", "browserprtinject") + | project-reorder Timestamp, ReportId, DeviceId, InitiatingProcessCommandLine, FileName, InitiatingProcessFileName, ProcessCommandLine, InitiatingProcessParentFileName, ProcessVersionInfoOriginalFileName, ProcessVersionInfoInternalFileName, InitiatingProcessVersionInfoOriginalFileName, InitiatingProcessFileName + + - Name: DEX-FederatedIdPAdditionActivity + DisplayName: Federated IdP Addition Activity + Description: Detects possible roadoidc activity related to federated identity provider addition. + 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 == "python.exe" + | where InitiatingProcessParentFileName == "roadtx.exe" + | where InitiatingProcessCommandLine has_any ("federatedappauth", "azurewebsites", "--key-pem", "--cert-pem") + | extend RemoteUrl = replace_regex(RemoteUrl, @'http(s)?://', '') + | where ActionType == "ConnectionSuccess" + | project DeviceId, Timestamp, ReportId, InitiatingProcessFileName, InitiatingProcessId, InitiatingProcessAccountSid, RemoteIP, RemoteUrl, ActionType + + - Name: DEX-RecentEntraIDJoinedDevices + DisplayName: Recent Entra ID-Joined Devices + Description: Identifies devices recently joined to Entra ID, potentially by threat actors to maintain access. + Inputs: + - Name: days + Description: Look back x amount of days, for example 10, 20, 30. + Required: true + Settings: + Target: Defender + Template: |- + AuditLogs + | where TimeGenerated > ago({{days}}d) + | extend userPrincipalName_ = tostring(parse_json(tostring(InitiatedBy.user)).userPrincipalName) + | extend DeviceRegInfo = tostring(AdditionalDetails[0].value) + | extend TrustType = tostring(AdditionalDetails[2].value) + | extend DeviceID = tostring(AdditionalDetails[4].value) + | where userPrincipalName_ contains " " //add user entity here + | project TimeGenerated, OperationName, Category, DeviceRegInfo, DeviceID, TrustType, userPrincipalName_ + + - Name: DEX-FakeGlobalProtectTool + DisplayName: Fake Global Protect Tool + Description: Detects possible use of a fake Global Protect tool by looking for known file and folder patterns. + 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 has_any ("GlobalProtect.exe", "GlobalProtectPortal.exe") + | where InitiatingProcessParentFileName endswith ".tmp" + | where InitiatingProcessFolderPath has_all ("appdata", "paloalto") + + - Name: DEX-FakeAuthenticatorExecutable + DisplayName: Fake Authenticator Executable + Description: Detects use of a fake authenticator executable for information stealing, requiring Microsoft Defender XDR. + Inputs: + - Name: days + Description: Look back x amount of days, for example 10, 20, 30. + Required: true + Settings: + Target: Defender + Template: |- + DeviceProcessEvents + | where TimeGenerated > ago({{days}}d) + | where InitiatingProcessFileName has "msedge.exe" + | where InitiatingProcessCommandLine has_all ("msedge.exe", "--no-startup-window") + | where FileName has "Authenticator.exe" + | join kind=inner + (DeviceNetworkEvents + | where InitiatingProcessFileName has "Authenticator.exe" + | where RemoteUrl has "paradiso4.fun" + | where ActionType == "ConnectionSuccess") + on DeviceId + + - Name: DEX-EmailBombingActivity + DisplayName: Email Bombing Activity Detection + Description: Detects possible email bombing activity using anomaly detection on inbound emails. + Inputs: + - Name: days + Description: Look back x amount of days, for example 10, 20, 30. + Required: true + Settings: + Target: Defender + Template: |- + EmailEvents + | where EmailDirection == "Inbound" + | where Timestamp > ago({{days}}d) + | make-series Emailcount = count() on Timestamp step 1h by RecipientObjectId + | extend (Anomalies, AnomalyScore, ExpectedEmails) = series_decompose_anomalies(Emailcount) + | mv-expand Emailcount, Anomalies, AnomalyScore, ExpectedEmails to typeof(double), Timestamp + | where Anomalies != 0 + | where AnomalyScore >= 10 + + - Name: DEX-TeamsPhishingActivity + DisplayName: Teams Phishing Activity Detection + Description: Detects possible Teams phishing activity by identifying suspicious chat creations. + Inputs: + - Name: alertedMachine + Description: DeviceId of the alerted machine. + Required: true + Settings: + Target: Defender + Template: |- + let suspiciousUpns = DeviceProcessEvents + | where DeviceId == "{{alertedMachine}}" + | where isnotempty(InitiatingProcessAccountUpn) + | project InitiatingProcessAccountUpn; + CloudAppEvents + | 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 RawEventData.Members[0].DisplayName in ("Microsoft Security", "Help Desk", "Help Desk Team", "Help Desk IT", "Microsoft Security", "office") + | where AccountId has "@" + | extend TargetUPN = tolower(tostring(RawEventData.Members[1].UPN)) + | where TargetUPN in (suspiciousUpns) + + - Name: DEX-CobaltStrikeDNSBeaconing + DisplayName: Cobalt Strike DNS Beaconing Detection + Description: Detects suspicious DNS queries associated with Cobalt Strike beacons. + Inputs: + - Name: days + Description: Look back x amount of days, for example 10, 20, 30. + Required: true + Settings: + Target: Defender + Template: |- + let badNames = dynamic(["aaa.stage.", "post.1"]); + (union isfuzzy=true + (DnsEvents + | where TimeGenerated > ago({{days}}d) + | where Name has_any (badNames) + | extend Domain = Name, SourceIp = ClientIP, RemoteIP = todynamic(IPAddresses) + | mv-expand RemoteIP + | extend RemoteIP = tostring(RemoteIP)), + (VMConnection + | where TimeGenerated > ago({{days}}d) + | where isnotempty(RemoteDnsCanonicalNames) + | parse RemoteDnsCanonicalNames with * '["' DNSName '"]' * + | where DNSName has_any (badNames) + | extend Domain = DNSName, RemoteIP = RemoteIp + )) + | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated) by Domain, SourceIp, RemoteIP, Computer + | extend timestamp = StartTimeUtc, HostName = split(Computer, '.', 0)[0], DnsDomain = strcat_array(array_slice(split(Computer, '.'), 1, -1), '.') + | extend Host_0_HostName = HostName + | extend Host_0_DnsDomain = DnsDomain + | extend IP_0_Address = RemoteIP + + - Name: DEX-ExcelLaunchingAnomalousProcesses + DisplayName: Excel Launching Anomalous Processes + Description: Detects Excel launching anomalous processes congruent with Qakbot payloads, indicating potential malicious activity. + Inputs: + - Name: days + Description: Look back x amount of days, for example 10, 20, 30. + Required: true + Settings: + Target: Defender + Template: |- + DeviceProcessEvents + | where TimeGenerated > ago({{days}}d) + | where InitiatingProcessParentFileName has "excel.exe" or InitiatingProcessFileName =~ "excel.exe" + | where InitiatingProcessFileName in~ ("excel.exe", "regsvr32.exe") + | where FileName in~ ("regsvr32.exe", "rundll32.exe") + | where ProcessCommandLine has @"..\" + + - Name: DEX-GeneralAttemptsToAccessLocalEmailStore + DisplayName: General Attempts to Access Local Email Store + Description: Detects attempts to access files in the local path containing Outlook emails. + Inputs: + - Name: days + Description: Look back x amount of days, for example 10, 20, 30. + Required: true + Settings: + Target: Defender + Template: |- + DeviceFileEvents + | where TimeGenerated > ago({{days}}d) + | where FolderPath hasprefix "EmailStorage" + | where FolderPath has "Outlook" + | project FileName, FolderPath, InitiatingProcessFileName, InitiatingProcessCommandLine, DeviceId, Timestamp + + - Name: DEX-QakbotCraigslistDomains + DisplayName: Qakbot Craigslist Domains Detection + Description: Detects malicious domains impersonating Craigslist used by Qakbot operators. + 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 RemoteUrl matches regex @"abuse\.[a-zA-Z]\d{2}-craigslist\.org" + + - Name: DEX-QakbotEmailTheft + DisplayName: Qakbot Email Theft Detection + Description: Detects email stealing activities by Qakbot using specific patterns. + Inputs: + - Name: days + Description: Look back x amount of days, for example 10, 20, 30. + Required: true + Settings: + Target: Defender + 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" + + - Name: DEX-QakbotReconnaissanceActivities + DisplayName: Qakbot Reconnaissance Activities Detection + Description: Detects reconnaissance and beaconing activities after code injection by Qakbot. + Inputs: + - Name: days + Description: Look back x amount of days, for example 7, 14, 21. + Required: true + Settings: + Target: Defender + Template: |- + DeviceProcessEvents + | 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") + | summarize dcount(FileName), make_set(ProcessCommandLine) by DeviceId, bin(Timestamp, 1d), InitiatingProcessFileName, InitiatingProcessCommandLine + | where dcount_FileName >= 8 + + - Name: DEX-SuspiciousNamedPipes + DisplayName: Suspicious Named Pipes Detection + Description: Detects named pipe events that may be linked to Cobalt Strike usage. + Inputs: + - Name: hours + Description: Look back x amount of hours, for example 1, 2, 3. + Required: true + Settings: + Target: Defender + Template: |- + let timeframe = {{hours}}h; + let CobaltStrikeDefaults = dynamic([@"msagent_", @"MSSE-", @"postex_", @"status_", @"mypipe-f", @"mypipe-h", @"ntsvcs_", @"scerpc_", @"mojo.5688.8052."]); + let CobaltStrikeMallable = dynamic([@"win_svc", @"ntsvcs", @"scerpc", @"status_", @"SearchTextHarvester", @"DserNamePipe", @"wkssvc_", @"scerpc_", @"spoolss_", @"CatalogChangeListener", @"fullduplex_", @"demoagent_", @"PGMessagePipe", @"MsFteWds", @"postex_ssh_", @"windows.update.manager", @"\f4c3", @"\f53f", @"halfduplex_"]); + DeviceEvents + | where Timestamp >= ago(timeframe) + | where ActionType == "NamedPipeEvent" + | extend AdditionalFields = parse_json(AdditionalFields) + | extend ThreadId = tostring(AdditionalFields.ThreadId) + | extend PipeName = tostring(AdditionalFields.PipeName) + | extend InitiatingPID = tostring(InitiatingProcessId) + | extend InitiatingParentPID = tostring(InitiatingProcessParentId) + | where PipeName has_any (CobaltStrikeDefaults) or + (PipeName matches regex @"\\mojo\.\d+\.\d+\." and not(PipeName matches regex @"\\mojo\.\d+\.\d+\.\d+$" or PipeName has InitiatingPID or PipeName has InitiatingParentPID or PipeName has ThreadId)) or + (PipeName matches regex @"\\(edge|chrome)\.sync\.\d+\.\d+\." and not(PipeName matches regex @"\\(edge|chrome|edge\.sync|chrome\.sync)\.\d+\.\d+\.\d+$" or PipeName has InitiatingPID or PipeName has InitiatingParentPID or PipeName has ThreadId)) or + (PipeName matches regex @"\\PSHost\.\d+\." and not(PipeName matches regex @"\\PSHost\.\d+\.\d+\." or PipeName has InitiatingPID or PipeName has InitiatingParentPID)) or + (PipeName matches regex @"\\crashpad_" and not(PipeName matches regex @"\\crashpad_\d+_[A-Z]+" or PipeName has InitiatingPID or PipeName has InitiatingParentPID)) or + (PipeName matches regex @"\\cubeb-pipe-" and not(PipeName matches regex @"\\cubeb-pipe-\d+_[0-9]{1,3}+" or PipeName has InitiatingPID)) or + (PipeName has_any (CobaltStrikeMallable) and PipeName matches regex @"[a-fA-F0-9]{2,10}$") or + (PipeName matches regex @"\\pipe\\[0-9a-f]{7,10}" or PipeName matches regex @"\\pipe\\[0-9a-f]{8}") + + - Name: DEX-CobaltStrikeInvokedWithWMI + DisplayName: Cobalt Strike Invoked with WMI Detection + Description: Detects possible invocation of Cobalt Strike using Windows Management Instrumentation (WMI). + Inputs: + - Name: days + Description: Look back x amount of days, for example 7, 14, 21. + Required: true + Settings: + Target: Defender + 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\\" + | 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. + Inputs: + - Name: days + Description: Look back x amount of days, for example 1, 2, 3. + Required: true + Settings: + Target: Defender + Template: |- + let usersWithSuspiciousEmails = EmailEvents + | where TimeGenerated > ago({{days}}d) + | where SenderFromAddress in ("no-reply@notify.microsoft.com", "no-reply@dropbox.com") or InternetMessageId startswith " ago({{days}}d) + | where AccountObjectId in (usersWithSuspiciousEmails) + | where RiskLevelDuringSignIn == 100 + + - Name: DEX-FileShareContentsAndSuspiciousSignInActivity + DisplayName: File Share Contents and Suspicious Sign-In Activity Detection + Description: Correlates file share emails with suspicious sign-ins to detect potential compromises. + Inputs: + - Name: days + Description: Look back x amount of days, for example 1, 2, 3. + Required: true + Settings: + Target: Defender + Template: |- + let usersWithSuspiciousEmails = EmailEvents + | where TimeGenerated > ago({{days}}d) + | where Subject has_all ("shared", "with you") + | where Subject has_any ("payment", "invoice", "urgent", "mandatory", "Payoff", "Wire", "Confirmation", "password") + | where isnotempty(RecipientObjectId) + | summarize RecipientCount = dcount(RecipientObjectId), RecipientList = make_set(RecipientObjectId) by Subject + | where RecipientCount >= 10 + | mv-expand RecipientList to typeof(string) + | distinct RecipientList; + AADSignInEventsBeta + | where TimeGenerated > ago({{days}}d) + | where AccountObjectId in (usersWithSuspiciousEmails) + | where RiskLevelDuringSignIn == 100 + + - Name: DEX-OneDriveOrSharePointMassFileSharingDetection + DisplayName: OneDrive or SharePoint Mass File Sharing Detection + Description: Detects files shared with multiple participants in OneDrive or SharePoint, indicating potential lateral movements or BEC attacks. + Inputs: + - Name: days + Description: Look back x amount of days, for example 1, 2, 3. + Required: true + Settings: + Target: Defender + Template: |- + let securelinkCreated = CloudAppEvents + | where TimeGenerated > ago({{days}}d) + | where ActionType == "SecureLinkCreated" + | project FileCreatedTime = Timestamp, AccountObjectId, ObjectName; + let filesCreated = securelinkCreated + | where isnotempty(ObjectName) + | distinct tostring(ObjectName); + CloudAppEvents + | where TimeGenerated > ago({{days}}d) + | where ActionType == "AddedToSecureLink" + | where Application in ("Microsoft SharePoint Online", "Microsoft OneDrive for Business") + | extend FileShared = tostring(RawEventData.ObjectId) + | where FileShared in (filesCreated) + | extend UserSharedWith = tostring(RawEventData.TargetUserOrGroupName) + | extend TypeofUserSharedWith = RawEventData.TargetUserOrGroupType + | where TypeofUserSharedWith == "Guest" + | where isnotempty(FileShared) and isnotempty(UserSharedWith) + | join kind=inner securelinkCreated on $left.FileShared == $right.ObjectName + | summarize NumofUsersSharedWith = dcount(UserSharedWith) by FileShared + | where NumofUsersSharedWith >= 20 + + - Name: DEX-DropboxMassFileSharingDetection + DisplayName: Dropbox Mass File Sharing Detection + Description: Detects files hosted on Dropbox that have been shared with multiple participants. + Inputs: + - Name: days + Description: Look back x amount of days, for example 1, 2, 3. + Required: true + Settings: + Target: Defender + Template: |- + CloudAppEvents + | where TimeGenerated > ago({{days}}d) + | where ActionType in ("Added users and/or groups to shared file/folder", "Invited user to Dropbox and added them to shared file/folder") + | where Application == "Dropbox" + | where ObjectType == "File" + | extend FileShared = tostring(ObjectName) + | where isnotempty(FileShared) + | mv-expand ActivityObjects + | where ActivityObjects.Type == "Account" and ActivityObjects.Role == "To" + | extend SharedBy = AccountId + | extend UserSharedWith = tostring(ActivityObjects.Name) + | 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: | + Threat actors may attempt to phish users to hijack sign-in sessions, even if MFA is enabled, by stealing and replaying credentials and session cookies. This detection looks for successful Entra ID sign-ins with a high-risk profile and then checks for network connections to the same IP address immediately before the sign-in, indicating possible phishing activity. + Inputs: + - Name: days + Description: Look back x amount of days, for example 1, 2, 3. + Required: true + Settings: + Target: Defender + Template: |- + let time_threshold = 10m; + let RiskySignins = materialize (SigninLogs + | where TimeGenerated > ago({{days}}d) + | where ResultType == 0 + | where RiskLevelDuringSignIn =~ "high" or RiskLevelAggregated =~ "high" + | extend SignInTime = TimeGenerated, Name = split(UserPrincipalName, "@")[0], UPNSuffix = split(UserPrincipalName, "@")[1]); + let ips = todynamic(toscalar(RiskySignins | summarize make_list(IPAddress))); + RiskySignins + | join kind=inner (_Im_WebSession(starttime=ago({{days}}d), ipaddr_has_any_prefix=ips, eventresult="Success", pack=True)) + on $left.IPAddress == $right.DstIpAddr + | where EventStartTime < TimeGenerated + | extend TimeDelta = TimeGenerated - EventStartTime + | where TimeDelta <= time_threshold + | extend NetworkEventStartTime = EventStartTime, NetworkEventEndTime = EventEndTime + | extend SrcUsername = column_ifexists("SrcUsername", "Unknown") + | project-reorder SignInTime, UserPrincipalName, IPAddress, AppDisplayName, ClientAppUsed, DeviceDetail, LocationDetails, NetworkLocationDetails, RiskEventTypes, UserAgent, NetworkEventStartTime, NetworkEventEndTime, SrcIpAddr, DstIpAddr, DstPortNumber, Dvc, DvcHostname, SrcBytes, NetworkProtocol, SrcUsername + + - Name: DEX-SigninsFromVPSProviders + DisplayName: Sign-ins From VPS Providers + Description: Detects successful logons from known VPS providers with suspicious token patterns. + Inputs: + - Name: riskScoreCutoff + Description: Adjust this based on the volume of results; default is 20. + Required: false + DefaultValue: 20 + Settings: + Target: Defender + Template: |- + let riskScoreCutoff = {{riskScoreCutoff}}; + let IP_Data = (externaldata(network:string) + [@"https://raw.githubusercontent.com/Azure/Azure-Sentinel/master/Sample%20Data/Feeds/VPS_Networks.csv"] with (format="csv")); + SigninLogs + | where ResultType == 0 + | extend additionalDetails = tostring(Status.additionalDetails) + | evaluate ipv4_lookup(IP_Data, IPAddress, network, return_unmatched = false) + | summarize make_set(additionalDetails), StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by IPAddress, UserPrincipalName + | extend timestamp = StartTime, UserPrincipalName = tolower(UserPrincipalName), AccountCustomEntity = UserPrincipalName, IPCustomEntity = IPAddress + | join kind=leftouter ( + IdentityInfo + | summarize LatestReportTime = arg_max(TimeGenerated, *) by AccountUPN + | extend UserPrincipalName = tolower(AccountUPN) + ) on UserPrincipalName + | join kind=leftouter ( + BehaviorAnalytics + | where ActivityType in ("FailedLogOn", "LogOn") + | where isnotempty(SourceIPAddress) + | where ActivityInsights["ISPUncommonlyUsedInTenant"] == True + | summarize IPInvestigationPriority = sum(InvestigationPriority) by IPAddress = SourceIPAddress + ) on IPAddress + | extend UEBARiskScore = IPInvestigationPriority + | where UEBARiskScore > riskScoreCutoff + | sort by UEBARiskScore desc + + - Name: DEX-SigninsFromNordVPNProviders + DisplayName: Sign-ins from Nord VPN Providers + Description: Tracks sign-ins via Nord VPN using a daily-updated API to detect unusual activity. + Inputs: + - Name: hours + Description: Look back x amount of hours, for example 4, 8, 12. + Required: true + Settings: + Target: Defender + Template: |- + let nord_vpn_feed = (externaldata(id:int, ip_address:string, search_keywords:dynamic, categories:dynamic, name:string, domain:string, price:int, flag:string, country:string, location:dynamic, load:int, features:dynamic) + [@"https://raw.githubusercontent.com/microsoft/mstic/master/nordvpn-servers.csv"] with (format="csv", ignoreFirstRecord=True)); + SigninLogs + | where TimeGenerated > ago({{hours}}h) + | where ResultType == 0 + | summarize TotalEvents = count(), AppList = make_set(AppDisplayName), StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by IPAddress, UserPrincipalName, ClientAppUsed, ConditionalAccessStatus, AuthenticationRequirement, RiskDetail + | join kind=inner nord_vpn_feed on $left.IPAddress == $right.ip_address + | extend timestamp = StartTime, UserPrincipalName = tolower(UserPrincipalName), AccountCustomEntity = UserPrincipalName, IPCustomEntity = IPAddress + | join kind=leftouter ( + IdentityInfo + | summarize LatestReportTime = arg_max(TimeGenerated, *) by AccountUPN + | extend UserPrincipalName = tolower(AccountUPN) + ) on UserPrincipalName + | join kind=leftouter ( + BehaviorAnalytics + | where ActivityType in ("FailedLogOn", "LogOn") + | where isnotempty(SourceIPAddress) + | where ActivityInsights["ISPUncommonlyUsedInTenant"] == True + | summarize IPInvestigationPriority = sum(InvestigationPriority) by IPAddress = SourceIPAddress + ) on IPAddress + | extend UEBARiskScore = IPInvestigationPriority + | sort by UEBARiskScore desc + + - Name: DEX-SuccessfulSigninFromNonCompliantDevice + DisplayName: Successful Sign-in From Non-Compliant Device + Description: Detects successful sign-ins from devices marked non-compliant. + Inputs: + - Name: riskScoreCutoff + Description: Adjust this based on the volume of results; default is 0. + Required: false + DefaultValue: 0 + Settings: + Target: Defender + Template: |- + SigninLogs + | where ResultType == 0 + | where tostring(DeviceDetail.isCompliant) == "false" + | extend Account_0_Name = tolower(UserPrincipalName) + | extend IP_0_Address = IPAddress + | join kind=leftouter ( + IdentityInfo + | summarize LatestReportTime = arg_max(TimeGenerated, *) by AccountUPN + | extend Account_0_Name = tolower(AccountUPN) + ) on Account_0_Name + | join kind=leftouter ( + BehaviorAnalytics + | where ActivityType in ("FailedLogOn", "LogOn") + | where isnotempty(SourceIPAddress) + | summarize IPInvestigationPriority = sum(InvestigationPriority) by IP_0_Address = SourceIPAddress + ) on IP_0_Address + | 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/Microsoft Defender Experts Plugin/Images/AddPluginDex.png b/Plugins/Community Based Plugins/Microsoft Security Experts/Microsoft Defender Experts Plugin/Images/AddPluginDex.png new file mode 100644 index 00000000..b7d44ff0 Binary files /dev/null and b/Plugins/Community Based Plugins/Microsoft Security Experts/Microsoft Defender Experts Plugin/Images/AddPluginDex.png differ diff --git a/Plugins/Community Based Plugins/Microsoft Security Experts/Microsoft Defender Experts Plugin/Images/LogoDex.png b/Plugins/Community Based Plugins/Microsoft Security Experts/Microsoft Defender Experts Plugin/Images/LogoDex.png new file mode 100644 index 00000000..25759740 Binary files /dev/null and b/Plugins/Community Based Plugins/Microsoft Security Experts/Microsoft Defender Experts Plugin/Images/LogoDex.png differ diff --git a/Plugins/Community Based Plugins/Microsoft Security Experts/Microsoft Defender Experts Plugin/Images/SkillsDex.png b/Plugins/Community Based Plugins/Microsoft Security Experts/Microsoft Defender Experts Plugin/Images/SkillsDex.png new file mode 100644 index 00000000..488e606f Binary files /dev/null and b/Plugins/Community Based Plugins/Microsoft Security Experts/Microsoft Defender Experts Plugin/Images/SkillsDex.png differ diff --git a/Plugins/Community Based Plugins/Microsoft Security Experts/Microsoft Defender Experts Plugin/Images/ViewPluginDex.png b/Plugins/Community Based Plugins/Microsoft Security Experts/Microsoft Defender Experts Plugin/Images/ViewPluginDex.png new file mode 100644 index 00000000..da2861a4 Binary files /dev/null and b/Plugins/Community Based Plugins/Microsoft Security Experts/Microsoft Defender Experts Plugin/Images/ViewPluginDex.png differ diff --git a/Plugins/Community Based Plugins/Microsoft Security Experts/Microsoft Defender Experts Plugin/Images/mainpageDex.png b/Plugins/Community Based Plugins/Microsoft Security Experts/Microsoft Defender Experts Plugin/Images/mainpageDex.png new file mode 100644 index 00000000..b7d9dee1 Binary files /dev/null and b/Plugins/Community Based Plugins/Microsoft Security Experts/Microsoft Defender Experts Plugin/Images/mainpageDex.png differ diff --git a/Plugins/Community Based Plugins/Microsoft Security Experts/Microsoft Defender Experts Plugin/Images/uploadpluginDex.png b/Plugins/Community Based Plugins/Microsoft Security Experts/Microsoft Defender Experts Plugin/Images/uploadpluginDex.png new file mode 100644 index 00000000..1469e603 Binary files /dev/null and b/Plugins/Community Based Plugins/Microsoft Security Experts/Microsoft Defender Experts Plugin/Images/uploadpluginDex.png differ diff --git a/Plugins/Community Based Plugins/Microsoft Security Experts/Microsoft Defender Experts Plugin/Readme.md b/Plugins/Community Based Plugins/Microsoft Security Experts/Microsoft Defender Experts Plugin/Readme.md new file mode 100644 index 00000000..84d8915b --- /dev/null +++ b/Plugins/Community Based Plugins/Microsoft Security Experts/Microsoft Defender Experts Plugin/Readme.md @@ -0,0 +1,57 @@ +![Defender Experts Logo](https://github.com/Azure/Copilot-For-Security/blob/main/Plugins/Community%20Based%20Plugins/Microsoft%20Security%20Experts/Microsoft%20Defender%20Experts%20Plugin/Images/LogoDex.png) + +# Introduction + +Welcome to the **Defender Experts for Hunting Plugin**! This plugin elevates your cybersecurity posture by providing proactive threat hunting across your entire organizational environment—including Office 365, cloud applications, and identity platforms. Leveraging insights from Microsoft's seasoned security experts, our plugin empowers your team to swiftly detect and respond to advanced threats. + +# Overview + +Defender Experts for Hunting is a proactive threat hunting service that extends beyond traditional endpoints to encompass Office 365, cloud applications, and identity platforms. Our seasoned security experts actively search for threats across your organization's environment, investigating anomalies and providing detailed contextual alerts along with clear remediation instructions to enable swift and effective responses. + +We delve deep to uncover advanced threats and assess the scope and impact of malicious activities, particularly those involving human adversaries or hands-on-keyboard attacks. A key deliverable of our service is sharing the advanced hunting queries developed during our analyses, empowering your security team to proactively search for similar threats and enhance risk mitigation. + +Additionally, through Copilot for Security, we integrate our findings back into automated tools to improve their capacity for discovery and prioritization, ensuring your defenses continuously evolve to meet emerging security challenges. + +# Prerequisites + +- **Copilot for Security Capacity** +- **Microsoft Defender for Endpoint** + +> **Note:** This plugin is KQL-based and leverages advanced hunting queries to scan across Defender tables for risky behavior and suspicious activities. The tables include `CloudAppEvents`, `EmailEvents`, `EmailAttachmentInfo`, `AADSignIn`, and more. + +# Step-by-Step Guided Walkthrough + +In this guide, we will provide high-level steps to get started using the new tooling. We will start by adding the custom plugin. + +1. **Go to Security Copilot** + + ![Main Page DEX](https://github.com/Azure/Copilot-For-Security/blob/main/Plugins/Community%20Based%20Plugins/Microsoft%20Security%20Experts/Microsoft%20Defender%20Experts%20Plugin/Images/mainpageDex.png) + + +2. **Download the Defender Experts plugin from GitHub** + +3. **Select the plugins icon in the bottom left corner** + +4. **Under Custom upload, select _Upload plugin_** + + ![Upload Plugin DEX](https://github.com/Azure/Copilot-For-Security/blob/main/Plugins/Community%20Based%20Plugins/Microsoft%20Security%20Experts/Microsoft%20Defender%20Experts%20Plugin/Images/uploadpluginDex.png) + + +5. **Select the Copilot for Security plugin and upload the DEX Plugin** + + ![Upload Plugin DEX](https://github.com/Azure/Copilot-For-Security/blob/main/Plugins/Community%20Based%20Plugins/Microsoft%20Security%20Experts/Microsoft%20Defender%20Experts%20Plugin/Images/uploadpluginDex.png) + + +6. **Click _Add_** + +7. **Under Custom, you will see the plugin** + + ![View Plugin DEX](https://github.com/Azure/Copilot-For-Security/blob/main/Plugins/Community%20Based%20Plugins/Microsoft%20Security%20Experts/Microsoft%20Defender%20Experts%20Plugin/Images/ViewPluginDex.png) + + +8. **The custom package contains the following prompts:** **Under DEX, you will find the following** + + ![Skills DEX](https://github.com/Azure/Copilot-For-Security/blob/main/Plugins/Community%20Based%20Plugins/Microsoft%20Security%20Experts/Microsoft%20Defender%20Experts%20Plugin/Images/SkillsDex.png) + + +Let us get started using this together with the Copilot for Security capabilities. 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 new file mode 100644 index 00000000..7855e60f --- /dev/null +++ b/Plugins/Community Based Plugins/Microsoft Security Experts/QR Code AiTM Phishing Detection Plugin/HuntingQrCodeAITM.yml @@ -0,0 +1,189 @@ +Descriptor: + Name: QRCodeHunter + DisplayName: Hunting for QR Code AiTM Phishing and User Compromise + Description: Hunting for QR Code AiTM Phishing and User Compromise based off https://techcommunity.microsoft.com/t5/microsoft-security-experts-blog/hunting-for-qr-code-aitm-phishing-and-user-compromise/ba-p/4053324 + +SkillGroups: + - Format: KQL + Skills: + - Name: QRCode_SigninventsLinkedtoQrCodePhishing + DisplayName: Hunting for User Behavior to Identify Suspicious Sign-In Events Linked to QR Code Phishing Campaigns + Description: This detection identifies suspicious sign-in attempts linked to QR code phishing campaigns by correlating email access with attached images or documents and risky sign-ins from non-trusted devices, validating differing locations between email access and sign-in attempts in the last 30 days + Settings: + Target: Defender + Template: |- + let timeback = (30d); + let successfulRiskySignIn = materialize(AADSignInEventsBeta| where Timestamp > ago(timeback)| where isempty(DeviceTrustType) + | where IsManaged != 1 + | where IsCompliant != 1 + | where RiskLevelDuringSignIn in (50, 100) + | project Timestamp, ReportId, IPAddress, AccountUpn, AccountObjectId, SessionId, Country, State, City + ); + let suspiciousSignInUsers = successfulRiskySignIn + | distinct AccountObjectId; + let suspiciousSignInIPs = successfulRiskySignIn + | distinct IPAddress; + let suspiciousSignInCities = successfulRiskySignIn + | distinct City; + CloudAppEvents + | where Timestamp > ago(timeback) + | where ActionType == "MailItemsAccessed" + | where AccountObjectId in (suspiciousSignInUsers) + | where IPAddress !in (suspiciousSignInIPs) + | where City !in (suspiciousSignInCities) + | join kind=inner successfulRiskySignIn on AccountObjectId + | where (Timestamp - Timestamp1) between (-5min .. 5min) + | extend folders = RawEventData.Folders + | mv-expand folders + | extend items = folders.FolderItems + | mv-expand items + | extend InternetMessageId = tostring(items.InternetMessageId) + | project Timestamp, ReportId, IPAddress, InternetMessageId, AccountObjectId, SessionId, Country; + - Name: QRCode_EmailswithImpersonationIntents + DisplayName: Fetch All Emails from Non-Prevalent Senders in the Organization with Impersonation Intents + Description: In this detection approach, we correlate email from non-prevalent senders in the organization with impersonation intents. + Settings: + Target: Defender + Template: |- + let PhishingSenderDisplayNames = pack_array( + "IT", "support", "Payroll", "HR", "admin", "2FA", "notification", "sign", "reminder", "consent", "workplace", + "administrator", "administration", "benefits", "employee", "update", "on behalf" + ); + let suspiciousEmails = EmailEvents + | where Timestamp > ago(30d) + | where isnotempty(RecipientObjectId) + | where isnotempty(SenderFromAddress) + | where EmailDirection == "Inbound" + | where DeliveryAction == "Delivered" + | join kind=inner ( + EmailAttachmentInfo + | where Timestamp > ago(1d) + | where isempty(SenderObjectId) + | where FileType has_any ("png", "jpg", "jpeg", "bmp", "gif") + ) on NetworkMessageId + | where SenderDisplayName has_any (PhishingSenderDisplayNames) + | project Timestamp, Subject, FileName, SenderFromDomain, RecipientObjectId, NetworkMessageId; + let suspiciousSenders = suspiciousEmails + | distinct SenderFromDomain; + let prevalentSenders = materialize( + EmailEvents + | where Timestamp between (ago(7d) .. ago(1d)) + | where isnotempty(RecipientObjectId) + | where isnotempty(SenderFromAddress) + | where SenderFromDomain in (suspiciousSenders) + | where EmailDirection == "Inbound" + | where DeliveryAction == "Delivered" + | distinct SenderFromDomain + ); + suspiciousEmails + | where SenderFromDomain !in (prevalentSenders) + | project Timestamp, Subject, FileName, SenderFromDomain, RecipientObjectId, NetworkMessageId; + - Name: QRCode_Huntingcampaignwithfewkeywords + DisplayName: Get Personalized Campaigns Based on the First Few Keywords + Description: Personalized campaigns based on the first few keywords + Settings: + Target: Defender + Template: |- + EmailEvents + | where Timestamp > ago(30d) + | where EmailDirection == "Inbound" + | where DeliveryAction == "Delivered" + | where isempty(SenderObjectId) + | extend words = split(Subject, " ") + | project + firstWord = tostring(words[0]), + secondWord = tostring(words[1]), + thirdWord = tostring(words[2]), + Subject, + SenderFromAddress, + RecipientEmailAddress, + NetworkMessageId + | summarize + SubjectsCount = dcount(Subject), + RecipientsCount = dcount(RecipientEmailAddress), + suspiciousEmails = make_set(NetworkMessageId, 10) by firstWord, secondWord, thirdWord, SenderFromAddress + | where SubjectsCount >= 10 + + - Name: QRCode_Huntingcampaignwithlastkeywords + DisplayName: Get Personalized Campaigns Based on the Last Few Keywords + Description: Personalized campaigns based on the last few keywords + Settings: + Target: Defender + Template: |- + EmailEvents + | where Timestamp > ago(1d) + | where EmailDirection == "Inbound" + | where DeliveryAction == "Delivered" + | where isempty(SenderObjectId) + | extend words = split(Subject," ") + | project firstLastWord = tostring(words[-1]), secondLastWord = tostring(words[-2]), thirdLastWord = tostring(words[-3]), Subject, SenderFromAddress, RecipientEmailAddress, NetworkMessageId + | summarize SubjectsCount = dcount(Subject), RecipientsCount = dcount(RecipientEmailAddress), suspiciousEmails = make_set(NetworkMessageId, 10) by firstLastWord, secondLastWord, thirdLastWord + , SenderFromAddress + | where SubjectsCount >= 10 + + - Name: QRCode_Campaignwithsuspiciouskeywords + DisplayName: Get Campaign with Suspicious Keywords + Description: Personalized Campaign with suspicious keywords in the last month + Inputs: + - Name: suspiciouskeywords + Description: Add your suspicious keyword you want to hunt for here + Required: true + - Name: suspiciouskeywords1 + Description: Add your suspicious keyword you want to hunt for here + Required: true + - Name: suspiciouskeywords2 + Description: Add your suspicious keyword you want to hunt for here + Required: true + - Name: suspiciouskeywords3 + Description: Add your suspicious keyword you want to hunt for here + Required: true + - Name: suspiciouskeywords4 + Description: Add your suspicious keyword you want to hunt for here + Required: true + Settings: + Target: Defender + Template: |- + let suspiciouskeywords ='{{suspiciouskeywords}}'; + let suspiciouskeywords1 ='{{suspiciouskeywords1}}'; + let suspiciouskeywords2 ='{{suspiciouskeywords2}}'; + let suspiciouskeywords3 ='{{suspiciouskeywords3}}'; + let suspiciouskeywords4 ='{{suspiciouskeywords4}}'; + let PhishingKeywords = pack_array("suspiciouskeywords4","suspiciouskeywords3","suspiciouskeywords2","suspiciouskeywords1","suspiciouskeywords"); + EmailEvents + | where Timestamp > ago(30d) + | where EmailDirection == "Inbound" + | where DeliveryAction == "Delivered" + | where isempty(SenderObjectId) + | where Subject has_any (PhishingKeywords()) + + - Name: QRCode_Huntingforattachmentnamepatterns + DisplayName: Hunt for Randomized Attachment Names Associated with QR Code Phishing Attacks + Description: Hunts for randomized attachment names associated with QR code phishing attacks in the last 7 days + Settings: + Target: Defender + Template: |- + EmailAttachmentInfo + | where Timestamp > ago(7d) + | where FileType in ("png", "jpg", "jpeg", "gif", "svg") + | where isnotempty(FileName) + | extend firstFourFileName = substring(FileName, 0, 4) + | summarize RecipientsCount = dcount(RecipientEmailAddress), FirstFourFilesCount = dcount(firstFourFileName), suspiciousEmails = make_set(NetworkMessageId, 10) by SenderFromAddress + | where FirstFourFilesCount >= 10 + + - Name: QRCode_Huntingforusersignalsclusters + DisplayName: Hunting for user Signal and Clusters associated with QR phishing scores + Description: Identify and scope QR code phishing campaigns targeting organizations triggered by user-reported alerts in the last 7 days + Settings: + Target: Defender + Template: |- + let suspiciousClusters = EmailEvents + | where Timestamp > ago(7d) + | where EmailDirection == "Inbound" + | where NetworkMessageId in ("") + | distinct EmailClusterId; + EmailEvents + | where Timestamp > ago(7d) + | where EmailDirection == "Inbound" + | where EmailClusterId in (suspiciousClusters) + | summarize make_set(Subject), make_set(SenderFromDomain), dcount(RecipientObjectId), dcount(SenderDisplayName) by EmailClusterId; + diff --git a/Plugins/Community Based Plugins/Microsoft Security Experts/QR Code AiTM Phishing Detection Plugin/images/Picture1.png b/Plugins/Community Based Plugins/Microsoft Security Experts/QR Code AiTM Phishing Detection Plugin/images/Picture1.png new file mode 100644 index 00000000..119464a4 Binary files /dev/null and b/Plugins/Community Based Plugins/Microsoft Security Experts/QR Code AiTM Phishing Detection Plugin/images/Picture1.png differ diff --git a/Plugins/Community Based Plugins/Microsoft Security Experts/QR Code AiTM Phishing Detection Plugin/images/Picture2.png b/Plugins/Community Based Plugins/Microsoft Security Experts/QR Code AiTM Phishing Detection Plugin/images/Picture2.png new file mode 100644 index 00000000..7afc6d66 Binary files /dev/null and b/Plugins/Community Based Plugins/Microsoft Security Experts/QR Code AiTM Phishing Detection Plugin/images/Picture2.png differ diff --git a/Plugins/Community Based Plugins/Microsoft Security Experts/QR Code AiTM Phishing Detection Plugin/images/Picture3.png b/Plugins/Community Based Plugins/Microsoft Security Experts/QR Code AiTM Phishing Detection Plugin/images/Picture3.png new file mode 100644 index 00000000..25c79fe6 Binary files /dev/null and b/Plugins/Community Based Plugins/Microsoft Security Experts/QR Code AiTM Phishing Detection Plugin/images/Picture3.png differ diff --git a/Plugins/Community Based Plugins/Microsoft Security Experts/QR Code AiTM Phishing Detection Plugin/images/Picture4.png b/Plugins/Community Based Plugins/Microsoft Security Experts/QR Code AiTM Phishing Detection Plugin/images/Picture4.png new file mode 100644 index 00000000..420f3f90 Binary files /dev/null and b/Plugins/Community Based Plugins/Microsoft Security Experts/QR Code AiTM Phishing Detection Plugin/images/Picture4.png differ diff --git a/Plugins/Community Based Plugins/Microsoft Security Experts/QR Code AiTM Phishing Detection Plugin/images/Picture5.png b/Plugins/Community Based Plugins/Microsoft Security Experts/QR Code AiTM Phishing Detection Plugin/images/Picture5.png new file mode 100644 index 00000000..641edf50 Binary files /dev/null and b/Plugins/Community Based Plugins/Microsoft Security Experts/QR Code AiTM Phishing Detection Plugin/images/Picture5.png differ diff --git a/Plugins/Community Based Plugins/Microsoft Security Experts/QR Code AiTM Phishing Detection Plugin/readme.md b/Plugins/Community Based Plugins/Microsoft Security Experts/QR Code AiTM Phishing Detection Plugin/readme.md new file mode 100644 index 00000000..7219a1a8 --- /dev/null +++ b/Plugins/Community Based Plugins/Microsoft Security Experts/QR Code AiTM Phishing Detection Plugin/readme.md @@ -0,0 +1,47 @@ +# QR Code AiTM Phishing Detection Plugin + +## Introduction +Welcome to the QR Code AiTM Phishing Detection Plugin! This plugin enhances your cybersecurity efforts by detecting and mitigating QR code phishing attacks. Leveraging insights from Microsoft's Defender Experts, our plugin offers robust protection against these sophisticated phishing campaigns. + +## Overview +QR code AiTM (Adversary-in-the-Middle) phishing is a technique where attackers exploit QR codes to redirect users to fraudulent sites. Once users scan the QR code and authenticate, attackers can steal session tokens and perform malicious activities. This plugin helps identify and prevent such attacks by monitoring suspicious email patterns, sender attributes, and user behavior to ensure your organization stays secure. + +For more detailed information, refer to the full article [here](https://techcommunity.microsoft.com/t5/microsoft-security-experts-blog/hunting-for-qr-code-aitm-phishing-and-user-compromise/ba-p/4053324). + +## Prerequisites +- **Copilot for Security instance** +- **Microsoft Defender for Endpoint** + +**Note:** This plugin is KQL-based and leverages hunting queries to hunt across Defender tables to identify risky behavior and suspicious activity. The tables include CloudAppEvents, EmailEvents, EmailAttachmentInfo, AADSignIn, etc. + +## Step-by-Step Guided Walkthrough +In this guide, we will provide high-level steps to get started using the new tooling. We will start by adding the custom plugin. +1. Go to [Security Copilot](https://securitycopilot.microsoft.com) +2. Download the QR Code AiTM Phishing Detection Plugin YAML File +3. Select the plugins icon in the bottom left corner + +![QR Code AiTM Phishing Detection Plugin](https://github.com/Azure/Copilot-For-Security/blob/main/Plugins/Community%20Based%20Plugins/Microsoft%20Security%20Experts/QR%20Code%20AiTM%20Phishing%20Detection%20Plugin/images/Picture1.png) + +4. Under Custom upload, select **Upload plugin** + +![QR Code AiTM Phishing Detection Plugin](https://github.com/Azure/Copilot-For-Security/blob/main/Plugins/Community%20Based%20Plugins/Microsoft%20Security%20Experts/QR%20Code%20AiTM%20Phishing%20Detection%20Plugin/images/Picture2.png) + +5. Select the Copilot for Security plugin and upload the QR Code AiTM Phishing Detection Plugin + +![QR Code AiTM Phishing Detection Plugin](https://github.com/Azure/Copilot-For-Security/blob/main/Plugins/Community%20Based%20Plugins/Microsoft%20Security%20Experts/QR%20Code%20AiTM%20Phishing%20Detection%20Plugin/images/Picture3.png) + +6. Click **Add** +7. Under Custom, you will see the plugin +![QR Code AiTM Phishing Detection Plugin](https://github.com/Azure/Copilot-For-Security/blob/main/Plugins/Community%20Based%20Plugins/Microsoft%20Security%20Experts/QR%20Code%20AiTM%20Phishing%20Detection%20Plugin/images/Picture4.png) + + + +The custom package contains the following prompts : + + +under **QRCODE** you will find the following +![QR Code AiTM Phishing Detection Plugin](https://github.com/Azure/Copilot-For-Security/blob/main/Plugins/Community%20Based%20Plugins/Microsoft%20Security%20Experts/QR%20Code%20AiTM%20Phishing%20Detection%20Plugin/images/Picture5.png) + + + +**Let us get started using this together with the Copilot for Security capabilities** 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/Anomalous App and Admin Activity/AnomalousAppandAdminActivity.yaml b/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/Anomalous App and Admin Activity/AnomalousAppandAdminActivity.yaml new file mode 100644 index 00000000..7c5ff326 --- /dev/null +++ b/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/Anomalous App and Admin Activity/AnomalousAppandAdminActivity.yaml @@ -0,0 +1,96 @@ +Descriptor: + Name: AnomalousAppandAdminUserActivity + DisplayName: Anomalous Application and Admin User Activity + Description: Uses UEBA normalized Insights in Sentinel UEBA to identify Applications and admin users exhibiting anomalous behavior. It applies to profiled activities across ARM, Azure sign-in, and audit logs + Settings: + - Name: TenantId + Required: true + - Name: WorkspaceName + Required: true + - Name: SubscriptionId + Required: true + - Name: ResourceGroupName + Required: true + SupportedAuthTypes: + - None + +SkillGroups: + - Format: KQL + Skills: + - Name: AnomalousAppActivity + DisplayName: Anomalous application activity + Description: Uses Sentinel UEBA to identify unsual or anomalous activities such as first time a user used an app, an uncommonly used app, an app uncommonly used among user peers, an app that is observed in a tenant for the first time or an app that is uncommonly used in the tenant + Inputs: + - Name: fromDateTime + Description: The start of the lookback window + Required: true + - Name: toDateTime + Description: The end of the lookback window + Required: true + - Name: InvestiGationPriority + Description: Calculated priority for investigation between 1 and 10 + Required: false + 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}}' + Template: |- + let fromDateTime=datetime('{{fromDateTime}}'); + let toDateTime=datetime('{{toDateTime}}'); + BehaviorAnalytics + | project-away TenantId, Type, SourceRecordId, EventSource, TimeProcessed + | where InvestigationPriority > 5 + | where ActivityInsights.FirstTimeUserUsedApp == true or + ActivityInsights.AppUncommonlyUsedByUser == true or + ActivityInsights.AppUncommonlyUsedAmongPeers == true or + ActivityInsights.FirstTimeAppObservedInTenant == true or + ActivityInsights.AppUncommonlyUsedInTenant == true + - Name: AnomalousAdminActions + DisplayName: Anomalous administrative actions performed by user + Description: Uses Sentinel UEBA to identify Users performing activities that are performed for the first time, uncommon by the user, uncommon among the user's peers, uncommon in the tenant, from an uncommon country or a user connecting from a country seen for the first time, or user accessing a resource for the first time or accessing a resource that is uncommon among their peers + Inputs: + - Name: fromDateTime + Description: The start of the lookback window + Required: true + - Name: toDateTime + Description: The end of the lookback window + Required: true + - Name: InvestiGationPriority + Description: Calculated priority for investigation between 1 and 10 + Required: false + Settings: + Target: Sentinel + # The ID of the Entra ID 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}}' + Template: |- + let fromDateTime=datetime('{{fromDateTime}}'); + let toDateTime=datetime('{{toDateTime}}'); + BehaviorAnalytics + | where datetime_utc_to_local(TimeGenerated, "US/Eastern") between ( fromDateTime .. toDateTime ) + | project-away TenantId, Type, SourceRecordId, EventSource, TimeProcessed + | where ActivityType =~ "Administrative" + | where isnotempty(UserName) + | where ActivityInsights.FirstTimeUserPerformedAction == true or + ActivityInsights.FirstTimeActionPerformedInTenant == true or + ActivityInsights.ActionUncommonlyPerformedByUser == true or + ActivityInsights.ActionUncommonlyPerformedAmongPeers == true or + ActivityInsights.FirstTimeUserAccessedResource == true or + ActivityInsights.CountryUncommonlyConnectedFromByUser == true or + ActivityInsights.IsDormantAccount == true or + ActivityInsights.IsLocalAdmin == true or + ActivityInsights.IsNewAccount == true + + + diff --git a/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/Anomalous App and Admin Activity/Readme.md b/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/Anomalous App and Admin Activity/Readme.md new file mode 100644 index 00000000..2359b311 --- /dev/null +++ b/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/Anomalous App and Admin Activity/Readme.md @@ -0,0 +1,29 @@ +# Security Copilot Custom KQL Plugin - Detect Anomalous App and User Activty using Microsoft Sentinel UEBA Insights + +#### Security Copilot Plugin to that brings in data from Microsoft Sentinel UEBA insights focussed on anomalies related to Applications and administrative user activity. For details refer to this blog: + [link](https://techcommunity.microsoft.com/t5/microsoft-security-copilot-blog/harnessing-the-power-of-kql-plugins-for-enhanced-security/ba-p/4221891) + +### 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. +* [Microsoft Sentinel UEBA enabled](https://learn.microsoft.com/en-us/azure/sentinel/identify-threats-with-entity-behavior-analytics) +* 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 [AnomalousAppandUserActivity.yaml](https://github.com/Azure/Copilot-For-Security/blob/main/Plugins/Community%20Based%20Plugins/Microsoft%20Sentinel%20Custom%20Plugin%20Scenarios/Anomalous%20App%20and%20Admin%20Activity/AnomalousAppandAdminActivity.yaml) from this directory. +2. Modify the KQL query as needed to suit your specific requirements. One such edit might be your Time Zone as converted in this line: | where datetime_utc_to_local(TimeGenerated, "US/Eastern") between ( fromDateTime .. toDateTime ). + + +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) + + +4. [Enter your workspace details in the Security Copilot UI](https://github.com/Azure/Copilot-For-Security/blob/main/Images/KQL Plugin images/Workspacedetails.png) + + + diff --git a/Plugins/Community Based Plugins/FetchIncidentCommentsfromSentinel/FetchIncidentComments.yaml b/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/FetchIncidentCommentsfromSentinel/FetchIncidentComments.yaml similarity index 54% rename from Plugins/Community Based Plugins/FetchIncidentCommentsfromSentinel/FetchIncidentComments.yaml rename to Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/FetchIncidentCommentsfromSentinel/FetchIncidentComments.yaml index 6fdfeeab..dc7e26f8 100644 --- a/Plugins/Community Based Plugins/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/GCP Audit Logs/KQL-Sentinel-GCP.yaml b/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/GCP Audit Logs/KQL-Sentinel-GCP.yaml similarity index 66% rename from Plugins/Community Based Plugins/GCP Audit Logs/KQL-Sentinel-GCP.yaml rename to Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/GCP Audit Logs/KQL-Sentinel-GCP.yaml index 0813c85f..0f12ffce 100644 --- a/Plugins/Community Based Plugins/GCP Audit Logs/KQL-Sentinel-GCP.yaml +++ b/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/GCP Audit Logs/KQL-Sentinel-GCP.yaml @@ -1,50 +1,75 @@ -Descriptor: - Name: Summarize recent audit logs from GCP (Google Cloud Platform) for a certain user, ip address and time range from logs in Microsoft Sentinel - DisplayName: "Sentinel KQL: GCP Audit Logs" - 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. - -SkillGroups: - - Format: KQL - Skills: - - Name: Get GCP AuditLogs - DisplayName: Get GCP Audit Logs - Description: Fetches audit logs from GCP (Google Cloud Platform) for a certain user, ip address and time range from logs in Microsoft Sentinel - Inputs: - - Name: userPrincipalName - Description: The username principal name to lookup - Required: false - - Name: ip - Description: The ip to lookup - Required: false - - Name: fromDateTime - Description: The from time and date - Required: true - - Name: toDateTime - Description: The to time and date - 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** - Template: |- - let userPrincipalName='{{userPrincipalName}}'; - let ip='{{ip}}'; - let fromDateTime=datetime('{{fromDateTime}}'); - let toDateTime=datetime('{{toDateTime}}'); - GCPAuditLogs - | extend Metadata = parse_json(RequestMetadata) - | extend callerIp = Metadata.callerIp - | extend userAgent = Metadata.callerSuppliedUserAgent - | where datetime_utc_to_local(TimeGenerated, "US/Pacific") between ( fromDateTime .. toDateTime ) - | where PrincipalEmail like userPrincipalName - | where ServiceName == "logging.googleapis.com" - | where callerIp like ip - | project TimeGenerated, PrincipalEmail, callerIp, userAgent, MethodName, GCPResourceName, Status, AuthenticationInfo, AuthorizationInfo, Request, ProjectId, Severity, GCPResourceType, Type - +Descriptor: + Name: Summarize recent audit logs from GCP (Google Cloud Platform) for a certain user, ip address and time range from logs in Microsoft Sentinel + DisplayName: "Sentinel KQL: GCP Audit Logs" + 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: + - Name: Get GCP AuditLogs + DisplayName: Get GCP Audit Logs + Description: Fetches audit logs from GCP (Google Cloud Platform) for a certain user, ip address and time range from logs in Microsoft Sentinel + Inputs: + - Name: userPrincipalName + Description: The username principal name to lookup + Required: false + - Name: ip + Description: The ip to lookup + Required: false + - Name: fromDateTime + Description: The from time and date + Required: true + - Name: toDateTime + Description: The to time and date + Required: true + Settings: + Target: Sentinel + TenantId: "{{TenantId}}" + SubscriptionId: "{{SubscriptionId}}" + ResourceGroupName: "{{ResourceGroupName}}" + WorkspaceName: "{{WorkspaceName}}" + Template: |- + let userPrincipalName='{{userPrincipalName}}'; + let ip='{{ip}}'; + let fromDateTime=datetime('{{fromDateTime}}'); + let toDateTime=datetime('{{toDateTime}}'); + GCPAuditLogs + | extend Metadata = parse_json(RequestMetadata) + | extend callerIp = Metadata.callerIp + | extend userAgent = Metadata.callerSuppliedUserAgent + | where datetime_utc_to_local(TimeGenerated, "US/Pacific") between ( fromDateTime .. toDateTime ) + | where PrincipalEmail like userPrincipalName + | where ServiceName == "logging.googleapis.com" + | where callerIp like ip + | project TimeGenerated, PrincipalEmail, callerIp, userAgent, MethodName, GCPResourceName, Status, AuthenticationInfo, AuthorizationInfo, Request, ProjectId, Severity, GCPResourceType, Type + diff --git a/Plugins/Community Based Plugins/GCP Audit Logs/README.md b/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/GCP Audit Logs/README.md similarity index 74% rename from Plugins/Community Based Plugins/GCP Audit Logs/README.md rename to Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/GCP Audit Logs/README.md index 8e830922..1222299e 100644 --- a/Plugins/Community Based Plugins/GCP Audit Logs/README.md +++ b/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/GCP Audit Logs/README.md @@ -1,10 +1,10 @@ -# Copilot for Security Custom Plugin - Sentinel KQL - GCPAudit Logs +# Security Copilot Custom Plugin - Sentinel KQL - GCPAudit Logs -#### Copilot for Security Plugin to run a Dynamic KQL Query for a particluar user, IP Address and time and date range against the GCPAudit logs in a Microsoft Sentinel workspace. +#### Security Copilot Plugin to run a Dynamic KQL Query for a particluar user, IP Address and time and date range against the GCPAudit logs in a Microsoft Sentinel workspace. ### Pre-requisites -* [Copilot for Security Enabled](https://learn.microsoft.com/en-us/security-copilot/get-started-security-copilot#onboarding-to-microsoft-security-copilot) +* [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. * Microsoft Sentinel [GCP Audit Logs Connector](https://learn.microsoft.com/en-us/azure/sentinel/connect-google-cloud-platform?tabs=terraform%2Cauditlogs) enabled. @@ -13,10 +13,10 @@ ### Instructions #### Upload the Custom Plugin -1. Obtain the file [KQL-Sentinel-GCP.yaml](https://github.com/Azure/Copilot-For-Security/blob/main/Plugins/Community%20Based%20Plugins/GCP%20Audit%20Logs/KQL-Sentinel-GCP.yaml) from this directory. +1. Obtain the file [KQL-Sentinel-GCP.yaml](https://github.com/Azure/Copilot-For-Security/blob/main/Plugins/Community%20Based%20Plugins/Microsoft%20Sentinel%20Custom%20Plugin%20Scenarios/GCP%20Audit%20Logs/KQL-Sentinel-GCP.yaml) from this directory. 2. Modify the yaml file to specify your specific Entra TentantId, SubscriptionId, ResourceGroupName and WorkspaceName for your Sentinel instance. -![KQLConnection](https://github.com/SCStelz/CopilotForSecurity/blob/main/Images/kql-connection.png) + 3. [Upload the custom plugin](https://learn.microsoft.com/en-us/security-copilot/manage-plugins?tabs=securitycopilotplugin#add-custom-plugins) and verify it's activated. 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 new file mode 100644 index 00000000..80955111 --- /dev/null +++ b/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/High Blast Radius User investigation/HighBlastRaidusUserInvestigation.yaml @@ -0,0 +1,76 @@ +Descriptor: + Name: HighBlastRadiusInvestigation + DisplayName: Investigate or hunt for anomalous activities performed by high blast radius accounts + Description: Uses normalized Insights from Sentinel UEBA to Investigate or hunt for Entra ID Accounts with a High Blast Radius exhibiting anomalous activities + Settings: + - Name: TenantId + Required: true + - Name: WorkspaceName + Required: true + - Name: SubscriptionId + Required: true + - Name: ResourceGroupName + Required: true + SupportedAuthTypes: + - None + +SkillGroups: + - Format: KQL + Skills: + - Name: HighBlastRaidusInvestigation + DisplayName: Investigate for anomalous activities by high blast radius accounts + Description: Uses normalized Insights from Sentinel UEBA to Investigate or hunt for AD Accounts with High Blast Radius exhibiting anomalous activities + Inputs: + - Name: days + Description: Look back x amount of days, for example 7, 10, or 14 + Required: true + - Name: InvestiGationPriority + Description: Calculated priority for investigation between 1 and 10 + Required: false + 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}}' + Template: |- + IdentityInfo + | where TimeGenerated > ago({{days}}d) + | where AccountDisplayName != "On-Premises Directory Synchronization Service Account" + | project + TimeGenerated, + AccountUPN, + AccountDisplayName, + AccountCreationTime, + BlastRadius, + GroupMembership, + AssignedRoles, + IsAccountEnabled, + IsServiceAccount, + DeletedDateTime, + IsMFARegistered + | summarize arg_max(TimeGenerated, *) by AccountUPN + | where isnotempty(AssignedRoles) and AssignedRoles != "[]" + | where BlastRadius == "High" + | join (BehaviorAnalytics + | where TimeGenerated > ago({{days}}d) + | where isnotempty(UserPrincipalName) + | project + UserPrincipalName, + TimeGenerated, + ActivityType, + ActionType, + SourceIPAddress, + SourceIPLocation, + UsersInsights, + DevicesInsights, + ActivityInsights, + InvestigationPriority) + on $left.AccountUPN == $right.UserPrincipalName + | summarize arg_max(TimeGenerated, *) by UserPrincipalName + | extend IsNewAccount_ = tostring(UsersInsights.IsNewAccount) + | extend Is_VIPUser = tostring(UsersInsights.IsVIPUser) \ No newline at end of file diff --git a/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/High Blast Radius User investigation/Readme.md b/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/High Blast Radius User investigation/Readme.md new file mode 100644 index 00000000..88564738 --- /dev/null +++ b/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/High Blast Radius User investigation/Readme.md @@ -0,0 +1,29 @@ +# Security Copilot Custom KQL Plugin - Investigate identity-based threats with Security Copilot and Microsoft Sentinel + +#### Security Copilot Plugin to that brings in anomaly data generated by Microsoft Sentinel UEBA pertaining to High Blast Radius users. + +### 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. +* [Microsoft Sentinel UEBA enabled](https://learn.microsoft.com/en-us/azure/sentinel/identify-threats-with-entity-behavior-analytics) +* Parameters for KQL Plugin - Microsoft Sentinel Workspace Name, Subscription ID, Resource Group Name and Entra Tenant ID + +### Instructions +#### Upload the Custom Plugin + +### 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 HighBlastRadiusUserInvestigation.yaml from this directory. +2. Upload the custom plugin +3. Input your specific Entra TentantId, SubscriptionId, ResourceGroupName and WorkspaceName in the resulting dialog box within Security Copilot \ No newline at end of file diff --git a/Plugins/Community Based Plugins/HuntIP-ADX/HuntIPColdData.yml b/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/HuntIP-ADX/HuntIPColdData.yml similarity index 100% rename from Plugins/Community Based Plugins/HuntIP-ADX/HuntIPColdData.yml rename to Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/HuntIP-ADX/HuntIPColdData.yml diff --git a/Plugins/Community Based Plugins/HuntSignInLogs-ADX/KQL-ADX-SignInLogs.yaml b/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/HuntSignInLogs-ADX/KQL-ADX-SignInLogs.yaml similarity index 98% rename from Plugins/Community Based Plugins/HuntSignInLogs-ADX/KQL-ADX-SignInLogs.yaml rename to Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/HuntSignInLogs-ADX/KQL-ADX-SignInLogs.yaml index 0ac3b9be..2c7239f7 100644 --- a/Plugins/Community Based Plugins/HuntSignInLogs-ADX/KQL-ADX-SignInLogs.yaml +++ b/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/HuntSignInLogs-ADX/KQL-ADX-SignInLogs.yaml @@ -1,49 +1,49 @@ -Descriptor: - Name: Summarize Sign-in logs from ADX Long Term Archive. - DisplayName: "ADX KQL: Long Term Archive Sign-in Logs" - Description: Get Sign-in logs from long term archive store in ADX (Azure data explorer) for a user or IP Address. - #Can you describe the Sign in log results in detail from the ADX Long Term Archive store over a few paragraphs for user@contoso.com between Oct 23, 2023 and Oct 27, 2023? - #Can you describe in detail the sign-in's where Conditional Access status was a failure from the ADX Long Term Archive store for user@contoso.com during December 2023? Be sure to include timestamps for the sign-in activities. -SkillGroups: - - Format: KQL - Skills: - - Name: Get ADX Long Term Sign in Logs - DisplayName: "Get ADX Long Term Sign in Logs" - Description: Get Sign-in logs from long term archive store in ADX (Azure Data Explorer) for a user or IP Address. - Inputs: - - Name: username - Description: The username to lookup - Required: false - - Name: ip - Description: The ip to lookup - Required: false - - Name: fromDateTime - Description: The from time and date - Required: true - - Name: toDateTime - Description: The to time and date - Required: true - Settings: - Target: Kusto - Cluster: **ADX CLUSTER URL** - Database: **ADX DATABASE NAME** - TenantId: **ENTRA TENANT ID** - Template: |- - let upn='{{username}}'; - let ip='{{ip}}'; - let fromDateTime=datetime('{{fromDateTime}}'); - let toDateTime=datetime('{{toDateTime}}'); - SigninLogs - | where datetime_utc_to_local(TimeGenerated, "US/Pacific") between ( fromDateTime .. toDateTime ) - | where IPAddress like ip - | where UserPrincipalName like upn - | extend LocationDetails=parse_json(LocationDetails) - | extend DeviceDetail=parse_json(DeviceDetail) - | extend City=tostring(LocationDetails.city) - | extend State=tostring(LocationDetails.state) - | extend Browser=DeviceDetail.browser - | extend OperatingSystem=DeviceDetail.operatingSystem - | extend displayName=DeviceDetail.displayName - | extend trustType=DeviceDetail.trustType - | extend isCompliant=DeviceDetail.isCompliant - | project TimeGenerated, UserPrincipalName, IPAddress, Location, City, State, AppDisplayName, ClientAppUsed, AuthenticationRequirement, ConditionalAccessStatus, ResultDescription, Browser, OperatingSystem, displayName, trustType, isCompliant +Descriptor: + Name: Summarize Sign-in logs from ADX Long Term Archive. + DisplayName: "ADX KQL: Long Term Archive Sign-in Logs" + Description: Get Sign-in logs from long term archive store in ADX (Azure data explorer) for a user or IP Address. + #Can you describe the Sign in log results in detail from the ADX Long Term Archive store over a few paragraphs for user@contoso.com between Oct 23, 2023 and Oct 27, 2023? + #Can you describe in detail the sign-in's where Conditional Access status was a failure from the ADX Long Term Archive store for user@contoso.com during December 2023? Be sure to include timestamps for the sign-in activities. +SkillGroups: + - Format: KQL + Skills: + - Name: Get ADX Long Term Sign in Logs + DisplayName: "Get ADX Long Term Sign in Logs" + Description: Get Sign-in logs from long term archive store in ADX (Azure Data Explorer) for a user or IP Address. + Inputs: + - Name: username + Description: The username to lookup + Required: false + - Name: ip + Description: The ip to lookup + Required: false + - Name: fromDateTime + Description: The from time and date + Required: true + - Name: toDateTime + Description: The to time and date + Required: true + Settings: + Target: Kusto + Cluster: **ADX CLUSTER URL** + Database: **ADX DATABASE NAME** + TenantId: **ENTRA TENANT ID** + Template: |- + let upn='{{username}}'; + let ip='{{ip}}'; + let fromDateTime=datetime('{{fromDateTime}}'); + let toDateTime=datetime('{{toDateTime}}'); + SigninLogs + | where datetime_utc_to_local(TimeGenerated, "US/Pacific") between ( fromDateTime .. toDateTime ) + | where IPAddress like ip + | where UserPrincipalName like upn + | extend LocationDetails=parse_json(LocationDetails) + | extend DeviceDetail=parse_json(DeviceDetail) + | extend City=tostring(LocationDetails.city) + | extend State=tostring(LocationDetails.state) + | extend Browser=DeviceDetail.browser + | extend OperatingSystem=DeviceDetail.operatingSystem + | extend displayName=DeviceDetail.displayName + | extend trustType=DeviceDetail.trustType + | extend isCompliant=DeviceDetail.isCompliant + | project TimeGenerated, UserPrincipalName, IPAddress, Location, City, State, AppDisplayName, ClientAppUsed, AuthenticationRequirement, ConditionalAccessStatus, ResultDescription, Browser, OperatingSystem, displayName, trustType, isCompliant diff --git a/Plugins/Community Based Plugins/HuntSignInLogs-ADX/README.md b/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/HuntSignInLogs-ADX/README.md similarity index 75% rename from Plugins/Community Based Plugins/HuntSignInLogs-ADX/README.md rename to Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/HuntSignInLogs-ADX/README.md index 87134eb6..1c6c68a3 100644 --- a/Plugins/Community Based Plugins/HuntSignInLogs-ADX/README.md +++ b/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/HuntSignInLogs-ADX/README.md @@ -1,20 +1,20 @@ -# Copilot for Security Custom Plugin - ADX KQL - Get Entra Sign-in Logs from Azure Data Explorer +# Security Copilot Custom Plugin - ADX KQL - Get Entra Sign-in Logs from Azure Data Explorer -#### Copilot for Security Plugin to retrieve Entra ID Sign-in logs from an Azure Data Explorer cluster. +#### Security Copilot Plugin to retrieve Entra ID Sign-in logs from an Azure Data Explorer cluster. ### Pre-requisites -* [Copilot for Security Enabled](https://learn.microsoft.com/en-us/security-copilot/get-started-security-copilot#onboarding-to-microsoft-security-copilot) +* [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) * [An Azure Data Explorer cluster](https://techcommunity.microsoft.com/t5/microsoft-sentinel-blog/using-azure-data-explorer-for-long-term-retention-of-microsoft/ba-p/1883947) with Entra ID Sign in logs exported from the Signinlogs table in Sentinel to ADX ### Instructions #### Upload the Custom Plugin -1. Obtain the file [KQL-ADX-SignInLogs.yaml](https://github.com/Azure/Copilot-For-Security/blob/main/Plugins/Community%20Based%20Plugins/HuntSignInLogs-ADX/KQL-ADX-SignInLogs.yaml) from this directory. +1. Obtain the file [KQL-ADX-SignInLogs.yaml](https://github.com/Azure/Copilot-For-Security/blob/main/Plugins/Community%20Based%20Plugins/Microsoft%20Sentinel%20Custom%20Plugin%20Scenarios/HuntSignInLogs-ADX/KQL-ADX-SignInLogs.yaml) from this directory. 2. Modify the .yaml file and specify your *ADX Cluster URL*, your *ADX Database* and your *Entra TenantId* parameters. - ![ADXPlugin](https://github.com/SCStelz/CopilotForSecurity/blob/main/Images/adx-connection.png) -4. [Upload the custom plugin](https://learn.microsoft.com/en-us/security-copilot/manage-plugins?tabs=securitycopilotplugin#add-custom-plugins) and verify it's activated. + +3. [Upload the custom plugin](https://learn.microsoft.com/en-us/security-copilot/manage-plugins?tabs=securitycopilotplugin#add-custom-plugins) and verify it's 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/Network Anomaly Detection/NetworkAnomalyDetection.yaml b/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/Network Anomaly Detection/NetworkAnomalyDetection.yaml new file mode 100644 index 00000000..817df209 --- /dev/null +++ b/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/Network Anomaly Detection/NetworkAnomalyDetection.yaml @@ -0,0 +1,71 @@ +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: + - Name: Detectnetworkcefanomalyeactivity + 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' + Inputs: + - Name: StartTime + Description: Set starTime Number of Days for the localback Network (CEF) anomaly detection activity in Microsoft Sentinel + Required: true + DefaultValue: 21d + - Name: EndTime + Description: Set endTime Number of Days for the localback Network (CEF) anomaly detection activity in Microsoft Sentinel + Required: true + DefaultValue: 0d + Settings: + Target: Sentinel + 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 diff --git a/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/Network Anomaly Detection/Readme.md b/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/Network Anomaly Detection/Readme.md new file mode 100644 index 00000000..76d5abc6 --- /dev/null +++ b/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/Network Anomaly Detection/Readme.md @@ -0,0 +1,15 @@ + +### Network (CEF) anomaly detection activity Custom Plugin + +Welcome to the Network (CEF) anomaly detection activity custom plugin! Here, you'll discover a Skills that that look up network CEF CommonSecurityLog Sentinel data anomaly activity - events. + +#### Features: + +- StartTime: + Description: Set startTime Number of Days for the localback Network (CEF) anomaly detection activity in Microsoft Sentinel + Required: true + DefaultValue: 21d +- EndTime: + Description: Set endTime Number of Days for the localback Network (CEF) anomaly detection activity in Microsoft Sentinel + Required: true + DefaultValue: 0d diff --git a/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/Network beaconing ASIM plugin/PotentialNetworkBeaconingActivity.yaml b/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/Network beaconing ASIM plugin/PotentialNetworkBeaconingActivity.yaml new file mode 100644 index 00000000..cf59bb8e --- /dev/null +++ b/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/Network beaconing ASIM plugin/PotentialNetworkBeaconingActivity.yaml @@ -0,0 +1,153 @@ +Descriptor: + Name: Potential network beaconing activity + DisplayName: Potential network beaconing activity + Description: identifies beaconing patterns from Network traffic logs based on recurrent frequency patterns. + Settings: + - Name: TenantId + Required: true + - Name: WorkspaceName + Required: true + - Name: SubscriptionId + Required: true + - Name: ResourceGroupName + Required: true + SupportedAuthTypes: + - None + +SkillGroups: + - Format: KQL + Skills: + - Name: BeaconingActivity + DisplayName: Potential network beaconing activity + Description: identifies beaconing patterns from Network traffic logs based on recurrent frequency patterns. + Inputs: + - Name: querystarttime + Description: Query start time + Required: true + - Name: queryendtime + Description: Query end time + Required: true + - Name: TimeDeltaThreshold + Description: Time Delta Threshold + Required: false + 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; + let LocalNetworks=dynamic(["169.254.0.0/16", "127.0.0.0/8"]); + _Im_NetworkSession(starttime=ago(querystarttime), endtime=ago(queryendtime)) + | where not(ipv4_is_private(DstIpAddr)) + | where not (ipv4_is_in_any_range(DstIpAddr, LocalNetworks)) + | project + TimeGenerated + , + SrcIpAddr + , + SrcPortNumber + , + DstIpAddr + , + DstPortNumber + , + DstBytes + , + SrcBytes + ,DvcHostname + ,DstDomain + ,SrcProcessIntegrityLevel + | sort by + SrcIpAddr asc + , + TimeGenerated asc + , + DstIpAddr asc + , + DstPortNumber 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 + , + SrcPortNumber + , + DstIpAddr + , + DstPortNumber + , + DstBytes + , + SrcBytes, + DvcHostname, + DstDomain, + SrcProcessIntegrityLevel + | summarize + count() + , + sum(DstBytes) + , + sum(SrcBytes) + , + make_list(TimeDeltainSeconds) + by + TimeDeltainSeconds + , + bin(TimeGenerated, 1h) + , + SrcIpAddr + , + DstIpAddr + , + DstPortNumber, + DvcHostname, + DstDomain, + SrcProcessIntegrityLevel + | summarize + (MostFrequentTimeDeltaCount, MostFrequentTimeDeltainSeconds) = arg_max(count_, TimeDeltainSeconds) + , + TotalEvents=sum(count_) + , + TotalSrcBytes = sum(sum_SrcBytes) + , + TotalDstBytes = sum(sum_DstBytes) + by + bin(TimeGenerated, 1h) + , + SrcIpAddr + , + DstIpAddr + , + DstPortNumber, + DvcHostname, + DstDomain, + SrcProcessIntegrityLevel + | extend BeaconPercent = MostFrequentTimeDeltaCount / toreal(TotalEvents) * 100 + | extend MitreTactics="Command and Control" + | extend MitreTechnique1 = "T1071 - Application Layer Protocol" + | extend MitreTechnique2 = "T1571 - Non Standard Port" + | where TotalEvents > TotalEventsThreshold + | where BeaconPercent > PercentBeaconThreshold \ 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 new file mode 100644 index 00000000..ac4f62cc --- /dev/null +++ b/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/Network beaconing ASIM plugin/Readme.md @@ -0,0 +1,22 @@ +# 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 Information Model** + +### 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 PotentialNetworkBeaconingActivity.yaml from this directory. +2. Upload the custom plugin +3. Input your specific Entra TentantId, SubscriptionId, ResourceGroupName and WorkspaceName in the resulting dialog box within Security Copilot + +### 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) diff --git a/Plugins/Community Based Plugins/Sentinel Cost Query Plugin/KQL-SentinelCost.yaml b/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/Sentinel Cost Query Plugin/KQL-SentinelCost.yaml similarity index 62% rename from Plugins/Community Based Plugins/Sentinel Cost Query Plugin/KQL-SentinelCost.yaml rename to 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/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/Sentinel Cost Query Plugin/Readme.md b/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/Sentinel Cost Query Plugin/Readme.md similarity index 76% rename from Plugins/Community Based Plugins/Sentinel Cost Query Plugin/Readme.md rename to Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/Sentinel Cost Query Plugin/Readme.md index 444df513..01ef4d69 100644 --- a/Plugins/Community Based Plugins/Sentinel Cost Query Plugin/Readme.md +++ b/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/Sentinel Cost Query Plugin/Readme.md @@ -1,10 +1,10 @@ -# Copilot for Security Custom Plugin - Sentinel KQL - Sentinel Costs +# Security Copilot Custom Plugin - Sentinel KQL - Sentinel Costs -#### Copilot for Security Plugin to run a Dynamic KQL Query to estimate Microsoft Sentinel Costs for a specific time and date range by running a KQL query against the Usage table in Microsoft Sentinel. +#### Security Copilot Plugin to run a Dynamic KQL Query to estimate Microsoft Sentinel Costs for a specific time and date range by running a KQL query against the Usage table in Microsoft Sentinel. ### Pre-requisites -* [Copilot for Security Enabled](https://learn.microsoft.com/en-us/security-copilot/get-started-security-copilot#onboarding-to-microsoft-security-copilot) +* [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 @@ -12,10 +12,10 @@ ### Instructions #### Upload the Custom Plugin -1. Obtain the file [KQL-Sentinel-SentinelCost.yaml](https://github.com/Azure/Copilot-For-Security/blob/main/Plugins/Community%20Based%20Plugins/Sentinel%20Cost%20Query%20Plugin/KQL-SentinelCost.yaml) from this directory. +1. Obtain the file [KQL-Sentinel-SentinelCost.yaml](https://github.com/Azure/Copilot-For-Security/blob/main/Plugins/Community%20Based%20Plugins/Microsoft%20Sentinel%20Custom%20Plugin%20Scenarios/Sentinel%20Cost%20Query%20Plugin/KQL-SentinelCost.yaml) from this directory. 2. Modify the yaml file to specify your specific Entra TentantId, SubscriptionId, ResourceGroupName and WorkspaceName for your Sentinel instance. You can also modify the KQL query at the bottom of the plugin to adjust your specific $ Per GB rate for Sentinel data ingestion (size * 4.3). -![KQLConnection](https://github.com/SCStelz/CopilotForSecurity/blob/main/Images/kql-connection.png) + 3. [Upload the custom plugin](https://learn.microsoft.com/en-us/security-copilot/manage-plugins?tabs=securitycopilotplugin#add-custom-plugins) and verify it's activated. 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 new file mode 100644 index 00000000..e7078767 --- /dev/null +++ b/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/Sentinel Table Ingestion/KQL_Plugin_BadTables.yaml @@ -0,0 +1,19 @@ +Descriptor: + Name: BadTable + DisplayName: Sentinel Tables Not Ingesting Data + Description: Check all Tables to see which ones have not ingested data in 3 days or more + +SkillGroups: + - Format: KQL + Skills: + - Name: BadTable + DisplayName: Sentinel Tables Not Ingesting Data + Description: Check all Tables to see which ones have not ingested data in 3 days or more + Settings: + Target: Sentinel + TenantId: + SubscriptionId: + 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 diff --git a/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/Sentinel Table Ingestion/Readme.md b/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/Sentinel Table Ingestion/Readme.md new file mode 100644 index 00000000..df1f17e9 --- /dev/null +++ b/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/Sentinel Table Ingestion/Readme.md @@ -0,0 +1,20 @@ +# Security Copilot Plugin: Sentinel Table Ingestion Issues + +### **If you want to identify potential ingestion issues in Microsoft Sentinel tables, here’s a KQL plugin for Security Copilot that you can use.** + +### 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_BadTables.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: Bad Tables](https://rodtrent.substack.com/p/copilot-for-security-plugin-bad-tables) diff --git a/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/Sentinel Watchlists/Manifest_WatchlistKQL.yaml b/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/Sentinel Watchlists/Manifest_WatchlistKQL.yaml new file mode 100644 index 00000000..35031e3a --- /dev/null +++ b/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/Sentinel Watchlists/Manifest_WatchlistKQL.yaml @@ -0,0 +1,19 @@ +Descriptor: + Name: WatchlistKQL + DisplayName: Trusted Users List + Description: Fetches the list of trusted users that is being maintained as a Microsoft Sentinel watchlist + +SkillGroups: + - Format: KQL + Skills: + - Name: GetTrustedUsers + DisplayName: Get the Trusted Users List + Description: Fetches the list of trusted users that is being maintained as a Microsoft Sentinel watchlist + Settings: + Target: Sentinel + TenantId: + SubscriptionId: + ResourceGroupName: + WorkspaceName: + Template: |- + _GetWatchlist('TrustedUsers') | distinct TrustedUsers 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 new file mode 100644 index 00000000..ae3062ad --- /dev/null +++ b/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/Sentinel Watchlists/Readme.md @@ -0,0 +1,53 @@ +# Sentinel Watchlist integration 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. + +``` kql +Template: |- + _GetWatchlist('TrustedUsers') | distinct TrustedUsers +``` +--- + +## TYPE AND REQUIREMENTS +**TYPE**: KQL (Sentinel)
+**SOURCE**: any watchlist in Sentinel
+**REQUIREMENTS**: Log Analytics Workspace with Sentinel enabled + +--- + +## SKILLS + + + + + + + + + + + + + + +
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 new file mode 100644 index 00000000..277c2436 --- /dev/null +++ b/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/SentinelDailyOperations/KQL_SentinelDailyOperationsSample.yml @@ -0,0 +1,61 @@ +Descriptor: + Name: SentinelDailyOperations + 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: + - Name: GetAzureNetworkAnalyticsIPDetails + DisplayName: Get Azure Network Analytics IP Details + Description: Get all the threat information from azure network analytics logs from one day ago + Inputs: + - Name: region + Description: provide any specific location you want to consider + Required: true + Settings: + Target: Sentinel + TenantId: "{{TenantId}}" + SubscriptionId: "{{SubscriptionId}}" + ResourceGroupName: "{{ResourceGroupName}}" + WorkspaceName: "{{WorkspaceName}}" + Template: |- + AzureNetworkAnalyticsIPDetails_CL + | where isnotempty(ThreatType_s) + | where ThreatType_s != '[]' + | where FlowIntervalStartTime_t > ago(1d) + | 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/SentinelDailyOperations/readme.md b/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/SentinelDailyOperations/readme.md similarity index 50% rename from Plugins/Community Based Plugins/SentinelDailyOperations/readme.md rename to Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/SentinelDailyOperations/readme.md index 7ea57ba4..3bea40ae 100644 --- a/Plugins/Community Based Plugins/SentinelDailyOperations/readme.md +++ b/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/SentinelDailyOperations/readme.md @@ -1,8 +1,8 @@ -# Copilot for Security Custom Plugin - Sentinel KQL - SentinelDailyOperations +# Security Copilot Custom Plugin - Sentinel KQL - SentinelDailyOperations ### Pre-requisites -* [Copilot for Security Enabled](https://learn.microsoft.com/en-us/security-copilot/get-started-security-copilot#onboarding-to-microsoft-security-copilot) +* [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 @@ -10,7 +10,6 @@ ### Instructions #### Upload the Custom Plugin -1. Obtain the file [KQL_SentinelDailyOperationsSample.yaml](https://github.com/Azure/Copilot-For-Security/blob/main/Plugins/Community%20Based%20Plugins/SentinelDailyOperations/KQL_SentinelDailyOperationsSample.yml) 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 - +1. Obtain the file [KQL_SentinelDailyOperationsSample.yaml](https://github.com/Azure/Copilot-For-Security/blob/main/Plugins/Community%20Based%20Plugins/Microsoft%20Sentinel%20Custom%20Plugin%20Scenarios/SentinelDailyOperations/KQL_SentinelDailyOperationsSample.yml) 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 \ No newline at end of file diff --git a/Plugins/Community Based Plugins/Purview/KQL_DataAnalyst.yml b/Plugins/Community Based Plugins/Purview/KQL_DataAnalyst.yml new file mode 100644 index 00000000..870a9c62 --- /dev/null +++ b/Plugins/Community Based Plugins/Purview/KQL_DataAnalyst.yml @@ -0,0 +1,1924 @@ +Descriptor: + Name: DataSecurityAnalyst + DisplayName: Data Security Analyst plugin + 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 bee 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 diff --git a/Plugins/Community Based Plugins/Purview/eDiscovery/CfS-Prompt-Sample.png b/Plugins/Community Based Plugins/Purview/eDiscovery/CfS-Prompt-Sample.png new file mode 100644 index 00000000..92eee136 Binary files /dev/null and b/Plugins/Community Based Plugins/Purview/eDiscovery/CfS-Prompt-Sample.png differ diff --git a/Plugins/Community Based Plugins/Purview/eDiscovery/CfS-Secret.png b/Plugins/Community Based Plugins/Purview/eDiscovery/CfS-Secret.png new file mode 100644 index 00000000..ec0a29d1 Binary files /dev/null and b/Plugins/Community Based Plugins/Purview/eDiscovery/CfS-Secret.png differ diff --git a/Plugins/Community Based Plugins/Purview/eDiscovery/CfS-add-plugin-part2.png b/Plugins/Community Based Plugins/Purview/eDiscovery/CfS-add-plugin-part2.png new file mode 100644 index 00000000..9903463d Binary files /dev/null and b/Plugins/Community Based Plugins/Purview/eDiscovery/CfS-add-plugin-part2.png differ diff --git a/Plugins/Community Based Plugins/Purview/eDiscovery/CfS-add-plugin.png b/Plugins/Community Based Plugins/Purview/eDiscovery/CfS-add-plugin.png new file mode 100644 index 00000000..5baafe86 Binary files /dev/null and b/Plugins/Community Based Plugins/Purview/eDiscovery/CfS-add-plugin.png differ diff --git a/Plugins/Community Based Plugins/Purview/eDiscovery/EntraID-ClientID-TenantID.png b/Plugins/Community Based Plugins/Purview/eDiscovery/EntraID-ClientID-TenantID.png new file mode 100644 index 00000000..68a182b2 Binary files /dev/null and b/Plugins/Community Based Plugins/Purview/eDiscovery/EntraID-ClientID-TenantID.png differ diff --git a/Plugins/Community Based Plugins/Purview/eDiscovery/EntraID-SecretValue.png b/Plugins/Community Based Plugins/Purview/eDiscovery/EntraID-SecretValue.png new file mode 100644 index 00000000..277e7db6 Binary files /dev/null and b/Plugins/Community Based Plugins/Purview/eDiscovery/EntraID-SecretValue.png differ diff --git a/Plugins/Community Based Plugins/Purview/eDiscovery/Readme.md b/Plugins/Community Based Plugins/Purview/eDiscovery/Readme.md new file mode 100644 index 00000000..ee34b442 --- /dev/null +++ b/Plugins/Community Based Plugins/Purview/eDiscovery/Readme.md @@ -0,0 +1,88 @@ +# eDiscovery Plugin for Security Copilot +Author: Amit Singh + +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. + +### Prerequisites for Using Security Copilot + +- **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) + +- **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) + +- **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 + +🔹 **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 new file mode 100644 index 00000000..30b17d71 --- /dev/null +++ b/Plugins/Community Based Plugins/Purview/eDiscovery/eDiscovery_API_Plugin.yaml @@ -0,0 +1,597 @@ +openapi: 3.0.0 + +info: + 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 + +paths: + /ediscoveryCases: + post: + 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: + application/json: + schema: + type: object + properties: + displayName: + type: string + description: The name of the eDiscovery case + description: + type: string + description: The eDiscovery case description + externalId: + type: string + description: The external case number for customer reference + required: + - displayName + responses: + "200": + description: OK + content: + application/json: + + /ediscoveryCases/{ediscoveryCaseId}/custodians: + post: + 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 + schema: + type: string + required: true + description: ediscoveryCaseId in eDiscovery + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + email: + type: string + description: Custodian's primary SMTP address supplied by the user + required: + - email + responses: + "200": + description: OK + content: + application/json: + + /ediscoveryCases/{ediscoveryCaseId}/custodians/{custodianId}/userSources: + post: + 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 + schema: + type: string + required: true + description: caseId in eDiscovery + - in: path + name: custodianId + schema: + type: string + required: true + description: custodianId in eDiscovery + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + email: + type: string + description: Custodian's primary SMTP address supplied by the user + includedSources: + type: string + description: Specifies which sources are included in this group. Possible values are (mailbox, site) supplied by the user, seperated by a comma + required: + - email + - includedSources + responses: + "200": + description: OK + content: + application/json: + + /ediscoveryCases/{ediscoveryCaseId}/custodians/{custodianId}/siteSources: + post: + operationId: Add a new siteSource object associated with an eDiscovery custodian + description: Add a new siteSource (sharepoint) object associated with an eDiscovery custodian in Purview + 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: true + content: + application/json: + schema: + type: object + properties: + webUrl: + type: string + description: URL of the sharepoint site supplied by the user + required: + - webUrl + responses: + "200": + description: OK + content: + application/json: + + /ediscoveryCases/{ediscoveryCaseId}/custodians/applyHold: + post: + 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 + schema: + type: string + required: true + description: caseId in eDiscovery + - in: path + name: custodianId + schema: + type: string + required: true + description: custodianId in eDiscovery + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + ids: + type: string + description: ids of custodian/s seperated by comma supplied by the user + required: + - ids + responses: + "200": + description: OK + headers: + location: + description: The URL to the operation status, location provides a URL that will return an eDiscoveryHoldOperation object. + schema: + type: string + example: "https://graph.microsoft.com/v1.0/security/cases/ediscoverycases('{ediscoveryCaseId}')/operations('{ediscoveryReviewSetId}')" + content: + application/json: + + /ediscoveryCases/{ediscoveryCaseId}/searches: + post: + 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 + schema: + type: string + required: true + description: caseId in eDiscovery + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + displayName: + type: string + description: The display name of the search, supplied by the user + description: + type: string + description: The description of the search, supplied by the user. Optional + contentQuery: + type: string + description: The query string used for the search. The query string in KQL (Keyword Query Language) format, supplied by the user. Optional + dataSourceScopes: + type: string + description: The option to search across all mailboxes or sites in the tenant. The possible values are none, allTenantMailboxes, allTenantSites, allCaseCustodians, allCaseNoncustodialDataSources, supplied by the user. Optional + required: + - displayName + responses: + "200": + 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: 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 + 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 + description: Run an estimate of the number of emails and documents in the eDiscovery search + responses: + "200": + description: OK + headers: + location: + description: The response contains also a Location header, which contains the location of the Export operation that was created to handle the export. + schema: + type: string + example: "https://graph.microsoft.com/v1.0/security/cases/ediscoverycases('{ediscoveryCaseId}')/operations('{ediscoverySearchId}')" + content: + application/json: + + /ediscoveryCases/{ediscoveryCaseId}/reviewSets: + post: + 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 + schema: + type: string + required: true + description: caseId in eDiscovery + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + displayName: + type: string + description: The name of the review set, supplied by the user. Required + required: + - displayName + responses: + "200": + description: OK + content: + application/json: + + /ediscoveryCases/{ediscoveryCaseId}/reviewSets/{eDiscoveryReviewSetId}/export: + post: + 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 + schema: + type: string + required: true + description: caseId in eDiscovery + - in: path + name: eDiscoveryReviewSetId + schema: + type: string + required: true + description: eDiscoveryReviewSetId in eDiscovery + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + outputName: + type: string + description: Name of the export, supplied by the user. Required + description: + type: string + description: Description of the export, supplied by the user. + exportOptions: + type: string + description: Specifies options that control the format of the export. Possible values are; originalFiles, text, pdfReplacement, tags, unknownFutureValue supplied by the user. + exportStructure: + type: string + description: Options that control file structure and packaging of the export. Possible values are; none, directory, pst, unknownFutureValue.supplied by the user. + required: + - outputName + responses: + "200": + description: OK + headers: + location: + description: The response contains also a Location header, which contains the location of the Export operation that was created to handle the export. + schema: + type: string + example: "https://graph.microsoft.com/v1.0/security/cases/ediscoverycases('{ediscoveryCaseId}')/operations('{ediscoveryReviewSetId}')" + content: + application/json: + + /ediscoveryCases/{ediscoveryCaseId}/operations: + get: + 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 + schema: + type: string + required: true + description: caseId in eDiscovery + requestBody: + required: true + description: Run an estimate of the number of emails and documents in the eDiscovery search + + 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: + + /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 new file mode 100644 index 00000000..9c851fcd --- /dev/null +++ b/Plugins/Community Based Plugins/Purview/eDiscovery/eDiscovery_OAuth_API_manifest.yaml @@ -0,0 +1,18 @@ +Descriptor: + Name: eDiscovery Case Plugin + DisplayName: eDiscovery Case Plugin + Description: The skills in this plugin will help create eDiscovery case & additional steps all the way to export in Purview via Graph API Call + DescriptionForModel: The skills in this plugin will help create eDiscovery case, add custodian, add hold, create search, create reviewset and kickoff export in Purview + + Authorization: + Type: OAuthAuthorizationCodeFlow + ClientId: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx #ClientId from App Registration in EntraID + AuthorizationEndpoint: https://login.microsoftonline.com/Directory(tenant)ID/oauth2/v2.0/authorize #Directory(tenant)ID from Overview page of your App Registration in EntraID + TokenEndpoint: https://login.microsoftonline.com/Directory(tenant)ID/oauth2/v2.0/token #Directory(tenant)ID from Overview page of your App Registration in EntraID + Scopes: offline_access user.read eDiscovery.Read.All eDiscovery.ReadWrite.All #EntraID permissions for this App & delegated permissions assigned to your App in EntraID + AuthorizationContentType: application/x-www-form-urlencoded + +SkillGroups: + - Format: API + Settings: + 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/Purview/readme.md b/Plugins/Community Based Plugins/Purview/readme.md new file mode 100644 index 00000000..ee2e1cbd --- /dev/null +++ b/Plugins/Community Based Plugins/Purview/readme.md @@ -0,0 +1,25 @@ +# Custom Data Security Plugin for Security Copilot + +The custom Data Security plugin for Security Copilot offers a range of functionalities to enhance your organization's cybersecurity capabilities. Here are the key features: + +## Key Features + +### Anomaly Detection +The plugin can detect anomalies in Data Loss Prevention (DLP) and sensitivity content by analyzing data over specific intervals. + +### Access Tracking +It tracks access to sensitive information by compromised accounts and can identify who accessed sensitive emails and documents. + +### CVE Exposure +The plugin helps check the exposure of sensitive information to Common Vulnerabilities and Exposures (CVE) and can pivot based on Internet Service Providers (ISP). + +### Policy Tuning +It provides suggestions for tuning DLP policies across various platforms like Exchange, Teams, SharePoint, and more. + +### Application Monitoring +The plugin can monitor applications accessing sensitive content and provide details on network connectivity and processes used. + +### Promptbooks +These are valuable resources for implementing standard operating procedures for security incidents, helping to identify various dimensions of an incident and summarize outcomes. + +[Learn more](https://techcommunity.microsoft.com/t5/security-compliance-and-identity/learn-how-to-customize-and-optimize-copilot-for-security-with/ba-p/4120147) 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 new file mode 100644 index 00000000..7855e60f --- /dev/null +++ b/Plugins/Community Based Plugins/QR Code AiTM Phishing Detection Plugin/HuntingQrCodeAITM.yml @@ -0,0 +1,189 @@ +Descriptor: + Name: QRCodeHunter + DisplayName: Hunting for QR Code AiTM Phishing and User Compromise + Description: Hunting for QR Code AiTM Phishing and User Compromise based off https://techcommunity.microsoft.com/t5/microsoft-security-experts-blog/hunting-for-qr-code-aitm-phishing-and-user-compromise/ba-p/4053324 + +SkillGroups: + - Format: KQL + Skills: + - Name: QRCode_SigninventsLinkedtoQrCodePhishing + DisplayName: Hunting for User Behavior to Identify Suspicious Sign-In Events Linked to QR Code Phishing Campaigns + Description: This detection identifies suspicious sign-in attempts linked to QR code phishing campaigns by correlating email access with attached images or documents and risky sign-ins from non-trusted devices, validating differing locations between email access and sign-in attempts in the last 30 days + Settings: + Target: Defender + Template: |- + let timeback = (30d); + let successfulRiskySignIn = materialize(AADSignInEventsBeta| where Timestamp > ago(timeback)| where isempty(DeviceTrustType) + | where IsManaged != 1 + | where IsCompliant != 1 + | where RiskLevelDuringSignIn in (50, 100) + | project Timestamp, ReportId, IPAddress, AccountUpn, AccountObjectId, SessionId, Country, State, City + ); + let suspiciousSignInUsers = successfulRiskySignIn + | distinct AccountObjectId; + let suspiciousSignInIPs = successfulRiskySignIn + | distinct IPAddress; + let suspiciousSignInCities = successfulRiskySignIn + | distinct City; + CloudAppEvents + | where Timestamp > ago(timeback) + | where ActionType == "MailItemsAccessed" + | where AccountObjectId in (suspiciousSignInUsers) + | where IPAddress !in (suspiciousSignInIPs) + | where City !in (suspiciousSignInCities) + | join kind=inner successfulRiskySignIn on AccountObjectId + | where (Timestamp - Timestamp1) between (-5min .. 5min) + | extend folders = RawEventData.Folders + | mv-expand folders + | extend items = folders.FolderItems + | mv-expand items + | extend InternetMessageId = tostring(items.InternetMessageId) + | project Timestamp, ReportId, IPAddress, InternetMessageId, AccountObjectId, SessionId, Country; + - Name: QRCode_EmailswithImpersonationIntents + DisplayName: Fetch All Emails from Non-Prevalent Senders in the Organization with Impersonation Intents + Description: In this detection approach, we correlate email from non-prevalent senders in the organization with impersonation intents. + Settings: + Target: Defender + Template: |- + let PhishingSenderDisplayNames = pack_array( + "IT", "support", "Payroll", "HR", "admin", "2FA", "notification", "sign", "reminder", "consent", "workplace", + "administrator", "administration", "benefits", "employee", "update", "on behalf" + ); + let suspiciousEmails = EmailEvents + | where Timestamp > ago(30d) + | where isnotempty(RecipientObjectId) + | where isnotempty(SenderFromAddress) + | where EmailDirection == "Inbound" + | where DeliveryAction == "Delivered" + | join kind=inner ( + EmailAttachmentInfo + | where Timestamp > ago(1d) + | where isempty(SenderObjectId) + | where FileType has_any ("png", "jpg", "jpeg", "bmp", "gif") + ) on NetworkMessageId + | where SenderDisplayName has_any (PhishingSenderDisplayNames) + | project Timestamp, Subject, FileName, SenderFromDomain, RecipientObjectId, NetworkMessageId; + let suspiciousSenders = suspiciousEmails + | distinct SenderFromDomain; + let prevalentSenders = materialize( + EmailEvents + | where Timestamp between (ago(7d) .. ago(1d)) + | where isnotempty(RecipientObjectId) + | where isnotempty(SenderFromAddress) + | where SenderFromDomain in (suspiciousSenders) + | where EmailDirection == "Inbound" + | where DeliveryAction == "Delivered" + | distinct SenderFromDomain + ); + suspiciousEmails + | where SenderFromDomain !in (prevalentSenders) + | project Timestamp, Subject, FileName, SenderFromDomain, RecipientObjectId, NetworkMessageId; + - Name: QRCode_Huntingcampaignwithfewkeywords + DisplayName: Get Personalized Campaigns Based on the First Few Keywords + Description: Personalized campaigns based on the first few keywords + Settings: + Target: Defender + Template: |- + EmailEvents + | where Timestamp > ago(30d) + | where EmailDirection == "Inbound" + | where DeliveryAction == "Delivered" + | where isempty(SenderObjectId) + | extend words = split(Subject, " ") + | project + firstWord = tostring(words[0]), + secondWord = tostring(words[1]), + thirdWord = tostring(words[2]), + Subject, + SenderFromAddress, + RecipientEmailAddress, + NetworkMessageId + | summarize + SubjectsCount = dcount(Subject), + RecipientsCount = dcount(RecipientEmailAddress), + suspiciousEmails = make_set(NetworkMessageId, 10) by firstWord, secondWord, thirdWord, SenderFromAddress + | where SubjectsCount >= 10 + + - Name: QRCode_Huntingcampaignwithlastkeywords + DisplayName: Get Personalized Campaigns Based on the Last Few Keywords + Description: Personalized campaigns based on the last few keywords + Settings: + Target: Defender + Template: |- + EmailEvents + | where Timestamp > ago(1d) + | where EmailDirection == "Inbound" + | where DeliveryAction == "Delivered" + | where isempty(SenderObjectId) + | extend words = split(Subject," ") + | project firstLastWord = tostring(words[-1]), secondLastWord = tostring(words[-2]), thirdLastWord = tostring(words[-3]), Subject, SenderFromAddress, RecipientEmailAddress, NetworkMessageId + | summarize SubjectsCount = dcount(Subject), RecipientsCount = dcount(RecipientEmailAddress), suspiciousEmails = make_set(NetworkMessageId, 10) by firstLastWord, secondLastWord, thirdLastWord + , SenderFromAddress + | where SubjectsCount >= 10 + + - Name: QRCode_Campaignwithsuspiciouskeywords + DisplayName: Get Campaign with Suspicious Keywords + Description: Personalized Campaign with suspicious keywords in the last month + Inputs: + - Name: suspiciouskeywords + Description: Add your suspicious keyword you want to hunt for here + Required: true + - Name: suspiciouskeywords1 + Description: Add your suspicious keyword you want to hunt for here + Required: true + - Name: suspiciouskeywords2 + Description: Add your suspicious keyword you want to hunt for here + Required: true + - Name: suspiciouskeywords3 + Description: Add your suspicious keyword you want to hunt for here + Required: true + - Name: suspiciouskeywords4 + Description: Add your suspicious keyword you want to hunt for here + Required: true + Settings: + Target: Defender + Template: |- + let suspiciouskeywords ='{{suspiciouskeywords}}'; + let suspiciouskeywords1 ='{{suspiciouskeywords1}}'; + let suspiciouskeywords2 ='{{suspiciouskeywords2}}'; + let suspiciouskeywords3 ='{{suspiciouskeywords3}}'; + let suspiciouskeywords4 ='{{suspiciouskeywords4}}'; + let PhishingKeywords = pack_array("suspiciouskeywords4","suspiciouskeywords3","suspiciouskeywords2","suspiciouskeywords1","suspiciouskeywords"); + EmailEvents + | where Timestamp > ago(30d) + | where EmailDirection == "Inbound" + | where DeliveryAction == "Delivered" + | where isempty(SenderObjectId) + | where Subject has_any (PhishingKeywords()) + + - Name: QRCode_Huntingforattachmentnamepatterns + DisplayName: Hunt for Randomized Attachment Names Associated with QR Code Phishing Attacks + Description: Hunts for randomized attachment names associated with QR code phishing attacks in the last 7 days + Settings: + Target: Defender + Template: |- + EmailAttachmentInfo + | where Timestamp > ago(7d) + | where FileType in ("png", "jpg", "jpeg", "gif", "svg") + | where isnotempty(FileName) + | extend firstFourFileName = substring(FileName, 0, 4) + | summarize RecipientsCount = dcount(RecipientEmailAddress), FirstFourFilesCount = dcount(firstFourFileName), suspiciousEmails = make_set(NetworkMessageId, 10) by SenderFromAddress + | where FirstFourFilesCount >= 10 + + - Name: QRCode_Huntingforusersignalsclusters + DisplayName: Hunting for user Signal and Clusters associated with QR phishing scores + Description: Identify and scope QR code phishing campaigns targeting organizations triggered by user-reported alerts in the last 7 days + Settings: + Target: Defender + Template: |- + let suspiciousClusters = EmailEvents + | where Timestamp > ago(7d) + | where EmailDirection == "Inbound" + | where NetworkMessageId in ("") + | distinct EmailClusterId; + EmailEvents + | where Timestamp > ago(7d) + | where EmailDirection == "Inbound" + | where EmailClusterId in (suspiciousClusters) + | summarize make_set(Subject), make_set(SenderFromDomain), dcount(RecipientObjectId), dcount(SenderDisplayName) by EmailClusterId; + diff --git a/Plugins/Community Based Plugins/QR Code AiTM Phishing Detection Plugin/images/Picture1.png b/Plugins/Community Based Plugins/QR Code AiTM Phishing Detection Plugin/images/Picture1.png new file mode 100644 index 00000000..119464a4 Binary files /dev/null and b/Plugins/Community Based Plugins/QR Code AiTM Phishing Detection Plugin/images/Picture1.png differ diff --git a/Plugins/Community Based Plugins/QR Code AiTM Phishing Detection Plugin/images/Picture2.png b/Plugins/Community Based Plugins/QR Code AiTM Phishing Detection Plugin/images/Picture2.png new file mode 100644 index 00000000..7afc6d66 Binary files /dev/null and b/Plugins/Community Based Plugins/QR Code AiTM Phishing Detection Plugin/images/Picture2.png differ diff --git a/Plugins/Community Based Plugins/QR Code AiTM Phishing Detection Plugin/images/Picture3.png b/Plugins/Community Based Plugins/QR Code AiTM Phishing Detection Plugin/images/Picture3.png new file mode 100644 index 00000000..25c79fe6 Binary files /dev/null and b/Plugins/Community Based Plugins/QR Code AiTM Phishing Detection Plugin/images/Picture3.png differ diff --git a/Plugins/Community Based Plugins/QR Code AiTM Phishing Detection Plugin/images/Picture4.png b/Plugins/Community Based Plugins/QR Code AiTM Phishing Detection Plugin/images/Picture4.png new file mode 100644 index 00000000..420f3f90 Binary files /dev/null and b/Plugins/Community Based Plugins/QR Code AiTM Phishing Detection Plugin/images/Picture4.png differ diff --git a/Plugins/Community Based Plugins/QR Code AiTM Phishing Detection Plugin/images/Picture5.png b/Plugins/Community Based Plugins/QR Code AiTM Phishing Detection Plugin/images/Picture5.png new file mode 100644 index 00000000..641edf50 Binary files /dev/null and b/Plugins/Community Based Plugins/QR Code AiTM Phishing Detection Plugin/images/Picture5.png differ diff --git a/Plugins/Community Based Plugins/QR Code AiTM Phishing Detection Plugin/readme.md b/Plugins/Community Based Plugins/QR Code AiTM Phishing Detection Plugin/readme.md new file mode 100644 index 00000000..eb81115e --- /dev/null +++ b/Plugins/Community Based Plugins/QR Code AiTM Phishing Detection Plugin/readme.md @@ -0,0 +1,47 @@ +# QR Code AiTM Phishing Detection Plugin + +## Introduction +Welcome to the QR Code AiTM Phishing Detection Plugin! This plugin enhances your cybersecurity efforts by detecting and mitigating QR code phishing attacks. Leveraging insights from Microsoft's Defender Experts, our plugin offers robust protection against these sophisticated phishing campaigns. + +## Overview +QR code AiTM (Adversary-in-the-Middle) phishing is a technique where attackers exploit QR codes to redirect users to fraudulent sites. Once users scan the QR code and authenticate, attackers can steal session tokens and perform malicious activities. This plugin helps identify and prevent such attacks by monitoring suspicious email patterns, sender attributes, and user behavior to ensure your organization stays secure. + +For more detailed information, refer to the full article [here](https://techcommunity.microsoft.com/t5/microsoft-security-experts-blog/hunting-for-qr-code-aitm-phishing-and-user-compromise/ba-p/4053324). + +## Prerequisites +- **Security Copilot instance** +- **Microsoft Defender for Endpoint** + +**Note:** This plugin is KQL-based and leverages hunting queries to hunt across Defender tables to identify risky behavior and suspicious activity. The tables include CloudAppEvents, EmailEvents, EmailAttachmentInfo, AADSignIn, etc. + +## Step-by-Step Guided Walkthrough +In this guide, we will provide high-level steps to get started using the new tooling. We will start by adding the custom plugin. +1. Go to [Security Copilot](https://securitycopilot.microsoft.com) +2. Download the QR Code AiTM Phishing Detection Plugin YAML File +3. Select the plugins icon in the bottom left corner + +![QR Code AiTM Phishing Detection Plugin](https://github.com/Azure/Copilot-For-Security/blob/main/Plugins/Community%20Based%20Plugins/Microsoft%20Security%20Experts/QR%20Code%20AiTM%20Phishing%20Detection%20Plugin/images/Picture1.png) + +4. Under Custom upload, select **Upload plugin** + +![QR Code AiTM Phishing Detection Plugin](https://github.com/Azure/Copilot-For-Security/blob/main/Plugins/Community%20Based%20Plugins/Microsoft%20Security%20Experts/QR%20Code%20AiTM%20Phishing%20Detection%20Plugin/images/Picture2.png) + +5. Select the Security Copilot plugin and upload the QR Code AiTM Phishing Detection Plugin + +![QR Code AiTM Phishing Detection Plugin](https://github.com/Azure/Copilot-For-Security/blob/main/Plugins/Community%20Based%20Plugins/Microsoft%20Security%20Experts/QR%20Code%20AiTM%20Phishing%20Detection%20Plugin/images/Picture3.png) + +6. Click **Add** +7. Under Custom, you will see the plugin +![QR Code AiTM Phishing Detection Plugin](https://github.com/Azure/Copilot-For-Security/blob/main/Plugins/Community%20Based%20Plugins/Microsoft%20Security%20Experts/QR%20Code%20AiTM%20Phishing%20Detection%20Plugin/images/Picture4.png) + + + +The custom package contains the following prompts : + + +under **QRCODE** you will find the following +![QR Code AiTM Phishing Detection Plugin](https://github.com/Azure/Copilot-For-Security/blob/main/Plugins/Community%20Based%20Plugins/Microsoft%20Security%20Experts/QR%20Code%20AiTM%20Phishing%20Detection%20Plugin/images/Picture5.png) + + + +**Let us get started using this together with the Security Copilot capabilities** diff --git a/Plugins/Community Based Plugins/Readme.md b/Plugins/Community Based Plugins/Readme.md index 0053795d..0bd59128 100644 --- a/Plugins/Community Based Plugins/Readme.md +++ b/Plugins/Community Based Plugins/Readme.md @@ -1,9 +1,9 @@ -![Copilot for Security Overview](https://github.com/Azure/Copilot-For-Security/blob/main/Images/ic_fluent_copilot_64_64%402x.png) +![Security Copilot Overview](https://github.com/Azure/Copilot-For-Security/blob/main/Images/ic_fluent_copilot_64_64%402x.png) # Disclaimer -- The plugins above were developed by the Community for Microsoft Security Copilot through contribtions. It's important to note that these plugins might not be entirely comprehensive or fully encapsulate the capabilities of third-party tools. For those interested in exploring a wider range of functionalities, access to both Vendor and Microsoft Published Plugins for Microsoft Copilot for Security proceed here: [**Published Plugins**](https://github.com/Azure/Copilot-For-Security/tree/main/Plugins/Published%20Plugins) +- The plugins above were developed by the Community for Microsoft Security Copilot through contribtions. It's important to note that these plugins might not be entirely comprehensive or fully encapsulate the capabilities of third-party tools. For those interested in exploring a wider range of functionalities, access to both Vendor and Microsoft Published Plugins for Microsoft Security Copilot proceed here: [**Published Plugins**](https://github.com/Azure/Copilot-For-Security/tree/main/Plugins/Published%20Plugins) -## Responsible AI Publishing Requirements for 3P Plugins for Microsoft Copilot for Security +## Responsible AI Publishing Requirements for 3P Plugins for Microsoft Security Copilot #### Responsible AI Publishing Requirements for 3P Plugins to GitHub: diff --git a/Plugins/Community Based Plugins/Redact PII/Manifest_RedactPIIGPT.yaml b/Plugins/Community Based Plugins/Redact PII/Manifest_RedactPIIGPT.yaml new file mode 100644 index 00000000..bc2f7e88 --- /dev/null +++ b/Plugins/Community Based Plugins/Redact PII/Manifest_RedactPIIGPT.yaml @@ -0,0 +1,30 @@ +Descriptor: + Name: RedactPIIGPT + DisplayName: Redact PII Skillset + Description: A GPT-based skillset for redacting PII from text/prompt output + +SkillGroups: + - Format: GPT + Skills: + - Name: RedactPII + DisplayName: Redact PII + Description: Redacts Personally Identifiable Information (PII) from the provided text + Inputs: + - Name: text + Description: The text that may contain PII to be redacted + Settings: + ModelName: gpt-4o + Template: |- + The goal of this task is to identify and redact any Personally Identifiable Information (PII) in the given text. PII may include email addresses, phone numbers, Social Security numbers, addresses, and other sensitive details. + + Examples of PII redaction: + 1. "Street address: 123, Chancellor Way, Springfield, VA 22154" --> "[REDACTED]" + 2. "Email: john.doe@woodgrove.ms" --> "Email: [REDACTED]@woodgrove.ms" + 3. "Phone: 555-123-4567" --> "Phone: [REDACTED]" + 4. "SSN: 123-77-4567" --> "SSN: [REDACTED]" + 5. "ZIP or postal code" --> "ZIP or postal code" + 6. "fc780465-2017-40d4-a0c5-307022471b92" --> "xxxx-yyyy-zzzz-aaaa-bbbb" + 7. "173.66.245.129" --> 173.***.***.*** + + Redact all PII from the following text and return the redacted version: + {{text}} diff --git a/Plugins/Community Based Plugins/Redact PII/Readme.md b/Plugins/Community Based Plugins/Redact PII/Readme.md new file mode 100644 index 00000000..1860bfdd --- /dev/null +++ b/Plugins/Community Based Plugins/Redact PII/Readme.md @@ -0,0 +1,52 @@ +# Redact Personally Identifiable Information + +## 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 + +--- + +## TYPE AND REQUIREMENTS +**TYPE**: GPT
+**SOURCE**: None
+**REQUIREMENTS**: None + +--- + +## SKILLS + + + + + + + + + + + + + + +
SkillNameDescriptionParameters
RedactPIIRedacts Personally Identifiable Information (PII) from the provided text +
    +
  • + text +
  • +
+
+ + +--- + +## SAMPLE PROMPTS + +- `« Redacts Personally Identifiable Information (PII) from the provided text: »` + +--- + +## SCREENSHOTS +
+ +
+ + diff --git a/Plugins/Community Based Plugins/SCU Changes/KQL_Plugin_SCU_Changes.yaml b/Plugins/Community Based Plugins/SCU Changes/KQL_Plugin_SCU_Changes.yaml new file mode 100644 index 00000000..ecb1d93a --- /dev/null +++ b/Plugins/Community Based Plugins/SCU Changes/KQL_Plugin_SCU_Changes.yaml @@ -0,0 +1,19 @@ +Descriptor: + Name: SCUChanges + DisplayName: SCU Changes + Description: Looks for SCU changes in the last 3 days + +SkillGroups: + - Format: KQL + Skills: + - Name: SCUChanges + DisplayName: SCU Changes + Description: Looks for SCU changes in the last 3 days + Settings: + Target: Sentinel + TenantId: + SubscriptionId: + ResourceGroupName: + WorkspaceName: + Template: |- + AzureActivity | where TimeGenerated >= ago(3d) | where ResourceProviderValue == "MICROSOFT.SECURITYCOPILOT" | extend resourceName = tostring(parse_json(Properties).resource) | extend resourceGroupName = tostring(parse_json(Properties).resourceGroup) | extend resourceActivity = tostring(parse_json(Properties).message) | where resourceActivity == "Microsoft.SecurityCopilot/capacities/write"| distinct Caller, CallerIpAddress, resourceName, resourceGroupName, resourceActivity diff --git a/Plugins/Community Based Plugins/SCU Changes/Readme.md b/Plugins/Community Based Plugins/SCU Changes/Readme.md new file mode 100644 index 00000000..5de05aa9 --- /dev/null +++ b/Plugins/Community Based Plugins/SCU Changes/Readme.md @@ -0,0 +1,20 @@ +# Security Copilot Custom Plugin to Track SCU Changes + +## Know about SCU changes directly in Security Copilot. + +### 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_SCU_Changes.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 Custom Plugin to Track SCU Changes](https://rodtrent.substack.com/p/copilot-for-security-custom-plugin) diff --git a/Plugins/Community Based Plugins/SCU_All-in-One/Readme.md b/Plugins/Community Based Plugins/SCU_All-in-One/Readme.md new file mode 100644 index 00000000..40c057f7 --- /dev/null +++ b/Plugins/Community Based Plugins/SCU_All-in-One/Readme.md @@ -0,0 +1,20 @@ +# Security Copilot Plugin: SCU All-in-One + +### **This plugin combines previous plugins into a single, all-in-one effort to capture and respond with Security Copilot activity. This plugin will be updated as additional monitoring activity is made available and identified.** + +### 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 SCU_All-in-One.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 Activity All-in-One Custom Plugin](https://rodtrent.substack.com/p/copilot-for-security-activity-all) diff --git a/Plugins/Community Based Plugins/SCU_All-in-One/SCU_All-in-One.yaml b/Plugins/Community Based Plugins/SCU_All-in-One/SCU_All-in-One.yaml new file mode 100644 index 00000000..ce679e50 --- /dev/null +++ b/Plugins/Community Based Plugins/SCU_All-in-One/SCU_All-in-One.yaml @@ -0,0 +1,19 @@ +Descriptor: + Name: CfSAllinOne + DisplayName: Copilot for Security Activity + Description: Looks for Copilot for Security Activity including SCU creation, changes and deletion and Standalone experience logins. Will be updated when new activities are discovered. + +SkillGroups: + - Format: KQL + Skills: + - Name: CfSAllinOne + DisplayName: Copilot for Security Activity + Description: Looks for Copilot for Security Activity including SCU creation, changes and deletion and Standalone experience logins. Will be updated when new activities are discovered. + Settings: + Target: Sentinel + TenantId: + SubscriptionId: + ResourceGroupName: + WorkspaceName: + Template: |- + let CfS_Logins = SigninLogs | where AppDisplayName == "Medeina Portal" | project TimeGenerated, Identity, Caller=UserPrincipalName, AppDisplayName, OperationName; let SCU_Changes = AzureActivity | where ResourceProviderValue == "MICROSOFT.SECURITYCOPILOT" | extend resourceName = tostring(parse_json(Properties).resource) | extend resourceGroupName = tostring(parse_json(Properties).resourceGroup) | extend resourceActivity = tostring(parse_json(Properties).message) | where resourceActivity == "Microsoft.SecurityCopilot/capacities/write"| project Caller, CallerIpAddress, resourceName, resourceGroupName, resourceActivity, TimeGenerated; let SCU_Deleted = AzureActivity | where ResourceProviderValue == "MICROSOFT.SECURITYCOPILOT" | extend resourceName = tostring(parse_json(Properties).resource) | extend resourceGroupName = tostring(parse_json(Properties).resourceGroup) | extend resourceActivity = tostring(parse_json(Properties).message) | where resourceActivity == "Microsoft.SecurityCopilot/capacities/delete" | project Caller, resourceName, resourceGroupName, resourceActivity, TimeGenerated; union CfS_Logins, SCU_Changes, SCU_Deleted diff --git a/Plugins/Community Based Plugins/SentinelDailyOperations/KQL_SentinelDailyOperationsSample.yml b/Plugins/Community Based Plugins/SentinelDailyOperations/KQL_SentinelDailyOperationsSample.yml deleted file mode 100644 index 8f2fece3..00000000 --- a/Plugins/Community Based Plugins/SentinelDailyOperations/KQL_SentinelDailyOperationsSample.yml +++ /dev/null @@ -1,31 +0,0 @@ -Descriptor: - Name: SentinelDailyOperations - DisplayName: Sentinel Daily Operations - Description: Sentinel Scenarios we use repeatedly for our daily operations. - -SkillGroups: - - Format: KQL - Skills: - - Name: GetAzureNetworkAnalyticsIPDetails - DisplayName: Get Azure Network Analytics IP Details - Description: Get all the threat information from azure network analytics logs from one day ago - Inputs: - - Name: region - Description: provide any specific location you want to consider - Required: true - Settings: - Target: Sentinel - TenantId: xxxxx - SubscriptionId: xxxx - ResourceGroupName: xxxx - WorkspaceName: xxxx - Template: |- - AzureNetworkAnalyticsIPDetails_CL - | where isnotempty(ThreatType_s) - | where ThreatType_s != '[]' - | where FlowIntervalStartTime_t > ago(1d) - | 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/SentinelOneEDR/SentinelOne_Manifest.yaml b/Plugins/Community Based Plugins/SentinelOneEDR/SentinelOne_Manifest.yaml new file mode 100644 index 00000000..79d5202e --- /dev/null +++ b/Plugins/Community Based Plugins/SentinelOneEDR/SentinelOne_Manifest.yaml @@ -0,0 +1,25 @@ +Descriptor: + Name: SentinelOne + DisplayName: SentinelOne EDR (Community) + Description: SentinelOne EDR + Settings: + - Description: The URL of the instance to connect to + Name: SentinelOneInstanceUrl + Label: SentinelOne Instance URL + HintText: e.g. https://.api.sentinelone.net + SettingType: String + Required: true + + SupportedAuthTypes: + - ApiKey + Authorization: + Type: APIKey + Key: Authorization + Location: Header + AuthScheme: 'APIToken' + +SkillGroups: + - Format: API + Settings: + OpenApiSpecUrl: https://gist.githubusercontent.com/Yaniv-Shasha/2896e9f9e76a56ce7ce6e7c657b92b80/raw/3fc396d1f26aede8ca1423f20b631bc1848a4ff0/s1OpenAPI.yaml + "EndpointUrlSettingName": "SentinelOneInstanceUrl" diff --git a/Plugins/Community Based Plugins/SentinelOneEDR/images/DeviceInfo_input.png b/Plugins/Community Based Plugins/SentinelOneEDR/images/DeviceInfo_input.png new file mode 100644 index 00000000..6d46e47b Binary files /dev/null and b/Plugins/Community Based Plugins/SentinelOneEDR/images/DeviceInfo_input.png differ diff --git a/Plugins/Community Based Plugins/SentinelOneEDR/images/application_input.png b/Plugins/Community Based Plugins/SentinelOneEDR/images/application_input.png new file mode 100644 index 00000000..da20344b Binary files /dev/null and b/Plugins/Community Based Plugins/SentinelOneEDR/images/application_input.png differ diff --git a/Plugins/Community Based Plugins/SentinelOneEDR/images/get_alerts_input.png b/Plugins/Community Based Plugins/SentinelOneEDR/images/get_alerts_input.png new file mode 100644 index 00000000..bb6d1806 Binary files /dev/null and b/Plugins/Community Based Plugins/SentinelOneEDR/images/get_alerts_input.png differ diff --git a/Plugins/Community Based Plugins/SentinelOneEDR/images/logo.png b/Plugins/Community Based Plugins/SentinelOneEDR/images/logo.png new file mode 100644 index 00000000..a02199b1 Binary files /dev/null and b/Plugins/Community Based Plugins/SentinelOneEDR/images/logo.png differ diff --git a/Plugins/Community Based Plugins/SentinelOneEDR/images/reputation_input.png b/Plugins/Community Based Plugins/SentinelOneEDR/images/reputation_input.png new file mode 100644 index 00000000..1f746b91 Binary files /dev/null and b/Plugins/Community Based Plugins/SentinelOneEDR/images/reputation_input.png differ diff --git a/Plugins/Community Based Plugins/SentinelOneEDR/images/skills_list.png b/Plugins/Community Based Plugins/SentinelOneEDR/images/skills_list.png new file mode 100644 index 00000000..ef63b3cc Binary files /dev/null and b/Plugins/Community Based Plugins/SentinelOneEDR/images/skills_list.png differ diff --git a/Plugins/Community Based Plugins/SentinelOneEDR/images/thread_input.png b/Plugins/Community Based Plugins/SentinelOneEDR/images/thread_input.png new file mode 100644 index 00000000..b3c0555e Binary files /dev/null and b/Plugins/Community Based Plugins/SentinelOneEDR/images/thread_input.png differ diff --git a/Plugins/Community Based Plugins/SentinelOneEDR/images/timeline_input.png b/Plugins/Community Based Plugins/SentinelOneEDR/images/timeline_input.png new file mode 100644 index 00000000..12c0e838 Binary files /dev/null and b/Plugins/Community Based Plugins/SentinelOneEDR/images/timeline_input.png differ diff --git a/Plugins/Community Based Plugins/SentinelOneEDR/images/upload_config.png b/Plugins/Community Based Plugins/SentinelOneEDR/images/upload_config.png new file mode 100644 index 00000000..09d0045f Binary files /dev/null and b/Plugins/Community Based Plugins/SentinelOneEDR/images/upload_config.png differ diff --git a/Plugins/Community Based Plugins/SentinelOneEDR/readme.md b/Plugins/Community Based Plugins/SentinelOneEDR/readme.md new file mode 100644 index 00000000..467259d7 --- /dev/null +++ b/Plugins/Community Based Plugins/SentinelOneEDR/readme.md @@ -0,0 +1,86 @@ +# SentinelOne EDR Custom Plugin +Welcome to the SentinelOne custom plugin! Here, you'll unlock powerful functionalities tailored to enhance your SenitnelOne experience: +### Instructions +#### Upload the Custom Plugin + +1. Obtain the file [SentinelOne_Manifest.yaml](https://github.com/Azure/Copilot-For-Security/blob/main/Plugins/Community%20Based%20Plugins/SentinelOneEDR/SentinelOne_Manifest.yaml) from this directory. +2. Upload the custom plugin +3. Add the SentinelOne Server instance Url, and API Key + +

+ +

+ + + +## Utilization of Plugins: + +- **Get_S1_alerts:** Get Alerts by Rule Name with SentinelOne
+ #ExamplePrompts Get Alerts by Rule Name with SentinelOne.
+ #ExamplePrompts Filter Alerts by Incident Status with SentinelOne.
+ #ExamplePrompts Search Alerts by Source File Path with SentinelOne.
+ #ExamplePrompts Limit Number of Returned Alerts with SentinelOne.
+ #ExamplePrompts Sort Alerts by Column with SentinelOne.
+ #ExamplePrompts Filter Alerts by Severity with SentinelOne.
+ #ExamplePrompts Search Alerts by Source Process Name with SentinelOne.
+ +

+ +

+ +- **Get_S1_DeviceInfo** Get the Agents, and their data, that match the filter. This command gives the Agent ID, which you can use in other commands.
+ #ExamplePrompts Retrieve agent information and their data from SentinelOne EDR product.
+ #ExamplePrompts Fetch agent details and related data with Sentinel One integration.
+ #ExamplePrompts Access agent data seamlessly with SentinelOne EDR.
+ #ExamplePrompts Gain insights into agent information through SentinelOne integration.
+ #ExamplePrompts Get agent IDs and related data for enhanced security operations with SentinelOne.
+ #ExamplePrompts Utilize SentinelOne EDR platform to retrieve agent information effortlessly.
+

+ +

+ +- **Get_S1_Application** Get the installed applications for a specific Agent with SentinelOne
+ #ExamplePrompts Retrieve the list of installed software on a designated device using SentinelOne.
+ #ExamplePrompts Gather data on the software installations linked to a specific agent utilizing
SentinelOne.
+ #ExamplePrompts Acquire information about the applications currently deployed on a designated machine via SentinelOne.
+ #ExamplePrompts Extract a catalog of installed programs from a specified agent's system using SentinelOne
. + #ExamplePrompts Access the inventory of installed applications tied to a particular SentinelOne agent.
+ #ExamplePrompts Fetch a report detailing the software installations associated with a specific SentinelOne endpoint.
+

+ +

+ +- **Get_S1_Threats** Get data of threats that match the filter. Best Use the filters. Each threat gives a number of data lines that will quickly fill the page limit.
+ #ExamplePrompts Retrieve threat data using SentinelOne's platform.
+ #ExamplePrompts Fetch threat information with SentinelOne integration.
+ #ExamplePrompts Access threat data that matches the filter using SentinelOne.
+ #ExamplePrompts Gather threat details efficiently by utilizing the filters with SentinelOne.
+ #ExamplePrompts Obtain threat data lines that quickly fill the page limit with SentinelOne.
+ #ExamplePrompts Utilize the 'Get_S1_Threats' operation in SentinelOne to retrieve threat data.
+

+ +

+ +- **Get_Hash_S1_Reputation** Get the reputation of a hash, given the required SHA1. To get hash, run "threats" (best if filtered for a Group or Site) and take the file Content Hash value.
+ #ExamplePrompts Retrieve the reputation of a hash using SentinelOne.
+ #ExamplePrompts Fetch the reputation score of a hash with SentinelOne integration.
+ #ExamplePrompts Access the reputation details for a specific hash using SentinelOne's platform.
+ #ExamplePrompts Gather the reputation information for a hash with the required SHA1 using SentinelOne.
+ #ExamplePrompts Obtain the reputation score for a hash, utilizing SentinelOne's reputation service.
+ #ExamplePrompts Utilize the 'Get_Hash_Reputation' operation in SentinelOne to retrieve the reputation of a hash.
+

+ +

+ +- **Get_S1_EventsTimeline** Get the timestamp of the first and last Events of the threat's attack storyline.
+ #ExamplePrompts Retrieve the timeline of events for a specific threat ID using SentinelOne.
+ #ExamplePrompts Fetch the first and last event timestamps of a threat's attack storyline with SentinelOne integration.
+ #ExamplePrompts Access the chronological sequence of events associated with a threat ID via SentinelOne's platform.
+ #ExamplePrompts Gather the timeline details, including the initial and final event timestamps, for a threat's attack storyline using SentinelOne.
+ #ExamplePrompts Obtain the event timeline for a threat, indicating the onset and conclusion of the attack with SentinelOne.
+ #ExamplePrompts Utilize the 'Get_S1_EventsTimeline' operation in SentinelOne to retrieve the timeline of events linked to a threat ID.
+

+ +

+ +Enhance your SentinelOne exploration with these powerful tools designed to streamline your search and analysis process. Let's dive in! 🚀
diff --git a/Plugins/Community Based Plugins/ServerAgentsAssessmentWithMDVM/Plugin-CfS-ServerAgentsAssessmentKQL.yaml b/Plugins/Community Based Plugins/ServerAgentsAssessmentWithMDVM/Plugin-CfS-ServerAgentsAssessmentKQL.yaml new file mode 100644 index 00000000..6ac9d9c4 --- /dev/null +++ b/Plugins/Community Based Plugins/ServerAgentsAssessmentWithMDVM/Plugin-CfS-ServerAgentsAssessmentKQL.yaml @@ -0,0 +1,58 @@ +Descriptor: + Name: ServerAgentsAssessment + DisplayName: Server Agents Assessment KQL Plugin + Description: Skills to read from Microsoft Defender Vulnerability Management (in Microsoft Defender XDR) the status and versions of the MDE, AMA and Azure Arc agents on the existing servers +SkillGroups: + - Format: KQL + Skills: + - Name: ServerAgentsAssessment + DisplayName: Get Server Agents Status and Versions + Description: Fetches the status and the versions of the MDE, AMA and Azure Arc agents on all the servers known by Defender + Settings: + Target: Defender + Template: |- + let DeviceInfo_latest = DeviceInfo + | where DeviceType == "Server" + | where OnboardingStatus == "Onboarded" + | where isnotempty(OSPlatform) + | extend MdeClientVersion = ClientVersion + | extend MdeClientType = case(ClientVersion startswith "10.3720", "MMA-Windows", ClientVersion startswith "10.8", "Unified-Windows","Other-OS") + | extend MdeOnboardingStatus = OnboardingStatus + | extend MdeSensorHealthState = SensorHealthState + | extend MdeExposureLevel = ExposureLevel + | summarize arg_max(Timestamp,*) by DeviceName; + let sw_arc = DeviceTvmSoftwareInventory + | extend ArcVersion = SoftwareVersion + | where SoftwareName =~ "azcmagent" or SoftwareName =~ "azure_connected_machine_agent"; + let sw_mma = DeviceTvmSoftwareInventory + | extend MmaVersion = SoftwareVersion + | where SoftwareName =~ "monitoring_agent"; + let sw_ama = DeviceTvmSoftwareInventory + | extend AmaVersion = SoftwareVersion + | where SoftwareName =~ "azuremonitoragent"; + let sw_mde = DeviceTvmSoftwareInventory + | extend MdeBuildNumber = SoftwareVersion + | where SoftwareName =~ "microsoft_defender_for_endpoint" or SoftwareName =~ "defender_for_linux"; + let sw_legacymde = DeviceTvmSoftwareInventory + | extend LegacyMdeBuildNumber = SoftwareVersion + | where SoftwareName =~ "defender_for_endpoint"; + let sw_oms = DeviceTvmSoftwareInventory + | extend OmsVersion = SoftwareVersion + | where SoftwareName =~ "omsagent_for_linux"; + DeviceInfo_latest + | join kind=leftouter sw_arc on DeviceName + | join kind=leftouter sw_mma on DeviceName + | join kind=leftouter sw_ama on DeviceName + | join kind=leftouter sw_legacymde on DeviceName + | join kind=leftouter sw_mde on DeviceName + | join kind=leftouter sw_oms on DeviceName + | project + DeviceName, OSDistribution, OSVersionInfo, MdeClientType, MdeClientVersion, + MdeBuildNumber=iif((MdeClientType == "Unified-Windows")and((OSDistribution endswith "2016")or(OSDistribution endswith "2012R2")),iif(LegacyMdeBuildNumber != "",LegacyMdeBuildNumber,"-"),iif(MdeBuildNumber != "",MdeBuildNumber,"-")), + LegacyMdeBuildNumber=iif((MdeClientType == "MMA-Windows"),iif(LegacyMdeBuildNumber != "",LegacyMdeBuildNumber,"-"),"-"), + ArcVersion=iif(ArcVersion != "",ArcVersion,"-"), + AmaVersion=iif(OSPlatform =~ "Linux",iif(AmaVersion != "",AmaVersion,"-"),"?"), + MmaVersion=iif(OSPlatform =~ "Linux",iif(OmsVersion != "",OmsVersion,"-"),iif(MmaVersion != "",MmaVersion,"-")), + PublicIP, + DeviceType, + MdeOnboardingStatus, MdeSensorHealthState, MdeExposureLevel \ No newline at end of file diff --git a/Plugins/Community Based Plugins/ServerAgentsAssessmentWithMDVM/README.md b/Plugins/Community Based Plugins/ServerAgentsAssessmentWithMDVM/README.md new file mode 100644 index 00000000..01abaac6 --- /dev/null +++ b/Plugins/Community Based Plugins/ServerAgentsAssessmentWithMDVM/README.md @@ -0,0 +1,33 @@ +# Security Copilot Custom Plugin - KQL - Server Agents Assessment with Defender Vulnerability Management + +The skill in this custom pluging is able to retrieve the versions of the Arc, AMA, MMA and MDE software components running on the Windows and Linux servers. The retrieval is done via KQL query made to the Advanced Hunting feature of Defender XDR on the servers' inventory data collected by Microsoft Defender Vulnerability Management. + +### Acronyms: +* Arc -> It's just Azure Arc +* AMA -> Azure Monitoring Agent +* MMA -> Microsoft Monitoring Agent +* MDE -> Microsoft Defender for Endpoint +* MDVM -> Microsoft Defender Vulnerability Management +* XDR -> eXtended Detection and Response + + +### Refereces of the versions of these components: +* MMA for Windows Server: https://learn.microsoft.com/en-us/azure/virtual-machines/extensions/oms-windows#agent-and-vm-extension-version +* MMA for Linux: https://learn.microsoft.com/en-us/azure/virtual-machines/extensions/oms-linux +* AMA for Windows Server and Linux: https://learn.microsoft.com/en-us/azure/azure-monitor/agents/azure-monitor-agent-extension-versions +* MDE for Windows Server: https://learn.microsoft.com/en-us/microsoft-365/security/defender-endpoint/windows-whatsnew?view=o365-worldwide +* MDE for Linux: https://learn.microsoft.com/en-us/microsoft-365/security/defender-endpoint/linux-whatsnew?view=o365-worldwide +* Arc for Windows Server and Linux: https://learn.microsoft.com/en-us/azure/azure-arc/servers/agent-release-notes + + +### Prerequisites: +* Microsoft Defender for Endpoint (MDE) or Microsoft Defender Vulnerability Management (MDVM) running on those servers (the software inventory is done by MDVM). + + +### Limitations: +Currently the skill is not able to retrieve the version of the AMA agent running on Windows Servers. + + +### Instructions: +1. Download the YAML file in this folder. Save it locally in your PC and open in for editing its content +2. Add the plugin by uploading the YAML in Security Copilot (https://learn.microsoft.com/en-us/copilot/security/manage-plugins?tabs=securitycopilotplugin) diff --git a/Plugins/Community Based Plugins/Shodan/Readme.md b/Plugins/Community Based Plugins/Shodan/Readme.md deleted file mode 100644 index 03b22871..00000000 --- a/Plugins/Community Based Plugins/Shodan/Readme.md +++ /dev/null @@ -1,26 +0,0 @@ -### Shodan Custom Plugin - -Welcome to the Shodan custom plugin! Here, you'll unlock powerful functionalities tailored to enhance your Shodan experience: -### Instructions -#### Upload the Custom Plugin - -1. Obtain the file [Shodan.yaml](https://github.com/Azure/Copilot-For-Security/blob/main/Plugins/Community%20Based%20Plugins/Shodan/Shodan.yaml) from this directory. -2. Upload the custom plugin -3. Add the Shodan API Key - -#### utilization of Plugins: - -- **ShodanGetServicesOnIP:** Obtain a comprehensive list of all services detected on a specified host IP address. - -- **ShodanGetHostResultCount:** Retrieve the total number of results matching a query, along with facet information, without consuming query credits. - -- **ShodanGetSearchSummary:** Utilize Shodan's query syntax to search for specific properties and generate summary information using facets. - -- **ShodanListFacets:** Access a list of search filters that can refine your search query for more precise results. - -- **ShodanListSearchFilters:** Explore a range of search filters available to further tailor your search parameters. - -- **ShodanSearchTokens:** Gain insights into the filters applied to your query string and the parameters associated with them. - -Enhance your Shodan exploration with these powerful tools designed to streamline your search and analysis process. Let's dive in! 🚀 - diff --git a/Plugins/Community Based Plugins/Shodan/Shodan.yaml b/Plugins/Community Based Plugins/Shodan/Shodan.yaml deleted file mode 100644 index eddcd154..00000000 --- a/Plugins/Community Based Plugins/Shodan/Shodan.yaml +++ /dev/null @@ -1,16 +0,0 @@ -Descriptor: - Name: Shodan - DisplayName: Shodan - Description: Shodan is a search engine scanning the entirety of the internet for connected devices. - SupportedAuthTypes: - - ApiKey - Authorization: - Type: APIKey - Key: key - Location: QueryParams - AuthScheme: '' - -SkillGroups: - - Format: API - Settings: - OpenApiSpecUrl: https://gist.github.com/jmf321/c70a93ab549dda5c7b97490ab3729ede/raw/740254168727fe3d87919460c58c02600eb12197/Shodan.txt \ No newline at end of file diff --git a/Plugins/Community Based Plugins/Shodan/shodanlogo.png b/Plugins/Community Based Plugins/Shodan/shodanlogo.png deleted file mode 100644 index a4a1cd2d..00000000 Binary files a/Plugins/Community Based Plugins/Shodan/shodanlogo.png and /dev/null differ diff --git a/Plugins/Community Based Plugins/VirusTotal/Readme.md b/Plugins/Community Based Plugins/VirusTotal/Readme.md deleted file mode 100644 index 0edd47e6..00000000 --- a/Plugins/Community Based Plugins/VirusTotal/Readme.md +++ /dev/null @@ -1,25 +0,0 @@ - -### VirusTotal Custom Plugin - -Welcome to the VirusTotal custom plugin! Here, you'll discover a suite of features designed to empower your malware analysis and threat intelligence efforts: -### Instructions -#### Upload the Custom Plugin - -1. Obtain the file [VirusTotal.yaml](https://github.com/Azure/Copilot-For-Security/blob/main/Plugins/Community%20Based%20Plugins/VirusTotal/VirusTotal.yaml) from this directory. -2. Upload the custom plugin -3. Add the Virus total API Key -#### Utlization of the plugin: - -- **VTDomainReport:** Receive a comprehensive summary report for a submitted domain, providing valuable insights into its reputation and associated threats. - -- **VTIPReport:** Obtain a detailed summary report for a submitted IP address, allowing you to assess its potential involvement in malicious activities. - -- **VTUrlSubmission:** Submit URLs for scanning, enabling VirusTotal to analyze them for potential threats and malicious content. - -- **VTGetUrlIDReport:** Retrieve the analysis results for a submitted URL by providing its corresponding ID obtained from VTUrlSubmission. - -- **VTDNSResolution:** Gain access to domain DNS resolution information, assisting in identifying potential malicious domains and their associated IP addresses. - -- **VTFileReport:** Submit file hashes for detailed reporting, allowing VirusTotal to analyze files and provide insights into their potential threats and behavior. - -Elevate your malware detection and threat analysis capabilities with these powerful tools. Let's defend against threats together! 🛡️ diff --git a/Plugins/Community Based Plugins/VirusTotal/VirusTotal.yaml b/Plugins/Community Based Plugins/VirusTotal/VirusTotal.yaml deleted file mode 100644 index a5a841a7..00000000 --- a/Plugins/Community Based Plugins/VirusTotal/VirusTotal.yaml +++ /dev/null @@ -1,16 +0,0 @@ -Descriptor: - Name: Virus Total Reports - DisplayName: Virus Total Reports - Description: Skills for looking up reports using the Free Virus Total API - SupportedAuthTypes: - - ApiKey - Authorization: - Type: APIKey - Key: x-apikey - Location: Header - AuthScheme: '' - -SkillGroups: - - Format: API - Settings: - OpenApiSpecUrl: https://gist.githubusercontent.com/jmf321/ad258c301ce9a6362583303b98a5bb9e/raw/d22b81a2fc12e027fce045c351d4487f2d88739b/vt1.yaml \ No newline at end of file diff --git a/Plugins/Community Based Plugins/VirusTotal/Virustotal logo.png b/Plugins/Community Based Plugins/VirusTotal/Virustotal logo.png deleted file mode 100644 index 19a45b5b..00000000 Binary files a/Plugins/Community Based Plugins/VirusTotal/Virustotal logo.png and /dev/null differ 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/GPT/GPT_based_expert_analysis_v1.yaml b/Plugins/MSFT_Plugin_Samples/GPT/GPT_based_expert_analysis_v1.yaml new file mode 100644 index 00000000..70fc210a --- /dev/null +++ b/Plugins/MSFT_Plugin_Samples/GPT/GPT_based_expert_analysis_v1.yaml @@ -0,0 +1,47 @@ +# Input Examples: +# Input field 1: IBM +# Input field 2: QRadar OCSF +# Input field 3: (copy raw file) https://github.com/ocsf/examples/blob/main/mappings/markdown/IBM/QRadar%20SIEM/samples/offense.ocsf +# +# Plugin updated on 2024-11-07 + +Descriptor: + Name: GPT Based Expert Analysis v1 + DisplayName: GPT Based Expert Analysis v1 + Description: This plugin serves as a temporary tool for manually analyzing and evaluating configurations, vulnerabilities, and other sensitive information that are not yet accessible through a dedicated plugin. The analysis focuses on functionality and its potential impact on operations, security, governance, risk, and compliance. + +SkillGroups: + - Format: GPT + Skills: + - Name: PerformGPTbasedExpertAnalysis + DisplayName: Perform a GPT based expert analysis + Description: Perform a GPT based expert analysis focused on OPSEC and GRC. + DescriptionForModel: Performs an expert analysis using GPT to assess and evaluate configurations, vulnerabilities, and other sensitive information. The output provides insights into functionality, potential impacts on operational performance, security posture, governance frameworks, risk levels, and compliance requirements. + Inputs: + - Name: personaExpert + Description: Provide a high-level reference to the type of expert persona that you'd like reviewing your data (e.g., Microsoft Azure, Cisco, Palo-Alto, Juniper Networks, IBM, etc.). + DefaultValue: null + Required: true + - Name: productNameOrFormatType + Description: What is the type of data that will be reviewed (e.g., Azure Policy JSON, ASA IOS configuration, Next-Generation Firewall (NGFW) XML, SRX Series Gateway Junos OS configuration, QRadar SIEM Log Event Extended Format (LEEF), OCSF, STIX, JSON, RegEx, etc.)? + DefaultValue: null + Required: true + - Name: productOutput + Description: Paste the data (configuration, rule, vulnerability, etc.), that you would like reviewed. + DefaultValue: null + Required: true + Settings: + ModelName: gpt-4o + Template: |- + Assume the role of an expert {{personaExpert}} engineer, specializing in security, engineering, operations, and risk management. Your task is to critically evaluate the following {{productNameOrFormatType}} output. + + For each major element, provide: + 1. A concise summary of its function and relevance. + 2. A detailed analysis of its potential impacts broken down by operations, security, governance, risk, and compliance. + + Additionally: + 3. Identify any concerns, vulnerabilities, or security risks, and recommend specific actions or best practices to mitigate these, referencing industry standards and {{personaExpert}} guidelines where relevant. + + 4. Ensure your review is thorough and offers clear, actionable insights to strengthen operational efficiency, security, governance, risk, and compliance posture." + + Data to review starts here: {{productOutput}} \ 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/Plugins/MSFT_Plugin_Samples/KQL/KQL_Combined_Defender_and_Sentinel_Example.yaml b/Plugins/MSFT_Plugin_Samples/KQL/KQL_Combined_Defender_and_Sentinel_Example.yaml new file mode 100644 index 00000000..6b15f367 --- /dev/null +++ b/Plugins/MSFT_Plugin_Samples/KQL/KQL_Combined_Defender_and_Sentinel_Example.yaml @@ -0,0 +1,194 @@ +# Spacing matters between parent and child objects. Hitting enter is fine, but tabular spaces will cause errors. +# Plugin updated 2024-11-08 + +Descriptor: + Name: Combined Defender and Sentinel Example Plugin + DisplayName: Combined Defender and Sentinel example plugin includes both Sentinel and Defender + Description: This combined KQL plugin integrates Defender and Sentinel capabilities, providing multiple configuration examples and detailed comments to guide users. Once configured you can invoke each skill directly or by using example prompts to retrieve high and critical Defender alerts from the past 7 days, fetch sign-in data based on UPN over a customizable time frame, and search Sentinel for all IP addresses a specific user has used to sign-in along with the frequency of each IP. + Icon: https://raw.githubusercontent.com/MicrosoftDocs/architecture-center/refs/heads/main/docs/_images/i-security.svg +# Source: https://github.com/MicrosoftDocs/architecture-center/blob/main/docs/_images/i-security.svg + + +############################################################################################# +# These settings will be prompted to the end user during plugin installation. +# Reference to values stored within the variables are used by Sentinel KQL queries. +############################################################################################# + + Settings: + - Name: TenantId + Description: Your Azure Tenant ID where the Sentinel workspace is in. + HintText: Enter your Azure Tenant ID + SettingType: string + Required: true + + - Name: SubscriptionId + Description: Your Azure Subscription ID where the Sentinel workspace is provisioned. + SettingType: string + Required: true + HintText: Enter your Azure Subscription ID. + + - Name: WorkspaceName + Description: The name Sentinel workspace that you'd like to reference. + SettingType: string + Required: true + HintText: Enter the Sentinel workspace that you'd like to reference. + + - Name: ResourceGroupName + Description: The Resource Group where the Sentinel workspace is provisioned.. + SettingType: string + HintText: Enter your Resource Group where the Sentinel workspace is provisioned. + Required: true + +##################################################################################### +# The 'SupportedAuthTypes' field set to 'None' is required for the plugin settings +# configuration window to present 'Settings' variable fields to the end user. +##################################################################################### + + SupportedAuthTypes: + - None + +##################################################################################### +# SkillGroups can be a single type (KQL, GPT, API) or contain a mix +##################################################################################### + +SkillGroups: + - Format: KQL + Skills: + +##################################################################################### +# Skill format +##################################################################################### +# +# - Name: uniqueSkillName +# DisplayName: Name of the skill that the user will see when manually selecting the skill +# Description: | +# Detailed description that the user will see when manually selecting a skill. +# This area can be multiple lines when using the pipe character after the 'Description' field. +# ExamplePrompts: +# - 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: 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: Defender, Sentinel, or Log Analytics +# Template: |- +# KQLtableYouAreReferencing +# //KQL comment to help the Orchestrator or users responsible for maintaining the code +# | KQLTable {{variableYouWantFromTheUserThatIsReferencedInYourKQL}} +# | Additional KQL content +# TemplateUrl: https://someDomain.com/template.txt // This can only be used when the 'Template' field is not in use. Using this allows for a single plugin to be deployed, while the KQL template continues to be updated over time. + +####################################################################################### +# KQL Example 1 - Defender KQL example with no user input required +# Defender KQL skills do not require configuring the TenantId, SubscriptionId, +# ResourceGroupName, or WorkspaceName but are limited to in their data storage. +####################################################################################### + + - Name: GetHighAndCriticalAlertsForTheLast7Days + DisplayName: Get high and critical alerts over the past 7 days + Description: | + Gets all high and critical Microsoft Defender alerts over the past 7 days + ExamplePrompts: + - Get high and critical Defender alerts for the last 7 days + - Get high and critical Microsoft Defender alerts for the last 7 days + - Get high and critical MDE alerts for the last 7 days + - Get high and critical Defender alerts for the last week + - Get high and critical Microsoft Defender alerts for the last week + - Get high and critical MDE alerts for the last week + - Get recent high and critical Defender alerts + - Get recent high and critical Microsoft Defender alerts + - Get recent high and critical MDE alerts + Settings: + Target: Defender + Template: |- + // This KQL query filters the AlertInfo table for high and critical alerts generated in the past 7 days, displaying only the AlertId, Title, and TimeGenerated fields. + AlertInfo + | where Severity in ('High', 'Critical') + | where TimeGenerated > ago(7d) + | project AlertId, Title, TimeGenerated + +####################################################################################### +# KQL Example 2 - Sentinel example with user input +# Sentinel KQL skills require configuring the TenantId, SubscriptionId, +# ResourceGroupName, and WorkspaceName. You can do this in-line and hard code +# the data, however a best practice is to simply reference this varable and have +# the user enter it in upon deployment. In the example below the Sentinel +# configuration is entered by the user. +####################################################################################### + + - Name: GetSigninData + DisplayName: Get sign-in data over the past n-days + Description: | + Gets Entra sign-in data for a user provided UPN between now and a user suppled day count. + ExamplePrompts: + - Get sign-in data for the last days + - Get login data for the last days + - Get Entra sign-in data for the last days + - Get Entra login data for the last days + Inputs: + - Name: dayRange + PlaceholderValue: The time range for the query (e.g., 1d, 7d) + Description: Time range to perform a KQL query in the format of day number followed by `d`. + DefaultValue: 1d + Required: false + Settings: +#===================================================================================== +# These 'Settings' are required when the 'Target' type uses Sentinel. Variables used +# within the curly brackets are refrences to those within 'Settings' area at the top +# of the plugin (line +#===================================================================================== + Target: Sentinel + TenantId: '{{TenantId}}' + SubscriptionId: '{{SubscriptionId}}' + ResourceGroupName: '{{ResourceGroupName}}' + WorkspaceName: '{{WorkspaceName}}' +#===================================================================================== + Template: |- + // This KQL query retrieves sign-in logs from the SigninLogs table for the past days, summarizing the most recent sign-in for each user by UPN, and then displays only the UPN and LastSignInDate fields. + SigninLogs + | where TimeGenerated >= ago({{dayRange}}) + | summarize LastSignInDate=max(TimeGenerated) by UserPrincipalName + | project UserPrincipalName, LastSignInDate + + +####################################################################################### +# KQL Example 3 - Sentinel example with hard coded configuration +# Sentinel KQL skills require configuring the TenantId, SubscriptionId, +# ResourceGroupName, and WorkspaceName. In this example I've hardcoded +# the Sentinel configuration with sample configuration information. +####################################################################################### + + - Name: ShowUserIPinfo + DisplayName: Show user IP addresses + Description: | + Lists unique IP logins for a given user and last login, searches for all IP addresses that a given user has ever used to authenticate, counts the frequency for each IP showing only unique results, and shows the the last time that IP was used. + ExamplePrompts: + - Use Sentinel and show all unique for all IP addresses for this user + - Show the unique IPs for user + - What are the unique IP addresses for user + Inputs: + - Name: UserPrincipalNameYouAreLookingUp + Description: User Principal Name to search for (e.g. user@mydomain.com) + Required: true +#===================================================================================== +# The following 'Settings' configuration uses a hardcoded configuration instead of +# those supplied by the user during set up of the plugin. This may be useful when +# creating plugins that require searching different workspaces and resource groups. +#===================================================================================== + Settings: + Target: Sentinel + TenantId: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + SubscriptionId: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + ResourceGroupName: yourSentinelResourceGroup + WorkspaceName: yourSentinelWorkspace + Template: |- + // This KQL query filters the SigninLogs table for entries associated with a specific user (UserPrincipalNameYouAreLookingUp), then aggregates the data to count the total sign-ins (Count) and determine the most recent sign-in time (LastUsed) for each unique `IPAddress` used by that user, displaying only the `IPAddress`, `Count`, and `LastUsed` fields. + SigninLogs + | where UserPrincipalName == "{{UserPrincipalNameYouAreLookingUp}}" + | summarize Count = count(), LastUsed = max(TimeGenerated) by IPAddress + | project IPAddress, Count, LastUsed diff --git a/Plugins/MSFT_Plugin_Samples/README.md b/Plugins/MSFT_Plugin_Samples/README.md index 3ccec5a7..836698ad 100644 --- a/Plugins/MSFT_Plugin_Samples/README.md +++ b/Plugins/MSFT_Plugin_Samples/README.md @@ -1,6 +1,6 @@ -![Copilot for Security Overview](https://github.com/Azure/Copilot-For-Security/blob/main/Images/ic_fluent_copilot_64_64%402x.png) +![Security Copilot Overview](https://github.com/Azure/Copilot-For-Security/blob/main/Images/ic_fluent_copilot_64_64%402x.png) -# Copilot for Security Guide to Create your Custom Plug-in +# Security Copilot Guide to Create your Custom Plug-in **Name of Plugin **Author: John DOE** @@ -23,12 +23,12 @@ Ensure you have enough API tokens before invoking any skills to avoid displeasur ![APIKey](https://learn.microsoft.com/en-us/security-copilot/media/plugin-button.png) -3. You’ll see a dialog box to enter your API name. Give your key a short, but descriptive name, such as "Copilot for Security API Key" +3. You’ll see a dialog box to enter your API name. Give your key a short, but descriptive name, such as "Security Copilot API Key" 4. Copy the API key to a safe place and then dispose carefully. -## Select or upload the attached manifest file into your Copilot for Security console +## Select or upload the attached manifest file into your Security Copilot console 1. Click on Deploy to Azure [![Deploy to Azure](https://aka.ms/deploytoazurebutton)](https://aka.ms/LINK) diff --git a/Plugins/Published Plugins/SGNL/readme.md b/Plugins/Published Plugins/SGNL/readme.md index 32c8aabf..076645df 100644 --- a/Plugins/Published Plugins/SGNL/readme.md +++ b/Plugins/Published Plugins/SGNL/readme.md @@ -2,7 +2,7 @@ **Author: Marc Jordan** **Publisher: SGNL** -This plugin enables SGNL Administrators to use Microsoft Copilot for Security to understand and identify fine-grained access decisions and trends across their enterprise. +This plugin enables SGNL Administrators to use Microsoft Security Copilot to understand and identify fine-grained access decisions and trends across their enterprise. [Technical Blog](https://sgnl.ai/2023/11/sgnl-for-microsoft-security-copilot/) @@ -10,11 +10,11 @@ This plugin enables SGNL Administrators to use Microsoft Copilot for Security to Ensure your account has sufficient permission to read the SGNL Access Decision Logs prior to proceeding. ## **Pre-requisites** -1. Authenticate to SGNL (via the Console or API) and retrieve your Tenant ID (Admin-->Environment) and your ID Token, you'll need these to configure Copilot for Security +1. Authenticate to SGNL (via the Console or API) and retrieve your Tenant ID (Admin-->Environment) and your ID Token, you'll need these to configure Security Copilot -## Select or upload the attached manifest file into your Copilot for Security console -1. From the plugin manager, locate the GreyNoise Enterprise Plugin and enable it +## Select or upload the attached manifest file into your Security Copilot console +1. From the plugin manager, locate the SGNL Plugin and enable it ![Enable Plugin](images/plugin-manager.png) diff --git a/Plugins/Published Plugins/Tanium/README.md b/Plugins/Published Plugins/Tanium/README.md index 81778806..b8bff16f 100644 --- a/Plugins/Published Plugins/Tanium/README.md +++ b/Plugins/Published Plugins/Tanium/README.md @@ -1,16 +1,16 @@ -## Configure Tanium integration with Copilot for Security +## Configure Tanium integration with Security Copilot -Establish communication between a Tanium instance and Copilot for Security using: +Establish communication between a Tanium instance and Security Copilot using: -- a Tanium plugin manifest URL to upload the Tanium Skills custom plugin to Copilot for Security +- a Tanium plugin manifest URL to upload the Tanium Skills custom plugin to Security Copilot -- a Tanium instance URL to connect Copilot for Security to the Tanium instance +- a Tanium instance URL to connect Security Copilot to the Tanium instance -- a Tanium API token to grant Copilot for Security permission to query your network using Tanium and gather endpoint data +- a Tanium API token to grant Security Copilot permission to query your network using Tanium and gather endpoint data -1. In a browser window, navigate to the Microsoft Copilot for Security home page. +1. In a browser window, navigate to the Microsoft Security Copilot home page. -2. Click Copilot for Security plugin. The Manage plugins modal window appears. +2. Click Security Copilot plugin. The Manage plugins modal window appears. ![image](https://github.com/shuhblam/copilot-for-security/assets/745064/ec05f712-7489-46a5-a2be-7576bca0b2f1) 3. In the Custom section, click Add plugin. The Add a plugin modal window appears. @@ -18,7 +18,7 @@ Establish communication between a Tanium instance and Copilot for Security using 4. Select Who can use this plugin?. -5. Click Copilot for Security plugin. +5. Click Security Copilot plugin. 6. Select the .json File type. @@ -26,26 +26,26 @@ Establish communication between a Tanium instance and Copilot for Security using 8. Select Modules > Connect > Overview. The Connect Overview page appears. -9. Click Settings. Then click Microsoft Copilot for Security. +9. Click Settings. Then click Microsoft Security Copilot. ![image](https://github.com/shuhblam/copilot-for-security/assets/745064/4c7c8e0d-6efc-48e2-874a-b6e6e022cde2) 10. Click Tanium Plugin Manifest URL Copy to copy the plugin manifest URL to the clipboard. -11. In Copilot for Security, select Upload as a link. Then paste the Tanium Plugin Manifest URL. +11. In Security Copilot, select Upload as a link. Then paste the Tanium Plugin Manifest URL. 12. Click Add. The Set up Tanium Skills modal window appears. ![image](https://github.com/shuhblam/copilot-for-security/assets/745064/82e9ad22-5031-4494-98dc-bafb4dc8971b) 13. In Tanium Console, click Tanium Instance URL Copy to copy the Tanium instance URL to the clipboard. -14. In Copilot for Security, paste the Tanium Instance URL in the Set up Tanium Skills modal window. +14. In Security Copilot, paste the Tanium Instance URL in the Set up Tanium Skills modal window. 15. In Tanium Console, click Generate to generate an API token, valid for 365 days, and copy the token value to the clipboard. > You cannot view the token value in Tanium Console. Paste the value in a plaintext editor and save the file in a secure location if you want to record it for future reference. -16. In Copilot for Security, paste the API token value in the Value field in the Set up Tanium Skills modal window. +16. In Security Copilot, paste the API token value in the Value field in the Set up Tanium Skills modal window. 17. Click Set up. diff --git a/Plugins/Published Plugins/ValenceSecurity/ValenceSecurity_manifest.yaml b/Plugins/Published Plugins/ValenceSecurity/ValenceSecurity_manifest.yaml deleted file mode 100644 index 3c9ec33c..00000000 --- a/Plugins/Published Plugins/ValenceSecurity/ValenceSecurity_manifest.yaml +++ /dev/null @@ -1,19 +0,0 @@ -Descriptor: - Name: ValenceSecurity - DisplayName: Valence Security - Description: Leverage the Valence SaaS Security Platform API to enhance your investigation with data from SaaS applications such as Salesforce, GitHub, Okta, Google Workspace and dozens others. Answer critical questions to understand your existing SaaS threats and suspicious activities, what access level your human and non-human identities have, where your business-critical data is potentially exposed, and what are the most significant SaaS risks you should prioritize to fix. - DescriptionDisplay: Find and fix your SaaS risks. Protect business-critical data and applications by streamlining incident investigations, and reducing SaaS identities, SaaS-to-SaaS integrations, data shares and misconfigurations risks. - Icon: https://app.valencesecurity.com/signin/symbol.png - SupportedAuthTypes: - - ApiKey - Authorization: - Type: ApiKey - AuthScheme: Bearer - Location: Header - Key: Authorization - Value: - -SkillGroups: - - Format: API - Settings: - OpenApiSpecUrl: https://app.valencesecurity.com/_074wktsp_microsoftsecuritycopilot/.well-known/swagger.yaml diff --git a/Plugins/Published Plugins/ValenceSecurity/images/ValenceSecurity_plugin.png b/Plugins/Published Plugins/ValenceSecurity/images/ValenceSecurity_plugin.png deleted file mode 100644 index 7fb72033..00000000 Binary files a/Plugins/Published Plugins/ValenceSecurity/images/ValenceSecurity_plugin.png and /dev/null differ diff --git a/Plugins/Published Plugins/ValenceSecurity/images/ValenceSecurity_settings.png b/Plugins/Published Plugins/ValenceSecurity/images/ValenceSecurity_settings.png deleted file mode 100644 index 22f6381f..00000000 Binary files a/Plugins/Published Plugins/ValenceSecurity/images/ValenceSecurity_settings.png and /dev/null differ diff --git a/Plugins/Published Plugins/ValenceSecurity/readme.md b/Plugins/Published Plugins/ValenceSecurity/readme.md deleted file mode 100644 index b74e77f4..00000000 --- a/Plugins/Published Plugins/ValenceSecurity/readme.md +++ /dev/null @@ -1,45 +0,0 @@ -# Valence Security -**Author: Shiv Patel + Shlomi Matichin + Lev Rado** - -**Publisher: Microsoft + Valence Security** - -This plugin helps users enhance investigations with data from SaaS applications such as Salesforce, GitHub, Okta, Google Workspace and dozens others: -1. Investigate user activity across multiple SaaS platforms -2. Create reports to understand a specific user's SaaS security posture - -Product Information: -https://www.valencesecurity.com/platform - -**Note** -Ensure you have a Valence Security subscription - -## **Pre-requisites** -1. Login to your [Valence Security Account](https://app.valencesecurity.com/) -2. Generate new API Key for plugin from [API Keys Setting](https://app.valencesecurity.com/settings/apikeys) -3. Copy the API key to enter Copilot. - - -## Select or upload the attached manifest file into your Copilot for Security console -1. From the Plugin manager, locate the Valence Security Plugin and enable it -![Enable Plugin](images/ValenceSecurity_plugin.png) - - ## Invoking the Plugin and Skills -2. **Important** - After selecting or uploading, click the "Set Up" button and enter the copied API key from the Pre-requisites above and press "Save." -![Enter Key](images/ValenceSecurity_settings.png) - -3. Use a Natural Language prompt from below examples or use Direct Skill Invocation ("/") - - -## Skills & Prompts -1. Investigate user actions across all SaaS platforms - - Example Prompt(s): "Which high privileged actions did [USER] take over the past day?", "Show me recent alerts for [USER]" - - Inputs: [USER] -2. Create a report for a user in all SaaS platforms - - Example Prompt(s): "Summarize [USER] security SaaS posture" - - Inputs: [USER] - - -## Frequently Asked Questions (FAQ) -1. **Important** - If prompts fail to invoke, please ensure you're using a supported Prompt above. Otherwise, invoke it using Direct Skill Invocation "/" diff --git a/Plugins/Readme.md b/Plugins/Readme.md index 744a483e..0e348905 100644 --- a/Plugins/Readme.md +++ b/Plugins/Readme.md @@ -1,10 +1,10 @@ -![Copilot for Security Overview](https://github.com/Azure/Copilot-For-Security/blob/main/Images/ic_fluent_copilot_64_64%402x.png) -# Microsoft Copilot for Security Plugins +![Security Copilot Overview](https://github.com/Azure/Copilot-For-Security/blob/main/Images/ic_fluent_copilot_64_64%402x.png) +# Microsoft Security Copilot Plugins Documentation : https://learn.microsoft.com/en-us/security-copilot/plugin_overview -The Microsoft Copilot for Security connector now supports custom plugins, boosting its third-party integration and automation capabilities. This document highlights custom plugin functionality, emphasizing bridging the gap for 3rd party connections to Copilot, enabling automation from the Standalone portal, and querying custom tables in KQL. +The Microsoft Security Copilot connector now supports custom plugins, boosting its third-party integration and automation capabilities. This document highlights custom plugin functionality, emphasizing bridging the gap for 3rd party connections to Copilot, enabling automation from the Standalone portal, and querying custom tables in KQL. -# Microsoft Copilot for Security Plugin Features +# Microsoft Security Copilot Plugin Features ### Custom Plugin Features @@ -14,18 +14,22 @@ The Microsoft Copilot for Security connector now supports custom plugins, boosti | Tailored Automation | Create custom automation workflows tailored to your organization's unique security processes and requirements. | | Enhanced Data Querying| Query custom tables in KQL (Kusto Query Language) to retrieve specific security data relevant to your investigations. | -# Copilot For Security Plugin Folders +# Security Copilot Plugin Folders **Please note:** We have three folders: -1. [**Published Plugins**](https://github.com/Azure/Copilot-For-Security/tree/main/Plugins/Published%20Plugins)- These are Microsoft certified Custom plugins for Microsoft Copilot for Security. -2. [**Community Based Plugins**](https://github.com/Azure/Copilot-For-Security/tree/main/Plugins/Community%20Based%20Plugins)- These are Plugins created and contibuted by the Microsoft Copilot for Security Community. -3. [**MSFT Plugin Samples**](https://github.com/Azure/Copilot-For-Security/tree/main/Plugins/MSFT_Plugin_Samples)-These are samples of Custom Plugins that will act as a guide you and give you the ability to create a custom plugin for Copilot For Security. The samples are API, GPT, and KQL samples. +1. [**Published Plugins**](https://github.com/Azure/Copilot-For-Security/tree/main/Plugins/Published%20Plugins)- These are Microsoft certified Custom plugins for Microsoft Security Copilot. +2. [**Community Based Plugins**](https://github.com/Azure/Copilot-For-Security/tree/main/Plugins/Community%20Based%20Plugins) - These are Plugins created and contibuted by the Microsoft Security Copilot Community. +3. [**MSFT Plugin Samples**](https://github.com/Azure/Copilot-For-Security/tree/main/Plugins/MSFT_Plugin_Samples) - These are samples of Custom Plugins that will act as a guide you and give you the ability to create a custom plugin for Security Copilot. The samples are API, GPT, and KQL samples. -Should you be interested in participating in a workshop designed to instruct on the creation of custom plugins, you are welcome to access our [Technical workshops](https://github.com/Azure/Copilot-For-Security/tree/main/Technical%20Workshops) +Should you be interested in participating in a workshop designed to instruct on the creation of custom plugins, you are welcome to access our [Technical workshops](https://github.com/Azure/Copilot-For-Security/tree/main/Technical%20Workshops) +### Microsoft Tech Community blog posts +1. [How to build a Security Copilot API Plugin – Part 1](https://techcommunity.microsoft.com/t5/microsoft-security-copilot-blog/how-to-build-a-copilot-for-security-api-plugin-part-1/ba-p/4107604) +2. [How to build a Security Copilot API Plugin – Part 2](https://techcommunity.microsoft.com/t5/microsoft-security-copilot-blog/how-to-build-a-copilot-for-security-api-plugin-part-2/ba-p/4163829) +3. [Using Microsoft Graph as a Microsoft Security Copilot Plugin with Delegated Access](https://techcommunity.microsoft.com/t5/microsoft-security-copilot-blog/using-microsoft-graph-as-a-microsoft-copilot-for-security-plugin/ba-p/4198148) 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/Email header analysis and investigation Promptbook.md b/Promptbook samples/Email header analysis and investigation Promptbook.md index 26c9d954..95073384 100644 --- a/Promptbook samples/Email header analysis and investigation Promptbook.md +++ b/Promptbook samples/Email header analysis and investigation Promptbook.md @@ -10,7 +10,7 @@ 1. Triage the email ``` -/AskGPT Can you triage the email header below and point out what you find suspicious? +Can you triage the email header below and point out what you find suspicious? ``` 2. Idenitfy technical indicators in the email header ``` @@ -40,6 +40,6 @@ What about the sender and return path emails are they associated with any intell ``` Hunt my environment and determine if any of the users clicked any URL in the email above ``` -9. Summarize the ivnestigation with certaity of suspicious elements +9. Summarize the investigation with certaity of suspicious elements ``` -/AskGPT Based on the above email investigation, summarize this investigation with all the investigation steps taken during this session. Also share supported evidence and let me know if this is a phishing email with certainty percentage: if the email is not suspicious give it 0-30% if it medium suspicious for phishing give it 30%-74% and if its high suspicious as phishing give it 75%-100%. Always include suspicious certainty percentage. +Based on the above email investigation, summarize this investigation with all the investigation steps taken during this session. Also share supported evidence and let me know if this is a phishing email with certainty percentage: if the email is not suspicious give it 0-30% if it medium suspicious for phishing give it 30%-74% and if its high suspicious as phishing give it 75%-100%. Always include suspicious certainty percentage. diff --git a/Promptbook samples/Entra Risky User investigation.md b/Promptbook samples/Entra Risky User investigation.md new file mode 100644 index 00000000..517a0898 --- /dev/null +++ b/Promptbook samples/Entra Risky User investigation.md @@ -0,0 +1,42 @@ +![Security CoPilot Logo](https://github.com/Azure/Copilot-For-Security/blob/main/Images/ic_fluent_copilot_64_64%402x.png) +# Entra Risky User investigation + +**Required plugins** : Microsoft Entra, Natural Language to KQL to Microsoft Defender XDR, Microsoft Defender XDR and Microsoft Defender Threat intelligence + +**Required Input**:User UPN: and Username: + + +**Description**: Entra Risky User investigation + +1. Generate a report using the Entra plugin to determine if the user identified by is considered risky. If the user is deemed risky, provide a detailed list of the reasons for this assessment. + ``` +Generate a report based on Entra plugin, Is the user considered risky? If yes, list the reasons. + ``` +2. Determine and list the authentication methods that are currently enabled for the user identified by . + ``` +What authentication methods are enabled for the user ? + ``` +3. Retrieve and list all login attempts made by the user identified by within the past 30 days. Include the corresponding IP addresses used for each login attempt. + ``` +Retrieve and list all login attempts made by the user within the past 30 days, along with the corresponding IP addresses used for each login attempt. + ``` +4. Provide the reputation of any IP addresses or hostnames identified in the login attempts of the user using MDTI +``` +Provide the reputation of any IPs or hostnames found +``` +5. If any IP address is found in the login attempts of the user , validate its association with any known threat actors using Microsoft Defender Threat intelligence. +``` +If any IP address is found, validate its association with any known Threat actor? +``` +6. List all the groups that the user identified by is a member of. +``` +List the groups user is part of +``` +7. Recommend remediation steps to mitigate the risk associated with the user identified by . +``` +Recommend remediation steps to mitigate the user risk +``` +8. Generate an executive report summarizing insights on a potentially risky user. +``` +Generate an executive report summarizing insights on a potentially risky user. Commence with metadata details for the user, encompassing their User Principal Name (UPN), suspected country of origin, configured authentication method, and any recorded failed login attempts. Follow this with a bullet-point list outlining key areas requiring remediation to address the identified risks. Subsequently, craft a coherent paragraph elucidating mitigation strategies aimed at ameliorating the highlighted concerns +``` diff --git a/Promptbook samples/High Blast Radius User investigation.md b/Promptbook samples/High Blast Radius User investigation.md new file mode 100644 index 00000000..23888f95 --- /dev/null +++ b/Promptbook samples/High Blast Radius User investigation.md @@ -0,0 +1,45 @@ +![Security CoPilot Logo](https://github.com/Azure/Copilot-For-Security/blob/main/Images/ic_fluent_copilot_64_64%402x.png) +# High Blast Radius User investigation + +**Required plugins** : Investigate anomalous activities by high blast radius user (custom KQL), AbuseIPDB, Microsoft Intune, Cybersixgill Threat Intelligence and Rare Processes Launched as a Service (custom KQL) + +**Required Input** None + + +**Description**: This promptbook investigates or hunts for evidence of possible identity compromise of high blast radius users + + +1. Leveraging UEBA retrieve a list of users who have a high blast radius and have recently performed anomalous activities + ``` +show me users who have a high blast radius and have recently performed anomalous activities + ``` +2. Identify the user with the highest priority for further investigation and provide an explanation for that conclusion. + ``` + Using Hunt for anomalous activities by high blast radius accounts plugin, which specific user from the above prompt should I focus on and why? + + ``` +3. Obtain IP reputation data for the public IP address used to perfom the anomalous activities + ``` +Using AbuseIPDB, tell me about the IP address used by the user + ``` +4. Identify any managed devices assigned to the user so as to further investigate suspicious activities that may have been performed on that device +``` +what device is assigned to this user +``` +5. Tap into Cybersixgill Threat intelligence to uncover whether the user id has been compromised and is on sale in the dark or deep web. +``` +According to Cybersixgill, tell whether this user's credentials have been compromised. +``` +6. Establish the policy compliance state of the device used by the user. +``` +tell me about the compliance state of the managed device. +``` +7. Check for any suspicious processes that may have been executed on the machine in the recent past +``` +have there been any rare processes executed on this device over the past 7 days ? +``` +8. Obtain a final summary of the investigation, pulling together insights gleaned from all the previous prompts and various security solutions +``` +/AskGPT Summarize this investigation. Include a verdict whether threat hunting is required and why. If yes suggest specific, follow up actions. +``` + diff --git a/Promptbook samples/Network URI Encoded Analysis.md b/Promptbook samples/Network URI Encoded Analysis.md new file mode 100644 index 00000000..fcacdcd4 --- /dev/null +++ b/Promptbook samples/Network URI Encoded Analysis.md @@ -0,0 +1,32 @@ +![Security CoPilot Logo](https://github.com/Azure/Copilot-For-Security/blob/main/Images/ic_fluent_copilot_64_64%402x.png) + +# Network Analysis - URI Request + +Inspired by [GreyNoise Sift](https://www.greynoise.io/blog/introducing-sift-automated-threat-hunting) and the possibilities to help in a number of areas where Layers 4-7 Web traffic is captured in SIEM and may need further analysis. Whether analyzing a URI, web traffic received, or using a Natural Language Prompt to KQL to provide further insights from Azure Web App Firewall among other use cases. + + +Disclaimer: Please know these are sample prompts and are subject to Change + + + + +**Required plugins** : Microsoft Defender Threat intelligence, Microsoft Defender XDR + +**Required Input**:< AppServer > + +**Required Input**:< URIRequest > + +**Description**: Analyze a App Server Request, check the URI parameters passed, indicate if this is related a to a known CVE, and methods to address the CVE on the App Server. + +1. Analyze the URI Request and parameters from App Server + ``` + A web server recieved a web call , please decode what is being passed in the URI parameters and explain what this URI with parameters is doing in each step + ``` +2. Idenitfy any known CVEs based on web request + ``` +Are there any known CVEs associated with this exploit ? + ``` +3. Identify a mitigation plan for the CVE on Web App Server + ``` +Can you provide a remediation plan with actionable steps for this exploit and the CVEs above + ``` diff --git a/Promptbook samples/Powershell to Manage Promptbooks/PromptBooksDownload.ps1 b/Promptbook samples/Powershell to Manage Promptbooks/PromptBooksDownload.ps1 deleted file mode 100644 index 768c7ad8..00000000 --- a/Promptbook samples/Powershell to Manage Promptbooks/PromptBooksDownload.ps1 +++ /dev/null @@ -1,93 +0,0 @@ -# Set the execution policy if PowerShell execution is not allowed -# Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser -# Copy the token from the Authorization Header (use Browser Devtools) and pass as command line arguement -token - -#--------- Temp -#$jsonContent = Get-Content './Sample.json' -#----------------------- -#Replace the region in URI with your own "https://api.securitycopilot.microsoft.com/geo//promptbooks" - -param ( - [Parameter(Mandatory=$true)][string]$token #Make it mandatory till upload can be done - ) - -$url = "https://api.securitycopilot.microsoft.com/geo/eastus/promptbooks" -$headers = @{ - "Content-Type" = "application/json" - "Authorization" = "Bearer $token" -} - - -Write-Host "Starting PromptBooks download ..." - -$response = Invoke-WebRequest -Uri $url -Method GET -Headers $headers -$jsonContent = $response.content -$fromJson = $jsonContent | ConvertFrom-Json - -Write-Host "Number of promptbooks Downloaded: $($fromJson.value.Count)" #$jsonContent - -# Iterate for each promptbook in JSON -$folder = './Downloads/' -foreach ($pb in $fromJson.value) { - - $fileName = $pb.name + ".json" - # Remove all special chars from filename - $fileName= $fileName.replace("'","") - $fileName= $fileName.replace("(","") - $fileName= $fileName.replace(")","") - $fileName= $fileName.replace("[","-") - $fileName= $fileName.replace("]","-") - $fileName= $fileName.replace(' ','_') - $fileName = $folder + $fileName - - # Remove II fields for security reasons - $pb.visibility = "Private" #Change all to private which can be changed later on - $pb.PSObject.Properties.Remove('promptbookId') - $pb.PSObject.Properties.Remove('createdAt') - $pb.PSObject.Properties.Remove('updatedAt') - $pb.PSObject.Properties.Remove('workspaceId') - $pb.PSObject.Properties.Remove('userId') - $pb.PSObject.Properties.Remove('tenantId') - $pb.PSObject.Properties.Remove('ownerName') - foreach ($p in $pb.prompts) # Update data for each prompt - { - $p.PSObject.Properties.Remove('promptbookPromptId') - $p.PSObject.Properties.Remove('plugins') - } - - $json = $pb | ConvertTo-Json - #Write-Host $json - "$json" | out-file -filepath $fileName #-encoding utf8NoBOM - - Write-Host "Written file $($fileName)" -} - -Exit - - -#------------ -param ( - [Parameter(Mandatory=$true)][string]$token #Make it mandatory till upload can be done - ) - -#Replace the region in URI with your own "https://api.securitycopilot.microsoft.com/geo//promptbooks" -$url = "https://api.securitycopilot.microsoft.com/geo/eastus/promptbooks" -$headers = @{ - "Content-Type" = "application/json" - "Authorization" = "Bearer $token" -} - -# Get all files in the Files folder -$allFiles = Get-ChildItem -Path "./Files" - -Write-Host "Starting PromptBooks upload ..." - -# Loop through each file and upload it via a POST call -foreach ($file in $allFiles) { - Write-Host "Uploading: " + $file.fullName - $jsonContent = Get-Content $file.fullName - $statusCode = Invoke-WebRequest -Uri $url -Method POST -Body $jsonContent -Headers $headers - Write-Output $statusCode.StatusDescription -} - -#az logout # i using Azure CLI diff --git a/Promptbook samples/Powershell to Manage Promptbooks/PromptbookDataschema.ps1 b/Promptbook samples/Powershell to Manage Promptbooks/PromptbooksDataschema.ps1 similarity index 90% rename from Promptbook samples/Powershell to Manage Promptbooks/PromptbookDataschema.ps1 rename to Promptbook samples/Powershell to Manage Promptbooks/PromptbooksDataschema.ps1 index cc0d8cde..0307e2ad 100644 --- a/Promptbook samples/Powershell to Manage Promptbooks/PromptbookDataschema.ps1 +++ b/Promptbook samples/Powershell to Manage Promptbooks/PromptbooksDataschema.ps1 @@ -1,3 +1,6 @@ +# This the Class definition for Promptbook related classes +# Can be used when strong types are needed + #---------------- Declare classes for strong typing class PromptBooks { diff --git a/Promptbook samples/Powershell to Manage Promptbooks/PromptbooksDownload.ps1 b/Promptbook samples/Powershell to Manage Promptbooks/PromptbooksDownload.ps1 new file mode 100644 index 00000000..af71a697 --- /dev/null +++ b/Promptbook samples/Powershell to Manage Promptbooks/PromptbooksDownload.ps1 @@ -0,0 +1,102 @@ +# Set the execution policy if PowerShell execution is not allowed +# Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser +# Copy the token from the Authorization Header (use Browser Devtools) and pass as command line arguement -token + +#--------- Temp +#$jsonContent = Get-Content './Sample.json' +#----------------------- +#Replace the region in URI with your own "https://api.securitycopilot.microsoft.com/geo//promptbooks" + +param ( + [Parameter(Mandatory=$true)][string]$token, #Make it mandatory till upload can be done + [Parameter(Mandatory=$false)][string]$namecontains #Only download promptbook where name contains phrase + ) + + +Write-Host "DISCLAIMER: This method to download Promptbooks uses unpublished API calls that are subject to change without notice. This is not an officially supported method from Microsoft and no guarantees are given that this method will work in your environment. Support tickets on its functionality cannot be raised." + + +$url = "https://api.securitycopilot.microsoft.com/geo/eastus/promptbooks" +$headers = @{ + "Content-Type" = "application/json" + "Authorization" = "Bearer $token" +} + + +Write-Host "Starting PromptBooks download ..." +Write-Host "Will only download promptbooks contains phrase: $namecontains " + + +$response = Invoke-WebRequest -Uri $url -Method GET -Headers $headers +$jsonContent = $response.content +$fromJson = $jsonContent | ConvertFrom-Json + +Write-Host "Number of promptbooks Downloaded: $($fromJson.value.Count)" #$jsonContent + +# Iterate for each promptbook in JSON +$folder = './Downloads/' +foreach ($pb in $fromJson.value) { + if($pb.name.contains($namecontains)) + { + $fileName = $pb.name + ".json" + # Remove all special chars from filename + $fileName= $fileName.replace("'","") + $fileName= $fileName.replace("(","") + $fileName= $fileName.replace(")","") + $fileName= $fileName.replace("[","-") + $fileName= $fileName.replace("]","-") + $fileName= $fileName.replace(' ','_') + $fileName = $folder + $fileName + + # Remove II fields for security reasons + $pb.visibility = "Private" #Change all to private which can be changed later on + $pb.PSObject.Properties.Remove('promptbookId') + $pb.PSObject.Properties.Remove('createdAt') + $pb.PSObject.Properties.Remove('updatedAt') + $pb.PSObject.Properties.Remove('workspaceId') + $pb.PSObject.Properties.Remove('userId') + $pb.PSObject.Properties.Remove('tenantId') + $pb.PSObject.Properties.Remove('ownerName') + foreach ($p in $pb.prompts) # Update data for each prompt + { + $p.PSObject.Properties.Remove('promptbookPromptId') + $p.PSObject.Properties.Remove('plugins') + } + + $json = $pb | ConvertTo-Json + #Write-Host $json + "$json" | out-file -filepath $fileName #-encoding utf8NoBOM + + Write-Host "Written file $($fileName)" + } +} + +Exit + + +#------------ +param ( + [Parameter(Mandatory=$true)][string]$token #Make it mandatory till upload can be done + ) + +#Replace the region in URI with your own "https://api.securitycopilot.microsoft.com/geo//promptbooks" +$url = "https://api.securitycopilot.microsoft.com/geo/eastus/promptbooks" +$headers = @{ + "Content-Type" = "application/json" + "Authorization" = "Bearer $token" +} + +# Get all files in the Files folder +$allFiles = Get-ChildItem -Path "./Files" + +Write-Host "Starting PromptBooks upload ..." + +# Loop through each file and upload it via a POST call +foreach ($file in $allFiles) { + Write-Host "Uploading: " + $file.fullName + $jsonContent = Get-Content $file.fullName + $statusCode = Invoke-WebRequest -Uri $url -Method POST -Body $jsonContent -Headers $headers + Write-Output $statusCode.StatusDescription +} + +#az logout # i using Azure CLI diff --git a/Promptbook samples/Powershell to Manage Promptbooks/PromptBooksUpload.ps1 b/Promptbook samples/Powershell to Manage Promptbooks/PromptbooksUpload.ps1 similarity index 68% rename from Promptbook samples/Powershell to Manage Promptbooks/PromptBooksUpload.ps1 rename to Promptbook samples/Powershell to Manage Promptbooks/PromptbooksUpload.ps1 index 3dee3630..89ee9e8c 100644 --- a/Promptbook samples/Powershell to Manage Promptbooks/PromptBooksUpload.ps1 +++ b/Promptbook samples/Powershell to Manage Promptbooks/PromptbooksUpload.ps1 @@ -7,6 +7,8 @@ [Parameter(Mandatory=$true)][string]$token #Make it mandatory till upload can be done ) +Write-Host "DISCLAIMER: This method to upload Promptbooks uses unpublished API calls that are subject to change without notice. This is not an officially supported method from Microsoft and no guarantees are given that this method will work in your environment. Support tickets on its functionality cannot be raised." + #Replace the region in URI with your own "https://api.securitycopilot.microsoft.com/geo//promptbooks" $url = "https://api.securitycopilot.microsoft.com/geo/eastus/promptbooks" $headers = @{ @@ -17,14 +19,14 @@ $headers = @{ # Get all files in the Files folder $allFiles = Get-ChildItem -Path "./Uploads" -Write-Output "Starting PromptBooks upload ..." +Write-Host "Starting PromptBooks upload ..." # Loop through each file and upload it via a POST call foreach ($file in $allFiles) { - Write-Output "Uploading: " + $file.fullName + Write-Host "Uploading: " + $file.fullName $jsonContent = Get-Content $file.fullName $statusCode = Invoke-WebRequest -Uri $url -Method POST -Body $jsonContent -Headers $headers - Write-Output $statusCode.StatusDescription + Write-Host $statusCode.StatusDescription } #az logout # i using Azure CLI diff --git a/Promptbook samples/Powershell to Manage Promptbooks/Uploads/3P-Plugins/Tanium_-_Asset_Inventory.json b/Promptbook samples/Powershell to Manage Promptbooks/Uploads/3P-Plugins/Tanium_-_Asset_Inventory.json new file mode 100644 index 00000000..9fd418a0 Binary files /dev/null and b/Promptbook samples/Powershell to Manage Promptbooks/Uploads/3P-Plugins/Tanium_-_Asset_Inventory.json differ diff --git a/Promptbook samples/Powershell to Manage Promptbooks/Uploads/3P-Plugins/Tanium_-_CVE_Search_with_Report.json b/Promptbook samples/Powershell to Manage Promptbooks/Uploads/3P-Plugins/Tanium_-_CVE_Search_with_Report.json new file mode 100644 index 00000000..db1d0589 Binary files /dev/null and b/Promptbook samples/Powershell to Manage Promptbooks/Uploads/3P-Plugins/Tanium_-_CVE_Search_with_Report.json differ diff --git a/Promptbook samples/Powershell to Manage Promptbooks/readme.md b/Promptbook samples/Powershell to Manage Promptbooks/readme.md index 54ed2505..972ff0a8 100644 --- a/Promptbook samples/Powershell to Manage Promptbooks/readme.md +++ b/Promptbook samples/Powershell to Manage Promptbooks/readme.md @@ -1,12 +1,17 @@ ![Security CoPilot Logo](https://github.com/Azure/Copilot-For-Security/blob/main/Images/ic_fluent_copilot_64_64%402x.png) -# Microsoft Copilot for Security PowerShell to manage Promptbooks + +**DISCLAIMER**: This method to upload Promptbooks uses unpublished API calls that are subject to change without notice. This is not an officially supported method from Microsoft and no guarantees are given that this method will work in your environment. Support tickets on its functionality cannot be raised. + +# Microsoft Security Copilot PowerShell to manage Promptbooks There are two different PowerShell scripts, one for upload one for download. # Downloading Promptbooks The PowerShell script 'PromptBooksDownload.ps1' allows you to download all your Promptbooks in your local disk inside the './Downloads' folder. The Promptbooks are stored in a JSON format and each Promptbook is in its own JSON file. Each file is given the promptbook name, with spaces and special characters removed. To download all the promptbooks execute the following command. -Powershell -file PromptBooksDownload.ps1 -token +Powershell -file PromptBooksDownload.ps1 -token -namecontains "CVE" + +The '-namecontains' parameter is optional. If specified, the script only downloads the promptbooks who's name contains the phrase specified by the '-namecontains' parameter ("CVE" in this case). Specify the phrase in double quotes. Description of the JSON format and instructions on obtaining the authorization token are given below. @@ -45,7 +50,7 @@ Several Promptbook JSON files have been provided in the Uploads folder so you ca # Access Token -Currently, the Azure CLI login command cannot fetch the token for Copilot for Security. Hence, you will need to get the access token from an existing session in the Browser. In the Microsoft Edge browser, login to the Copilot for Security console and after authentication process is complete via MFA etc, press 'F12' to open Browser devtools. +Currently, the Azure CLI login command cannot fetch the token for Security Copilot. Hence, you will need to get the access token from an existing session in the Browser. In the Microsoft Edge browser, login to the Security Copilot console and after authentication process is complete via MFA etc, press 'F12' to open Browser devtools. ![Browser Tools](https://github.com/Azure/Copilot-For-Security/blob/main/Images/Promptbook_images/promptBook_devtools.png) @@ -57,9 +62,9 @@ Note that you can get the token from other URIs in the Copilot console, going to # Execution for Download and Upload -Navigate to the folder where the 'PromptBooksDownload.ps1' and 'PromptBooksUpload.ps1' files are placed, same folder should have the 'Downloads' and 'Uploads' sub-folder. The 'Uploads' sub-folder should contain all the Promptbook JSON's you need to upload in your Copilot for Security instance. +Navigate to the folder where the 'PromptBooksDownload.ps1' and 'PromptBooksUpload.ps1' files are placed, same folder should have the 'Downloads' and 'Uploads' sub-folder. The 'Uploads' sub-folder should contain all the Promptbook JSON's you need to upload in your Security Copilot instance. -If your Copilot for Security instance is not in 'eastus' you will have to change the URI in line 11 so the URI contains the correct region. The upload will fail is this is not corrected. +If your Security Copilot instance is not in 'eastus' you will have to change the URI in line 11 so the URI contains the correct region. The upload will fail is this is not corrected. Execute the following command, pasting the token you copied from browser after the -token command line arguement. @@ -75,7 +80,7 @@ As the PowerShell executes it will output the file name and the status of each o ![PowerShell Successful execution](https://github.com/Azure/Copilot-For-Security/blob/main/Images/Promptbook_images/promptBook_results.png) -IMPORTANT: After the download/upload is successful, please logout of your Copilot for Security console so that the authorization token is no longer valid. While the token is valid for only a fixed amount of time, for security reasons you should do an explicit logout so the token cannot be used again. +IMPORTANT: After the download/upload is successful, please logout of your Security Copilot console so that the authorization token is no longer valid. While the token is valid for only a fixed amount of time, for security reasons you should do an explicit logout so the token cannot be used again. # Troubleshooting: 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/Promptbook samples/User Sign in Analysis and Investigation Promptbook.md b/Promptbook samples/User Sign in Analysis and Investigation Promptbook.md index 2fac5aff..2315db5e 100644 --- a/Promptbook samples/User Sign in Analysis and Investigation Promptbook.md +++ b/Promptbook samples/User Sign in Analysis and Investigation Promptbook.md @@ -9,7 +9,7 @@ 1. Retreive the history of the User you are investigating ``` -/AskGPT What is sign-in history? +What is sign-in history? ``` 2. retrieve infromation on any failed sign ins ``` @@ -49,4 +49,4 @@ When did the user last change their password? ``` 11. Summarizing the investigation ``` -/AskGpt can you summarize the above investigation and provide a conclusion and recommendation \ No newline at end of file +Can you summarize the above investigation and provide a conclusion and recommendation diff --git a/Promptbook samples/readme.md b/Promptbook samples/readme.md index aad6bdb9..93c746f8 100644 --- a/Promptbook samples/readme.md +++ b/Promptbook samples/readme.md @@ -1,7 +1,7 @@ ![Security CoPilot Logo](https://github.com/Azure/Copilot-For-Security/blob/main/Images/ic_fluent_copilot_64_64%402x.png) -# Microsoft Copilot for Security Sample Promptbooks Library +# Microsoft Security Copilot Sample Promptbooks Library -Promptbooks in Microsoft Copilot for Security contain one or more prompts that have been put together to accomplish specific security-related tasks. They run one prompt after another, building on previous responses. +Promptbooks in Microsoft Security Copilot contain one or more prompts that have been put together to accomplish specific security-related tasks. They run one prompt after another, building on previous responses. You can create your own promptbook with the promptbook builder to automate investigation flows and optimize repetitive steps in Copilot that’s customized to your needs and requirements. diff --git a/README.md b/README.md index f08c4be6..b78a4c4c 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ ![Security CoPilot Logo](https://github.com/Azure/Copilot-For-Security/blob/main/Images/ic_fluent_copilot_64_64%402x.png) -# Microsoft Copilot For Security Community -Welcome to the Copilot for Security Repository! +# Microsoft Security Copilot Community +Welcome to the Security Copilot Repository! -Microsoft Copilot for Security is a generative AI-powered assistant for daily operations in security and IT that empowers teams to protect at the speed and scale of AI. +Microsoft Security Copilot is a generative AI-powered assistant for daily operations in security and IT that empowers teams to protect at the speed and scale of AI. - https://learn.microsoft.com/en-us/security-copilot/ - https://learn.microsoft.com/en-us/security-copilot/get-started-security-copilot - https://learn.microsoft.com/en-us/security-copilot/plugin_overview @@ -31,7 +31,7 @@ Here are the steps you can take to begin contributing to this project: - Click "Fork" -> "Create a new fork" ![Github New Fork](https://github.com/Azure/Copilot-For-Security/blob/main/Images/CFSGithubfork.png) ![Create New Fork](https://github.com/Azure/Copilot-For-Security/blob/main/Images/CFSGithubforkCreate.png) -- This will create a copy of the Copilot for Security repository in your own GitHub account: +- This will create a copy of the Security Copilot repository in your own GitHub account: ![View Your New Fork](https://github.com/Azure/Copilot-For-Security/blob/main/Images/CFSGithubforknew.png) **Clone the fork to your local machine:**: @@ -109,4 +109,4 @@ Once the changes are merged to the main GitHub, you should be able to see change After your Pull Request has been merged, please check to make sure it got into the main repository. We recommend checking both in the GitHub website as well as by doing a sync to your local repo and verifying the changes are now in place. -**Good Luck and Happy Contributing to Copilot for Security Community !!!!** +**Good Luck and Happy Contributing to Security Copilot Community !!!!** diff --git a/SUPPORT.md b/SUPPORT.md index 291d4d43..eaf439ae 100644 --- a/SUPPORT.md +++ b/SUPPORT.md @@ -1,25 +1,25 @@ -# TODO: The maintainer of this repo has not yet edited this file - -**REPO OWNER**: Do you want Customer Service & Support (CSS) support for this product/project? - -- **No CSS support:** Fill out this template with information about how to file issues and get help. -- **Yes CSS support:** Fill out an intake form at [aka.ms/onboardsupport](https://aka.ms/onboardsupport). CSS will work with/help you to determine next steps. -- **Not sure?** Fill out an intake as though the answer were "Yes". CSS will help you decide. - -*Then remove this first heading from this SUPPORT.MD file before publishing your repo.* - -# Support - -## How to file issues and get help - -This project uses GitHub Issues to track bugs and feature requests. Please search the existing -issues before filing new issues to avoid duplicates. For new issues, file your bug or -feature request as a new Issue. - -For help and questions about using this project, please **REPO MAINTAINER: INSERT INSTRUCTIONS HERE -FOR HOW TO ENGAGE REPO OWNERS OR COMMUNITY FOR HELP. COULD BE A STACK OVERFLOW TAG OR OTHER -CHANNEL. WHERE WILL YOU HELP PEOPLE?**. - -## Microsoft Support Policy - -Support for this **PROJECT or PRODUCT** is limited to the resources listed above. +# TODO: The maintainer of this repo has not yet edited this file + +**REPO OWNER**: Do you want Customer Service & Support (CSS) support for this product/project? + +- **No CSS support:** Fill out this template with information about how to file issues and get help. +- **Yes CSS support:** Fill out an intake form at [aka.ms/onboardsupport](https://aka.ms/onboardsupport). CSS will work with/help you to determine next steps. +- **Not sure?** Fill out an intake as though the answer were "Yes". CSS will help you decide. + +*Then remove this first heading from this SUPPORT.MD file before publishing your repo.* + +# Support + +## How to file issues and get help + +This project uses GitHub Issues to track bugs and feature requests. Please search the existing +issues before filing new issues to avoid duplicates. For new issues, file your bug or +feature request as a new Issue. + +For help and questions about using this project, please **REPO MAINTAINER: INSERT INSTRUCTIONS HERE +FOR HOW TO ENGAGE REPO OWNERS OR COMMUNITY FOR HELP. COULD BE A STACK OVERFLOW TAG OR OTHER +CHANNEL. WHERE WILL YOU HELP PEOPLE?**. + +## Microsoft Support Policy + +Support for this **PROJECT or PRODUCT** is limited to the resources listed above. diff --git a/Sample Prompts/3P-Plugins/Tanium/readme.md b/Sample Prompts/3P-Plugins/Tanium/readme.md new file mode 100644 index 00000000..e9530c67 --- /dev/null +++ b/Sample Prompts/3P-Plugins/Tanium/readme.md @@ -0,0 +1,38 @@ + +# Tanium + +- [Tanium](#Tanium) + - [Endpoint Information](#Endpoint-Information) + - [CVE and Security Information](#CVE-and-Security-Information) + +*** +  +## Endpoint Information + +- Using Tanium return the installed applications in **_``_**. Display the results in a table, and return a Tanium Console Question Results URL so that I can view the real-time list of endpoints. +- Using Tanium, return the Driver Details of **_``_**. Display the results in a table, alphabetically sorted by computer name, and return a Tanium Console Question Results URL so that I can view the real-time list of endpoints. +- Using Tanium return the CPU Details of **_``_**. +- Using Tanium return the BIOS Vendor of **_``_**. +- Using Tanium return the BIOS Version of **_``_**. +- Using Tanium return Disk Drive Details of **_``_**. + + +  +[![alt text](../../../Images/backtotop.svg)](#Tanium) + +## CVE and Security Information +- Using Tanium, return the endpoints vulnerable to **_``_**, so that I can remediate the vulnerability on these endpoints. Display the results in a table, sorted alphabetically by host name, and return a Tanium Console Question Results URL so that I can view the real-time list of endpoints. +- Using Tanium Skills, examine the reasons why endpoint **_``_** is vulnerable to **_``_**, along with a suggested plan of action to remediate the vulnerability. +- Using Tanium, list all autoruns details on **_``_**. +- Create a summary report for this session +- Using Tanium, get real-time data for "Running Processes with MD5 Hash" from **_``_** + + +  +[![alt text](../../../Images/backtotop.svg)](#Tanium) + + +  +**Disclaimer**: Please know these are sample prompts and are subject to Change + +*** diff --git a/Sample Prompts/Ciso Experience Sample Prompts/Prompt pics/CISO 2 prommpt 1.png b/Sample Prompts/Ciso Experience Sample Prompts/Prompt pics/CISO 2 prommpt 1.png deleted file mode 100644 index cb383928..00000000 Binary files a/Sample Prompts/Ciso Experience Sample Prompts/Prompt pics/CISO 2 prommpt 1.png and /dev/null differ diff --git a/Sample Prompts/Ciso Experience Sample Prompts/Prompt pics/CISO 2 prommpt 2.png b/Sample Prompts/Ciso Experience Sample Prompts/Prompt pics/CISO 2 prommpt 2.png deleted file mode 100644 index 6f2702ba..00000000 Binary files a/Sample Prompts/Ciso Experience Sample Prompts/Prompt pics/CISO 2 prommpt 2.png and /dev/null differ diff --git a/Sample Prompts/Ciso Experience Sample Prompts/Prompt pics/CISO 2 prommpt 3.png b/Sample Prompts/Ciso Experience Sample Prompts/Prompt pics/CISO 2 prommpt 3.png deleted file mode 100644 index 9d892fd1..00000000 Binary files a/Sample Prompts/Ciso Experience Sample Prompts/Prompt pics/CISO 2 prommpt 3.png and /dev/null differ diff --git a/Sample Prompts/Ciso Experience Sample Prompts/Prompt pics/CISO 2 prommpt 4.png b/Sample Prompts/Ciso Experience Sample Prompts/Prompt pics/CISO 2 prommpt 4.png deleted file mode 100644 index 51826037..00000000 Binary files a/Sample Prompts/Ciso Experience Sample Prompts/Prompt pics/CISO 2 prommpt 4.png and /dev/null differ diff --git a/Sample Prompts/Ciso Experience Sample Prompts/Prompt pics/CISO 2 prommpt 5.png b/Sample Prompts/Ciso Experience Sample Prompts/Prompt pics/CISO 2 prommpt 5.png deleted file mode 100644 index 0ad50a87..00000000 Binary files a/Sample Prompts/Ciso Experience Sample Prompts/Prompt pics/CISO 2 prommpt 5.png and /dev/null differ diff --git a/Sample Prompts/Ciso Experience Sample Prompts/Prompt pics/CISO 2 prommpt 6.png b/Sample Prompts/Ciso Experience Sample Prompts/Prompt pics/CISO 2 prommpt 6.png deleted file mode 100644 index 59d93c1d..00000000 Binary files a/Sample Prompts/Ciso Experience Sample Prompts/Prompt pics/CISO 2 prommpt 6.png and /dev/null differ diff --git a/Sample Prompts/Ciso Experience Sample Prompts/Prompt pics/CISO 2 prommpt 7.png b/Sample Prompts/Ciso Experience Sample Prompts/Prompt pics/CISO 2 prommpt 7.png deleted file mode 100644 index 04972884..00000000 Binary files a/Sample Prompts/Ciso Experience Sample Prompts/Prompt pics/CISO 2 prommpt 7.png and /dev/null differ diff --git a/Sample Prompts/Ciso Experience Sample Prompts/Prompt pics/CISO 2 prommpt 8.png b/Sample Prompts/Ciso Experience Sample Prompts/Prompt pics/CISO 2 prommpt 8.png deleted file mode 100644 index 47345d4a..00000000 Binary files a/Sample Prompts/Ciso Experience Sample Prompts/Prompt pics/CISO 2 prommpt 8.png and /dev/null differ diff --git a/Sample Prompts/Ciso Experience Sample Prompts/Prompt pics/CISO 2 prommpt 9.png b/Sample Prompts/Ciso Experience Sample Prompts/Prompt pics/CISO 2 prommpt 9.png deleted file mode 100644 index b0966b25..00000000 Binary files a/Sample Prompts/Ciso Experience Sample Prompts/Prompt pics/CISO 2 prommpt 9.png and /dev/null differ diff --git a/Sample Prompts/Ciso Experience Sample Prompts/Prompt pics/Ciso Pic prompt 3.png b/Sample Prompts/Ciso Experience Sample Prompts/Prompt pics/Ciso Pic prompt 3.png deleted file mode 100644 index 267a7a99..00000000 Binary files a/Sample Prompts/Ciso Experience Sample Prompts/Prompt pics/Ciso Pic prompt 3.png and /dev/null differ diff --git a/Sample Prompts/Ciso Experience Sample Prompts/Prompt pics/Ciso pic prompt 2.png b/Sample Prompts/Ciso Experience Sample Prompts/Prompt pics/Ciso pic prompt 2.png deleted file mode 100644 index 7547536f..00000000 Binary files a/Sample Prompts/Ciso Experience Sample Prompts/Prompt pics/Ciso pic prompt 2.png and /dev/null differ diff --git a/Sample Prompts/Ciso Experience Sample Prompts/Prompt pics/ciso Pic prompt 1.png b/Sample Prompts/Ciso Experience Sample Prompts/Prompt pics/ciso Pic prompt 1.png deleted file mode 100644 index 2a5849fa..00000000 Binary files a/Sample Prompts/Ciso Experience Sample Prompts/Prompt pics/ciso Pic prompt 1.png and /dev/null differ diff --git a/Sample Prompts/Ciso Experience Sample Prompts/Prompt pics/ciso Pic prompt 4.png b/Sample Prompts/Ciso Experience Sample Prompts/Prompt pics/ciso Pic prompt 4.png deleted file mode 100644 index dd72433a..00000000 Binary files a/Sample Prompts/Ciso Experience Sample Prompts/Prompt pics/ciso Pic prompt 4.png and /dev/null differ diff --git a/Sample Prompts/Ciso Experience Sample Prompts/Prompt pics/ciso Pic prompt 5 2.png b/Sample Prompts/Ciso Experience Sample Prompts/Prompt pics/ciso Pic prompt 5 2.png deleted file mode 100644 index 284ed72b..00000000 Binary files a/Sample Prompts/Ciso Experience Sample Prompts/Prompt pics/ciso Pic prompt 5 2.png and /dev/null differ diff --git a/Sample Prompts/Ciso Experience Sample Prompts/Prompt pics/ciso Pic prompt 5.png b/Sample Prompts/Ciso Experience Sample Prompts/Prompt pics/ciso Pic prompt 5.png deleted file mode 100644 index 8dddd684..00000000 Binary files a/Sample Prompts/Ciso Experience Sample Prompts/Prompt pics/ciso Pic prompt 5.png and /dev/null differ diff --git a/Sample Prompts/Ciso Experience Sample Prompts/Scenario 1/readme.md b/Sample Prompts/Ciso Experience Sample Prompts/Scenario 1/readme.md deleted file mode 100644 index 76ad6057..00000000 --- a/Sample Prompts/Ciso Experience Sample Prompts/Scenario 1/readme.md +++ /dev/null @@ -1,32 +0,0 @@ -## Microsoft Copilot For Security -![Security CoPilot Logo](https://github.com/Azure/Copilot-For-Security/blob/main/Images/ic_fluent_copilot_64_64%402x.png) - -# CISO EXPERIENCE WITH COPILOT FOR SECURITY - -Chief Information Security Officers (CISOs) leverage the Microsoft Security Copilot to fortify their organization's cybersecurity posture. Proficient in the Microsoft Security Stack, including Azure Sentinel and Microsoft Defender for Endpoint, CISOs utilize this platform for advanced threat intelligence and script analysis. The collaborative features empower CISOs to foster a culture of security awareness and resilience within their teams, effectively navigating evolving threats and enhancing overall security measures. - -# Scenario -A CISO aims to evaluate the security posture of the organization, examining threats, assessing vulnerabilities, and proactively devising strategies to address and resolve potential issues. This entails conducting comprehensive security reviews to gauge organizational performance and identify areas for improvement. The focus lies on identifying gaps, anticipating emerging threats, and formulating proactive measures to mitigate risks effectively. - -# Sample prompts -**Prompt 1:** I am a CISO and need to understand how well my organization is performing, can you please look at the last 30 days of defender incidents and let me know what threats we are facing and if we are prepared based upon incidents being closed or resolved? - -![CISO Experience Sample Prompt 1](https://github.com/Azure/Copilot-For-Security/raw/main/Sample%20Prompts/Ciso%20Experience%20Sample%20Prompts/Prompt%20pics/ciso%20Pic%20prompt%201.png) - -**Prompt 2:** Can you determine what threat actors are targeting my organization? - -![CISO Experience Sample Prompt 2](https://github.com/Azure/Copilot-For-Security/raw/main/Sample%20Prompts/Ciso%20Experience%20Sample%20Prompts/Prompt%20pics/Ciso%20pic%20prompt%202.png) - -**Prompt 3:** What can we do to prevent and protect the organization? What are some best practices we can implement and can you tell me if those practices are currently in place? - -![CISO Experience Sample Prompt 3](https://github.com/Azure/Copilot-For-Security/raw/main/Sample%20Prompts/Ciso%20Experience%20Sample%20Prompts/Prompt%20pics/Ciso%20Pic%20prompt%203.png) - -**Prompt 4:** What should my organization do first? Can you create a detailed plan with three phases for the next 90 days? - -![CISO Experience Sample Prompt 4](https://github.com/Azure/Copilot-For-Security/raw/main/Sample%20Prompts/Ciso%20Experience%20Sample%20Prompts/Prompt%20pics/ciso%20Pic%20prompt%204.png) - -**Prompt 5:** Can you create a detailed report summarizing this information that I could present to our board of directors. Please include a technical and non-technical version. - -![CISO Experience Sample Prompt 5](https://github.com/Azure/Copilot-For-Security/raw/main/Sample%20Prompts/Ciso%20Experience%20Sample%20Prompts/Prompt%20pics/ciso%20Pic%20prompt%205.png) - -![CISO Experience Sample Prompt 5](https://github.com/Azure/Copilot-For-Security/raw/main/Sample%20Prompts/Ciso%20Experience%20Sample%20Prompts/Prompt%20pics/ciso%20Pic%20prompt%205%20%202.png) diff --git a/Sample Prompts/Ciso Experience Sample Prompts/Scenario 2/readme.md b/Sample Prompts/Ciso Experience Sample Prompts/Scenario 2/readme.md deleted file mode 100644 index 93447d71..00000000 --- a/Sample Prompts/Ciso Experience Sample Prompts/Scenario 2/readme.md +++ /dev/null @@ -1,45 +0,0 @@ -## Microsoft Copilot For Security -![Security CoPilot Logo](https://github.com/Azure/Copilot-For-Security/blob/main/Images/ic_fluent_copilot_64_64%402x.png) - -# CISO EXPERIENCE WITH COPILOT FOR SECURITY - -## Scenario -A Chief Information Security Officer (CISO) seeks to identify the most pressing threats within an organization and develop strategies to prevent their recurrence. - -## Sample prompts - -**Prompt 1:** I am a CISO and what are the top five types of questions should I be asking to understand to understand the threats my organization is facing and how to prevent them from happening? - -![CISO Experience Sample Prompt](https://github.com/Azure/Copilot-For-Security/blob/main/Sample%20Prompts/Ciso%20Experience%20Sample%20Prompts/Prompt%20pics/CISO%202%20prommpt%201.png) - -**Prompt 2:** What are the most common threats targeting my industry financial services. - -![CISO Experience Sample Prompt 2](https://github.com/Azure/Copilot-For-Security/blob/main/Sample%20Prompts/Ciso%20Experience%20Sample%20Prompts/Prompt%20pics/CISO%202%20prommpt%202.png) - -**Prompt 3:** What threats and threat actors are targeting my organization by analyzing Defender Incidents. - -![CISO Experience Sample Prompt 3](https://github.com/Azure/Copilot-For-Security/blob/main/Sample%20Prompts/Ciso%20Experience%20Sample%20Prompts/Prompt%20pics/CISO%202%20prommpt%203.png) - -**Prompt 4:** looking at Intune data what are my organizations most critical assets and why? - -![CISO Experience Sample Prompt 4](https://github.com/Azure/Copilot-For-Security/blob/main/Sample%20Prompts/Ciso%20Experience%20Sample%20Prompts/Prompt%20pics/CISO%202%20prommpt%204.png) - -**Prompt 5:** I need to determine what users need to attend additional security awareness training. Over the last 90 days, what users have clicked on malicious URLs in emails:. - -![CISO Experience Sample Prompt 5](https://github.com/Azure/Copilot-For-Security/raw/main/Sample%20Prompts/Ciso%20Experience%20Sample%20Prompts/Prompt%20pics/ciso%20Pic%20prompt%205.png) - -**Prompt 6:** what information can you provide about jonaw@parkcity.alpineskihouse.co and create an email explaining that he needs to attend additional security awareness training because he clicked on the malicious URL in an email. - -![CISO Experience Sample Prompt 6](https://github.com/Azure/Copilot-For-Security/blob/main/Sample%20Prompts/Ciso%20Experience%20Sample%20Prompts/Prompt%20pics/CISO%202%20prommpt%206.png) - -**Prompt 7:** what type of topics should be emphasized in the security awareness training for Jonathan? - -![CISO Experience Sample Prompt 7](https://github.com/Azure/Copilot-For-Security/blob/main/Sample%20Prompts/Ciso%20Experience%20Sample%20Prompts/Prompt%20pics/CISO%202%20prommpt%207.png) - -**Prompt 8:** what should we be doing to stop and prevent threats and threat actors from targeting my organization? Please create a three phase plan alone with milestones. - -![CISO Experience Sample Prompt 8](https://github.com/Azure/Copilot-For-Security/blob/main/Sample%20Prompts/Ciso%20Experience%20Sample%20Prompts/Prompt%20pics/CISO%202%20prommpt%208.png) - -**Prompt 9:** using all of this information create a non-technical report I can present to my board of directors giving the state of my organization, threats we are facing, security awareness training that will be given and our plan to stop and prevent threats to the organization. - -![CISO Experience Sample Prompt 9](https://github.com/Azure/Copilot-For-Security/blob/main/Sample%20Prompts/Ciso%20Experience%20Sample%20Prompts/Prompt%20pics/CISO%202%20prommpt%209.png) diff --git a/Sample Prompts/Ciso Experience Sample Prompts/readme.md b/Sample Prompts/Ciso Experience Sample Prompts/readme.md deleted file mode 100644 index 13ee1b73..00000000 --- a/Sample Prompts/Ciso Experience Sample Prompts/readme.md +++ /dev/null @@ -1,10 +0,0 @@ -## Microsoft Copilot For Security -![Security CoPilot Logo](https://github.com/Azure/Copilot-For-Security/blob/main/Images/ic_fluent_copilot_64_64%402x.png) - -# CISO EXPERIENCE WITH COPILOT FOR SECURITY - -Microsoft Copilot for Security empowers Chief Information Security Officers (CISOs) to bolster their organization's cybersecurity defenses effectively. By providing a comprehensive suite of tools and functionalities, Copilot enables CISOs to identify, analyze, and mitigate potential cybersecurity threats. Through targeted prompts and prompts, CISOs can gain insights into their organization's security landscape, understand prevalent threats within their industry, and devise proactive strategies to safeguard critical assets. With Copilot's assistance, CISOs can navigate the evolving threat landscape with confidence, fostering a culture of security awareness and resilience within their teams. - -## Scenarios -1. [Scenario1](https://github.com/Azure/Copilot-For-Security/blob/main/Sample%20Prompts/Ciso%20Experience%20Sample%20Prompts/Scenario%201/readme.md) -2. [Scenario 2](https://github.com/Azure/Copilot-For-Security/blob/main/Sample%20Prompts/Ciso%20Experience%20Sample%20Prompts/Scenario%202/readme.md) diff --git a/Sample Prompts/Microsoft Entra/Readme.md b/Sample Prompts/Microsoft Entra/Readme.md index 16a2b822..7aa6d55c 100644 --- a/Sample Prompts/Microsoft Entra/Readme.md +++ b/Sample Prompts/Microsoft Entra/Readme.md @@ -4,11 +4,9 @@ - [Entra](#entra) - [User Details](#user-details) - [Audit Logs](#audit-logs) + - [Terms of Use Agreements](terms-of-use-agreements) - [Sign in Logs](#sign-in-logs) - - [Conditional Access Policies](#conditional-access-policies) - - [Access Reviews](#access-reviews) - - [Entitlement Management](#entitlement-management) - - [Privileged Access Management](#privileged-access-management) + ***   ## User Details @@ -32,6 +30,7 @@ reasons. - Tell me about security group <**_``_**? including who created it and how many members are in it. - What is the status of the user account for **_``_**? Is it locked out? - Show me the successfully deleted users in the last 7 days. +- Why was Lee Majors prompted for MFA?   [![alt text](../../Images/backtotop.svg)](#entra) @@ -54,7 +53,9 @@ reasons. ## Sign in Logs - - show me sign-in logs for the last 24 hours + - Show me sign-in logs for the last 24 hours + - Have there been any failed sign-ins during the last weekend? + - Tell me about Lee Majors most recent failed sign-in - Show me the sign-in logs for **_``_**? - Show me the sign-in logs of users who signed in from **_``_**? - Show me the sign-in logs from **_``_**? @@ -71,73 +72,24 @@ reasons.   [![alt text](../../Images/backtotop.svg)](#entra) -## Conditional Access Policies +## Terms of Use Agreements - - Show me all the Conditional Access policies in my tenant -- List conditional Access policies configured in my tenant -- List conditional Access policies configured in the tenant - - Show me all the Conditional Access policies in my tenant with state -- List conditional Access policies configured in my tenant with state -- Show me the details for this specific Conditional Access policy - - list conditional access policies created using template -- Get conditional access policy related to 'authentication' in my tenant -- Show me newly created conditional access policies from audit logs. + - List user terms of use agreements + - What are the terms of use agreements for this user + - List all the terms of use agreements in my tenant for this user + - Get all terms of use agreements acceptance for user + - Show me all agreement acceptances this user + - Show me all terms of use agreements of user + - List all TOU agreements of user + - Get my terms of use agreements + - Show my terms of use agreements   [![alt text](../../Images/backtotop.svg)](#entra) - -## Access Reviews - -- Get access review by **_``_**? -- Show access review by review **_``_**? -- Get all access review associated with -- Get all access review by **_``_**? -- show status of access review -- access review createdBy -- show settings of access review **_``_**? - - -  -[![alt text](../../Images/backtotop.svg)](#entra) - -## Entitlement Management - - -- Show details of an access package for **_``_**? -- Show objects which are incompatible for **_``_**? access package -- Show groups which are incompatible for **_``_**access package -- Show access package assignment requests in my tenant -- Show me the assignment activity for the **_` -* Deploy an Azure Logic App capable of handling Microsoft Copilot for Security prompts using the Logic app connector.
+* Deploy an Azure Logic App capable of handling Microsoft Security Copilot prompts using the Logic app connector.
#### Prerequisites To successfully complete this task, you must meet the following prerequisites:
-1. You need your own tenant and Microsoft Copilot for Security instance.
+1. You need your own tenant and Microsoft Security Copilot instance.
2. **Azure Subscription**: You need an active [Azure subscription](https://azure.microsoft.com/en-us/free/) to create and deploy a Logic App. 3. **Azure Resource Group**: Logic Apps are deployed within [Azure resource groups](https://docs.microsoft.com/en-us/azure/azure-resource-manager/management/manage-resource-groups). Ensure that you have an appropriate resource group created or create one during the deployment process. 4. **Access Permissions**: Ensure that you have the necessary [permissions](https://docs.microsoft.com/en-us/azure/role-based-access-control/overview) to create resources in the Azure subscription and resource group where you plan to deploy the Logic App. @@ -46,14 +46,14 @@ To successfully complete this task, you must meet the following prerequisites:', methods=['GET']) -def get_simple(data): - obj = ReflectorSimple(data,getIpAddr(request),request.user_agent.string) - response = jsonify(obj.getDict()) - return response - -@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 - -# ---------------- 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 c615df86..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 @@ -1,14 +1,13 @@ #Filename: API_Plugin_Reflection_GET_Params.yaml Descriptor: - Name: Elman's Reflection API plug-in using GET params - DisplayName: Elman's Reflection API plug-in using GET params - Description: Skills for getting a GET REST API call reflection based on parameters that were passed - + 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". 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_OAI_GET_Params.yaml - - - + OpenApiSpecUrl: http://172.13.112.25:5000/file/API_Plugin_Reflection_OAPI_GET_Params.yaml \ No newline at end of file diff --git a/Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_GET_Params_Auth_AAD.yaml b/Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_GET_Params_Auth_AAD.yaml new file mode 100644 index 00000000..a88ae76e --- /dev/null +++ b/Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_GET_Params_Auth_AAD.yaml @@ -0,0 +1,19 @@ +#Filename: API_Plugin_Reflection_GET_Params.yaml +Descriptor: + Name: Elman's Reflection API plug-in using GET params and Authorization + DisplayName: Elman's Reflection API plug-in using GET params + Description: Skills for getting a GET REST API call reflection based on parameters that were passed + SupportedAuthTypes: + - AAD + Authorization: + Type: AAD + EntraScopes: https://graph.microsoft.com/.default + +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_OAI_GET_Params_Auth_Header.yaml + + + diff --git a/Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_GET_Params_Auth_Data.yaml b/Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_GET_Params_Auth_Data.yaml index d22be613..8b252e83 100644 --- a/Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_GET_Params_Auth_Data.yaml +++ b/Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_GET_Params_Auth_Data.yaml @@ -8,14 +8,14 @@ Descriptor: Authorization: Type: APIKey Key: Authorization - Location: Header + Location: QueryParams AuthScheme: '' 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_OAI_GET_Params_Auth_Data.yaml + OpenApiSpecUrl: http://172.13.112.25:5000/file/API_Plugin_Reflection_OAPI_GET_Params_Auth_Data.yaml diff --git a/Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_GET_Params_Auth.yaml b/Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_GET_Params_Auth_Header.yaml similarity index 93% rename from Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_GET_Params_Auth.yaml rename to Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_GET_Params_Auth_Header.yaml index 76c8b8f1..e3942f20 100644 --- a/Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_GET_Params_Auth.yaml +++ b/Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_GET_Params_Auth_Header.yaml @@ -15,7 +15,7 @@ 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_OAI_GET_Params_Auth.yaml + OpenApiSpecUrl: http://172.13.112.25:5000/file/API_Plugin_Reflection_OAPI_GET_Params_Auth_Header.yaml 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 new file mode 100644 index 00000000..98b93f51 --- /dev/null +++ b/Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_GET_Params_Custom_Setting.yaml @@ -0,0 +1,29 @@ +#Filename: API_Plugin_Reflection_GET_Params.yaml +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". + SupportedAuthTypes: + - ApiKey + Authorization: + Type: APIKey + Label: Authorization API Key + Key: Authorization + Location: Header + AuthScheme: '' + Settings: + - Name: CustomValue + Label: Custom Value + Description: This is a custom value part of plugin + HintText: "e.g. Dummy Hint" + SettingType: String + Required: true + +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_OAPI_GET_Params.yaml \ No newline at end of file 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 new file mode 100644 index 00000000..7680c0ca --- /dev/null +++ b/Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_GET_Params_Defaults.yaml @@ -0,0 +1,19 @@ +#Filename: API_Plugin_Reflection_GET_Params.yaml +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 + +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_OAPI_GET_Params_Defaults.yaml + + + 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 new file mode 100644 index 00000000..9105d686 --- /dev/null +++ b/Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_GET_Params_Partial_JSON.yaml @@ -0,0 +1,15 @@ +#Filename: API_Plugin_Reflection_GET_Params.yaml +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". +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_OAPI_GET_Params_Partial_JSON.yaml \ No newline at end of file diff --git a/Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_GET_Simple.yaml b/Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_GET_Simple.yaml index 750189c8..eb356921 100644 --- a/Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_GET_Simple.yaml +++ b/Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_GET_Simple.yaml @@ -9,7 +9,7 @@ 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_OAI_GET_Simple.yaml + OpenApiSpecUrl: http://172.13.112.25:5000/file/API_Plugin_Reflection_OAPI_GET_Simple.yaml 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 new file mode 100644 index 00000000..7b8f778b --- /dev/null +++ b/Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_GET_Simple_Header.yaml @@ -0,0 +1,12 @@ +#Filename: API_Plugin_Reflection_GET_Simple.yaml +Descriptor: + Name: McCulloch–Pitts's Reflection API plug-in using GET + DisplayName: McCulloch–Pitts's Reflection API plug-in using GET + Description: Skills for getting a GET REST API call reflection + DescriptionForModel: Skills for getting a GET REST API call reflection. Uses ReflectionData as operationId + +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 \ No newline at end of file diff --git a/Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_GET_Simple_Multiple.yaml b/Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_GET_Simple_Multiple.yaml new file mode 100644 index 00000000..f96248d9 --- /dev/null +++ b/Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_GET_Simple_Multiple.yaml @@ -0,0 +1,15 @@ +#Filename: API_Plugin_Reflection_GET_Params.yaml +Descriptor: + Name: McCulloch–Pitts's Reflection API plug-in using GET with multiple JSON output + DisplayName: McCulloch–Pitts's Reflection API plug-in using GET + Description: Skills for getting a GET REST API call reflection + DescriptionForModel: Skills for getting a GET REST API call reflection. Uses ReflectionData as operationId + +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_OAPI_GET_Simple_Multiple.yaml + + + diff --git a/Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_GET_Simple_OAuth2.yaml b/Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_GET_Simple_OAuth2.yaml new file mode 100644 index 00000000..7a33be8c --- /dev/null +++ b/Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_GET_Simple_OAuth2.yaml @@ -0,0 +1,21 @@ +#Filename: API_Plugin_Reflection_GET_Simple.yaml +Descriptor: + Name: McCulloch–Pitts's Reflection API plug-in using GET + DisplayName: McCulloch–Pitts's Reflection API plug-in using GET + Description: Skills for getting a GET REST API call reflection + DescriptionForModel: Skills for getting a GET REST API call reflection. Uses ReflectionData as operationId + Authorization: + #Type: OAuthAuthorizationCodeFlow + Type: OAuthClientCredentialsFlow + ClientId: + TokenEndpoint: https://sample.com/oauth2/v2.0/token + Scopes: + AuthorizationContentType: application/x-www-form-urlencoded +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_OAPI_GET_Simple_Header.yaml + + + diff --git a/Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_OAI_POST_JSON.yaml b/Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_OAI_POST_JSON.yaml deleted file mode 100644 index 20cba3a8..00000000 --- a/Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_OAI_POST_JSON.yaml +++ /dev/null @@ -1,72 +0,0 @@ -openapi: 3.0.0 - -#Write me an OpenAPI spec document to query QRadar REST API to get all incidents in past 24 hours - -info: - title: REST API Reflection using POST - description: Skills for getting reflection input for a GET REST API POST - version: "v1" - -servers: - - url: http://172.13.112.25:5000 - -paths: - /json/{input}: - post: - operationId: ReflectionDataPOSTJson - summary: A Reflection Data Plugin that reads values from payload JSON and returns them - parameters: - - in: path - name: input - schema: - type: string - required: true - description: Parameter Input - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - param1: - type: string - description: Value Parameter 1 - param2: - type: string - description: Value Parameter 2 - required: - - value1 - - value2 - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/ReflectionDataParamsPluginResponse" - -# This is referred to by $ref -components: - schemas: - ReflectionDataParamsPluginResponse: - type: object - properties: - objecttype: - type: string - description: Object type - userdata: - type: string - description: Userdata - value1: - type: string - description: Reflected Parameter 1 - value2: - type: string - description: Reflected Parameter 2 - sourceip: - type: string - description: The Source IP - useragent: - type: string - description: The User Agent diff --git a/Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_OAPI_GET_Params.yaml b/Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_OAPI_GET_Params.yaml index 8b3e49cd..1a3d67c5 100644 --- a/Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_OAPI_GET_Params.yaml +++ b/Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_OAPI_GET_Params.yaml @@ -6,7 +6,7 @@ info: version: "v1" servers: -# - url: https://agreeable-moss-9d3fccb3a39246119040a8136f8fefa4.azurewebsites.net + # Replace this with your own URL where the OpenAPI spec file is located. - url: http://172.13.112.25:5000 paths: @@ -22,17 +22,17 @@ paths: required: true description: Parameter Input - in: query - name: param1 + name: value1 schema: type: string required: true - description: Parameter 1 + description: Value Parameter 1, default to "Dummy Value 1" - in: query - name: param2 + name: value2 schema: type: string required: true - description: Parameter 2 + description: Value Parameter 2, default to "Dummy Value 2" responses: "200": description: OK diff --git a/Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_OAI_GET_Params.yaml b/Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_OAPI_GET_Params_Auth_Data.yaml similarity index 94% rename from Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_OAI_GET_Params.yaml rename to Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_OAPI_GET_Params_Auth_Data.yaml index 0b00b23a..df1db5e9 100644 --- a/Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_OAI_GET_Params.yaml +++ b/Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_OAPI_GET_Params_Auth_Data.yaml @@ -10,7 +10,7 @@ servers: - url: http://172.13.112.25:5000 paths: - /params/{input}: + /params_auth_data/{input}: get: operationId: ReflectionDataGETParams summary: A Reflection Data Plugin that reads values from URL Params and returns them @@ -50,6 +50,9 @@ components: objecttype: type: string description: Object type + auth: + type: string + description: Authorization Token userdata: type: string description: Userdata diff --git a/Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_OAI_GET_Params_Auth.yaml b/Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_OAPI_GET_Params_Auth_Header.yaml similarity index 90% rename from Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_OAI_GET_Params_Auth.yaml rename to Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_OAPI_GET_Params_Auth_Header.yaml index 6345aa0b..97915d51 100644 --- a/Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_OAI_GET_Params_Auth.yaml +++ b/Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_OAPI_GET_Params_Auth_Header.yaml @@ -10,7 +10,7 @@ servers: - url: http://172.13.112.25:5000 paths: - /params_auth/{input}: + /params_auth_header/{input}: get: operationId: ReflectionDataGETParams summary: A Reflection Data Plugin that reads values from URL Params and returns them @@ -33,12 +33,6 @@ paths: type: string required: true description: Value Parameter 2 - - in: query - name: Authorization - schema: - type: string - required: true - description: Authorization Token responses: "200": description: OK diff --git a/Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_OAI_GET_Params_Auth_Data.yaml b/Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_OAPI_GET_Params_Defaults.yaml similarity index 79% rename from Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_OAI_GET_Params_Auth_Data.yaml rename to Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_OAPI_GET_Params_Defaults.yaml index a915c06f..a9914d78 100644 --- a/Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_OAI_GET_Params_Auth_Data.yaml +++ b/Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_OAPI_GET_Params_Defaults.yaml @@ -6,11 +6,11 @@ info: version: "v1" servers: -# - url: https://agreeable-moss-9d3fccb3a39246119040a8136f8fefa4.azurewebsites.net + # Replace this with your own URL where the OpenAPI spec file is located. - url: http://172.13.112.25:5000 paths: - /params_auth_d/{input}: + /params/{input}: get: operationId: ReflectionDataGETParams summary: A Reflection Data Plugin that reads values from URL Params and returns them @@ -25,20 +25,16 @@ paths: name: value1 schema: type: string + default: Dummy 1 required: true - description: Value Parameter 1 + description: Value for Parameter 1, default is "Dummy Value 1" - in: query name: value2 schema: type: string + default: Dummy 2 required: true - description: Value Parameter 2 - - in: query - name: Authorization - schema: - type: string - required: true - description: Authorization Token + description: Value for Parameter 2, default is "Dummy Value 2" responses: "200": description: OK @@ -56,9 +52,6 @@ components: objecttype: type: string description: Object type - auth: - type: string - description: Authorization Token userdata: type: string description: Userdata diff --git a/Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_OAPI_GET_Params_Partial_JSON.yaml b/Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_OAPI_GET_Params_Partial_JSON.yaml new file mode 100644 index 00000000..2f5b9f51 --- /dev/null +++ b/Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_OAPI_GET_Params_Partial_JSON.yaml @@ -0,0 +1,58 @@ +openapi: 3.0.0 + +info: + title: REST API Reflection using GET params + description: Skills for getting reflection input for a GET REST API call using Params + version: "v1" + +servers: + # Replace this with your own URL where the OpenAPI spec file is located. + - url: http://172.13.112.25:5000 + +paths: + /params/{input}: + get: + operationId: ReflectionDataGETParams + summary: A Reflection Data Plugin that reads values from URL Params and returns them + parameters: + - in: path + name: input + schema: + type: string + required: true + description: Parameter Input + - in: query + name: value1 + schema: + type: string + required: true + description: Value Parameter 1, default to "Dummy Value 1" + - in: query + name: value2 + schema: + type: string + required: true + description: Value Parameter 2, default to "Dummy Value 2" + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/ReflectionDataParamsPluginResponse" + +# This is referred to by $ref, +# several fields will be in orig. REST API but not accounted for in following schema. +# Copilot will be able to display the json values not specified explicitly below, but since their +# description field is missing it wont be able to format them correctly. +components: + schemas: + ReflectionDataParamsPluginResponse: + type: object + properties: + objecttype: + type: string + description: Object type + + + diff --git a/Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_OAPI_GET_Simple_Header.yaml b/Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_OAPI_GET_Simple_Header.yaml new file mode 100644 index 00000000..d4567899 --- /dev/null +++ b/Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_OAPI_GET_Simple_Header.yaml @@ -0,0 +1,61 @@ +# Filename: API_Plugin_Reflection_OAI_GET_Simple.yaml +# The basic template of this file can be generated using Bing Copilot using the following prompt: +# Write an OpenAPI specification document for an API that receives a variable called input in the path via a GET +# request to /simple/{input} that outputs a JSON with the following fields: object, userdata,sourceip, useragent + +openapi: 3.0.0 + +info: + title: REST API Reflection using GET + description: Skills for getting reflection input for a GET REST API call + version: "v1" + +servers: +# Replace this with out own URL + - url: http://172.13.112.25:5000 + +paths: + /simple/{input}: + get: + operationId: ReflectionData + summary: A Reflection Data Plugin + parameters: + - in: path + name: input + schema: + type: string + required: true + description: Parameter Input + - in: header + name: User-Agent + schema: + type: string + required: true + default: Copilot-For-Security + description: Fixed value for the User-Agent header + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/ReflectionDataPluginResponse" + +# This is referred to by $ref +components: + schemas: + ReflectionDataPluginResponse: + type: object + properties: + object: + type: string + description: main data + sourceip: + type: string + description: The Source IP + useragent: + type: string + description: The User Agent + value: + type: string + description: Fixed value diff --git a/Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_OAI_GET_Simple.yaml b/Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_OAPI_GET_Simple_Multiple.yaml similarity index 98% rename from Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_OAI_GET_Simple.yaml rename to Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_OAPI_GET_Simple_Multiple.yaml index 2931462c..aa2e7f81 100644 --- a/Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_OAI_GET_Simple.yaml +++ b/Technical Workshops/Custom Plugin Calling Webservice/Plugins/Reflection/API_Plugin_Reflection_OAPI_GET_Simple_Multiple.yaml @@ -15,7 +15,7 @@ servers: - url: http://172.13.112.25:5000 paths: - /simple/{input}: + /simple/multiple/{input}: get: operationId: ReflectionData summary: A Reflection Data Plugin diff --git a/Technical Workshops/Custom Plugin Calling Webservice/readme.md b/Technical Workshops/Custom Plugin Calling Webservice/readme.md index 403c2425..8a08efe4 100644 --- a/Technical Workshops/Custom Plugin Calling Webservice/readme.md +++ b/Technical Workshops/Custom Plugin Calling Webservice/readme.md @@ -5,13 +5,13 @@ ### Objectives -This folder contains a three plugins to send data via GET (with and without parameters) and POST (parameters sent using JSON). The plugins make REST calls to a Python/Flask based website which is available in the folder FlaskWebsite. The objective of the plugins YAML files and the Flask website is to show how you can make a call to specific REST API, along with making the correct prompts so that Copilot for Security planner/orchestrator selects your plugin and makes the REST call. Since the calls are made to a custom webserice you can see the what type of data exchange takes place between the Copilot for Security and your WebService. +This folder contains a three plugins to send data via GET (with and without parameters) and POST (parameters sent using JSON). The plugins make REST calls to a Python/Flask based website which is available in the folder FlaskWebsite. The objective of the plugins YAML files and the Flask website is to show how you can make a call to specific REST API, along with making the correct prompts so that Security Copilot planner/orchestrator selects your plugin and makes the REST call. Since the calls are made to a custom webserice you can see the what type of data exchange takes place between the Security Copilot and your WebService. ### Steps 1. While we can use any standard application that exposes a REST API, it will be easier and more clearer from the server-side if we build a simple web service of our own and have our plugin call it. We have build this web service in Python using the Flask web framework and first make the webservice work in your local machine and test it via a browser by entering the URL http://127.0.0.1:5000 -2. Once you have proven your Flask web application is running locally, you will now need to run the above website in a host that is reachable from the Internet since Copilot for Security is a Cloud based application. The best option is to use an Azure App Service and instructions to host and run a Python web application in Azure App Service are given [here](https://learn.microsoft.com/en-us/azure/app-service/quickstart-python?tabs=flask%2Cwindows%2Cazure-cli%2Czip-deploy%2Cdeploy-instructions-azportal%2Cterminal-bash%2Cdeploy-instructions-zip-azcli). Azure App Service also builds a TLS layer on your website (allowing you to use https) even though our webservice is non-TLS. The other option is to run the webserver from an Azure hosted VM, but you will not have the benefit of a TLS layer. While Copilot for Security can call both http and https websites, for any production application TLS is a must. +2. Once you have proven your Flask web application is running locally, you will now need to run the above website in a host that is reachable from the Internet since Security Copilot is a Cloud based application. The best option is to use an Azure App Service and instructions to host and run a Python web application in Azure App Service are given [here](https://learn.microsoft.com/en-us/azure/app-service/quickstart-python?tabs=flask%2Cwindows%2Cazure-cli%2Czip-deploy%2Cdeploy-instructions-azportal%2Cterminal-bash%2Cdeploy-instructions-zip-azcli). Azure App Service also builds a TLS layer on your website (allowing you to use https) even though our webservice is non-TLS. The other option is to run the webserver from an Azure hosted VM, but you will not have the benefit of a TLS layer. While Security Copilot can call both http and https websites, for any production application TLS is a must. 3. Upload the OpenAPI document (with _OAPI_ in filename) in an Internet accessible location or download from this github. You can also use the Flask Webservice to serve them by moving the files in the Files folder. diff --git a/Technical Workshops/Custom Plugin Workshop/Task01_Send_jokeByemail/readme.md b/Technical Workshops/Custom Plugin Workshop/Task01_Send_jokeByemail/readme.md index 64f708bb..a8367e07 100644 --- a/Technical Workshops/Custom Plugin Workshop/Task01_Send_jokeByemail/readme.md +++ b/Technical Workshops/Custom Plugin Workshop/Task01_Send_jokeByemail/readme.md @@ -7,14 +7,14 @@ Upon completing this technical guide, you will gain the following abilities:
-* Upload a Microsoft Copilot for Security custom plugin of the Logic App type.
-* Deploy an Azure Logic App capable of handling Microsoft Copilot for Security prompts.
-* Create a Microsoft Copilot for Security session that triggers this Logic App.
+* Upload a Microsoft Security Copilot custom plugin of the Logic App type.
+* Deploy an Azure Logic App capable of handling Microsoft Security Copilot prompts.
+* Create a Microsoft Security Copilot session that triggers this Logic App.
#### Scenario -In this technical workshop, participants will learn how to upload a Microsoft Copilot for Security custom plugin of the Logic App type and deploy the corresponding Logic App. To successfully complete this task, you must meet the following prerequisites:
+In this technical workshop, participants will learn how to upload a Microsoft Security Copilot custom plugin of the Logic App type and deploy the corresponding Logic App. To successfully complete this task, you must meet the following prerequisites:
-* You need your own tenant and Microsoft Copilot for Security instance.
+* You need your own tenant and Microsoft Security Copilot instance.
* You should have permission to upload a custom plugin.
* You should have permission to deploy a Logic App.
diff --git a/Technical Workshops/Custom Plugin Workshop/Task02_Defang_URL/readme.md b/Technical Workshops/Custom Plugin Workshop/Task02_Defang_URL/readme.md index 2ce64958..32822e51 100644 --- a/Technical Workshops/Custom Plugin Workshop/Task02_Defang_URL/readme.md +++ b/Technical Workshops/Custom Plugin Workshop/Task02_Defang_URL/readme.md @@ -15,10 +15,10 @@ Upon completing this technical guide, you will gain the following abilities:
### Scenario -In this technical workshop, participants will learn how to upload a Microsoft Copilot for Security custom plugin of the GPT type.
+In this technical workshop, participants will learn how to upload a Microsoft Security Copilot custom plugin of the GPT type.
To successfully complete this task, you must meet the following prerequisites:
-* You need your own tenant and Microsoft Copilot for Security instance.
+* You need your own tenant and Microsoft Security Copilot instance.
* You should have permission to upload a custom plugin.
diff --git a/Technical Workshops/Custom Plugin Workshop/Task03_GEO_IP_report/readme.md b/Technical Workshops/Custom Plugin Workshop/Task03_GEO_IP_report/readme.md index 828115e1..f9529d9a 100644 --- a/Technical Workshops/Custom Plugin Workshop/Task03_GEO_IP_report/readme.md +++ b/Technical Workshops/Custom Plugin Workshop/Task03_GEO_IP_report/readme.md @@ -14,7 +14,7 @@ Upon completing this technical guide, you will gain the following abilities:
### Scenario -In this technical workshop, participants will learn how to upload a Microsoft Copilot for Security custom plugin of the API type.
+In this technical workshop, participants will learn how to upload a Microsoft Security Copilot custom plugin of the API type.
> ⭐ Notice:
**When utilizing a custom plugin from an API type, be aware that data is transferred out of your tenant and sent to a remote endpoint. In this lab, we are working with sample data. Please keep this in consideration when handling internal data.** @@ -22,7 +22,7 @@ In this technical workshop, participants will learn how to upload a Microsoft Co To successfully complete this task, you must meet the following prerequisites:
-* You need your own tenant and Microsoft Copilot for Security instance.
+* You need your own tenant and Microsoft Security Copilot instance.
* You should have permission to upload a custom plugin.
diff --git a/Technical Workshops/Custom Plugin Workshop/Task04_GEO_IP_script/readme.md b/Technical Workshops/Custom Plugin Workshop/Task04_GEO_IP_script/readme.md index 699e5cc4..d6dd4068 100644 --- a/Technical Workshops/Custom Plugin Workshop/Task04_GEO_IP_script/readme.md +++ b/Technical Workshops/Custom Plugin Workshop/Task04_GEO_IP_script/readme.md @@ -13,7 +13,7 @@ Upon completing this technical guide, you will gain the following abilities:
* Generete report that will levrage the custom plugin source.
### Scenario -In this technical workshop, participants will learn how to upload a Microsoft Copilot for Security custom plugin of the API type.
+In this technical workshop, participants will learn how to upload a Microsoft Security Copilot custom plugin of the API type.
> ⭐ Notice:
**When utilizing a custom plugin from an API type, be aware that data is transferred out of your tenant and sent to a remote endpoint. In this lab, we are working with sample data. Please keep this in consideration when handling internal data.** @@ -21,7 +21,7 @@ In this technical workshop, participants will learn how to upload a Microsoft Co To successfully complete this task, you must meet the following prerequisites:
-* You need your own tenant and Microsoft Copilot for Security instance.
+* You need your own tenant and Microsoft Security Copilot instance.
* You should have permission to upload a custom plugin.
diff --git a/Technical Workshops/Custom Plugin Workshop/Task05_KQL/Sentinel_Incident.json b/Technical Workshops/Custom Plugin Workshop/Task05_KQL/Sentinel_Incident.json index eaf7d55a..4dba5057 100644 --- a/Technical Workshops/Custom Plugin Workshop/Task05_KQL/Sentinel_Incident.json +++ b/Technical Workshops/Custom Plugin Workshop/Task05_KQL/Sentinel_Incident.json @@ -40,14 +40,14 @@ "techniques": [] }, "relatedAnalyticRuleIds": [ - "/subscriptions/ab48f397-fc82-xxxx-xxxx-xxxxxxx/resourceGroups/woodgrove-rg/providers/Microsoft.OperationalInsights/workspaces/workspace/providers/Microsoft.SecurityInsights/alertRules/daf3f4df-bb2f-4da9-82a3-20da534c0ae8" + "/subscriptions/ab48f397-fc82-xxxx-xxxx-xxxxxxx/resourceGroups/My-RG/providers/Microsoft.OperationalInsights/workspaces/workspace/providers/Microsoft.SecurityInsights/alertRules/daf3f4df-bb2f-4da9-82a3-20da534c0ae8" ], "incidentUrl": "https://portal.azure.com/#asset/Microsoft_Azure_Security_Insights/Incident/subscriptions/ab48f397-xxxx-xxxx-xxxx-62dd91xxxx/resourceGroups/rg/providers/Microsoft.OperationalInsights/workspaces/workspace/providers/Microsoft.SecurityInsights/Incidents/2483d213-ea73-45c2-951c-437c3a61e43b", "providerName": "Azure Sentinel", "providerIncidentId": "37664", "alerts": [ { - "id": "/subscriptions/ab48f397-fc82-4634-aa52-62dd91b3ebaa/resourceGroups/woodgrove-rg/providers/Microsoft.OperationalInsights/workspaces/woodgrove-loganalyiticsworkspace/providers/Microsoft.SecurityInsights/Entities/71e460c1-119b-cbab-99e1-d3fa5867729e", + "id": "/subscriptions/ab48f397-fc82-xxxx-xxxx-xxxxxxxx/resourceGroups/My-RG/providers/Microsoft.OperationalInsights/workspaces/My-workspace/providers/Microsoft.SecurityInsights/Entities/71e460c1-119b-cbab-99e1-d3fa5867729e", "name": "71e460c1-119b-cbab-99e1-d3fa5867729e", "type": "Microsoft.SecurityInsights/Entities", "kind": "SecurityAlert", @@ -98,7 +98,7 @@ "bookmarks": [], "relatedEntities": [ { - "id": "/subscriptions/ab48f397-fc82-4634-aa52-62dd91b3ebaa/resourceGroups/woodgrove-rg/providers/Microsoft.OperationalInsights/workspaces/woodgrove-loganalyiticsworkspace/providers/Microsoft.SecurityInsights/Entities/5f097292-3a78-8e63-e9ee-1e4310681b3c", + "id": "/subscriptions/ab48f397-xxxx-xxxx-xxxx-62dd91b3ebaa/resourceGroups/My-RG/providers/Microsoft.OperationalInsights/workspaces/My-workspace/providers/Microsoft.SecurityInsights/Entities/5f097292-3a78-8e63-e9ee-1e4310681b3c", "name": "5f097292-3a78-8e63-e9ee-1e4310681b3c", "type": "Microsoft.SecurityInsights/Entities", "kind": "Account", @@ -116,7 +116,7 @@ } }, { - "id": "/subscriptions/ab48f397-fc82-4634-aa52-62dd91b3ebaa/resourceGroups/woodgrove-rg/providers/Microsoft.OperationalInsights/workspaces/woodgrove-loganalyiticsworkspace/providers/Microsoft.SecurityInsights/Entities/adfc0103-c2d1-acc7-6bc2-7d3fa46a3c32", + "id": "/subscriptions/ab48f397-xxxx-xxxx-xxxx-xxxxxxx/resourceGroups/My-RG/providers/Microsoft.OperationalInsights/workspaces/My-workspace/providers/Microsoft.SecurityInsights/Entities/adfc0103-c2d1-acc7-6bc2-7d3fa46a3c32", "name": "adfc0103-c2d1-acc7-6bc2-7d3fa46a3c32", "type": "Microsoft.SecurityInsights/Entities", "kind": "Ip", @@ -126,7 +126,7 @@ } }, { - "id": "/subscriptions/ab48f397-fc82-4634-aa52-62dd91b3ebaa/resourceGroups/woodgrove-rg/providers/Microsoft.OperationalInsights/workspaces/woodgrove-loganalyiticsworkspace/providers/Microsoft.SecurityInsights/Entities/beeec36e-9d77-78c6-6b59-96127a531134", + "id": "/subscriptions/xxxxx-fc82-4634-xxxx-62dd91b3xxxxebaa/resourceGroups/My-RG/providers/Microsoft.OperationalInsights/workspaces/My-workspace/providers/Microsoft.SecurityInsights/Entities/beeec36e-9d77-78c6-6b59-96127a531134", "name": "beeec36e-9d77-78c6-6b59-96127a531134", "type": "Microsoft.SecurityInsights/Entities", "kind": "Host", diff --git a/Technical Workshops/Custom Plugin Workshop/Task05_KQL/readme.md b/Technical Workshops/Custom Plugin Workshop/Task05_KQL/readme.md index 48035161..6f46699f 100644 --- a/Technical Workshops/Custom Plugin Workshop/Task05_KQL/readme.md +++ b/Technical Workshops/Custom Plugin Workshop/Task05_KQL/readme.md @@ -12,10 +12,10 @@ After finishing this technical guide, you will acquire the following skills:
* Proficiency in generating reports utilizing the custom plugin source.
### Scenario -In this technical workshop, participants will learn how to upload a Microsoft Copilot for Security custom plugin of the API type.
+In this technical workshop, participants will learn how to upload a Microsoft Security Copilot custom plugin of the API type.
To successfully complete this task, you must meet the following prerequisites:
-* You need your own tenant and Microsoft Copilot for Security instance.
+* You need your own tenant and Microsoft Security Copilot instance.
* You should have permission to upload a custom plugin.
diff --git a/Technical Workshops/Knowledge base Workshop/Module 1- Upload a file to Copilot for Security KB workshop/readme.md b/Technical Workshops/Knowledge base Workshop/Module 1- Upload a file to Copilot for Security KB workshop/readme.md index 0d3dcb60..f71b6eb5 100644 --- a/Technical Workshops/Knowledge base Workshop/Module 1- Upload a file to Copilot for Security KB workshop/readme.md +++ b/Technical Workshops/Knowledge base Workshop/Module 1- Upload a file to Copilot for Security KB workshop/readme.md @@ -1,7 +1,7 @@ -# Welcome to Microsoft Copilot for Security Labs! +# Welcome to Microsoft Security Copilot Labs! # Introduction ![Security CoPilot Logo](https://github.com/Azure/Copilot-For-Security/blob/main/Images/ic_fluent_copilot_64_64%402x.png) -## Upload a File to Copilot for Security +## Upload a File to Security Copilot #### ⌛ Estimated time to complete this lab: 100 minutes #### 🎓 Level: 100 (Beginner) @@ -9,17 +9,17 @@ Upon completing this technical guide, you will gain the following abilities:
-* Add Sample files to Copilot For Security
+* Add Sample files to Security Copilot
#### Scenario "In this technical workshop, participants will learn how to upload a file to Copilot for Secuirty .To successfully complete this task, you must meet the following prerequisites:
-* You need your own tenant and Microsoft Copilot for Security instance.
+* You need your own tenant and Microsoft Security Copilot instance.
* Linked to the Sample files folder
-# Workshop Agenda: Upload a File to Copilot for Security +# Workshop Agenda: Upload a File to Security Copilot # Workshop Agenda: File Upload KB Workskshop diff --git a/Technical Workshops/Knowledge base Workshop/Module 2- Prompting an uploaded file content Using Copilot For Security/readme.md b/Technical Workshops/Knowledge base Workshop/Module 2- Prompting an uploaded file content Using Copilot For Security/readme.md index 0fd1d2d3..53f64b4e 100644 --- a/Technical Workshops/Knowledge base Workshop/Module 2- Prompting an uploaded file content Using Copilot For Security/readme.md +++ b/Technical Workshops/Knowledge base Workshop/Module 2- Prompting an uploaded file content Using Copilot For Security/readme.md @@ -1,7 +1,7 @@ -# Welcome to Microsoft Copilot for Security Labs! +# Welcome to Microsoft Security Copilot Labs! # Introduction ![Security CoPilot Logo](https://github.com/Azure/Copilot-For-Security/blob/main/Images/ic_fluent_copilot_64_64%402x.png) -## Prompting for an uploaded file in Copilot for Security +## Prompting for an uploaded file in Security Copilot #### ⌛ Estimated time to complete this lab: 20 minutes #### 🎓 Level: 200 (Proficient) @@ -9,26 +9,26 @@ Upon completing this technical guide, you will gain the following abilities:
-* Leverage the File upload capability plugin with prompts direclty from Copilot For Security.
+* Leverage the File upload capability plugin with prompts direclty from Security Copilot.
#### Scenario "In this technical workshop, participants will learn how to search for documents and leverage the infromation in practical Scurity Operation scenarios .To successfully complete this task, you must meet the following prerequisites:
-* You need your own tenant and Microsoft Copilot for Security instance.
+* You need your own tenant and Microsoft Security Copilot instance.
* uplaoded files from the Previous Module .
-# Workshop Agenda: Prompting for an uploaded file in Copilot for Security +# Workshop Agenda: Prompting for an uploaded file in Security Copilot ## 1. Access the SANS (" Hunt Evil Poster Document") - Access the Document from the samples folder in the GitHub Here [Hunt Evil Poster PDF](https://github.com/Azure/Copilot-For-Security/blob/main/Technical%20Workshops/Knowledge%20base%20Workshop/Sample%20Files/Hunt%20Evil%20Poster.pdf) or access it from the Link here : https://www.sans.org/posters/hunt-evil/ ![Hunt Evil 3](https://github.com/Azure/Copilot-For-Security/blob/main/Images/KB%20Images/huntevil3.png?raw=true) -- Ensure the document is added on your file upload capability in Copilot For Security +- Ensure the document is added on your file upload capability in Security Copilot -## 2. Prompting Uploade files using Copilot for Security +## 2. Prompting Uploade files using Security Copilot - send out the first Prompt Querying the data in the document , we will focus on a couple of senarios , in the hunt evil document we will focus on pulling infromation associated with Lateral Movement ( key focus on how its achieed with Remote Access or Remote Execution) diff --git a/Technical Workshops/Knowledge base Workshop/Readme.md b/Technical Workshops/Knowledge base Workshop/Readme.md index 6f1d96ad..4eba034e 100644 --- a/Technical Workshops/Knowledge base Workshop/Readme.md +++ b/Technical Workshops/Knowledge base Workshop/Readme.md @@ -1,25 +1,25 @@ -# Welcome to Microsoft Copilot for Security Labs! +# Welcome to Microsoft Security Copilot Labs! ![Security CoPilot Logo](https://github.com/Azure/Copilot-For-Security/blob/main/Images/ic_fluent_copilot_64_64%402x.png) ## Introduction -Our lab initiatives are tailored to expedite your acclimatization to Microsoft Copilot for Security. They provide immersive, hands-on experiences, empowering you to seamlessly connect with your knowledge base. +Our lab initiatives are tailored to expedite your acclimatization to Microsoft Security Copilot. They provide immersive, hands-on experiences, empowering you to seamlessly connect with your knowledge base. -## Connect your Org Knowledge Base to Microsoft Copilot For Security +## Connect your Org Knowledge Base to Microsoft Security Copilot -**Microsoft Copilot for Security** allows you to integrate your organization’s knowledge base as an additional source of information. The inclusion of knowledge bases gives **Copilot** more context, resulting in responses that are more relevant, specific, and customized to the user. +**Microsoft Security Copilot** allows you to integrate your organization’s knowledge base as an additional source of information. The inclusion of knowledge bases gives **Copilot** more context, resulting in responses that are more relevant, specific, and customized to the user. -## Options To integrate your Knowledge Base into Microsoft Copilot For Security +## Options To integrate your Knowledge Base into Microsoft Security Copilot -| Options to Integrate KBs into Copilot for Security | Ways to integrate KBs into Microsoft Copilot For Security | +| Options to Integrate KBs into Security Copilot | Ways to integrate KBs into Microsoft Security Copilot | |----------------------------------------------------|-----------------------------------------------------------------------------------------------------------------| | Azure AI Search plugin | Follow the steps in [Prompting for a KB connected using Azure AI Search](https://learn.microsoft.com/en-us/security-copilot/connect-org-kb#prompting-for-a-knowledge-base-connected-using-azure-ai-search). | | File upload | Follow the steps in [Prompting for an uploaded file](https://learn.microsoft.com/en-us/security-copilot/upload-file). | ## Modules -- [**Module 1 - Upload a file to Copilot for Security**](https://github.com/Azure/Copilot-For-Security/tree/main/Technical%20Workshops/Knowledge%20base%20Workshop/Module%201-%20Upload%20a%20file%20to%20Copilot%20for%20Security%20KB%20workshop) +- [**Module 1 - Upload a file to Security Copilot**](https://github.com/Azure/Copilot-For-Security/tree/main/Technical%20Workshops/Knowledge%20base%20Workshop/Module%201-%20Upload%20a%20file%20to%20Copilot%20for%20Security%20KB%20workshop) -- [**Module 2 - Prompting an uploaded file content Using Copilot For Security**](https://github.com/Azure/Copilot-For-Security/tree/main/Technical%20Workshops/Knowledge%20base%20Workshop/Module%202-%20Prompting%20an%20uploaded%20file%20content%20Using%20Copilot%20For%20Security) +- [**Module 2 - Prompting an uploaded file content Using Security Copilot**](https://github.com/Azure/Copilot-For-Security/tree/main/Technical%20Workshops/Knowledge%20base%20Workshop/Module%202-%20Prompting%20an%20uploaded%20file%20content%20Using%20Copilot%20For%20Security) 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) diff --git a/Technical Workshops/readme.md b/Technical Workshops/readme.md index 1ea59e71..14f870ec 100644 --- a/Technical Workshops/readme.md +++ b/Technical Workshops/readme.md @@ -1,8 +1,8 @@ -![Copilot for Security Overview](https://github.com/Azure/Copilot-For-Security/blob/main/Images/ic_fluent_copilot_64_64%402x.png) +![Security Copilot Overview](https://github.com/Azure/Copilot-For-Security/blob/main/Images/ic_fluent_copilot_64_64%402x.png) -# Technical Workshops for Microsoft Copilot for Security +# Technical Workshops for Microsoft Security Copilot -Welcome to the series of technical workshops designed to enhance your skills and knowledge in utilizing Microsoft Copilot for Security. Microsoft Copilot for Security is an innovative tool that integrates seamlessly with Microsoft's security solutions, providing users with enhanced capabilities to automate responses, generate security narratives, and much more, using advanced AI technologies. These workshops are tailored for security professionals, system administrators, and anyone interested in harnessing the power of AI to bolster their security infrastructure. +Welcome to the series of technical workshops designed to enhance your skills and knowledge in utilizing Microsoft Security Copilot. Microsoft Security Copilot is an innovative tool that integrates seamlessly with Microsoft's security solutions, providing users with enhanced capabilities to automate responses, generate security narratives, and much more, using advanced AI technologies. These workshops are tailored for security professionals, system administrators, and anyone interested in harnessing the power of AI to bolster their security infrastructure. ## Workshop Topics @@ -10,7 +10,7 @@ Below is a table summarizing the focus areas of our workshop series: | Workshop Topic | Brief Description | |-----------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------| -|[Copilot for Security: Knowledge Base Workshop](https://github.com/Azure/Copilot-For-Security/tree/main/Technical%20Workshops/Knowledge%20base%20Workshop)| Learn how to integrate various knowledge bases with Copilot for Security to enhance its ability to provide accurate, contextually relevant security insights. | -| [Copilot for Security: Custom Plugin Workshop](https://github.com/Azure/Copilot-For-Security/tree/main/Technical%20Workshops/Custom%20Plugin%20Workshop)| Dive into custom plugin creation to extend Copilot for Security's capabilities, enabling tailored solutions for your specific security needs. | -|[Copilot for Security: Automation Workshop](https://github.com/Azure/Copilot-For-Security/tree/main/Technical%20Workshops/Automation%20Workshop).| Discover how to use Microsoft Logic Apps to automate workflows and security responses, leveraging Copilot for Security for enhanced security management. | +|[Security Copilot: Knowledge Base Workshop](https://github.com/Azure/Copilot-For-Security/tree/main/Technical%20Workshops/Knowledge%20base%20Workshop)| Learn how to integrate various knowledge bases with Security Copilot to enhance its ability to provide accurate, contextually relevant security insights. | +| [Security Copilot: Custom Plugin Workshop](https://github.com/Azure/Copilot-For-Security/tree/main/Technical%20Workshops/Custom%20Plugin%20Workshop)| Dive into custom plugin creation to extend Security Copilot's capabilities, enabling tailored solutions for your specific security needs. | +|[Security Copilot: Automation Workshop](https://github.com/Azure/Copilot-For-Security/tree/main/Technical%20Workshops/Automation%20Workshop).| Discover how to use Microsoft Logic Apps to automate workflows and security responses, leveraging Security Copilot for enhanced security management. |