Forum Discussion

Nick165's avatar
Nick165
Copper Contributor
May 01, 2025

How to exclude IPs & accounts from Analytic Rule, with Watchlist?

We are trying to filter out some false positives from a Analytic rule called "Service accounts performing RemotePS". Using automation rules still gives a lot of false mail notifications we don't want so we would like to try using a watchlist with the serviceaccounts and IP combination we want to exclude. Anyone knows where and what syntax we would need to exlude the items on the specific Watchlist?

Query:

let InteractiveTypes = pack_array(                                  // Declare Interactive logon type names

    'Interactive',

    'CachedInteractive',

    'Unlock',

    'RemoteInteractive',

    'CachedRemoteInteractive',

    'CachedUnlock'

);

let WhitelistedCmdlets = pack_array(                                // List of whitelisted commands that don't provide a lot of value

    'prompt',

    'Out-Default',

    'out-lineoutput',

    'format-default',

    'Set-StrictMode',

    'TabExpansion2'

);

let WhitelistedAccounts = pack_array('FakeWhitelistedAccount');     // List of accounts that are known to perform this activity in the environment and can be ignored

DeviceLogonEvents                                                         // Get all logon events...

| where AccountName !in~ (WhitelistedAccounts)                      // ...where it is not a whitelisted account...

| where ActionType == "LogonSuccess"                                // ...and the logon was successful...

| where AccountName !contains "$"                                   // ...and not a machine logon.

| where AccountName !has "winrm va_"                                // WinRM will have pseudo account names that match this if there is an explicit permission for an admin to run the cmdlet, so assume it is good.

| extend IsInteractive=(LogonType in (InteractiveTypes))            // Determine if the logon is interactive (True=1,False=0)...

| summarize HasInteractiveLogon=max(IsInteractive)                  // ...then bucket and get the maximum interactive value (0 or 1)...

            by AccountName                                          // ... by the AccountNames

| where HasInteractiveLogon == 0                                    // ...and filter out all accounts that had an interactive logon.

// At this point, we have a list of accounts that we believe to be service accounts

// Now we need to find RemotePS sessions that were spawned by those accounts

// Note that we look at all powershell cmdlets executed to form a 29-day baseline to evaluate the data on today

| join kind=rightsemi (                                             // Start by dropping the account name and only tracking the...

    DeviceEvents                                                      // ...

    | where ActionType == 'PowerShellCommand'                         // ...PowerShell commands seen...

    | where InitiatingProcessFileName =~ 'wsmprovhost.exe'            // ...whose parent was wsmprovhost.exe (RemotePS Server)...

    | extend AccountName = InitiatingProcessAccountName             // ...and add an AccountName field so the join is easier

) on AccountName

// At this point, we have all of the commands that were ran by service accounts

| extend Command = tostring(extractjson('$.Command', tostring(AdditionalFields))) // Extract the actual PowerShell command that was executed

| where Command !in (WhitelistedCmdlets)                                          // Remove any values that match the whitelisted cmdlets

| summarize (Timestamp, ReportId)=arg_max(TimeGenerated, ReportId),               // Then group all of the cmdlets and calculate the min/max times of execution...

    make_set(Command, 100000), count(), min(TimeGenerated) by                     // ...as well as creating a list of cmdlets ran and the count..

    AccountName, AccountDomain, DeviceName, DeviceId                                             // ...and have the commonality be the account, DeviceName and DeviceId

// At this point, we have machine-account pairs along with the list of commands run as well as the first/last time the commands were ran

| order by AccountName asc                                        // Order the final list by AccountName just to make it easier to go through

| extend HostName = iff(DeviceName has '.', substring(DeviceName, 0, indexof(DeviceName, '.')), DeviceName)

| extend DnsDomain = iff(DeviceName has '.', substring(DeviceName, indexof(DeviceName, '.') + 1), "")                                          

1 Reply

  • Ankit's avatar
    Ankit
    Brass Contributor

    You want to stop getting false alerts for certain service accounts and machines (or IPs). 

    What to do:
    Make a watchlist in Sentinel with two columns:

    AccountName (the service account name)
    DeviceName (the machine or IP you want to ignore)

    In your query, load that watchlist and filter out any events where the account name and device name match a pair in the watchlist.

    To exclude those pairs in your query:
    Add this part to your query (replace ServiceAccountExclusion with your actual watchlist name):

    let exclusions = ServiceAccountExclusion | project AccountName, DeviceName;
    | join kind=leftanti (exclusions) on AccountName, DeviceName

    This means: Ignore any logs where both the account name and device name match the watchlist.

    Adusted query

    let exclusions = ServiceAccountExclusion | project AccountName, DeviceName;
    let InteractiveTypes = pack_array(
        'Interactive',
        'CachedInteractive',
        'Unlock',
        'RemoteInteractive',
        'CachedRemoteInteractive',
        'CachedUnlock'
    );

    let WhitelistedCmdlets = pack_array(
        'prompt',
        'Out-Default',
        'out-lineoutput',
        'format-default',
        'Set-StrictMode',
        'TabExpansion2'
    );

    let WhitelistedAccounts = pack_array('FakeWhitelistedAccount');

    DeviceLogonEvents
    | where AccountName !in~ (WhitelistedAccounts)
    | where ActionType == "LogonSuccess"
    | where AccountName !contains "$"
    | where AccountName !has "winrm va_"
    | extend IsInteractive = (LogonType in (InteractiveTypes))
    | summarize HasInteractiveLogon = max(IsInteractive) by AccountName
    | where HasInteractiveLogon == 0
    | join kind=rightsemi (
        DeviceEvents
        | where ActionType == 'PowerShellCommand'
        | where InitiatingProcessFileName =~ 'wsmprovhost.exe'
        | extend AccountName = InitiatingProcessAccountName
    ) on AccountName
    | extend Command = tostring(extractjson('$.Command', tostring(AdditionalFields)))
    | where Command !in (WhitelistedCmdlets)
    // EXCLUDE events where (AccountName + DeviceName) is in your watchlist
    | join kind=leftanti (exclusions) on AccountName, DeviceName
    | summarize (Timestamp, ReportId) = arg_max(TimeGenerated, ReportId),
        make_set(Command, 100000), count(), min(TimeGenerated)
        by AccountName, AccountDomain, DeviceName, DeviceId
    | order by AccountName asc
    | extend HostName = iff(DeviceName has '.', substring(DeviceName, 0, indexof(DeviceName, '.')), DeviceName)
    | extend DnsDomain = iff(DeviceName has '.', substring(DeviceName, indexof(DeviceName, '.') + 1), "")

     



     

Resources