This vulnerability for Log4j have been exploding 💥 around the world and to keep track - that’s not so easy. And it’s not easy when you have this great community contributing in creating awesome KQL for Microsoft Sentinel and then you need to find the time to import them all into Sentinel.

There’s also a limitation for Azure CSP Subscriptions when it comes to the new Solutions part in Sentinel. So I really hope that you can use this script to import the most important queries that you need.

In this post, we will see if my script can help you to import all of those Hunting Queries. I have made two options for you, to create new hunting query via PowerShell - or import from a GitHub repo, directly from PowerShell.

But first, let see if you have all the pre-reqs installed.

Pre-Reqs:

Now, if you want to create a new hunting query:

1
New-MsSentinelHuntingRule -CreateNewOrImport CreateNew -DisplayName "NameOfYourQuery" -Query "InKqlFormat" -Description "WhatDoTheQueryDo" -Tactics <Choose from list, no ""> -Techniques <Add one or more T-number, no ""> -Category 'Hunting Queries' -SubscriptionId "LookInAzurePortal" -ResourceGroupName "WheresSentinel" -WorkspaceName "LogAnalyticsWorkspaceName" [-Verbose]

To import from GitHub, we are using the API URI and in that we can find the download_url, like raw.githubusercontent.com URL. So what I need in the PowerShell one-liner is:

1
New-MsSentinelHuntingRule -CreateNewOrImport Import -GitHubPath "https://api.github.com/repos/pthoor/MS_Sentinel/contents/Hunting/Log4j_Log4Shell/YAML/" -YAMLimportPath "LocalPathToSaveFiles" -SubscriptionId "LookInAzurePortal" -ResourceGroupName "WheresSentinel" -WorkspaceName "LogAnalyticsWorkspaceName" [-Verbose]

For the GitHubPath, adjust it for your needs.

Now you will be prompted to select the YAML files you want to download and then import via Out-Gridview.

After a short while, the queries will be uploaded via the cmdlet New-AzOperationalInsightsSavedSearch.

Here’s the New-MsSentinelHuntingRule - available at GitHub - https://github.com/pthoor/MS_Sentinel/blob/main/New-MsSentinelHuntingRule.ps1

New-MsSentinelHuntingRule

