Friday fun: follow URI redirects

Lee Holmes twitted recently the following:

It’s available on the PowerShell gallery: resolve-uri.ps1

It appears that I wrote another script to achieve the same because of VSCode that introduced another redirect back in November.
Mine looks like this:

  • What are the differences between the 2 approaches?
    • Mine sends back only a single string in the output stream while his solution sends all the URI used and being redirected:

    • Mine uses a recursive approach and his approach uses a while loop.
    • Mine shows in the verbose stream what kind of redirect happened, if it’s a temporary redirect or if it’s permanent.

    As usual, there are always more than one way to skin a cat πŸ˜€

    Advertisements

More on PowerShell Constrained Language mode and the Dot-Source Operator

The PowerShell team recently published a blog post about PowerShell Constrained Language mode and the Dot-Source Operator

It’s worth reading because it clearly explains how and when you can cross language mode boundaries.

It also shows that mixing language modes usually results in getting an error message.

I have been experiencing constrained-language-mode for a few months and I must say that it’s has a sharp learning curve.

Here’s what I’d like to highlight about what I’ve learned so far:

  • “-File” and CmdletBinding don’t play well together

Apart the profile.ps1 catch-22, there’s another caveat. “-File” and CmdletBinding don’t play well together especially if the files are store in trusted location on the network. The solution is very simple, remove the “-File”.

Let’s say you have Applocker in whitelist mode and have trusted a remote share location \\localhost\c$\*.ps1 in this applocker policy. I’ve been using the same script content as the one provided in the blog post PowerShell Constrained Language mode and the Dot-Source Operator except that it’s an advanced script with a begin/process/end structure. As you can see the only difference between the two files named NoCmdletBinding.ps1 and WithCmdletBinding.ps1 is the presence of the CmdletBinding statement at the begining of the script.
Here’s what the problem looks like because a picture is worth a thousand words:

  • Remoting using “-File” is also broken

Remoting using “-File” is also broken the same way. In the picture below that shows the issue, I’m using the content of the MyHelper.ps1 file found in the blog post PowerShell Constrained Language mode and the Dot-Source Operator except that there’s no function.

cat C:\Windows\myFile.ps1
$ExecutionContext.SessionState.LanguageMode
icm -ComputerName localhost -ScriptBlock { $ExecutionContext.SessionState.LanguageMode }
icm -ComputerName localhost -FilePath C:\Windows\myFile.ps1
icm -ComputerName localhost { C:\Windows\myFile.ps1 }


Although my account used for remoting is an admin on the target remote computer, the Applocker whitelist mode enforces the Constrained Language mode with the “-File” parameter.
When I use the Scriptblock and just run C:\windows\myFile.ps1, it runs because there’s a rule in Applocker that allows the file to be executed. So it’s allowed to cross languade mode boundaries and run in FullLanguage mode. The second line doesn’t throw an error this time and it’s also discarded from the output because remoting doesn’t transport back console paintings.
The workaround in this case consists in first copying the file locally and then invoking it using a scriptblock.

# Solution
Copy-Item ~/Documents/myFile.ps1 -Destination \\TargetPC\c$\Windows\temp\myFile.ps1
Invoke-Command -ComputerName TargetPC -ScriptBlock { C:\Windows\temp\myFile.ps1 }
  • The r alias of the Invoke-History cmdlet may be broken based on what you want to re-execute

The r alias of the Invoke-History cmdlet may be broken based on what you want to re-execute:

Adobe FlashPlayer Emergency Group Policy

After posting a message to the patchmanagement.org distribution list about my strategy as a reaction to the following article, where I said that:

My strategy has always been a risk based approach.
If there’s a vulnerability, something needs to be done about the risk. The risk needs first to be identified and assessed.
The risk can then be:
– accepted (just inventory and evaluate your specific context, wait for a patch when it’s a 0-day)
– reduced, mitigated (apply the workaround instead of patching first, that gives you more time and you can patch later)
– shared, transferred (get more budget and buy a more expensive insurance)
– avoided (patch immediately or remove the offending software/component)

I’ve been contacted by Mitch Tulloch who is a widely recognized expert on Windows Server and cloud technologies who has written more than a thousand articles and has authored or been series editor for over 50 books for Microsoft Press. He is a twelve-time recipient of the Microsoft Most Valuable Professional (MVP) award in the technical category of Cloud and Datacenter Management.

I provided some recent examples to illustrate the above strategy.
He wrote a nice article on http://techgenix.com/patch-management/

I mentioned in the above article that:

Whenever there’s a zero-day in Flash, you can apply the workaround and set a kill-bit in the registry

The kill-bit is a registry value to tell the browser to avoid loading the vulnerable component.
It’s always documented as a mitigation in the workaround section of every Adobe FlashPlayer security bulletin posted by Microsoft:
https://portal.msrc.microsoft.com/en-us/security-guidance/advisory/ADV180014
https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/ADV180030
https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/ADV180031

The Office part is also well documented on this support page: https://support.microsoft.com/en-hk/help/4058123/security-settings-for-com-objects-in-office
https://support.office.com/en-us/article/flash-silverlight-and-shockwave-controls-blocked-in-microsoft-office-55738f12-a01d-420e-a533-7cef1ff6aeb1
Let’s see how to easily achieve using #PowerShell πŸ™‚

You end up with the following GPO settings:

