Follow-up on Microsoft Advisory ADV170012

If you are lucky and have System Center Configuration Manager, aka ConfigMgr, in you environment, you can get the inventory of the TPM embedded in you workstations or laptops.

You need to enable the Win32_TPM WMI class in the Hardware Inventory settings of your clients.

If you’ve only laptops, you can filter the query with the chassis types.
Then the problem is that the Manufacturer Id is returned as int32 and it doesn’t tell you what’s the manufacturer name and when the TPM manufacturer actually is Infineon whether its vulnerable or not.

Luckily, there’s a way to get the Manufacturer name from the int32 that is described on the the Win32_TPM WMI class on msdn.

Using the example provided, we can do

('{0:X0}' -f 1414548736) -split "(?<=\G.{2})",4 | 
ForEach-Object { 
 [char][int]"0x$($_)"
}

If I combine the ConfigMgr query, test if the TPM is vulnerable and get its manufacturer name from its id, I’ve the following code:

And if you use the 2nd example provided in the help, you can quickly have relevant results (IFX is Infineon)

If you cannot get results and have a WMI quota violation instead, see this post

Happy TPM madness scoping using #ConfigMgr and #PowerShell 😎

Advertisements

About Microsoft Advisory ADV170012

After this year Intel AMT fiasco, we’ve got not a new TPM madness. I ❀ IT

If you don’t know what it’s about, please take the time to read first the Microsoft Advisory ADV170012

Now, let’s quickly jump into the only question we care about: How do I find out whether I’m affected or not?

The PowerShell script provided on this page is supposed to help IT achieve this task.


Unfortunately, it doesn’t scale very well and Microsoft doesn’t give you too much details and just tells us to use PSRemoting to scale and query multiple computers.

The script has other major issues like:

  • it doesn’t send an object through the pipeline and just uses Write-Host to paint/color my console
  • it doesn’t handle gracefully the fact that you must be running the script with elevated user rights (Run as Administrator).
    (please note that there’s a warning about administrative privileges in bold in the forewords)
  • it uses aliases which is not a best practice
  • it doesn’t respect the Verb-Noun format for function names
  • worse, it uses Get-TPM that is cmdlet that was introduced as of Windows 8 and that isn’t available on Windows 7

Don’t get me wrong, the script is good enough for my home computer and the code exposes very well the core logic to determine if my device is affected.
I especially love the way the return statement is used in the switch block.
Anyway, I rewrote the script to ease the scoping of this TPM madness πŸ˜€

Let’s see how to use it:

# Example 1:
$c = Get-Credential
$targets = @(
'PCHPModel1',
'PCHPModel2',
'PCFujitsuModel1',
'PCFujitsuModel2',
'PCLenovoModel1',
'PCLenovoModel2'
)
Invoke-Command -ComputerName $targets -ScriptBlock ${Function:\Test-InfineonTPMVulnerability} -ErrorAction SilentlyContinue -Credential $c |
Select -Property ComputerName,TPMVersion,Vulnerable,Unknown,ClearRequired,Reason |
Format-Table -AutoSize

# Example 2:
Invoke-Command -ComputerName $targets -ScriptBlock ${Function:\Test-InfineonTPMVulnerability} -ErrorAction SilentlyContinue -AsJob -Credential $c |
Wait-Job -Any | Receive-Job | Out-GridView

Bonus 1:
In Windows 7, you cannot use the new Suspend-Bitlocker cmdlet introduced as of Windows 8.
You can use manage-bde.exe

Manage-bde.exe –protectors –disable c:

or you can use WMI

$HT = @{
 Namespace = 'root/cimv2/Security/MicrosoftVolumeEncryption'
 Class = 'Win32_EncryptableVolume'
}
(Get-WmiObject @HT -Filter 'DriveLetter="C:"').DisableKeyProtectors()

Bonus 2: On Windows 10, if you want to use the detection option 1 and query events from the Event Source TPM-WMI, the fastest way to achieve this is by using an XML query that only targets the microsoft-windows-tpm-wmi provider like this:


$xml = @'
<QueryList><Query Id="0" Path="system"><Select Path="system">*[System/Provider[@Name='microsoft-windows-tpm-wmi']]</Select></Query></QueryList>
'@
Get-Winevent -FilterXml $xml -ErrorAction SilentlyContinue

Happy TPM madness scoping 😎

Service Control Manager ACL module


When I saw this trick from John Lambert being retweeted after the Petya malware campaign (remember, the one after the Wannacry campaign that exploited SMBv1 protocol and vulnerability called EternalBlue), it was clear it can be used to stop other ways used by Petya to propagate over the wire. Of course, you could block wmic.exe and psexec.exe if you’ve Applocker and an Enterprise version of Windows. But the above trick blocks the remote use of psexec and is a hardening measure with a broader scope. I thought, it would be nice to have a PowerShell module that would help playing with this defensive configuration of the Service Controller Manager.

