About January 2019 LCU (Latest Cumulative Update)

All the LCU (Latest Cumulative Update) in January 2019 contain the following note:

After reading that I just wondered:
Is PowerShell or remoting vulnerable and not mentioned in any CVEs released in January 2019.
How to assess the impact of the LCU on any endpoint that deals with remoting?
What’s the scenario?

  • January 8, 2019—KB4480116 (OS Build 17763.253) for Windows 10 version 1809 and Windows Server 2019
  • January 8, 2019—KB4480966 (OS Build 17134.523) for Windows 10 version 1803
  • January 8, 2019—KB4480978 (OS Build 16299.904) for Windows 10 version 1709
  • January 8, 2019—KB4480973 (OS Build 15063.1563) for Windows 10 version 1703
  • January 8, 2019—KB4480961 (OS Build 14393.2724) for Windows 10 version 1607 and Windows Server 2016
  • January 8, 2019—KB4480963 (Monthly Rollup) for Windows 8.1 and Windows Server 2012 R2
  • January 8, 2019—KB4480970 (Monthly Rollup) for Windows 7 SP1 and Windows Server 2008 R2 SP1

Based on feedback from MVP raised on the private distribution list, the PowerShell team published the following blog post: Windows Security change affecting PowerShell.

After the blog post was published, Paul Higinbotham from the PowerShell Team also asked for all KB articles to be modified as follow:

My first concern was: if it’s a security vulnerability, what’s its CVE? The blog post answer is: CVE-2019-0543 discovered by James Forshaw of Google Project Zero

My second concern was twofold. Is the chapter about A Least Privilege Model Implementation Using Windows PowerShell published in the PowerShell Conference Book impacted by this change? Should I stop deploying Windows 10 at work because the LCU of January 2019 breaks my loopback scenario?

The answer is no and explained by the blog post Windows Security change affecting PowerShell

you would not be affected by this change, unless you explicitly set up loopback endpoints on your machine to allow non-Administrator account access

Let’s boot a 1709 VM, apply the LCU of January 2019, reboot and compare the endpoint permissions that will be impacted by the change named MyNonAdmin and the one set in the PowerShell Conference Book‘s chapter named MakeMeAdmin:

Now let’s test the behavior:

Holy moly! Cool down!! The odds you are impacted is very very low.

And if you’re in trouble, Microsoft proposed these two solutions:

A workaround for a loopback connection is to always use Administrator credentials.
Another option is to use PSCore6 with SSH remoting.

Then I tried to set the same permissions that work for the MakeMeAdmin endpoint (EP) on the MyNonAdmin EP.
The answer was crystal clear. Having the same permissions doesn’t work.

I then wondered why my loopback scenario works?

Based on my 2nd testing wave, it appears that:
– a non admin local account can only use loopback remoting if the account is a member of the local administrators group when remoting is being accessed.
– endpoint permissions isn’t what will give access to an non admin local account when using loopback remoting.
– when it doesn’t work, opening the session or handling a remoting request/response takes more time.
– when it doesn’t work, opening the session or handling a remoting request/response creates errors in the ‘Windows Remote Management’ operational log:

Get-WinEvent -FilterHashtable @{ 
 LogName = 'Microsoft-Windows-WinRM/Operational' ; 
 Level = 2
}

Request response: WSMan operation CreateShell failed, error code 2148007941
Request handling: The WSMan service could not launch a host process to process the given request. Make sure the WSMan provider host server and proxy are properly registered. Error code 2148007941

The article also says:

The breaking change is not in PowerShell but in a system security fix that restricts process creation between Windows sessions. This fix is preventing WinRM (which PowerShell uses as a remoting transport and host) from successfully creating the remote session host, for this particular scenario. There are no plans to update WinRM.

So, it’s the identity that really matters. In other words, a non admin local account cannot open a local (loopback) remote session that uses WinRM.

Again, why my loopback scenario works?

The answer is also in the article:

This does not affect JEA (Just Enough Administration) sessions.

🙄

Ok, if JEA isn’t affected, what it’s so special in JEA? Permissions? No. Roles/Capability? Yes, for sure!
Let’s dig into JEA. If it’s not permissions that made a difference, then what is it?
Is it the SchemaVersion, the SessionType, the LanguageMode,…?

My testing shows that the endpoint (EP) must run under another identity whether it’s a virtual local admin account, a gMSA or just another account (it can be the local admin account (RID:500)).

Ok, how can I find endpoints that are not affected by the non admin user loopback issue?
Well, like this:

Get-PSSessionConfiguration | Where {
 ($_.RunAsVirtualAccount -eq 'True') -or
 ([string]::empty -ne $_.RunAsUser)  -or 
 ([string]::Empty -ne $_.RunAsVirtualAccountGroups)
} | Select Name,*Run* | ft -AutoSize

NB: isn’t it a shame that these properties are only strings?

To summarize,
Here are two EP:

# Common parameters for both endpoints: permissions
$HT = @{
 SecurityDescriptorSddl = 'O:NSG:BAD:P(A;;GA;;;BA)(A;;GA;;;BU)S:P(AU;FA;GA;;;WD)(AU;SA;GXGW;;;WD)'
 Force = [switch]::Present
}
# Create an EP that won't work
Register-PSSessionConfiguration -Name NOKMyNonAdminEP @HT

# Create an EP that will work
New-PSSessionConfigurationFile -RunAsVirtualAccount -Path c:\OKMyNonAdminEP.pssc
Register-PSSessionConfiguration -Name OKMyNonAdminEP @HT -Path c:\OKMyNonAdminEP.pssc

# Compare their properties
Get-PSSessionConfiguration | 
Select Name,Schema*,GUID,Session*,RunAs*,Perm* |ogv

Let’s quickly measure the performance issue I mentioned above:

Measure-Command { 
 New-PSSession -ConfigurationName OkMyNonAdminEP -ComputerName localhost
}
Measure-Command {
 New-PSSession -ConfigurationName NOkMyNonAdminEP -ComputerName localhost
}


30 seconds compared to 288 milliseconds…

Advertisements

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 😀

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