Automated Software Vulnerability Notification

Automated Software Vulnerability Notification

Today, I’ll guide you through setting up an automation system that notifies your users about vulnerable software detected on their devices. This information will be sourced from the Microsoft Defender API.

In this post, I’ll demonstrate how to create this automation within your own Azure tenant and provide all the necessary PowerShell code.

Below is an example of the email your user will receive if a vulnerable .NET app is found on their device 👇

vulnerable software found email example

As shown, the email includes a list of vulnerable apps, their versions, clickable CVEs, and details on how each app was detected.

But why should we make users update their apps in the first place?

  • Users can install software on their own avoiding any interruptions

  • Automatic update of the app can cause compatibility issues

    • this applies mostly to developer tools like Visual Studio, Python, .Net, NodeJS, etc

These are some of the reasons why involving your users in the update process of the apps can be necessary.

But just informing the users without any sort of "punishment" wouldn't be sufficient. That is the reason why we are sending a second email in case the user doesn't fix the issue in time. This second email can be sent to the user manager, security team, etc. Or another more brutal approach can be chosen like isolating the device 😎.


Summary

After you finish this tutorial you will have:

  • Azure Automation that will monitor vulnerabilities on company devices and send email notifications based on the set thresholds to the device owner

    • first email by default 30 days after the first vuln. detection

      • TIP: A list of apps that should trigger immediate notification is supported
    • second email by default 14 days after the first one

  • (optional) Azure KeyVault that will safely store SendGrid API key

  • Azure Storage blob that will be used as persistent storage for Azure Automation runbook


Prerequisites

  • License for 'Microsoft Defender Vulnerability Management'

  • Permission to create Azure Automation and assign required Microsoft Defender for Endpoint API permissions

  • Willing to pay 1$/month for required Azure services (Storage, Automation, and KeyVault)

    • Or rewrite the code and host it on-premises for free
  • SendGrid account (API token) for sending notification emails

    • Runbook code can be customized to use any SMTP service, sending Teams messages, etc.

Let's go

Create Automation Account

Create a new Automation Account named VulnerableApps.

Make a note of the ID of the automatically created System Managed Identity. You will need it later when granting permission.

Create a Runbook

Inside the Automation Account create a new PowerShell 5.1 Runbook and copy-paste code from my VulnerableApps.ps1 script

Now modify #region CORE variables section to suit your environment and save the result!

💥
This is vital for it to work so read carefully the comments above the variables and set the correct values

$sendGridParam has to contain values set in the section Create Azure KeyVault for storing SendGrid API key.

Grant required permissions for accessing Microsoft Defender API

In order for our automation to be able to read data from the Microsoft Defender API, it is necessary to assign it the following WindowsDefenderATP permissions:

  • User.Read.All

  • SecurityRecommendation.Read.All

  • Alert.Read.All

  • Software.Read.All

  • Vulnerability.Read.All

  • Machine.Read.All

  • AdvancedQuery.Read.All

You can do it easily using my function Grant-AzureServicePrincipalPermission (part of the AzureApplicationStuff module) or manually in the Azure portal

# example code, you need to adjust it to match your environment!

Connect-AzAccount 

Grant-AzureServicePrincipalPermission -servicePrincipalId 'yourManagedIdentityId' -permissionType application -permissionList 'User.Read.All','SecurityRecommendation.Read.All','Alert.Read.All','Software.Read.All','Vulnerability.Read.All','Machine.Read.All','AdvancedQuery.Read.All' -resourceAppId 'fc780465-2017-40d4-a0c5-307022471b92'

Import required modules

Runbook code depends on several other modules that I've created, so in order for it to work, we need to import them into our Automation Account environment.

Required modules:

  • AzureResourceStuff

  • M365DefenderStuff

  • CommonStuff

  • PSSendGrid

You can do it easily using my function New-AzureAutomationModule or New-AzureAutomationRuntimeModule in case you are using Runtime Environments (both functions are part of the AzureResourceStuff module) which has a huge benefit of importing but just required modules, but also their required dependencies 👍.

