Bluekeep, why would you still be vulnerable ? SHA2 signing

Bluekeep, why would you still be vulnerable? SHA2 signing.

Blueekeep

Patch management is a pain, and the more obsolete the OS, the trickier it becomes. Windows 7 and 2008 R2 (Windows 6.1) are due to reach End Of Life (EOL) on 14 January 2020 (tomorrow at the time of this writing) but your computers may not have been patched since August 2019 if you haven’t been careful.

Indeed, August 2019 was the last month where Microsoft KB were signed using SHA1. Since then, KBs are only signed using SHA2, but this hash function must be installed on Windows 6.1 in order for your computers and servers to install them. Otherwise they will fail with error code 0x80092004 (CRYPT_E_NOT_FOUND).

Since Windows Server 2012R2, this hash function is already included so there is nothing to worry about for 2012R2, 2016 and 2019.

Unfortunately, in August and September 2019, Bluekeep, a critical security vulnerability on Microsoft’s Remote Desktop Protocol, which allow s for the possibility of remote code execution as system, was released, and patched by Microsoft during those two months. Therefore, if some computers running Windows 6.1 have not installed the SHA2 hash function before Septembre 2019, they have not been patched since then and are vulnerable to Bluekeep. I hope it’s not your DC, or your WSUS…

But don’t panic, here is what you can do :

  • Find the machines in error
  • Fix the machines in error

Finding the machines in error :

If you’re using WSUS to patch your domain, I got something for you. Since WSUS Report Viewer is awfully slow and a pain to use, I developed a quick script using Powershell to retrieve every computer in error.

This script must be run as Administrator on the WSUS server delivering the patch to the clients. It was only tested on WSUS running on at least Windows 2012R2. The script simply looks for computers in error according to wsus reports and writes their names, the error codes and the KBs in failure in a CSV file.

SCCM is out of scope in this article but aleardy provides the necessary dashboard if you’re patching with it. It can be used alongside WSUS to quickly deploy a KB on you infrastructure.

Here is a quick usage, followed by a possible result and the script itself. You can also download it.

1.\wsus_computers_in_error.ps1 ServerName wsus.ipfyx.fr ServerPort 8531 RelativeTime -168 ErrorCode 0x80092004 CsvPath ".\computer_no_sha2_$(Get-Date Format yyyy-MM-dd).csv"
  1<#
  2    .SYNOPSIS
  3    Retrieve every computer in error when installing a Microsoft KB, with a specifi error code or not (SHA2 error : 0x80092004)
  4
  5    .PARAMETER ErrorCode
  6    Error code
  7
  8    .PARAMETER ServerName
  9    Name of the WSUS Server
 10
 11    .PARAMETER ServerPort
 12    Port of the WSUS Server (8531 if it is using SSL, else 8530)
 13
 14    .PARAMETER RelativeTime
 15    Relative time between now and a past time (ex : -24 is -24h)
 16
 17    .PARAMETER CsvPath
 18    Output file in CSV
 19
 20    .NOTES
 21    (c) ipfyx 2019
 22    version 1.5
 23
 24    Example
 25    .\wsus_computers_in_error.ps1 -ServerName <Server> -ServerPort 8531 -RelativeTime -168 -Errorcode 0x80092004 -CsvPath ".\computer_no_sha2_$(Get-Date -Format yyyy-MM-dd).csv"
 26
 27#>
 28
 29# This script must be run with Administrator privileges.
 30#Requires -RunAsAdministrator
 31
 32Param([Parameter(Mandatory=$True)][string]$ServerName, [Parameter(Mandatory=$True)][string]$ServerPort, 
 33[Parameter(Mandatory=$False)][int32]$RelativeTime=-48, [Parameter(Mandatory=$False)][string]$Errorcode='',
 34[Parameter(Mandatory=$True)][string]$CsvPath)
 35
 36Function Get_Computer_Scope {
 37    [CmdletBinding()]
 38    Param(
 39    [Parameter(Mandatory=$True)]
 40    $Wsus,
 41
 42    [Parameter(Mandatory=$False)]
 43    [string]
 44    $InstallationStates = 'Failed',
 45
 46    [Parameter(Mandatory=$False)]
 47    [boolean]
 48    $IncludeDownstreamComputerTargets = $true
 49    )
 50
 51    # Retrieve every computer in error according to WSUS reports
 52    $computerScope = new-object Microsoft.UpdateServices.Administration.ComputerTargetScope
 53    $computerScope.IncludedInstallationStates = $InstallationStates
 54
 55    # Retrieve every compuer in error accoring to downtream servers too
 56    $computerScope.IncludeDownstreamComputerTargets = $InstallationStates
 57
 58    $Wsus.GetComputerTargets($computerScope)
 59}
 60
 61Function Get_Event_History {
 62    [CmdletBinding()]
 63    Param(
 64    [Parameter(Mandatory=$True)]
 65    $Wsus,
 66
 67    [Parameter(Mandatory=$True)]
 68    [string]
 69    $RelativeTime,
 70
 71    [Parameter(Mandatory=$False)]
 72    [string]
 73    $InstallationStates = 'Failed',
 74
 75    [Parameter(Mandatory=$False)]
 76    [string]
 77    $ErrorCode = ''
 78    )
 79
 80    
 81    # Quelques messages d'erreur sont juste "Installation Failure: Windows failed to install the following update with error %1: %2"
 82    # Et l'updateID associé n'existe pas dans la bdd (2ce8a11d-7f90-4489-92f3-a202585b793b), pourquoi, je sais pas, mais ca fout le sbeul
 83
 84    # Retrieve every computer in error, no matter the error code, if the error code is not spectified
 85    if ($ErrorCode -eq '') {
 86
 87        $Wsus.GetUpdateEventHistory("$((Get-Date).AddHours($RelativeTime))","$(Get-Date)")|
 88                    Where-Object {$_.Status -eq $InstallationStates}|
 89                    Select-Object ComputerId, CreationDate, Message, ErrorCode, UpdateId
 90    }
 91    else {
 92        $Wsus.GetUpdateEventHistory("$((Get-Date).AddHours($RelativeTime))","$(Get-Date)")|
 93                    Where-Object {$_.Status -eq $InstallationStates}|
 94                    Where-Object {$_.ErrorCode -eq $Errorcode} |
 95                    Select-Object ComputerId, CreationDate, Message, ErrorCode, UpdateId
 96    }
 97}
 98
 99
