Integrate your Windows Defender scan logs with AWS Security Hub using PowerShell

Integrage Windos Defender logs into AWS Security Hub

AWS has recently announced the general availability of AWS Security Hub which provides a comprehensive view of compliance views with the security standards and their high priority AWS security alerts, or findings. You can also import other third-party products and security alerts into AWS Securty Hub which allows organisations to centralize their various security alerts from firewalls, endpoints, and intrusion detection applications, as well as database security, vulnerability, and compliance scanners into one location. We all like centralized dashboards, what's not to like?

Today I'd like to show you how easy is to integrate Windows Defender Antivirus logs into AWS Security Hub. Why Defender, you ask? Well, Defender does not provide any centralized alerts dashboard by default unless you use System Center Configuration Manager or Microsoft Intune. Even though Defender is not the most commonly used solution it still shows a good use case that can also be easily translated into tons of other valid use cases where you want to send important security events from the Windows Event log to AWS Security Hub.

I wrote a PowerShell PoC function that gets the latest Defender scan alert log, converts the data into a custom findings data format and uses the AWS BatchImportFindings API. Let's get started.

Prerequisites
Let's start with configuring a new AWS account that will be used by the PowerShell function. I add a new user using AWS IAM with API access only:

AWSIAM1

and attach the AWSSecurityHubFullAccess policy to it:

AWSIAM2

Obviously, in production make sure you follow the principle of least privilege and grant only the necessary permissions to the account.

The next step is to install AWS Tools for PowerShell:
PS C:\> Install-Module -Name AWSPowerShell

and configure the API keys in the default profile:
PS C:\> Set-AWSCredential -AccessKey <YOURACCESSKEY> -SecretKey <YOURAPISECRET> -StoreAs default

