FRPSUG workshop

I had recently the opportunity to present a workshop to the French PowerShell User Group (FRPSUG) about one of the first post I made on my blog.

The Windows 10 1709 feature update was failing on my computer because of a lack of free space.

The main goal of the workshop was to show how to fix and update some old code as well as follow best practices.
Using the updated version, it would show how many superseded updates for Office could be deleted from the C:\windows\installer folder

It’s in French…

Here’s the result of the workshop Get-MSIZapInfo.ps1

Here are each step presented split into commits
Here’s another way to view changes using the blame view under github.


How to audit NTLM?

I’ve seen the following blog post last week about Stop using LAN Manager and NTLMv1!

The first step proposed that sounds reasonable and wise is to audit event ID 4624.
Here’s one way to do it:

$c =  Get-Credential 
$xml = @'
 <Query Id="0" Path="security">
  <Select Path="security">
     *[EventData[Data[@Name='LmPackageName']!='NTLM V2']]
(Get-ADDomainController  -Filter *).HostName | 
Where { $_ -notin @('dc1.fqdn','dc3.fqdn') } | 
ForEach-Object {
 Get-WinEvent -ComputerName $_ -FilterXml $xml  -ErrorAction SilentlyContinue -Credential $c | # -MaxEvents 1
 ForEach-Object {
  $h = @{}
  ([xml]$_.Toxml()).Event.EventData.Data | 
  ForEach-Object {
} | Out-GridView

The above example shows how to audit 4624 events on domain controllers but you can also audit 4624 events on any computer.

What else could be done to audit NTLM?
You can also enable specific NTLM auditing on every computer using group policies.
By default, it’s empty and off.

But once you’ve activated these 3 settings:

You can also activate these settings using the following registry modifications:

# Audit NTLM Authentication in this domain: Enable all
$HT = @{ Path = 'HKLM:\SYSTEM\CurrentControlSet\services\Netlogon\Parameters' }
Set-ItemProperty @HT -Name AuditNTLMInDomain -Value 7

# Audit incoming NTLM traffic: Enable auditing for all accounts
$HT = @{ Path = 'HKLM:\SYSTEM\CurrentControlSet\Control\Lsa\MSV1_0' }
Set-ItemProperty @HT -Name AuditReceivingNTLMTraffic -Value 2

# Restrict NTLM: Outgoing NTLM traffic to remote servers: Audit All
$HT = @{ Path = 'HKLM:\SYSTEM\CurrentControlSet\Control\Lsa\MSV1_0' }
Set-ItemProperty @HT -Name RestrictSendingNTLMTraffic -Value 1

To list what events will be logged you can do:

(Get-WinEvent -ListProvider Microsoft-Windows-NTLM).Events | 
Select Id,Description | 

When auditing is enabled, we should look at 8001,8002 and 8003 events.
Here’s how to have a quick overview of both 8001 and 8002 events combined:

# NTLM client blocked audit: 
# Audit outgoing NTLM authentication traffic that would be blocked.
Get-WinEvent -FilterHashtable @{ LogName = 'Microsoft-Windows-NTLM/Operational' ; Id = 8001,8002 } |
ForEach-Object {
 $e = $_
 switch ($e.Id) {
  8001 {
   $Direction = 'Out'
   $TargetName = $e.Properties[0].Value ;
   $ProcessID = $e.Properties[3].Value 
   $ProcessName = $e.Properties[4].Value ;
   $Identity =  "$($e.Properties[2].Value)\$($e.Properties[1].Value)"
  8002 {
   $Direction = 'In'
   $TargetName = $env:COMPUTERNAME
   $ProcessID = $e.Properties[0].Value 
   $ProcessName = $e.Properties[1].Value ;
   $Identity =  "$($e.Properties[4].Value)\$($e.Properties[3].Value)"
  default {}
  TargetName = $TargetName
  Direction = $Direction
  ProcessId = $ProcessID
  ProcessName  = $ProcessName
  Identity = $Identity
} | Out-GridView

Remoting error 0x80090311

Last week, I had to enable someone in a domain to restart computers in a another domain.
I’ve first created a restricted endpoint on the Domain Controller itself.
I could enter the endpoint and use the only cmdlet exposed (Restart-Computer) with its limited parameters and values.
But, when I tried to use the endpoint from the computer in the other domain using valid credentials I had the following error:

WARNING: Failed to create and import session because Connecting to remote server server.fqdn failed with the following error message : WinRM cannot process the request. The following error with errorcode 0x80090311 occurred while using Kerberos authentication: There are currently no logon servers available to service the logon request.
Possible causes are:

How can I have a no logon servers available for authentication with the DC itself?
To make it work, I only changed the way I entered credentials.
I initially typed

-Credential (Get-Credential NetBiosDomainName\UserName)

and replaced it with

-Credential (Get-Credential FullyQualifiedDomainName\UserName)

Fine but afterward I encountered the issue I reported a few weeks ago on this blog post D’oh!
Running the Get-Command command in a remote session reported the following error: A parameter cannot be found that matches parameter name ‘PowerShellVersion’

No problem, it’s easier to move the endpoint to a server that runs PowerShell 4.0 than to remove PowerShell 5.1 from client computers.

Again with this other server, I had the same error 0x80090311: no logon servers available for Kerberos authentication ๐Ÿ˜ฆ
This time, I had to modify the target computer name where the endpoint is located and write it using the correct case that matches the way SPN (Service Principal Name) are identified in Active Directory.
The Test-WSMan cmdlet behaved the same way:

Test-WSMan -ComputerName TargetserverFqDNincorretcase -Authentication Kerberos -Credential (Get-Credential FullyQualifiedDomainName\UserName)

and replaced it with

Test-WSMan -ComputerName TargetServerFQDNCorretCase -Authentication Kerberos -Credential (Get-Credential FullyQualifiedDomainName\UserName)

Now that the authentication worked, I hit another unexpected road block.
Using domain admin credentials I could do on the DC:

 Enter-PSSession -ComputerName localhost -ConfigurationName 'MySecureEndPoint'

But not on the member server. I got the following error: Enter-PSSession : AuthorizationManager check failed.

Well, I only found one configuration item that was inconsistent with the way my endpoint is configured that could explain the fact I get the equivalent of an “access denied”.
I’m using a RunAs account who is able to restart remote computers and both the DC and the member server have this policy set:

After removing the whole GPO that configured WinRM, I was finally able to deliver the reboot button:

Achievement unlocked ๐Ÿ˜‰

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 { 

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 ๐Ÿ˜Ž

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 = @(
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 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)
    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 {
   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 ๐Ÿ˜Ž