Blog Post

Microsoft Sentinel Blog
7 MIN READ

The power of Data Collection Rules: Monitoring PowerShell usage

miriamwiesner's avatar
miriamwiesner
Icon for Microsoft rankMicrosoft
Sep 16, 2024

This is Part 2 of our Blog series on how to collect events using DCRs for advanced use cases. For Part 1, please check The power of Data Collection Rules: Collecting events for advanced use cases in Microsoft USOP - Microsoft Community Hub.

 

PowerShell is a great tool for administrators to manage devices and servers in their environment. When using it to administer remote systems with PSRemoting, you don’t leave credentials behind on the target systems – as opposed to RDP with which your credentials would be stored in the Local Security Authority (LSA). This provides many security benefits and helps prevent Pass-The-Hash attacks and other credential theft scenarios

 

Since it is a preinstalled tool, adversaries have been known to use PowerShell to attack organizations. Companies that have set up a robust PowerShell configuration and monitoring have a clear advantage against those adversaries! Thanks to PowerShell’s numerous built-in security and monitoring features, it is easy to detect and disrupt adversaries.

 

In this article we will look how you can set up your own monitoring mechanism to spot executed PowerShell code in your environment using Microsoft Sentinel and the Unified SecOps Platform. We will not discuss the various security features that can be configured for a robust PowerShell environment, there are other resources for it, as mentioned in our first article.

 

Step 1: Configure ScriptBlockLogging

 

In PowerShell, you can imagine a script block as a collection of commands and expressions that are executed together as one command: the “script block”.

 

Many companies become aware to the importance of logging only after an incident has occurred. At that point, it is not possible to detect what happened. Therefore, the PowerShell team decided to implement basic script block logging, starting with PowerShell 5, which can be useful to trace malicious activities performed prior to the incident.

 

The basic script block logging feature only captures some basic security-relevant script blocks as the default if not configured otherwise. This way, in case of a security incident, you have insights of what basic malicious activities were executed on your machine, however this also means that with this default configuration not every activity is captured. Therefore, to keep track of all the activities on business-critical servers and high value assets, we need to configure script block logging before we can start collecting and reviewing all relevant events.

 

This can be done by using Group Policy. Depending on the PowerShell version for which you want to configure script block logging, navigate to the following GPO path(s):

  • Windows PowerShell: Computer Configuration > Policies > Administrative Templates > Windows Components > Windows PowerShell > Turn on PowerShell Script Block Logging
  • PowerShell Core: Computer Configuration > Administrative Templates > PowerShell Core > Turn on PowerShell Script Block Logging

Configure this Setting as “Enabled” and confirm with “OK”. Do not check the box to “Log script block invocation start / stop events” as this setting is verbose and would generate a lot of noise.

 

 

 

 

If you have both PowerShell versions in use in your environment, we recommend configuring and monitoring both.

In the PowerShell Core policy, you can find an option to “Use Windows PowerShell Policy Setting”. Unless you have a use case to have different configurations for both versions, you can simply enable this option to sync your Windows PowerShell script block settings.

 

Please note that the PowerShell Core administrative template files (*.admx)might need to be imported first before you can use them. This article points out how you can locate and install them: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_group_policy_settings

 

Step 2: Detect and review executed PowerShell code

 

Now that you have configured everything, it is time to collect PowerShell script block logging events and review the executed code.  The following script retrieves and filters event ID 4104 from both Windows PowerShell and PowerShell Core (excluding certain paths and user IDs) and then formats and displays the filtered event details :

 

 

$PSWinEventLog = @{ ProviderName = "Microsoft-Windows-PowerShell"; Id = 4104 }

$PSCoreEventLog = @{ ProviderName = "PowerShellCore"; Id = 4104 }