⬇️ Click here to expand/unexpand
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
#requires -module @{ModuleName = 'Az.Accounts'; ModuleVersion = '1.5.2'}
#requires -version 6.2
function New-MsSentinelHuntingRule {
<#
    .SYNOPSIS
    Create Microsoft Sentinel Hunting Rule
    .DESCRIPTION
    Use this function to creates Microsoft Sentinal Hunting rule
    .PARAMETER SubscriptionId
    Enter the subscription ID
    .PARAMETER WorkspaceName
    Enter the Log Analytics Workspace name
    .PARAMETER DisplayName
    Enter the Display Name for the hunting rule
    .PARAMETER Description
    Enter the Description for the hunting rule
    .PARAMETER Tactics
    Enter the Tactics, valid values: 
    "Reconnaissance", "ResourceDevelopment", "InitialAccess", "Execution", "Persistence", "PrivilegeEscalation", "DefenseEvasion", "CredentialAccess", "Discovery", "LateralMovement", "Collection", "CommandAndControl", "Exfiltration", "Impact", "ImpairProcessControl", "InhibitResponseFunction"
    .PARAMETER Query
    Enter the query in KQL format
    .PARAMETER CreateNewOrImport
    Valid values: "CreateNew", "Import".
    If CreateNew, you have to fill in all other parameters.
    If Import, you have to fill in GitHubPath parameter and YAMLimportPath parameter.
    .PARAMETER YAMLimportPath
    Local path to save YAML files downloaded by GitHubPath
    .PARAMETER GitHubPath
    Enter the full GitHub URI (api.github.com) to YAML files for download
    .EXAMPLE
    New-MsSentinelHuntingRule -CreateNewOrImport CreateNew -WorkspaceName "<LogAnalyticsWorkspaceName>" -DisplayName "<DisplayNameOfRule>" -Description "<DescriptionForYourRule>" -Tactics "<AddTactics>","<Tactic2>" -Query 'InKQLFormat'
    In this example you create a new hunting rule by defining the rule properties directly in PowerShell
    .EXAMPLE
    New-MsSentinelHuntingRule -CreateNewOrImport Import -SubscriptionId "<FindIdInAzurePortal>" -ResourceGroupName "WhereYouHaveSentinel" -WorkspaceName "<LogAnalyticsWorkspaceName>" -GitHubPath "<URIToApi>" -YAMLimportPath "<LocalPathToSaveFiles>"
#>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')]
    param (
        [parameter(Mandatory)]
        [ValidateSet("CreateNew", "Import")]
        [String] $CreateNewOrImport,

        [parameter()]
        [string] $GitHubPath,
        
        [ValidateScript({ (Test-Path -Path $_) })]
        [IO.DirectoryInfo] $YAMLimportPath,

        [Parameter(Mandatory,
            ParameterSetName = "Sub")]
        [ValidateNotNullOrEmpty()]
        [string] $SubscriptionId,

        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string] $ResourceGroupName,

        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string] $WorkspaceName,

        [Parameter()]
        [string] $DisplayName,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string] $Query,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string] $Description,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [ValidateSet("Reconnaissance", "ResourceDevelopment", "InitialAccess", "Execution", "Persistence", "PrivilegeEscalation", "DefenseEvasion", "CredentialAccess", "Discovery", "LateralMovement", "Collection", "CommandAndControl", "Exfiltration", "Impact", "ImpairProcessControl", "InhibitResponseFunction")]
        [string[]] $Tactics,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string[]] $Techniques,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [ValidateSet("Hunting Queries")]
        [string] $Category = "Hunting Queries"
    )

    $subIdContext = (Get-AzContext).Subscription.Id 
    if ($subIdContext -ne $subscriptionId) {
        $setSub = Set-AzContext -SubscriptionName $subscriptionId -ErrorAction SilentlyContinue
        if ($null -eq $setSub) {
            Write-Warning "$subscriptionId is not set, please login"
            Login-AzAccount
            Set-AzContext -SubscriptionName $subscriptionId -ErrorAction SilentlyContinue
        }
    }

    # If not installed, import the PowerShell-YAML community module, installed from https://www.powershellgallery.com/packages/powershell-yaml/0.4.2
    $powershellYamlModule = Get-Module -Name "powershell-yaml" -ErrorAction SilentlyContinue
    #if ($powershellYamlModule -eq $null) {
    if ($null -eq $powershellYamlModule) {
        Write-Warning "The PowerShell-YAML module is not found"
            #check for Admin Privleges
            $currentPrincipal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())

            if (-not ($currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator))) {
                #Not an Admin, install to current user
                Write-Warning -Message "Can not install the PowerShell-YAML module. You are not running as Administrator"
                Write-Warning -Message "Installing the PowerShell-YAML module to current user Scope"
                Install-Module powershell-yaml -Scope CurrentUser -Force
            }
            Else {
                #Admin, install to all users
                Write-Warning -Message "Installing the powershell-yaml module to all users"
                Install-Module -Name powershell-yaml -Force
                Import-Module -Name powershell-yaml -Force
            }
    }
    
    # Import the Az.SecurityInsights module
    Import-Module -Name Az.SecurityInsights

    # Create new or import existing from GitHub

    # Import from GitHub
    if($CreateNewOrImport -eq "Import"){
        try {
            #Ask GitHub API for all of the files in repo and let the user choose
            $wr = Invoke-WebRequest -Uri $GitHubPath
            $objects = $wr.Content | ConvertFrom-Json
            $files = $objects | Where-Object {$_.type -eq "file"} | Select-Object -ExpandProperty download_url | Out-GridView -PassThru
            
            if (-not (Test-Path $YAMLimportPath)) {
                # Destination path does not exist, let's create it
                try {
                    New-Item -Path $YAMLimportPath -ItemType Directory -ErrorAction Stop
                } catch {
                    throw "Could not create path '$YAMLimportPath'!"
                }
            }

            foreach ($file in $files) {
                Write-Verbose "Start downloading files from GitHub Path"
                $fileDestination = Join-Path $YAMLimportPath (Split-Path $file -Leaf)
                $outputFilename = $fileDestination.Replace("%20", " ");
                try {
                    Invoke-WebRequest -Uri "$file" -OutFile "$outputFilename" -ErrorAction Stop -Verbose
                    Write-Verbose "Downloaded '$($file)' to '$outputFilename'";
                } catch {
                    throw "Unable to download '$($file)'";
                }
            }

            $myNewRules = Get-ChildItem $YAMLimportPath -Filter *.yaml
            Write-Verbose "Found $($myNewRules.count) files"
            #Stop if we don't have YAML rules found to import
            if ($null -eq $myNewRules) {
                Write-Warning "Cannot find YAML rules to import, is your path correct?"
                break
            }
            Write-Verbose "Starting foreach loop"
            foreach ($myNewRule in $myNewRules) {
                Write-Verbose "Processing $($myNewRule.Name)"
                $myRuleObject = [pscustomobject](Get-Content $myNewRule.FullName -Raw | ConvertFrom-Yaml)
                $myRuleObject | Add-Member -MemberType NoteProperty -Name DisplayName -Value $myRuleObject.name
                $ruletactics = $myRuleObject.Tactics -join ","
                $ruleTechniques = $myRuleObject.relevantTechniques -join ","
                $ruleQuery = $myRuleObject.Query
                $ruleDescription = $myRuleObject.description

                Write-Verbose "Construct URI for uploading Hunting Query via API"
                $myruleDisplayName = $myRuleObject.DisplayName
                $uri = "https://management.azure.com/subscriptions/$($SubscriptionId)/resourcegroups/$($ResourceGroupName)/providers/Microsoft.OperationalInsights/workspaces/$($WorkspaceName)/savedSearches/$($myruleDisplayName)?api-version=2020-08-01"
                
                $Date = Get-Date -UFormat "%m/%d/%Y %R"
                Write-Verbose "Using date of $($Date)"
                
                $body = @{
                    properties = @{
                            DisplayName = $myruleDisplayName
                            Query = $ruleQuery
                            Description = $ruleDescription
                            Category = 'Hunting Queries'
                            Tags = @(
                                @{
                                    Name = 'description'
                                    Value = $myruleDisplayName
                                },
                                @{
                                    Name = 'techniques'
                                    Value = $ruleTechniques
                                },
                                @{
                                    Name = 'tactics'
                                    Value = $ruletactics
                                },
                                @{
                                    Name = 'createdTimeUtc'
                                    Value = $Date
                                }
                            )
                    }
                }

                try {
                    Write-Verbose "Construct Headers for API Put call and convert Body to JSON"
                    $Headers = @{
                        'Content-Type' = 'application/json'
                        "Authorization" = (Get-AzAccessToken).Type + " " + (Get-AzAccessToken).Token
                    }
                    $result = Invoke-WebRequest -Uri $uri -Method Put -Headers $Headers -Body ($body | ConvertTo-Json -Depth 10 -EnumsAsStrings)
                    $body.Properties | Add-Member -NotePropertyName status -NotePropertyValue $($result.StatusDescription) -Force
                    $return += $body.Properties
    
                    Write-Verbose "Successfully updated hunting rule: $($item.displayName) with status: $($result.StatusDescription)"
                }
                catch {
                    Write-Verbose $_
                    Write-Error "Unable to invoke webrequest for rule $($item.displayName) with error message: $($_.Exception.Message)" -ErrorAction Continue
                }
            }

        }
        catch {
            Write-Warning "Script terminated, please try again."
        }
    }

    # Create new
    if($CreateNewOrImport -eq "CreateNew"){
        try {
            Write-Verbose "Formulating the rule to be created"
            $TacticsJoin = $Tactics -join ","
            $TechniquesJoin = $Techniques -join ","
            $Date = Get-Date -UFormat "%m/%d/%Y %R"        

            $TagsSplatt = @{
                description = $Description
                techniques  = $TechniquesJoin
                tactics     = $TacticsJoin
                createdTimeUtc  = $Date
            }

            $ruleSplatt = @{
                WorkspaceName = $workspaceName
                ResourceGroupName = $ResourceGroupName
                SavedSearchId = $DisplayName
                DisplayName = $DisplayName
                Category = $Category
                Query = $Query
                Tag = $TagsSplatt
            }
            Write-Verbose "Hunting Query to be created"
            Write-Output $ruleSplatt
            Write-Output $TagsSplatt
            Write-Warning "Do you wish to create the Hunting Query?" -WarningAction Inquire

            New-AzOperationalInsightsSavedSearch @ruleSplatt
        }
        catch {
            Write-Warning "Script terminated, please try again."
        }
    }
}

Please let me know if the script don’t work for you - I know it cannot update existing queries yet. I see you at the next post!

Happy hunting!

Ninja Cat