Let's get the party started
To cut to the chase this PowerShell function below submits the corresponding event to AWS Security Hub. A big thanks to Matteo Prosperi at AWS (https://github.com/matteo-prosperi) for his help in putting this together and Brandon West for the inspiration :) . You can find the latest PoC at my GitHub repo

function Import-SecurityFinding {
    <#
    .SYNOPSIS
        Submits Windows Defender Antivirus scan logs from Windows Event Logs to AWS Security Hub.
    .DESCRIPTION
        The Import-SecurityFinding cmdlet submits the latest Windows Defender Antivirus scan alert 
        from the Windows Event log from (event ID: 1116) and submits it to AWS Security Hub.
    #>
    [CmdletBinding()]
    param(
    [Parameter(Mandatory = $true)]
    [string]$AwsRegion
    )
    begin {
    $ProductFields = New-Object 'System.Collections.Generic.Dictionary[String,String]'  
    $virusalertevent = Get-WinEvent -FilterHashtable @{Logname='Microsoft-Windows-Windows Defender/Operational';ID=1116} -MaxEvents 1 
    $ProductFields.Add('Defender Version',$virusalertevent.properties[1].value)
    $ProductFields.Add('Defender Signature Version',$virusalertevent.properties[40].value)
    $ProductFields.Add('Defender Engine Version',$virusalertevent.properties[41].value)
    $ProductFields.Add('Defender Account',$virusalertevent.properties[19].value)
    $ProductFields.Add('Defender Action Result',$virusalertevent.properties[33].value)
    $ProductFields.Add('Defender User Result',$virusalertevent.properties[37].value)
    $finding = [Amazon.SecurityHub.Model.AwsSecurityFinding] @{
      AwsAccountId = (Get-STSCallerIdentity).Account;
      Compliance = @{
        Status = 'FAILED' };
      CreatedAt = [Xml.XmlConvert]::ToString((get-date),[Xml.XmlDateTimeSerializationMode]::Utc);
      Criticality = 100;
      Description = 'Windows Defender Antivirus malware alert!';
      GeneratorId = 'WindowsDefender';
      Id = $AwsRegion + '/' + $(Get-STSCallerIdentity).Account + '/' + [guid]::newguid().ToString("N");
      Malware = [Amazon.SecurityHub.Model.Malware[]]@(
        @{
         Name = $virusalertevent.properties[7].value;
     Path = $virusalertevent.properties[21].value;
     State = "OBSERVED";
     Type = $virusalertevent.properties[11].value
        });
      ProductArn = 'arn:aws:securityhub:' + $AwsRegion + ':' + $(Get-STSCallerIdentity).Account + ':product/' + $(Get-STSCallerIdentity).Account + '/default'
      ProductFields = $ProductFields
      Resources = [Amazon.SecurityHub.Model.Resource[]]@(
        @{
          Type = 'Other';
          Id = (Get-WmiObject win32_computersystem).DNSHostName + "." + (Get-WmiObject win32_computersystem).Domain     #log the FQDN of the server/workstation 
          });
      SchemaVersion = '2018-10-08';
      Severity = @{
          Normalized = 100
        };
       Title = 'Windows Defender Antivirus malware alert!';
       UpdatedAt = [Xml.XmlConvert]::ToString((Get-Date),[Xml.XmlDateTimeSerializationMode]::Utc);

       Types = @('Unusual Behaviors/Process')
       
    }
    Import-SHUBFindingsBatch -Finding $finding
    }
}

Let's break it down bit by bit:

begin { $ProductFields = New-Object 'System.Collections.Generic.Dictionary[String,String]' $virusalertevent = Get-WinEvent -FilterHashtable @{Logname='Microsoft-Windows-Windows Defender/Operational';ID=1116} -MaxEvents 1 $ProductFields.Add('Defender Version',$virusalertevent.properties[1].value) $ProductFields.Add('Defender Signature Version',$virusalertevent.properties[40].value) $ProductFields.Add('Defender Engine Version',$virusalertevent.properties[41].value) $ProductFields.Add('Defender Account',$virusalertevent.properties[19].value) $ProductFields.Add('Defender Action Result',$virusalertevent.properties[33].value) $ProductFields.Add('Defender User Result',$virusalertevent.properties[37].value)

In this part I create a System.Collections.Generic.Dictionary object (more on this later) and query the Windows Event Log looking for the latest malware deceted event logged by Defender (ID 1116;MALWAREPROTECTION_STATE_MALWARE_DETECTED) from the Microsoft-Windows-Windows Defender/Operational log. I'm using a FilterHashtable and then get the event log data through the Properties EventProperty object which exposes all the data we need. In this example I'm using the EICAR antivirus testfile which looks like this:

defender_event

$finding = [Amazon.SecurityHub.Model.AwsSecurityFinding] @{
In this line I start to assemble the data in the AWS custom finding format AWS Security Hub ris able to consume. The format is in JSON but we need to pass objects. Originally I thought this is a simple matter of putting together a JSON string but instead you have to study the corresponding .NET SDK carefully to understand what objects the Import-SHUBFindingsBatch cmdlet desires. A couple of notes here:

  • CreatedAt = [Xml.XmlConvert]::ToString((get-date),[Xml.XmlDateTimeSerializationMode]::Utc); This is how you can generate datetime stamps in the format AWS Security Hub requires
  • Id = $AwsRegion + '/' + $(Get-STSCallerIdentity).Account + '/' + [guid]::newguid().ToString("N"); Here I'm inserting the caller account ID ($(Get-STSCallerIdentity).Account) and also generating a unique ID using the Guid.NewGuid Method in .NET to ensure uniqueness for each alert that is being submitted by PowerShell.
  • Resources = [Amazon.SecurityHub.Model.Resource[]]@(@{Type = 'Other';Id = (Get-WmiObject win32_computersystem).DNSHostName + "." + (Get-WmiObject win32_computersystem).Domain

Here I'm using WMI to generate the FQDN and incorporate it in the alert. In the Severity field I've decided to log this as the highest severity (Normalized = 100). When I put together the data using the AWS format I've either defined the corresponding data object using the New-Object cmdlet (ProductFields) in a variable and added to the $finding object or defined explicitely within the $finding object.

Importing the Defender scan event


FailedCount FailedFindings SuccessCount
----------- -------------- ------------
0           {}             1           

I'm calling the function with eu-west-2 (London) region and the SuccessCount column shows the import was successful. Let's take a closer look what it looks like in the AWS Security Hub console:

aws_alert_malware3

This looks great however not everything is shown in the AWS Security Hub Console. Can you spot the difference? :) The ProductDetails data is not shown in the console where I included a few additional details about Defender (version, signature information, etc.), but to show you it's really there I use the Get-SHUBFinding cmdlet:

aws_GETSHUB

You can also observe the import event in CloudTrail:

cloudtrailevent

End notes
I'll wrap this up now but please bear in mind this is only the first step in a long journey of security incident response processes. It's a good starting point in getting visibility, but AWS Security Hub can also be integrated with response and remediation workflows. Depending on the organisation's matureness this opens up a lot of opportunities for automation and automated security responses using a bit of Lambda magic glued together with CloudWatch events. This PoC was tested with PowerShell v5 but having a PowerShell.Core version of it could further improve it. Do you have any improvement ideas?