100If ($ServerPort -eq '8531') {
101    $Wsus = Get-WsusServer -Name $ServerName -PortNumber $ServerPort -UseSsl
102} Else {
103    $Wsus = Get-WsusServer -Name $ServerName -PortNumber $ServerPort
104}
105
106$ComputersInFailure = Get_Computer_Scope -Wsus $Wsus
107
108$ComputersAffected = @{}
109
110$ErrorMessages = Get_Event_History -RelativeTime $RelativeTime -ErrorCode $ErrorCode -Wsus $Wsus
111$ErrorMessages | ForEach-Object {
112
113  $ComputerId = $_.ComputerId
114  $ComputerWithError = $ComputersInFailure | Where-Object {$_.id -eq $ComputerId}
115
116  if($ComputerWithError -ne $null) {
117  
118      try {
119        # Retrieve the Knowledge Base number associated with the update guid
120        $updateGuid = $_.UpdateId.UpdateId.guid
121        $updateId = New-Object Microsoft.UpdateServices.Administration.UpdateRevisionId($updateGuid)
122        $update = $wsus.GetUpdate($updateId)
123
124        # A computer could fail multiple KB install so the key to distinguish every case is FullDomaineName+KB
125        $Key = $ComputerWithError.FullDomainName+[System.Convert]::ToString($_.ErrorCode,16)+$update.KnowledgebaseArticles[0]
126
127        if ($ComputersAffected.ContainsKey($Key)) {
128
129            # We only want the last report in error
130            if ($ComputerWithError.LastReportedStatusTime -gt $ComputersAffected[$Key].LastReportedStatusTime) {
131
132                $ComputersAffected[$Key]=    
133                [pscustomobject]@{  
134                FullDomaineName = $ComputerWithError.FullDomainName
135                IPAddress = $ComputerWithError.IPAddress
136                OSDescription = $ComputerWithError.OSDescription
137                ErrorCode = [System.Convert]::ToString($_.ErrorCode,16)
138                LastReportedStatusTime = $ComputerWithError.LastReportedStatusTime
139                UpdateTitle = $update.Title
140                KB = $update.KnowledgebaseArticles[0]
141                ArrivalDate = $update.ArrivalDate
142
143                }                
144            }
145          
146        } else {
147
148            $ComputersAffected[$Key]=    
149            [pscustomobject]@{  
150            FullDomaineName = $ComputerWithError.FullDomainName
151            IPAddress = $ComputerWithError.IPAddress
152            OSDescription = $ComputerWithError.OSDescription
153            ErrorCode = [System.Convert]::ToString($_.ErrorCode,16)
154            LastReportedStatusTime = $ComputerWithError.LastReportedStatusTime
155            UpdateTitle = $update.Title
156            KB = $update.KnowledgebaseArticles[0]
157            ArrivalDate = $update.ArrivalDate
158
159            }
160        }
161
162      }
163      catch [Microsoft.UpdateServices.Administration.WsusObjectNotFoundException]{
164            # Some "ghost" reports can mess up the script
165            # We don't do anything
166      }
167   }
168
169}
170
171$ComputersAffected.Values | Export-Csv -Encoding UTF8 -Path $CsvPath

Here is a possible output :

1#TYPE System.Management.Automation.PSCustomObject
2"FullDomaineName","IPAddress","OSDescription","ErrorCode","LastReportedStatusTime","UpdateTitle","KB","ArrivalDate"
3"toto.ipfyx.fr","1.1.1.1","Windows 7","80092004","01/01/1970 00:00:00","Cumulative Security Update","4474333","01/01/1970 01:00:00"

Fixing the machines in error :

Since Windows 6.1 is due to reach End Of Life (EOL) on 14 January 2020, so why bother ? Well, Bluekeep is such a critical vulnerability that you should not neglect it. After that, you should hurry to upgrade your server to at least Windows 2012 R2, and you computers to Windows 10.

In the meantime, to fix this issue, you should install :

  • For Windows 2008R2 et Windows 7 :
    • KB 4474419 (cumulative security update from July 2019 or before, last KB signed using SHA1 for Windows 2008R2)
    • KB 4490628 (Stack update from March 2019)
  • For Windows 2008 :
    • KB 4474419 (cumulative security update from June 2019, last KB signed using SHA1 for Windows 2008)
    • KB 4493730 (Stack update from April 2019)

Quick bonus :

If you don’t specify any error code to the script, it will return every computer in error, whatever the error code. You can therefore use this script to diagnose your patch management. The CSV result could, for example, be put in splunk to build dashboard.

1.\wsus_computers_in_error.ps1 ServerName wsus.ipfyx.fr ServerPort 8531 RelativeTime -168 CsvPath ".\computer_in_error_$(Get-Date Format yyyy-MM-dd).csv"

I wish you good luck ! Any feedback will of course be greatly appreciated.