I wanted to use the same approach I used for the NetCease module, where I’d just set the hardened configuration in the registry.
It appeared to be a bad idea because the registry value doesn’t exist by default

It’s also a very bad idea because you can have a different configuration based on the roles and features installed on your computer.

This is what you’d typically find on a Windows 7 computer

And this is what you’d find on Windows 2012 R2 with Hyper-V

These limits explain how and why the Set-SCManagerPermission function in the Service Control Manager ACL module (SCManager) adds a Deny to the network service (NT AUTHORITY\NETWORK, S-1-5-2).

I’ve also chosen to rely on sc.exe and the functions are most of the time of wrapper around sc.exe mainly because sc.exe is required when the registry key and value don’t exist and using sc.exe apply changes immediately without a reboot.

You can find the SCManager module in this Github repository

I’ve also published a digitally signed version on the PowerShell Gallery.

Set-SCManagerPermission -Verbose -Confirm:$false
Get-SCManagerPermission |
Select Transl*,Secu*,AccessMask,AceType | 
ft -AutoSize

If sc.exe is used to access any service remotely, it will end with an Access Denied error.

This module and the hardened configuration it sets will for sure block the remote use of psexec.exe or sc.exe.

But, it could also break some Microsoft or third party products or services.

It has the capability to undo the change made using the Restore-SCManagerPermission function without a reboot.

Restore-SCManagerPermission -Verbose -Confirm:$false

Please use it first in a testing environment and report any broken service/product you may encounter.

Get the status of WSUS clients installing the September Cumulative Update

A few days ago, a PM.org list member asked the following question:

I’m trying to find out what the status is on clients installing the September Cumulative Update.

He also reported that he was using WID (Windows Internal Database) and not SQL. He was also struggling with the Microsoft Report Viewer and Microsoft System CLR Types for SQL Server.

I replied that he can achieve this using only #PowerShell and the WSUS API πŸ˜€ but my first try was:

$updateScope = New-Object Microsoft.UpdateServices.Administration.UpdateScope
$updateScope.TextIncludes= '4038777'
(Get-WsusServer).GetUpdateApprovals($updateScope) | 
ForEach-Object {
 $tg = (Get-WsusServer).GetComputerTargetGroup($($_.ComputerTargetGroupId))
 Write-Verbose -Message "Dealing with approval type $($_.Action) on to target group '$($tg.Name)'" -Verbose
 $tg.GetComputerTargets($true) | 
 ForEach-Object {
  $computer = $_
   $State = $computer.GetUpdateInstallationSummary($updateScope)
   [PSCustomObject]@{
    ComputerName = $_.FullDomainName ;
    Unknown = $( if($State.UnknownCount) { $true } else { $false} );
    NotApplicable = $( if($State.NotApplicableCount) { $true } else { $false } );
    NotInstalled = $( if($State.NotInstalledCount) { $true } else { $false } );
    Downloaded = $( if($State.DownloadedCount) { $true } else { $false } );
    Installed = $( if($State.InstalledCount) { $true } else { $false } );
    InstalledPendingReboot = $( if($State.InstalledPendingRebootCount) { $true } else { $false } );
    Failed = $( if($State.FailedCount) { $true } else { $false } );
   }
 }
} | ogv

Well, the above code did the job but performed very poorly. It took more than a minute to display the results for a hundred client computers.

I took the update scope approach with a custom text filter and I’ve been inspired by my previous blog post about WSUS reporting.

I wasn’t satisfied and I believed I took the wrong approach and that there should be one or more efficient ways to get the results.
So, the next morning, I gave it another try and found another way to skin the cat:

$kb = '4038777' | % { (Get-WsusServer).SearchUpdates($_) } | ? Title -match 'Windows 7 for x64-based ' |  ? { $_.IsLatestRevision }
(Get-WsusServer).GetComputerTargetGroups() | 
ForEach-Object {
 $kb.GetUpdateInstallationInfoPerComputerTarget($_)  | 
 ForEach-Object {
  [PSCustomObject]@{
   Computer = (Get-WsusServer).GetComputerTarget($_.ComputerTargetId).FullDomainName
   State = $_.UpdateInstallationState
  }
 }
} | ogv

The above way performs much faster (max 8 seconds for the same 100 computers) and has less code 😎

PSGallery and catalog files