What’s next? Just link the GPO in Active Directory where it makes sense to apply it to computers beneath.

About File Associations

The following article Microsoft Broke Windows 10’s File Associations With a Botched Update appeared recently and states that:

File associations no longer work properly on Windows 10 after a buggy update. Windows won’t let you select certain applications as your defaults.
[…]
For example, here’s what happens when we try setting Notepad++ as our default application for .txt files in Windows 10’s Settings app. Windows just ignores our choice and chooses Notepad as the default.

behavior

It appears that I’ve experienced the same thing on a Windows 10 Enterprise (1803). Just keep in mind that we see in the above gif that only “registered” applications can be set. Applications like notepad++ isn’t registered and cannot be set and used for the .txt file association.

I’ve set successfully a group policy that defines the .pdf file to be opened by Adobe Reader. It’s still there and works fine. I’ve created it using the official guidance from Microsoft and the ProgID found in the Adobe documentation on this page.

Using my Google-fu, I also found the following articles that shed some lights on how the file associations work:

I was not able to define and load a custom xml file where I could set notepad++ as the default .txt handler.

After I removed the anti-tampering protection where Microsoft sets a DENY permission on the
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.xxx\UserChoice registry key, I could delete values under that key.

I didn’t need to reverse engineer secret hashes under that key like some known tools (setuserfta) do.

All I did actually is remove anything under the UserChoice, create my own ProgId under the OpenWithProgids key and create its related value under the OpenWithList list.

Bonus: If you use the Reset button under Default Apps, Microsoft will gracefully restore the previous behavior.
With the above function or script, your user (admin or not) is autonomous and can restore his favorite file associations until Microsoft changes the rules…

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 πŸ˜€

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

Not all PowerShell shells are equals

I’ve been recently involved in fixing 2 issues for my colleagues.

  • The first issue
    • Context
    • My colleagues send an message with a link that points to a script located on a shared drive to help our users reinstall their software.
      Our users just click on the link in their Outlook and got a message saying:
      \\servername.fqdn\share\softwarename\install.ps1 cannot be loaded because running scripts is disabled on this system. For more information, see about_Execution_Policies at https:/go.microsoft.com/fwlink/?LinkID=135170.

    • Issue
    • Users use Outlook that is a 32-bit process. If they click on link that points to a script, it will spawn a 32-bit console and run a 32-bit powershell.exe child process.
      It appears that the ExecutionPolicy isn’t defined in the 32-bit PowerShell and set to its default value: “Restricted” although it’s defined in the 64-bit Powershell.
      Needless to say that you cannot run a script with a restricted execution policy.

    • Solution
    • While there are many ways to solve this issue, we’ve decided to address the issue when computers are provisionned. The post-install of a workstation runs a 64-bit PowerShell script where we’ve just added:

      C:\Windows\SysWOW64\WindowsPowerShell\v1.0\powershell.exe { Set-ExecutionPolicy -ExecutionPolicy 'RemoteSigned' -Force -Scope LocalMachine }
      

      The above solution just writes the missing ExecutionPolicy value in this registry key:

  • The second issue
    • Context
    • We’ve a short quick and dirty Pester test to perform some operational validation of our configuration. We’ve decided to add a quick test about the execution policy value for a 32-bit PowerShell. But other tests failed and that was unexpected because they don’t when they are executed in a 64-bit PowerShell console.
      The error message thrown was:
      CommandNotFoundException: The term ‘Get-LocalGroupMember’ is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct a
      nd try again.

    • Issue
    • As you can see, there isn’t any Microsoft.PowerShell.LocalAccounts module in the 32-bit modules location. It means that you cannot use the Get-LocalGroupMember cmdlet in a 32-bit PowerShell console.

      I started to compare the module names like this:

      Compare-Object (dir $PSHOME\Modules -Directory).Name `
      -DifferenceObject (
      dir "$($PSHOME -replace "system32","syswow64")\Modules"`
      -Directory).Name
      

      Yes, on my 1803, this is the list of 64-bit only modules:

      • AppBackgroundTask
      • AssignedAccess
      • ConfigCI (or CIPolicy?)
      • HgsClient
      • Microsoft.PowerShell.LocalAccounts
      • NetworkSwitchManager
      • PcsvDevice
      • PersistentMemory
      • ProcessMitigations
      • PSWorkflow
      • PSWorkflowUtility
      • SmbShare
      • SmbWitness
      • StartLayout
      • WindowsSearch
      • WindowsUpdateProvider
    • Solution
    • Well, it depends how far you want to go. I’ve chosen to execute only Pester tests in a 64-bit PowerShell console and do the following:

       It 'WMF local machine 32-bit execution policy should be set to RemoteSigned' {
        (
         Get-ItemProperty -Path 'HKLM:\SOFTWARE\WOW6432Node\Microsoft\PowerShell\1\ShellIds\Microsoft.PowerShell' `
         -Name 'ExecutionPolicy' -ErrorAction SilentlyContinue
        ).'ExecutionPolicy' -eq 'RemoteSigned'| 
        should be $true
       }
      

      The above registry key exists only when it’s executed in a 64-bit shell.

      Who executes Pester tests in a 32-bit shell when you run a 64-bit OS?
      My above unit test doesn’t handle gracefully the 32-bit issue and will actually throw an error if it’s executed in a 32-bit shell.