Adding the MCAS Alert URL to a Sentinel Incident using PowerShell

Introduction

Microsoft is making great strides and making Azure Sentinel one of the best SIEM products out there. One way they do this is to allow other Azure security products to forward their alerts into Azure Sentinel to make a one-stop-shop kind of experience. While this is great, one feature that, IMHO, is lacking is the ability to easily go back to the original alert to investigate it.

It turns out that the URL to do this is present, it just isn’t exposed in the Incident page (yet?). It is passed along as part of the MCAS alert, just hidden away.

This blog post will show you how you can obtain that URL and then expose it as a comment in the Incident’s details page.

Overview

This is going to be handled in two steps. The first is to get a listing of all the MCAS alerts that have been raised. These are stored in the Log Analytics workspace logs in the SecuityAlert table. We will filter on the ProductName column to make sure only MCAS alerts are handled.

Since this is stored in the LA workspace, we can use a KQL query to obtain the data. The first thing we will need to do is to get a reference to our workspace. To get that we will need to know the resource group and workspace names

$workspaceName = 'my-workspace'
$resourceGroupName = 'my-resourcegroup'

Using those variables, we can get the workspace reference

$workspace = Get-AzOperationalInsightsWorkspace -ResourceGroupName $resourceGroupName -Name $workspaceName

We then define the query we are going to use and perform the query against our workspace

$query = 'SecurityAlert | where  ProductName == "Microsoft Cloud App Security"'
$queryResults = Invoke-AzOperationalInsightsQuery -Workspace $workspace -Query $query

The query is very simple. In the real world this would be filtered based on the the date and time this was last run to avoid processing the same entry over and over, but for now we will just look at everything.

We then look search all the Incidents we have loaded, see Your first Azure Sentinel REST API call on how to load these if you don’t remember, and see if the any of them have a matching alert Id.

