Create multiple Azure Sentinel rules from selected templates

Introduction

IMHO, one of the biggest PITA when setting up a new instance of Azure Sentinel is that while Microsoft gives you all these great Analytic rule templates, you have to select each, one at a time, to create a rule from them. These PowerShell scripts will avoid that.

First, there is a PowerShell command, Export-AzSentinelAnalyticsRuleTemplates.ps1, that is going to take the code that was used to read all the Azure Sentinel Analytic rule templates and export those to a CSV file. This CSV file will have a column where you can place an “X” in it to designate that you want a rule created from it.

Then, there is a second PowerShell script, New-AzSentinelAnalyticsRulesFromCSV.ps1, that will read in that CSV and create the rules from it.

There are a few caveats to these scripts:

  1. I am not a professional PowerShell developer so I am sure there are better ways to perform some actions (like actually creating the CSV file). I would be interested in hearing about them if there are any.
  2. For some reason that baffles me, when using this code to create rule based off of a Microsoft Security template, it never shows as being in use. All the other rules do. Would also be interested in knowing if anyone has a fix for that.
  3. There are two rules that fail each and every time. This one bugs me to no end! I have no idea if there is something in the query that I am not catching. Everything else looks fine. Would definitely like to know if anyone figures this one out! 🙂
    1. Powershell Empire cmdlets seen in command line
    2. Network endpoint to host executable correlation
  4. There is no checking to see if the required data sources exist. There are new REST APIs to help determine this so maybe in the future. For now you just get an error message and the code continues.

OK, enough CYA 🙂 Let’s get to it.

If you have not made a REST API call to Azure Sentinel before, I recommend reading my post Your first Azure Sentinel REST API call to see what you need to do to get your environment setup.

Remember, these scripts have assumed you already logged into your Azure environment and selected the appropriate subscription if needed.

Export-AzSentinelAnalyticsRuleTemplates

This is a long name but it does describe what the script does. It will output the listing of all the Azure Sentinel Analytic rule templates to a CSV file.

This expands on the work done in Working with Analytics rules Part 1 – Templates by exporting the information to the CSV file. There are a couple of translations being performed along the way. The times for how often the query gets run and how far back it looks are converted into something a bit more readable. The rule threshold text is also converted. Note that these are just basic conversions right now but handle everything in the templates.

Take a look at the next section to see all the fields being exported. Feel free to add or remove any of the other columns either in the CSV or in the code itself. For example, is you want to see all the query code for all the templates, you could add that column as one to be output

The CSV File

The CSV file is pretty basic as well. It contains the following fields.

  • Selected
  • Severity
  • DisplayName
  • Kind
  • Name
  • Description
  • Tactics
  • Required Data Connectors
  • Rule Frequency
  • Rule Period
  • Rule Threshold
  • Status

The reason DisplayName is not separated into two words is that I need to access it in the PowerShell code and it is easier to do it this way.

The Selected column is where you would place a capital X to state you want to create a rule from the entry in the CSV file. Actually, it is creating the rule from the template listing which gets loaded in the script as well just in case the CSV file is old.

The Name column is used to perform the match against the listing of templates so don’t mess with that one! And the Kind column is used to determine how the body of the REST call needs to be setup so leave that one alone as well 🙂

New-AzSentinelAnalyticsRulesFromCSV

This code expands on the work done in Working with Analytics rules Part 3 – Create Fusion / ML Rule and Working with Analytics rules Part 4 – Create Microsoft Security Rule in that it takes the code needed to create these rules and puts them all into one place.

There are a few new lines of code that are interesting enough to discuss.

