Using PowerShell Injection Hunter at scale

I’ve been waiting for a long time the blog post from the PowerShell team about
PowerShell Injection Hunter: Security Auditing for PowerShell Scripts and the module written by Lee Holmes.

The module is available on the PowerShell Gallery at this address: https://www.powershellgallery.com/packages/InjectionHunter

I wanted to test some of my code and see what issues the InjectionHunter module reports back.

The module has a dependency on the PSScriptAnalyzer because it uses custom detection rules and ScriptAnalyzer.Generic.DiagnosticRecord as output type.
You need to have the PSScriptAnalyzer loaded first before you can use the InjectionHunter module.

The problem with this module is that is has only a bunch of Measure-* cmdlets that only accept a ScriptBlockAst parameter and not a file as parameter 😦

Get-Command Measure-UnsafeEscaping -Syntax

The first part of the solution came from the Pester tests where we can see that the Invoke-ScriptAnalyzer is being used to exercise the code in this module.

The above blog post from the PowerShell team about
PowerShell Injection Hunter: Security Auditing for PowerShell Scripts also shows that all you need to do in VSCode is creating and “registering” a PSScriptAnalyzerSettings.psd1 file that has a pointer to the original InjectionHunter.psd1 file contained in the module.

The 2nd part of the solution came from a series of blog posts MVP Mike F. Robbins posted about the Abstract Syntax Tree (AST):

To keep it short, I loved what Mike showed in his Get-MrToken.ps1 function and the fact that his function accepts both a piece of code or files as parameters.

The Invoke-ScriptAnalyzer also accepts the same parameters:

By combining Mike’s code and what the Pester scripts showed, I created the following function Test-InjectionHunter.

In my case, I’ve also created a custom default value for my Test-InjectionHunter function to set automatically the correct path to my InjectionHunter module file.
The InjectionHunterModuleFilePath is not a mandatory parameter and by default the function will try to resolve it automatically as long as you installed the InjectionHunter module in standard locations that the Get-Module cmdlet is able to discover.

$PSDefaultParameterValues=@{'Test-InjectionHunter:InjectionHunterModuleFilePath'='~/Downloads/PSGallery/InjectionHunter/1.0.0/InjectionHunter.psm1'}

With that in place, I can now simply use the function like this:

It can analyze a piece of code using the Code parameter:

$testcode = @'
 function Invoke-InvokeExpressionInjection
 {
  param($UserInput)
 Invoke-Expression "Get-Process -Name $UserInput"
 }
'@
Test-InjectionHunter -Code $testcode

It can analyze a file using the Path parameter

Test-InjectionHunter -Path ~/Documents/PSHunter/Invoke-ExpandStringInjection.ps1

Last but not least, when there’s no problem, there isn’t any output and the function can also analyze a function already loaded in memory:

# No result when there's no problem 🙂
Test-InjectionHunter -Code (gc Function:\Test-InjectionHunter )
# Load a dangerous function where iex can be abused
. ~/Documents/PSHunter/Invoke-InvokeExpressionInjection.ps1
# Analyze it
Test-InjectionHunter -Code (gc Function:\Invoke-InvokeExpressionInjection)

Wanna get all the issues that the InjectionHunter is able to detect, just do:

Test-InjectionHunter -Path ~/downloads/psgallery/InjectionHunter/1.0.0| 
Sort -Unique -Property RuleName

It will actually discover the Pester.InjectionHunter.Tests.ps1 file in the Tests folder of the original InjectionHunter module and report all the issues 😉

For 4n6 investigations, you can also inspect any code executed previously by analyzing protected event logs if you tweak the Read-ProtectedEventlog code I showed in this post. It would reveal code that could be abused and that you previously executed:

Huge thanks to Mike F. Robbins, Lee Holmes and the PowerShell Team
PowerShell rocks! 😎
You can now test your own code and see what the InjectionHunter module reveals that you will need to fix 😀

Advertisements

Exchange 2010 and Constrained language mode

  • Context

I’ve automated the user provisioning on Windows 7 computers and their mailbox were automatically created on Exchange 2010.
We’ve recently started a Windows 10 migration project where we use Applocker in whitelist mode and have PowerShell running in constrained language mode.

  • Issue

We had Applocker on all our Windows 7 endpoints and it was more or less permissive. It allowed us to have PowerShell running in Full Language Mode.
To create mailboxes, the script was importing Exchange cmdlets from a remote session.

$s = New-PSSession -ConnectionUri http:/servername.fqdn/PowerShell/ `
-ConfigurationName Microsoft.Exchange
Import-PSSession -Session $s

Now, on Windows 10 with constrained language mode, importing session just failed with the following message:
Import-PSSession : Index was out of range. Must be non-negative and less than the size of the collection.

  • Solution

I’m very busy and copied all the ActiveDirectoy and Exchange cmdlets we were using into a scriptblock with parameters.
My straightforward solution was to replace the Import-PSSession cmdlet by Invoke-Command:

Invoke-Command -Session $s -ScriptBlock $sb `
-Argumentlist $User,$Store

But it also failed 😦
The error was: A Begin statement block, Process statement block, or parameter statement is not allowed in a Data section
I’ve just forgotten the fact that the Exchange remote endpoint only exposes Exchange cmdlets, nothing else.
There’s for example no Get-Random cmdlet and no Active Directory module loaded into that Exchange remote config.
My scriptblock was actually born to fail. My bad, oops 🙄

I jumped onto another solution.
I can use Get-Random and other Active Directory cmdlet on the client but I’ll need to execute each and every Exchange cmdlet in a very simple scriptblock without using any pipeline…

To get a better idea of what I did, here are some examples:

Example 1:

#...
$DB = Invoke-Command -Session $session -ScriptBlock `
{ Get-MailboxDatabase -ErrorAction SilentlyContinue } |
Where { $_.Server -match "$($ServerPrefixName)" } | 
Get-Random
#...

I’ve hashtables defined on the client and passing it to the remote session using the magic using keyword 😀

Example 2:

#...
Invoke-Command -Session $session -ScriptBlock `
{ Set-Mailbox "DomainName\$($using:UserName)" @using:MailboxQuota @using:extraparams}
#...
  • Bonus: more on Constrained Language Mode