foreach ($result in $queryResults.Results) { 
    #Get the incident (AKA case) from the list
    $incident = $incidents | Where-Object {$_.properties.relatedAlertIds -eq $result.SystemAlertId}
    #If there is a match (it could be the incident is not in the first 200) add the comment
    if ($null -ne $incident)
    {
    .
    .
    .
    }

The syntax on line 3 may be new for some people. What it is doing is going through the entire listing of all the Incidents, stored in the $incidents variable, and looking at each entry’s properties.relatedAlertIds field to compare it against the result’s SystemAlertId.

Assuming there is a match, further processing will be done. The first step will be to obtain the actual URL we need. We can get that from the ExtendedLinks column of the $result variable. The issue is that this is stored as a JSON encoded array so it will need to be decoded before we can use it. Luckily, PowerShell has just what we need

$extendedLinks = ConvertFrom-Json $result.ExtendedLinks

Now we can access the $extendedLinks variable to get the URL we need, which is stored as the second entry in the array.

The rest is not much different than what steps were taken to create a new Analytics rule taken in Working with Analytics rules Part 3 – Create Fusion / ML Rule. You need to:

  1. Create a new GUID. In this case the GUID is for the comment since the Incident is already created.
  2. Define the URL that will be called using the GUID from step 1
  3. Setup the body to pass into the REST call
  4. Make the REST call as a PUT using the URL from step 2 and the body from step 3.

The code for the steps is

$guid = (New-Guid).Guid
 #Create the URL to add the comment to the found Incident
$incidentCommentUrl = "https://management.azure.com/subscriptions/$($subscriptionId)/resourceGroups/$($resourceGroupName)"
$incidentCommentUrl += "/providers/Microsoft.OperationalInsights/workspaces/$($workspaceName)/providers/"
$incidentCommentUrl += "Microsoft.SecurityInsights/cases/$($incident.name)/comments/$($guid)?api-version=2019-01-01-preview"
#Use the Href field of the second entry in ExtendedLinks which is the actual URL back to MCAS
$body = @{
  "properties" = @{
    "message"       = "MCAS Alert URL: $($extendedLinks[1].Href)"
  }
} 
#Save the new comment
try {
  $result = Invoke-RestMethod -Uri $incidentCommentUrl -Method Put -Headers $authHeader -Body ($body | ConvertTo-Json -EnumsAsStrings)
  Write-Output "Successfully updated rule: $($incident.properties.caseNumber) with status: $($result.StatusDescription)"
}
catch {
  $errorReturn = $_
  Write-Error $errorReturn
}

As you can see:

  • Line 1 creates the GUID
  • Lines 3-5 create the URL needed. Note it is the same URL that was used to get a listing of all the Incidents except that, in line 5, the specific Incident ID is added after the cases keyword to state that we are working with a specific Incident. After that is the GUID to designate a new comment. If for some reason, a comment with this GUID already existed for this case, that comment would be overwritten.
  • Lines 7-11 setup the new body variable. Note on line 9 is where we setup the actual comment text that will be added.
  • Line 14 makes the REST call. This is the exact same call that was used to create a new Analytics rule, just the URL and the Body will be different.

That is all there is to it. The complete listing is as follows

$workspaceName = 'my-workspace'
$resourceGroupName = 'my-resourcegroup'

$context = Get-AzContext
$profile = [Microsoft.Azure.Commands.Common.Authentication.Abstractions.AzureRmProfileProvider]::Instance.Profile
$profileClient = New-Object -TypeName Microsoft.Azure.Commands.ResourceManager.Common.RMProfileClient -ArgumentList ($profile)
$token = $profileClient.AcquireAccessToken($context.Subscription.TenantId)
$authHeader = @{
    'Content-Type'  = 'application/json' 
    'Authorization' = 'Bearer ' + $token.AccessToken 
}
    
#Create the URL to get all the cases
$subscriptionId = (Get-AzContext).Subscription.Id
$incidentUrl = "https://management.azure.com/subscriptions/$($subscriptionId)/resourceGroups/$($resourceGroupName)"
$incidentUrl += "/providers/Microsoft.OperationalInsights/workspaces/$($workspaceName)/providers/Microsoft.SecurityInsights/"
$incidentUrl += "cases?api-version=2019-01-01-preview"
#Get all the cases (or at least the top 200)
$incidents = (Invoke-RestMethod -Method "Get" -Uri $incidentUrl -Headers $authHeader ).value

#Get the Log Analytics workspace
$workspace = Get-AzOperationalInsightsWorkspace -ResourceGroupName $resourceGroupName -Name $workspaceName
#You would most likely add a time filter here and store the last used time to
#make sure you don't add multiple comments to the same incident (not that it
# would hurt anything if you did)
$query = 'SecurityAlert | where  ProductName == "Microsoft Cloud App Security"'
#Run the query against the Log Analytics workspace
$queryResults = Invoke-AzOperationalInsightsQuery -Workspace $workspace -Query $query
#Each $result will be a MCAS alert
foreach ($result in $queryResults.Results) { 
    #Get the incident (AKA case) from the list
    $incident = $incidents | Where-Object {$_.properties.relatedAlertIds -eq $result.SystemAlertId}
    #If there is a match (it could be the incident is not in the first 200) add the comment
    if ($null -ne $incident)
    {
        #This is where the URL is located.  The ExtendedLinks is a JSON string so
        #we need to convert it before we can use it.
        $extendedLinks = ConvertFrom-Json $result.ExtendedLinks
    
        $guid = (New-Guid).Guid
        #Create the URL to add the comment to the found Incident
        $incidentCommentUrl = "https://management.azure.com/subscriptions/$($subscriptionId)/resourceGroups/$($resourceGroupName)"
        $incidentCommentUrl += "/providers/Microsoft.OperationalInsights/workspaces/$($workspaceName)/providers/"
        $incidentCommentUrl += "Microsoft.SecurityInsights/cases/$($incident.name)/comments/$($guid)?api-version=2019-01-01-preview"
        #Use the Href field of the second entry in ExtendedLinks which is the actual URL back to MCAS
        $body = @{
            "properties" = @{
                "message"       = "MCAS Alert URL: $($extendedLinks[1].Href)"
            }
        }
        #Save the new comment
        try {
            $result = Invoke-RestMethod -Uri $incidentCommentUrl -Method Put -Headers $authHeader -Body ($body | ConvertTo-Json -EnumsAsStrings)
            Write-Output "Successfully updated rule: $($incident.properties.caseNumber) with status: $($result.StatusDescription)"
            Write-Output ($body.Properties | Format-List | Format-Table | Out-String)
        }
        catch {
            $errorReturn = $_
            Write-Error $errorReturn
        }
    }
}

If everything goes well, the new comment will look like

Conclusion

It took some work, there is some left if this is going to be run on a schedule, but we can get the MCAS URL to show up as a comment. This can probably be expanded to handle some of the other security products as well but, as college text books love to say, that is an exercise left to the reader 🙂

Leave a Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.