$fileContents = Import-csv  $FileName
$fileContents | ForEach-object {
    $selected = $_.Selected
    if ($selected.ToUpper() -eq "X") {
        $name = $_.Name
        $kind = $_.Kind
        $displayName = $_.DisplayName
        $template = $results | Where-Object { $_.name -eq $name }
            if ($null -ne $template) {
LineDescription
1 Reads the file name that is passed in as a parameter and reads all the contents of that file
2Iterates through all the rows that have been read in
3Reads the first column of each row, called Selected
4Checks to see if the is an uppercase X in it to denote that we want to create a rule from this template and continue if it is found
5-7Reads a few more variables that we need.
8Performs a search against all the Analytics rule templates, loaded earlier, and try\ies to match the Name column from the row that was read in from the CSV file against the Name field in the template listings.
9Continues if we have a match.

Then the body of the REST call will be created and the REST call will be made like in the other posts.

Code

All the code can be downloaded from https://github.com/garybushey/AzSentinelAnalyticsRules

Summary

This wraps up all the other blog posts in the series to allow you to easily create the rules from the templates. Hope you find it useful!

6 thoughts on “Create multiple Azure Sentinel rules from selected templates

  • Hi Gary
    I was able to sort the query for the PowerShell Empire and it has been validated on my environment.
    If you copy all of the contents from the template, then use the prettify button, it will adjust it. That helped me to get it working as below:

    let timeframe = 1d;
    let regexEmpire = @”SetDelay|GetDelay|Set-LostLimit|Get-LostLimit|Set-Killdate|Get-Killdate|Set-WorkingHours|Get-WorkingHours| Get-Sysinfo|Add-Servers|Invoke-ShellCommand|Start-AgentJob|Update-Profile|Get-FilePart|Encrypt-Bytes|Decrypt-Bytes|Encode-Packet| Decode-Packet|Send-Message|Process-Packet|Process-Tasking|Get-Task|Start-Negotiate|Invoke-DllInjection|Invoke-ReflectivePEInjection| Invoke-Shellcode|Invoke-ShellcodeMSIL|Get-ChromeDump|Get-ClipboardContents|Get-IndexedItem|Get-Keystrokes|Invoke-Inveigh|Invoke-NetRipper| local:Invoke-PatchDll|Invoke-NinjaCopy|Get-Win32Types|Get-Win32Constants|Get-Win32Functions|Sub-SignedIntAsUnsigned|Add-SignedIntAsUnsigned| Compare-Val1GreaterThanVal2AsUInt|Convert-UIntToInt|Test-MemoryRangeValid|Write-BytesToMemory|Get-DelegateType|Get-ProcAddress| Enable-SeDebugPrivilege|Invoke-CreateRemoteThread|Get-ImageNtHeaders|Get-PEBasicInfo|Get-PEDetailedInfo|Import-DllInRemoteProcess| Get-RemoteProcAddress|Copy-Sections|Update-MemoryAddresses|Import-DllImports|Get-VirtualProtectValue|Update-MemoryProtectionFlags| Update-ExeFunctions|Copy-ArrayOfMemAddresses|Get-MemoryProcAddress|Invoke-MemoryLoadLibrary|Invoke-MemoryFreeLibrary|Out-Minidump| Get-VaultCredential|Invoke-DCSync|Translate-Name|Get-NetDomain|Get-NetForest|Get-NetForestDomain|Get-DomainSearcher|Get-NetComputer| Get-NetGroupMember|Get-NetUser|Invoke-Mimikatz|Invoke-PowerDump|Invoke-TokenManipulation|Exploit-JMXConsole|Exploit-JBoss|Invoke-Thunderstruck| Invoke-VoiceTroll|Set-WallPaper|Invoke-PsExec|Invoke-SSHCommand|Invoke-PSInject|Invoke-RunAs|Invoke-SendMail|Invoke-Rule|Get-OSVersion| Select-EmailItem|View-Email|Get-OutlookFolder|Get-EmailItems|Invoke-MailSearch|Get-SubFolders|Get-GlobalAddressList|Invoke-SearchGAL| Get-SMTPAddress|Disable-SecuritySettings|Reset-SecuritySettings|Get-OutlookInstance|New-HoneyHash|Set-MacAttribute|Invoke-PatchDll| Get-SecurityPackages|Install-SSP|Invoke-BackdoorLNK|New-ElevatedPersistenceOption|New-UserPersistenceOption|Add-Persistence|Invoke-CallbackIEX| Add-PSFirewallRules|Invoke-EventLoop|Invoke-PortBind|Invoke-DNSLoop|Invoke-PacketKnock|Invoke-CallbackLoop|Invoke-BypassUAC|Get-DecryptedCpassword| Get-GPPInnerFields|Invoke-WScriptBypassUAC|Get-ModifiableFile|Get-ServiceUnquoted|Get-ServiceFilePermission|Get-ServicePermission| Invoke-ServiceUserAdd|Invoke-ServiceCMD|Write-UserAddServiceBinary|Write-CMDServiceBinary|Write-ServiceEXE|Write-ServiceEXECMD|Restore-ServiceEXE| Invoke-ServiceStart|Invoke-ServiceStop|Invoke-ServiceEnable|Invoke-ServiceDisable|Get-ServiceDetail|Find-DLLHijack|Find-PathHijack|Write-HijackDll| Get-RegAlwaysInstallElevated|Get-RegAutoLogon|Get-VulnAutoRun|Get-VulnSchTask|Get-UnattendedInstallFile|Get-Webconfig|Get-ApplicationHost| Write-UserAddMSI|Invoke-AllChecks|Invoke-ThreadedFunction|Test-Login|Get-UserAgent|Test-Password|Get-ComputerDetails|Find-4648Logons| Find-4624Logons|Find-AppLockerLogs|Find-PSScriptsInPSAppLog|Find-RDPClientConnections|Get-SystemDNSServer|Invoke-Paranoia| Invoke-WinEnum{|Get-SPN|Invoke-ARPScan|Invoke-Portscan|Invoke-ReverseDNSLookup|Invoke-SMBScanner|New-InMemoryModule|Add-Win32Type| Export-PowerViewCSV|Get-MacAttribute|Copy-ClonedFile|Get-IPAddress|Convert-NameToSid|Convert-SidToName|Convert-NT4toCanonical|Get-Proxy| Get-PathAcl|Get-NameField|Convert-LDAPProperty|Get-NetDomainController|Add-NetUser|Add-NetGroupUser|Get-UserProperty|Find-UserField|Get-UserEvent| Get-ObjectAcl|Add-ObjectAcl|Invoke-ACLScanner|Get-GUIDMap|Get-ADObject|Set-ADObject|Get-ComputerProperty|Find-ComputerField|Get-NetOU|Get-NetSite| Get-NetSubnet|Get-DomainSID|Get-NetGroup|Get-NetFileServer|SplitPath|Get-DFSshare|Get-DFSshareV1|Get-DFSshareV2|Get-GptTmpl|Get-GroupsXML| Get-NetGPO|Get-NetGPOGroup|Find-GPOLocation|Find-GPOComputerAdmin|Get-DomainPolicy|Get-NetLocalGroup|Get-NetShare|Get-NetLoggedon|Get-NetSession| Get-NetRDPSession|Invoke-CheckLocalAdminAccess|Get-LastLoggedOn|Get-NetProcess|Find-InterestingFile|Invoke-CheckWrite|Invoke-UserHunter| Invoke-StealthUserHunter|Invoke-ProcessHunter|Invoke-EventHunter|Invoke-ShareFinder|Invoke-FileFinder|Find-LocalAdminAccess|Get-ExploitableSystem| Invoke-EnumerateLocalAdmin|Get-NetDomainTrust|Get-NetForestTrust|Find-ForeignUser|Find-ForeignGroup|Invoke-MapDomainTrust|Get-Hex| Create-RemoteThread|Get-FoxDump|Decrypt-CipherText|Get-Screenshot|Start-HTTP-Server|Local:Invoke-CreateRemoteThread|Local:Get-Win32Functions| Local:Inject-NetRipper|GetCommandLine|ElevatePrivs|Get-RegKeyClass|Get-BootKey|Get-HBootKey|Get-UserName|Get-UserHashes|DecryptHashes|DecryptSingleHash| Get-UserKeys|DumpHashes|Enable-SeAssignPrimaryTokenPrivilege|Enable-Privilege|Set-DesktopACLs|Set-DesktopACLToAllowEveryone|Get-PrimaryToken| Get-ThreadToken|Get-TokenInformation|Get-UniqueTokens|Find-GPOLocation|Find-GPOComputerAdmin|Get-DomainPolicy|Get-NetLocalGroup|Get-NetShare| Get-NetLoggedon|Get-NetSession|Get-NetRDPSession|Invoke-CheckLocalAdminAccess|Get-LastLoggedOn|Get-NetProcess|Find-InterestingFile|Invoke-CheckWrite| Invoke-UserHunter|Invoke-StealthUserHunter|Invoke-ProcessHunter|Invoke-EventHunter|Invoke-ShareFinder|Invoke-FileFinder|Find-LocalAdminAccess| Get-ExploitableSystem|Invoke-EnumerateLocalAdmin|Get-NetDomainTrust|Get-NetForestTrust|Find-ForeignUser|Find-ForeignGroup|Invoke-MapDomainTrust| Get-Hex|Create-RemoteThread|Get-FoxDump|Decrypt-CipherText|Get-Screenshot|Start-HTTP-Server|Local:Invoke-CreateRemoteThread|Local:Get-Win32Functions| Local:Inject-NetRipper|GetCommandLine|ElevatePrivs|Get-RegKeyClass|Get-BootKey|Get-HBootKey|Get-UserName|Get-UserHashes|DecryptHashes|DecryptSingleHash| Get-UserKeys|DumpHashes|Enable-SeAssignPrimaryTokenPrivilege|Enable-Privilege|Set-DesktopACLs|Set-DesktopACLToAllowEveryone|Get-PrimaryToken| Get-ThreadToken|Get-TokenInformation|Get-UniqueTokens|Invoke-ImpersonateUser|Create-ProcessWithToken|Free-AllTokens|Enum-AllTokens|Invoke-RevertToSelf| Set-Speaker(\$Volume){\$wshShell|Local:Get-RandomString|Local:Invoke-PsExecCmd|Get-GPPPassword|Local:Inject-BypassStuff| Local:Invoke-CopyFile\(\$sSource,|ind-Fruit|New-IPv4Range|New-IPv4RangeFromCIDR|Parse-Hosts|Parse-ILHosts|Exclude-Hosts|Get-TopPort|Parse-Ports |Parse-IpPorts|Remove-Ports|Write-PortscanOut|Convert-SwitchtoBool|Get-ForeignUser|Get-ForeignGroup”;
    let ProcessCreationEvents=()
    {
    let processEvents=SecurityEvent
    | where EventID==4688
    | where isnotempty(CommandLine)
    | project TimeGenerated, Computer, Account = SubjectUserName, AccountDomain = SubjectDomainName, FileName = Process, CommandLine, ParentProcessName;
    processEvents
    };
    let decodedPS = ProcessCreationEvents
    | where TimeGenerated >= ago(timeframe)
    | where CommandLine contains ” -encodedCommand”
    | parse kind=regex flags=i CommandLine with * “-EncodedCommand ” encodedCommand
    | project StartTimeUtc = TimeGenerated, encodedCommand = tostring(split(encodedCommand, ‘ ‘)[0]), CommandLine
    // Note: currently the base64_decodestring function is limited to supporting UTF8
    | extend decodedCommand = translate(‘\0’,”, base64_decodestring(substring(encodedCommand, 0, strlen(encodedCommand) – (strlen(encodedCommand) %8)))), encodedCommand, CommandLine , strlen(encodedCommand);
    (decodedPS
    | union (ProcessCreationEvents
    | where TimeGenerated >= ago(timeframe)
    | where FileName in~ (“powershell.exe”,”powershell_ise.exe”)
    | where CommandLine !contains “-encodedcommand”)
    | extend StartTimeUtc = TimeGenerated )
    | where CommandLine matches regex regexEmpire
    | extend timestamp = StartTimeUtc, AccountCustomEntity = Account, HostCustomEntity = Computer

    • Thank you. I can create the rule from the template in the portal, just not using my code. Did you create your rule using the Portal or some other means?

  • Hi Gary
    I have tried your script but the exported CSV is coming up all messed up so cannot be used for importing.

    It seems that the script it is not splitting the contents into the correct columns/rows on the CSV.
    Any suggestions?
    Thanks,
    Caio

    • Not sure why it would be messed up. Did you expand the headers so that the text in the columns is easier to read?

  • Hi Gary,

    This looks great, but the script keeps erroring out for me unfortunately. I’m seeing InvalidArgument errors at Line
    132 and 128:

    Line |
    132 | if ([int]$timeLength -gt 1) { $returnText += “s” }
    | ~~~~~~~~~~~~~~~~~~~~~~
    | Cannot convert value “1DT1” to type “System.Int32”. Error: “Input string was not in a correct format.”

    Am I missing something or did MSFT change something with the time designation that broke this?

    • Looks like there was some changes so my time converter doesn’t work anymore. I should go back and rework the entire script since I have learned so much since then

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.