About
PowerShell can log all executed source code, and serve as a honey-pot for malicious scripts. No matter how good an attacker has hidden a PowerShell script, scriptblock logging will expose it.
This won't help much, though, if no one looks at the logged data. That's why this module adds two new cmdlets that help you control scriptblock logging and read the logged data.
Scriptblock logging is a great tool:
- it helps companies establish security workflows that identifies the PowerShell code that runs in their environments, plus identify who ran the code.
- it can also be used to raise awareness of how vulnerable sensitive data stored inside scripts is (i.e. by actually exposing clear-text passwords found in scripts).
- Blue teamers can use the techniques to expose PowerShell source code that is running inside of applications.
- you can even do statistics and identify what the commands are that scripts in your company typically use, analyze code quality, etc.
By default, PowerShell logs only selected (suspicious) scripts. Enable-SBL
turns on full scriptblock logging and log all PowerShell code executing anywhere on the machine. This is just setting a registry key so you could control scriptblock logging via Group Policies as well.
The source code is logged to the eventlog system, and when scripts are large, the source code is separated into many chunks of eventlog data. Get-SBLEvent
reads the logged source code and recomposes the full script source code.
Areas of Improvement
This module is currently a proof-of-concept: it works perfectly well but there are a couple of areas that need more love:
- Windows PowerShell: currently the module is tailored towards Windows PowerShell. PowerShell 7 logs the data in a different eventlog. It should be fairly easy though to add these eventlog queries as well.
- Performance:
Get-SBLEvent
does a simple eventlog query based onGet-WinEvent
. It won't expose advanced filtering (yet) so you can only dump all logged source codes and then filter the results client-side withWhere-Object
. A much faster approach would be to expose a-Filter
parameter that uses the native XPath filters found inGet-WinEvent
to quickly search for i.e. .exe-files that contain PowerShell code or do specific queries for suspicious commands. - Clean-Up: scriptblock logging logs any PowerShell code including custom prompt functions etc. It would be nice to have the option to exclude such data from the results provided by
Get-SBLEvent
.
Since this module definitely has the potential to become a very useful analytic tool for blue teamers and basically any security-aware PowerShell admin, you are cordially invited to help evolve this module (see end of this file).
Install
To install the module from the PowerShell Gallery, run this:
Install-Module -Name ScriptBlockLoggingAnalyzer -Scope CurrentUser
Enable ScriptBlock Logging
To enable scriptblock logging to the eventlog, with Administrator privileges, run this:
Enable-SBL
Note: it may take few minutes until scriptblock logging is fully enabled. The most relaxed approach is to enable scriptblock logging and check back after lunch or the next day.
Hardening
When enabled, all scriptblocks anywhere (manually executed PowerShell code, running scripts, or PowerShell code executing inside an application) will be logged. Obviously, source code is sensitive and would be a great find for any attacker that wants to understand your IT.
By default, the eventlog access is not restricted so any user can see the logged source code. In production environments (or anywhere else for that matter), you want to restrict read access to the log and make sure only Administrators can view the logged source code.
The script below is one way to restict access: it copies the access restrictions from the Security eventlog to the eventlog that stores the scriptblock source code:
# read current access control for eventlog 'Security':
$sddlSecurity = ((wevtutil gl security) -like 'channelAccess*').Split(' ')[-1]
# apply this to the eventlog that logs the scriptblocks:
$Path = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\winevt\Channels\Microsoft-Windows-PowerShell/Operational"
Set-ItemProperty -Path $Path -Name ChannelAccess -Value $sddlSecurity
# restart service to apply settings:
Restart-Service -Name EventLog -Force
Microsoft supports other hardening strategies as well. You can protect the source code with certificates as well. This however isn't trivial to set up.
Log Size
By default, the eventlog logging the source code is limited to 15MB. To log more source code, you may want to adjust the eventlog (requires Administrator privileges). This expands the maximum eventlog size to 100MB:
Set-SBLLogSize -MaxSizeMB 100
Known Limitations
Currently, only code executed by Windows PowerShell will be logged and retrieved. PowerShell 7 saves the logged scriptblock code to a different eventlog. You are welcome to adapt this code to PowerShell 7. If you do, please let me know or issue a pull request.
Note: ScriptBlock logging was introduced to Windows PowerShell in version 4 and fully implemented in version 5. That's yet another good reason why on Windows you should check your Windows PowerShell version and make sure you are not using an outdated version. Plus you should make sure that in your Windows Features the old optional PowerShell 2 is not available. Hackers love that old version exactly because it does not log:
PS> Get-WindowsOptionalFeature -FeatureName *powershellv2* -Online
FeatureName : MicrosoftWindowsPowerShellV2Root
DisplayName : Windows PowerShell 2.0
Description : Adds Windows PowerShell 2.0 or removes this component.
RestartRequired : Possible
State : Disabled
CustomProperties :
FeatureName : MicrosoftWindowsPowerShellV2
DisplayName : Windows PowerShell 2.0 Engine
Description : Adds Windows PowerShell 2.0 Engine or removes this component.
RestartRequired : Possible
State : Disabled
CustomProperties :
Due to a long-standing bug in all versions of PowerShell (including PowerShell 7), when scriptblock logging is enabled, pipeline operations are slowed down. This can affect scripts that process a large number of objects. More details and workarounds can be found here: https://powershell.one/tricks/performance/pipeline
Reading Logged Source Code
To read the logged source code, use Get-SBLEvent
. By default, reading the logged source code is not restricted. If you hardened the access to the eventlog with the example above, Administrator privileges are required. In this case, non-Admins always receive the warning: No events found..
This pulls the newest 100 PowerShell source codes captured:
PS> Get-SBLEvent | Select-Object -First 100
TimeCreated : 15.01.2021 10:33:31
Name : [from memory]
Code : cls
Path : [from memory]
UserName : DELL7390\tobia
ComputerName : DELL7390
ProcessId : 10000
ThreadId : 8536
Sid : S-1-5-21-2770831484-2260150476-2133527644-1001
TotalParts : 1
CodeId : 27f07d4d-ed9d-410c-b6eb-a569f8ddac6c
TimeCreated : 15.01.2021 10:33:25
Name : Get-SBLEvent.ps1
Code : {
$eventData = $_
$path = $eventData.Properties[4].Value
if ($path.Trim().Length -eq 0) { $Path = "[from memory]" }
$part = $eventData.Properties[0].Value
$parts = $eventData.Properties[1].Value
$id = $eventData.Properties[3].Value
$code = $eventData.Properties[2].Value
(...)
Name returns the name of the logged script. Interactive commands show [from memory] instead. The source code is returned in Code.
Since Get-SBLEvent
currently has no built-in way of filtering the logged scriptblocks, dumping everything can take a long time. That's why the example above uses Select-Object -First x
which is the second-best approach: once Get-SBLEvent
has returned the requested number of logged scriptblocks, Select-Object
aborts the pipeline.
In future releases, obviously Get-SBLEvent
should expose its own set of filtering parameters like -Newest
, -FileType
, -Filter
, etc.
Note: When scriptblock logging is not enabled, this returns only very few suspicious scripts. When you do enable scriptblock logging via Enable-SBL
, all code is logged, including interactive code. However, it may take some time before the scriptblock logging system is fully operationalt:
- On some systems, logging starts momentarily.
- On other systems, you may have to wait an hour.
If you know more about this initial delay, and why it happens, please share. Once scriptblock logging is running, it then logs code in real-time and does so immediately after reboots. So it's just the initial turning on that may be delayed.
Identifying Suspicious Activity
You can automate scanning logged source code and for example routinely search for suspicious commands.
You could also check to see which file types have been executed in the past, and for example identify whether unknown .exe-Applications executed PowerShell code. If so, you can even see and examine the PowerShell code that executed inside such an application.
Identifying Suspicious Applications
This example reads all logged scripts and returns the file extensions found that executed PowerShell code:
PS> Get-SBLEvent |
Foreach-Object { [System.IO.Path]::GetExtension($_.Name) } |
Group-Object -NoElement
Count Name
----- ----
41227
885 .ps1
496 .psm1
734 .psd1
5 .exe
Note: based on the number of logged scripts, this can take a long time to run. Blank file extensions represent interactively entered PowerShell code.
Exposing PowerShell Code Inside Applications
The next example would search for .exe-Applications and return file path and PowerShell source code content:
Get-SBLEvent | Where-Object { $_.Name -like '*.exe' } | Select-Object -Property Path, Code
Obviously, this script yields nothing if there was no .exe application on your system that ran PowerShell code.
Conclusions
The previous examples prove that it is unacceptable to ever save sensitive data such as passwords in PowerShell scripts, no matter how you wrap them into applications. Even if you launched a script with sensitive passwords only once, it will end up in the log and compromises all secrets hard-coded inside of it.
Obviously this is not a limitation of PowerShell but a huge benefit: scriptblock logging exposes to you what attackers can do (with a plethora of other means).
Use the examples here to raise sensitivity among your co-workers.
Default Logging
Note: Even if scriptblock logging is not enabled, PowerShell will log selected code based on hard-coded trigger words.
Contribute
If you have questions or suggestions, please join our Discussions.
A much more preferred way is to submit issues and pull requests: if you identify areas of improvement, i.e. expanding it to PowerShell 7 and adding eventlog filters for better performance, I'd greatly appreciate if you not just asked for the improvements but actually helped code the improvements and send the actual code to me via pull requests.
This way we can share the load of development, and I could review your code suggestions and quickly integrate them to the module. Many thanks!