I’ve been using catalog files associated with modules published on the PowerShellGallery the wrong way and got a warning from the PowerShell gallery administrator because my catalog files were breaking the Install-Module cmdlet experience.

As of PowerShell 5.1, you can create catalog files with the new New-FileCatalog cmdlet. Let’s quickly see what is a catalog file?

A digitally-signed catalog file (.cat) can be used as a digital signature for an arbitrary collection of files. A catalog file contains a collection of cryptographic hashes, or thumbprints. Each thumbprint corresponds to a file that is included in the collection.

(Source) May I insist on “can be used” in the above definition πŸ™„

That’s what I initially did. I was using catalog files as containers for a collection of file hashes. I didn’t use the catalog file as a digital signature and didn’t sign digitally catalog files. Catalog files allowed me to replace CSV files that I stored in my ADK repository and have a much better solution to check the integrity of each ADK files downloaded using the Test-FileCatalog cmdlet. See the following commit. The core functionality of catalog files just served very well my scenario where I just wanted to check that files downloaded match their hash.
Just a quick digression about performance. In the above scenario, using Get-AuthenticodeSignature (yes, all ADK files are digitally signed πŸ˜€ ) is the slowest (took 1 minute 28 seconds) 😦 , using Test-FileCatalog took 46 seconds πŸ™‚ and my CSV file with the Get-FileHash approach took only 28 seconds 😎 .

The code signing requirements for modules on the PowerShell gallery however are much higher than what I did with the ADK files. These requirements can be found in the PowerShellGallery Publishing Guidelines and Best Practices
modulo the fact that Save-Module doesn’t validate the catalog file (issue introduced on GH)

I’ve acquired a code signing certificate and here are the steps I use to sign a module before publishing it to the gallery.

  • Commit last changes to the module in the master branch
  • git.exe push -u origin master
    

    Copy the local github repo for the module to ~/Documents\WindowsPowerShell\Modules\$MyModule without folders like .git

    robocopy $GHRepoSource ~/Documents\WindowsPowerShell\Modules\$MyModule /R:0 /Z /S /XD .git /XD .vscode /XF README.md *.cat /NP
    
  • Use the latest version of the PSScriptAnalyzer to check if my module is compliant with coding best practices
  • # Run PSScriptAnalyzer
    Invoke-ScriptAnalyzer -Path ~/Documents\WindowsPowerShell\Modules\$MyModule -Recurse -Verbose
    
  • Sign the module and its manifest
  • # Sign the module
    $cert = Get-ChildItem Cert:\CurrentUser\My\ -CodeSigningCert | 
    Where { $_.HasPrivateKey -and ( $_.NotAfter -gt (Get-Date)) }
    
    Get-ChildItem ~/Documents\WindowsPowerShell\Modules\$MyModule -Include *.psd1,*.psm1 -Recurse |
    Set-AuthenticodeSignature -Certificate $cert -TimestampServer http://timestamp.digicert.com -Verbose -HashAlgorithm SHA256
    # at this stage only .psd1 and psm1 are signed
    Get-AuthenticodeSignature ~/Documents\WindowsPowerShell\Modules\$MyModule\*
    
  • Create the catalog file
  • # Create the catalog file
    New-FileCatalog -Path  ~/Documents\WindowsPowerShell\Modules\$MyModule -CatalogFilePath ~/Documents\WindowsPowerShell\Modules\$MyModule\MyModule.cat -CatalogVersion 2.0 -Verbose
    
  • Sign the catalog file
  • # Sign the catalog file
    Get-ChildItem ~/Documents\WindowsPowerShell\Modules\$MyModule\MyModule.cat -EA 0 |
    Set-AuthenticodeSignature -Certificate $cert -TimestampServer http://timestamp.digicert.com -Verbose -HashAlgorithm SHA256
    
  • Test the catalog file
  • # Test the catalog file
    Test-FileCatalog -Path ~/Documents\WindowsPowerShell\Modules\$MyModule -CatalogFilePath ~/Documents\WindowsPowerShell\Modules\$MyModule\MyModule.cat -Detailed 
    Get-AuthenticodeSignature ~/Documents\WindowsPowerShell\Modules\$MyModule\*
    

At this point everything is set and I can use the Publish-Module cmdlet.

Conclusion: If you want to use catalog files on PowerShellGallery, remember that you’ve to sign them digitally along with your regular module files.

PS5.1 import-pssession and $PSSenderInfo

Last month when I got back from holidays, helpdesk staff members complained that they could not reset user passwords anymore using the PowerShell constrained endpoint I setup almost a year ago.
They got a message saying Running the Get-Command command in a remote session reported the following error: A parameter cannot be found that matches parameter name ‘PowerShellVersion’