$PSWinEventLog, $PSCoreEventLog | ForEach-Object {
    try {
        Get-WinEvent -FilterHashtable $_ | Where-Object  {
            !($_.Properties[4].Value -Match "C:\\ProgramData\\Microsoft\\Windows Defender Advanced Threat Protection\\DataCollection\\") `
            -and ($_.Properties[2].Value -ne "prompt") `
            -and ($_.UserId -ne "S-1-5-18") `
            -and ($_.UserId -ne "S-1-5-19") `
            -and !( $_.Properties[4].Value -Match ".vscode\\extensions\\") `
            -and !($_.Properties[4].Value -Match "C:\\Windows\\TEMP\\SDIAG_([A-Za-z0-9]+(-[A-Za-z0-9]+)+)\\CL_Utility\.ps1")
        } | Select-Object TimeCreated, `
            @{Name='ExecutedCode';Expression={ $_.Properties[2].Value }},`
            UserId, `
            LevelDisplayName, `
            @{Name='Path';Expression={$_.Properties[4].Value}}, `
            ProviderName, `
            @{Name='ScriptblockId';Expression={$_.Properties[3].Value}},`
            @{Name='CurrentPart';Expression={$_.Properties[0].Value}},`
            @{Name='TotalParts';Expression={$_.Properties[1].Value}}`
        | fl
    }
    catch {}

 

 

PowerShell script block events can be split into multiple events if the executed script block was too large. In that case, the “CurrentPart” field would indicate this by containing a value higher than 1. The “CurrentPart" field indicates the order of the script block pieces.

It is recommended to adjust this query to your environment; are there script blocks that are being run by certain programs in your environment that you can safely exclude from your review. You can use the following documentation as a reference for filtering: Creating Get-WinEvent queries with FilterHashtable - PowerShell | Microsoft Learn.

 

PowerShell is a great tool for administrators to manage devices and servers in their environment. When using it to administer remote

 

PowerShell script block events can be split into multiple events if the executed script block was too large. In that case, the “CurrentPart” field would indicate this by containing a value higher than 1. The “CurrentPart" field indicates the order of the script block pieces.

It is recommended to adjust this query to your environment; are there script blocks that are being run by certain programs in your environment that you can safely exclude from your review. You can use the following documentation as a reference for filtering: Creating Get-WinEvent queries with FilterHashtable - PowerShell | Microsoft Learn.

 

 

Step 3: Configure the data collection Rule (DCR) to collect the required events

 

Using the Azure Monitor agent (AMA), you can select the events you would like to collect from your servers using Xpath queries (please see Filter Windows events using Xpath queries for reference). If you are not familiar with the agent or you have not installed AMA yet in the servers you would like to monitor, please check the first article of this series: include link.

To create your DCR, as this time we are collecting non-Security events, go to Connectors and select Windows Forwarded Events. From here, select create DCR, add your servers, and under Collect select Custom. Paste the following xPath queries:

For Windows PowerShell:  

 

 

Microsoft-Windows-PowerShell/Operational!*[System[(EventID=4104)]]

 

 

For PowerShell Core:

 

 

PowerShellCore/Operational!*[System[(EventID=4104)]]

 

 

 

To prevent getting events that we don’t need (e.g. events from background processes or system accounts), based on the query we constructed in Step 2, we can create a transformation in our DCR that will prevent those events from being ingested into Microsoft Sentinel, as they could be too verbose and we may not need them. For this purpose, you can add this transformation in your DCR:

 

 

"transformKql": "source | where SystemUserId !in ('S-1-5-18', 'S-1-5-19') | extend ScriptBlockText =  parse_json(EventData).ScriptBlockText, ScriptBlockId = tostring(EventData.ScriptBlockId), MessageNumber = tostring(EventData.MessageNumber), MessageTotal = tostring(EventData.MessageTotal), Path = tostring(EventData.Path) | where tostring(ScriptBlockText) != 'prompt' | where Path != '.vscode\\\\extensions\\\\' and Path != 'C:\\\\Windows\\\\TEMP\\\\SDIAG_([A-Za-z0-9]+(-[A-Za-z0-9]+)+)\\\\CL_Utility.ps1' and Path != 'C:\\\\ProgramData\\\\Microsoft\\\\Windows Defender Advanced Threat Protection\\\\DataCollection\\\\'"

 

 

Alternatively, you can deploy this template from GitHub, which already includes the xPath queries described above, plus the transformation: Azure-Sentinel/DataConnectors/WindowsEvents/DataCollectionRulePowerShellEvents at master · Azure/Azure-Sentinel (github.com)

 

Step 4: Creating your detections

 

Now, we can go to Microsoft Sentinel or to the Unified security operations platform, which brings Microsoft Sentinel and Microsoft Defender XDR into a single unified portal (see how to Connect Microsoft Sentinel to Microsoft Defender XDR) and start querying your logs and generate detections.

 

It also makes sense to monitor for devices that never ran PowerShell code before, from which PowerShell code is executed unexpectedly. Are there devices that should never run PowerShell code (e.g. devices from Accounting or Marketing)? For this purpose, we could use a watchlist. Watchlists allow you to create a list of items you would like to use for correlation (e.g. high-value assets, terminated employees, service accounts, etc.). In our scenario, we have created a watchlist that determines to which team the machine belongs (Operations, Security, Marketing).

 

First, let’s have a look at our logs. On Microsoft Defender XDR, we can find them under Advanced Hunting. We are using this query to detect machines that are not part of the groups (Security and Operations) that we would expect to run PowerShell code using this query:

 

 

let AllowedGroups = dynamic(["Security","Operations"]);
WindowsEvent
| where EventID == 4104
| extend ScriptBlockText =  parse_json(EventData).ScriptBlockText, ScriptBlockId = tostring(EventData.ScriptBlockId), MessageNumber = tostring(EventData.MessageNumber), MessageTotal = tostring(EventData.MessageTotal), Path = tostring(EventData.Path)
| lookup kind=inner _GetWatchlist('devicegroups') on $left.Computer == $right.SearchKey
| where Group !in (AllowedGroups)
| project TimeGenerated, ScriptBlockText, SystemUserId, DeviceName, Group, EventLevelName, Path, MessageNumber, MessageTotal, ScriptBlockId, Channel
| sort by TimeGenerated, ScriptBlockId, MessageNumber

 

 

 

 

Above, we can see what machines that were not from the expected departments have ran PowerShell code.

After polishing our watchlist and query, we are ready to create a detection under Analytics:

 

Please remember to match the Host entity under “Entity mapping” when you create your analytic rule. This is critical for correlation across data sources and alerts.

 

This concludes Part 2 of our 3 Part blog series on how to collect events using DCRs for advanced use cases. For Part 1, please check The power of Data Collection Rules: Collecting events for advanced use cases in Microsoft USOP - Microsoft Community Hub, and stay tuned for Part 3, in which we show how to monitor for indications that Defender for Endpoint (MDE) was shut down.

We welcome your feedback and questions on this or any of the other parts of this blog article series and look forward to hearing from you whether you would like to see more articles like this.

 

Special thanks to our reviewers Ashwin Patil and Yaniv Carmel from the Security Research team.

 

Authors: Miriam Wiesner (@miriamxyra) – Senior Security Research PM for Microsoft Defender XDR | Maria de Sousa-Valadas Castaño MariaSousaValadas  – Senior Product Manager Unified SocOps Platform | Shirley Kochavi skochavi  – Senior Product Manager Unified SocOps Platform

 

Updated Sep 17, 2024
Version 2.0
  • tkirwan regarding the watchlist, you can do it in different ways: you can use our built-in watchlist schemas Schemas for Microsoft Sentinel watchlist templates | Microsoft Learn, like High Value Assets if it applies to the devices you would like to add; or you can create a custom watchlist like we did Create new watchlists - Microsoft Sentinel | Microsoft Learn. We only used those fields (hostname and department) because that was enough in our case, but you can add more fields.

    As for your second question, you can create granular DCRs, targeting individual servers, and it doesn't matter whether they are on Azure, other clouds or on-prem (if they are not Azure, you will need to onboard them on Arc first) Please check AMA installation options . If you are asking specifically about clients, like laptops, then yes, you are correct and you can't target individual machines.

  • SpitFire-666's avatar
    SpitFire-666
    Iron Contributor

    Hey FYI looks like the script is missing a closing curly bracket 🙂

  • tkirwan's avatar
    tkirwan
    Copper Contributor

    How did you set up the device watchlist, Is there just two fields, hostname and department? Also in the AMA documentation is says "Granular targeting using data collection rules is not supported for Windows client devices yet" Is this use case currently only for azure VMs?

  • ZaferBalkan's avatar
    ZaferBalkan
    Brass Contributor

    Hi Miriam, 

     

    Is it possible for any time in the future PowerShell 5 and 7 can have a separate event log that is initially the same with existing command and script logging, but triggered by engine if the command is base64 encoded? Therefore, the log would consist of the decoded command and we can understand the difference by the event ID. That'd help detections smoother.