Or you can import the modules manually in the Automation account settings.

# example code, you need to adjust it to match your environment!

Connect-AzAccount -Tenant "contoso.onmicrosoft.com" -SubscriptionName "AutomationSubscription"

'AzureResourceStuff','M365DefenderStuff','CommonStuff','PSSendGrid' | % {
    New-AzureAutomationModule -resourceGroupName XXX -automationAccountName YYY -moduleName $_
}
💡
For more details on how New-AzureAutomationModule works, check this article

Create an Azure Storage Account for storing Runbook persistent data

Because Azure Automation Runbook doesn't have the option to store complex persistent data, we need to store such data in Azure Blob storage.

💡
For more details about creating an Azure Storage Account for storing Runbook variables check this article

In the subscription where you host your Runbook please create the following structure:

  • Resource Group Name named PersistentRunbookVariables

    • In that group create a Storage Account named persistentvariablesstore

      • In that Storage Account create a container named variables
💡
You can choose your own names, but in such case, you will have to modify parameters when calling my Export-VariableToStorage and Import-VariableFromStorage functions

Grant Runbook Managed Identity permissions to the Azure Storage Account container

Grant our Managed Identity Storage Blob Data Contributor IAM role over Storage Account container created in the previous step. So it can modify files in this container.

Create Azure KeyVault for storing SendGrid API key

For sending emails via SendGrid I am using my function Send-EmailViaSendGrid. This function supports retrieval of the SendGrid API key directly from Azure KeyVault, which is ideal solution for Azure Automations.

The only thing you have to do is to create such secret in existing KeyVault (or some newly created one) and set $sendGridParam hashtable values accordingly in your Runbook code.

Grant Runbook Managed Identity permissions to read stored SendGrid token

Grant our Managed Identity Key Vault Secrets User IAM role over the created secret to allow it to read it.


Gotchas

  • Defender vulnerability report doesn't have to be 100% accurate, because if app is incorrectly uninstalled therefore some registry keys or files remain on the device disk, Defender can still detect it even though the app isn't installed. In such rare cases, you need to manually remove some leftovers.

  • If you use MDT for software installation, apps are installed under built-in administrator account, which can lead to situations where the vulnerable app is detected in the built-in administrator profile/registry! This can be a significant problem for your users because it can be hard for them to remove such leftover data.

    • This can be solved by removing the built-in admin profile (not the account, just the profile data). For example, using Intune remediation script and PSH code

        Get-CimInstance -Class Win32_UserProfile | ? { $_.SID -like "*-500" } | Remove-CimInstance
      

Tips

  • Inform your users about those notifications beforehand! So they don't consider them as a scam 😀

  • You can exclude the specific application from this notification automation, for example, because it is the last existing version, so your users cannot update it anyway

    • Just update the $exclusionList variable in the automation code

        <#
        - 'CveId' and/or 'ProductName' property/ies (string) have to be defined
        - 'ProductVersion' (string) is optional
            - property values can be extracted from $vulnerabilityPerMachine.vulnswdata
        - 'ValidUntil' (datetime) is optional (since this date, exclustion will be ignored)
            - BEWARE that in Azure pipeline EN date has to be used a.k.a. month.day.year!!!
        #>
      
        $exclusionList = @(
            [PSCustomObject]@{
                CveId       = 'CVE-2024-32002'
                ProductName = 'visual_studio_2022'
                ValidUntil  = (Get-Date 8.1.2024) # M.d.yyyy
            }
        )
      
  • Check this article if you want to update all installed applications gradually using Winget

    • exceptions can be made
  • If your helpdesk interactively logs in to employees' computers, you can avoid identifying such accounts as device owners by altering the following line of code in the runbook

  •       $user = Get-M365DefenderMachineUser -header $header -machineId $machineId | ? { $_.logonTypes -like '*Interactive*' -and $_.accountName -ne "administrator" }
    

Did you find this article valuable?

Support Ondrej Sebela by becoming a sponsor. Any amount is appreciated!