You know, there’s no tab completion in an interactive constrained endpoint. Your only friend is Get-Command.
So, to ease their pain with constrained endpoints, I usually create another script that imports the remote constrained commands using the Import-PSSession cmdlet.

What happened or changed during my summer holidays?
Before leaving, I started to deploy WMF 5.1 to computers mainly because:

As of June 1, 2017, users with WMF 5.0 must upgrade to WMF 5.1 to receive support.

(source)

Helpdesk members experienced this behavior after they rebooted their computers (WMF 5.1 installation wasn’t pending anymore). Entering interactively the constrained endpoint just works. It was only the import of the session that failed.

When I googled the error, there was already a github issue opened and a merge into the PowerShell Core branch. Nice, isn’t it? πŸ˜€

It appears that the server hosting the constrained endpoint was already running PowerShell 5.1 and connecting helpdesk workstations just shifted to this version and fall into this Import-PSSession issue.

What should I do?
As Joey Aiello said in this post:

Windows PowerShell 5.1, much like .NET Framework 4.x, will continue to be a built-in, supported component of Windows 10 and Windows Server 2016. However, it will likely not receive major feature updates or lower-priority bug fixes. With PowerShell Core, we are actively addressing bugs that may have existed in previous versions of Windows PowerShell.

The issue was fixed in a few days in PowerShell 6.0 (see https://github.com/PowerShell/PowerShell/pull/4222) and won’t be fixed in PowerShell 5.1 at the same speed unfortunately. Likely means “not” most of the time in this context unless it’s security related or really, really bad. The only issue fixed in PowerShell 5.0 as far as I know is cve-2017-8565.


Here’s the dilemma. Should I remove the only supported version of PowerShell from the server or from the clients?
Well, I chose the server because there are still some products that are incompatible with WMF 5.1 and 5.0.
I was lazy and didn’t even have to remove any PowerShell from any server.
I just found a Windows 2012 R2 running a default version of PowerShell, which is version 4.0.

I moved my module to the server and registered the constrained endpoint.
Now, I got another problem. I couldn’t get an alert telling me who did a password reset.
In PowerShell 5.1, I successfully used

$PSSenderInfo.ConnectedUser

To be able to get back the visibility on who did what, I had to use the following in PowerShell 4.0

$PSSenderInfo.UserInfo.Identity.Name

I wonder why. Isn’t a restricted endpoint running in NoLanguage mode the same in PowerShell 4.0 and 5.x?
What’s the difference between the two syntaxes if $PSSenderInfo is just a read-only variable?

Using Get-Member, it looks the same, except that one is property and ConnectedUser appears to be a script property.

May I conclude and say that script properties on read-only variables are allowed in a restricted remote sessions running PowerShell 5.x in no lanugage mode?

NetCease module

Almost a year ago, Itai Grady released a script on the technet gallery that blocks the reconnaissance done by using the Server Message Block (SMB) Session Enumeration method.

I’ve studied, changed the script and made a module of it. Why?
It’s easier to share and install once it’s uploaded on the PowerShellGallery.
The approach inside the module is a bit different and allows to move more easily from the default state to the hardened state and vice-versa.
Let’s say that the module assumes breach and is somehow “more” idem-potent than the original version πŸ˜€

Let’s see quickly how to use it:

# Download the module
Save-Module -Name NetCease -Repository PSGallery -Path ~/Downloads
# Load the module
Import-Module ~/Downloads/NetCease/1.0.1/NetCease.psd1 -Force -Verbose
# View current NetSessionEnum permissions
Get-NetSessionEnumPermission | 
Select TranslatedSID,SecurityIdentifier,AccessMask,AceType | ft -AutoSize
# Harden permissions
Set-NetSessionEnumPermission -Verbose -Confirm:$false
# Restart the Server service for changes to take effect
Restart-Service LanmanServer -Force -Verbose


How to test if it works?
To quickly test if, I borrowed the pieces of code: the Get-NetSession function from @harmj0y and the PSReflect module from @mattifestation

In the above screenshot, you can see that the Get-NetSession doesn’t return anything after I hardened the configuration of the targeted Domain Controller.

Note that the Server service has some dependencies on other services. Restarting these services could cause a little disruption, so be careful.

The ATA gateway detected my previous attempts:

The module will work on Windows 7, 8.1, 10, Windows Server 2008 R2, 2012 R2 and 2016 and isn’t only for domain controllers. If you want to limit the recon performed by an insider attacker, you’ll want to apply it to every machine.

Bonus: I didn’t immediately realized it but it’s possible to create a Desired State Configuration (DSC) config 😎

Here’s the result of applying twice the DSC config and having first restored the default permissions: