Remediating Nessus Plugin ID 139239 "Windows Security Feature Bypass in Secure Boot (BootHole)"

There are a series of vulnerabilities that are detected on a Windows host by Nessus Plugin ID 139239 identified as BootHole. This post discusses that vulnerability and how to effectively remediate it against Windows hosts. Some guidance suggests installing Windows Update KB4535680: Security update for Secure Boot DBX, but that update is only available for Windows 10 up to version 1909.

If you, like many I would hope, run a currently supported version of Windows 10 there is no (that I could find at least) Windows Update that will apply the required security updates to take care of this BootHole vulnerability. Below we will be taking a look at what options are available to install the required patches into the systems firmware to take care of this vulnerability.

Remediating via DBX Updates

This vulnerability is remediated by installing some DBX updates into the UEFI firmware of the affected system, so that Secure Boot knows what boot loaders it should or should not allow to be installed.

UEFI has published a series of updates to the DBX database that deny a Secure Boot system from installing any of the vulnerable GRUB bootloaders. The hashes of these bootloaders are added a revocation list that Secure Boot checks to make sure the bootloader is not revoked. Once these DBX Updates are installed into the firmware of a system the vulnerable GRUB bootloaders can no longer be installed, thus remediating this vulnerability.

Windows Update (Win10 versions 20H1+)

If you happen to be running any of the below versions of Windows, then you can install KB4535680 which allegedly updates the Secure Boot with these new DBX updates.

KB4535680 Applies To:

  • Windows Server 2012 x64-bit
  • Windows Server 2012 R2 x64-bit
  • Windows 8.1 x64-bit
  • Windows Server 2016 x64-bit
  • Windows Server 2019 x64-bit
  • Windows 10, version 1607 x64-bit
  • Windows 10, version 1803 x64-bit
  • Windows 10, version 1809 x64-bit
  • Windows 10, version 1909 x64-bit

These were taken directly from Windows Update KB4535680: Security update for Secure Boot DBX

Installing the above KB should remediate the vulnerability as found by Nessus. I have not been able to use this KB, however, since I only have newer versions of Windows 10 that were being identified by Nessus as vulnerable to BootHole. I am also not sure if this KB includes the absolute latest updates from the UEFI Forum.

Manual Windows DBX Updates

Microsoft published an article titled Microsoft guidance for applying Secure Boot DBX update that provides the manual steps required to install the DBX updates. The steps are summarized below.

Obtain the DBX Updates

Download the relevant (x64, x86, ARM) updates directly from Unified Extensible Firmware Interface Forum. You will also need the older updates, which can be found in the linked archive containing Prior Versions of DBX files as these updates are not cumulative. You will be downloading a single .bin binary file for each round of updates.

Split the DBX files

To apply the updates we need to split the .bin files that were downloaded into two parts, a .p7 signature file and another .bin binary file. This can be done via PowerShell.

You will need to download the SplitDbxContent.ps1 script which was authored by Microsoft. You can get it from the PowerShell Gallery. Follow the instructions on the Gallery to obtain that file.

Once you have the SplitDbxContent.ps1 script, run the following PowerShell to create the signature and contents files.

1.\SplitDbxContent.ps1 <path to downloaded dbxupdate.bin>
Warning

You must run this from powershell.exe built into Windows, if you try to run this from pwsh.exe for PowerShell version 7 this script will fail due to changes in the commands in later versions of PowerShell.

You should receive feedback showing that two new files were created, a signature.p7 and a content.bin

Tip

If you have multiple dbxupdates that will need to be done, save them each into a folder named after their update date, and run the SplitDbxContent.ps1 from that folder. Otherwise the above script will overwrite the signature and content files each time you re-run it.

Install DBX Update via PowerShell

Now that you have one or more pairs of signature and content files, you can use PowerShell to install the updates.

Warning

The DBX updates provided by UEFI Forum are not cumulative. This means that you must install each update individually in order to be fully protected from BootHole.

Launch an Administrative PowerShell and enter the following to apply the updates.

1Set-SecureBootUefi -Name dbx -ContentFilePath "content.bin" -SignedFilePath "signature.p7" -Time "2010-03-06T19:17:21Z" -AppendWrite
Timestamp

Make sure you use the -Time "2010-03-06T19:17:21Z" as-is! Do not change this value otherwise it will not apply. Use this same timestamp for each and every update file.

You should receive output showing the bytes as written.

Tip

Microsoft advises to restart the PC before confirming the update applied, however in my experience you can check for the existance of these updates immediately after applying them. Though they may require a restart to actually take affect.

Confirm Updates Applied

You can confirm the updates were properly applied by using these PowerShell scripts. Download both the Check-Dbx.ps1 and Get-UEFIDatabaseSignatures.ps1 scripts. Run them against your content.bin files that were installed to confirm they exist in the UEFI firmware. Repeat for each update file that was applied.

1Get-UEFIDatabaseSignatures.ps1 "content.bin"

You should get feedback that the update was applied.

All-in-One PowerShell Update Script

I had a need to remediate BootHole on dozens of PC's, both local and remote, and since these are running a current version of Windows 10 the KB update was not an option. I followed Microsoft's guidance and created a PowerShell script that will apply the applicable updates to a PC remotely, and without interaction.

There are some other PowerShell scripts available on GitHub that perform this task, but one of my requirements was that the script be completely self-contained. On the systems I was pushing this update script to I didn't have a reliable way to grab the dbx update files from a file share. As such, I split them ahead of time and converted them into base64 to embed them directly into the script. The script then saves these files to disk temporarily right before applying them, then deletes them afterwards.

If you wish to use this script in a similar fashion, with the update files embedded within, grab the latest and the archived update files and follow the instructions above to split the .bin into the signature.p7 and content.bin files. You can then follow the instructions below to Base64 encode these files and paste them into the script.

Fix-BootHole.ps1 script on GitHub

Update Script Variables

If you don't want to embed the update files directly into the script, you can optionally load them from a file share. To do so, follow the instructions previously in this article to download the dbx updates and split the .bin files into their respective signature.p7 and content.bin files.

Store these files on a file share accessible to the system you will be running this script on. Next, edit the script on line 19 to set $EmbeddedFiles to $false and update the file paths on lines 24 through 59 to the signature and content files.

Note

If you intend to embed the files, there is no need to modify the $EmbeddedFiles or $DbxUpdateFiles variables.

Converting DBX Updates to Base64 Encoded Strings

If you want to embed the files into the script so that it is a self-contained script, follow the steps previously in this article to download the dbx updates and to split the .bin files into their respective signature.p7 and content.bin files.

With your individual signature and content files, you need to convert them into Base64 and then copy/paste the Base64 data into the appropriate variables in the script.

Run the following PowerShell code against each of the signature.p7 and content.bin files to generate a rather large string of Base64 encoded text. Piping these to clip will copy the contents directly into your clipboard so that you can paste them.

1[System.Convert]::ToBase64String((Get-Content -Path ".\signature.p7" -Encoding byte)) | clip
2[System.Convert]::ToBase64String((Get-Content -Path ".\content.p7" -Encoding byte)) | clip

Paste each of the signature and content Base64 code into their respective variables in the script. These are under the $DbxUpdateFiles variable. You only need to add in the architectures that you plan to deploy to, you can leave the others empty if you won't need them.

Running the Script

You can run Fix-BootHole.ps1 after either setting the file share locations, or pasting in the embedded base64 versions of the files.

1.\Fix-BootHole.ps1

Fix-BootHole.ps1 Script

For the latest script, please see the GitHub

  1<#
  2.SYNOPSIS
  3Applies UEFI dbx updates to fix BootHole vulnerability.
  4
  5.DESCRIPTION
  6Applies the UEFI dbxupdates to fix the BootHole vulnerability. Prior to running,
  7edit this script to choose between Embedded files or link to a file share.
  8See https://www.rufflabs.com/post/nessus-plugin-139239-remediating-boothole-windows/
  9
 10.EXAMPLE
 11Run the script after making required modifications as mentioned in the descriptions.
 12PS C:\>.\Fix-BootHole.ps1
 13
 14#>
 15
 16# Set this to false if you want to load the .p7 and .bin files from a file share
 17# instead of having them embedded within this script as base64 encoded data.
 18#$EmbeddedFiles = $False
 19$EmbeddedFiles = $True
 20
 21# If using a file share instead of embedding files, update these file paths to point
 22# to the .p7 and .bin files for each respective update. You can only specify those that
 23# will be used (such as, if not using arm, ignore the arm ones.)
 24$DbxUpdatePaths = @{
 25    "x64" = @{
 26        "April 2021" = @{
 27            "signature" = "\\sampleserver\sampleshare\dbxupdates\april2021_x64\signature.p7"
 28            "content" = "\\sampleserver\sampleshare\dbxupdates\april2021_x64\content.bin"
 29        }
 30        "October 2020" = @{
 31            "signature" = "\\sampleserver\sampleshare\dbxupdates\october2020_x64\signature.p7"
 32            "content" = "\\sampleserver\sampleshare\dbxupdates\october2020_x64\content.bin"
 33        }
 34        "July 2020" = @{
 35            "signature" = "\\sampleserver\sampleshare\dbxupdates\july2020_x64\signature.p7"
 36            "content" = "\\sampleserver\sampleshare\dbxupdates\july2020_x64\content.bin"
 37        }
 38    }
 39    "x86" = @{
 40        "April 2021" = @{
 41            "signature" = "\\sampleserver\sampleshare\dbxupdates\april2021_x86\signature.p7"
 42            "content" = "\\sampleserver\sampleshare\dbxupdates\april2021_x86\content.bin"
 43        }
 44        "July 2020" = @{
 45            "signature" = "\\sampleserver\sampleshare\dbxupdates\july2020_x86\signature.p7"
 46            "content" = "\\sampleserver\sampleshare\dbxupdates\july2020_x86\content.bin"
 47        }
 48    }
 49    "arm" = @{
 50        "April 2021" = @{
 51            "signature" = "\\sampleserver\sampleshare\dbxupdates\april2021_arm64\signature.p7"
 52            "content" = "\\sampleserver\sampleshare\dbxupdates\april2021_arm64\content.bin"
 53        }
 54        "July 2020" = @{
 55            "signature" = "\\sampleserver\sampleshare\dbxupdates\july2020_arm64\signature.p7"
 56            "content" = "\\sampleserver\sampleshare\dbxupdates\july2020_arm64\content.bin"
 57        }
 58    }
 59}
 60
 61# Paste in Base64 encoded files below, both signature.p7 and content.bin
 62# for each update and architecture as needed. Any left blank will not be applied.
 63$DbxUpdateFiles =  @{
 64    "x64" = @{
 65        "April 2021" = @{
 66            "signature" = ""
 67            "content" = ""
 68        }
 69        "October 2020" = @{
 70            "signature" = ""
 71            "content" = ""
 72        }
 73        "July 2020" = @{
 74            "signature" = ""
 75            "content" = ""
 76        }
 77    }
 78    "x86" = @{
 79        "April 2021" = @{
 80            "signature" = ""
 81            "content" = ""
 82        }
 83        "July 2020" = @{
 84            "signature" = ""
 85            "content" = ""
 86        }
 87    }
 88    "arm" = @{
 89        "April 2021" = @{
 90            "signature" = ""
 91            "content" = ""
 92        }
 93        "July 2020" = @{
 94            "signature" = ""
 95            "content" = ""
 96        }
 97    }
 98}
 99
100# CA Check adapted from https://github.com/synackcyber/BootHole_Fix/blob/main/boothole.ps1
101Write-Output "Checking for matching UEFI CA"
102$CAMatch = [System.Text.Encoding]::ASCII.GetString((Get-SecureBootUEFI db).bytes) -match 'Microsoft Corporation UEFI CA 2011'
103if(-not $CAMatch){
104    Write-Output "Microsoft Corporation UEFI CA 2011 was not found. Updates are not required on this system."
105    exit
106}
107Write-Output "Certificate found, continuing."
108
109# Check architecture
110$ArchitectureOptions = @{
111    0 = "x86"
112    1 = "MIPS"
113    2 = "Alpha"
114    3 = "PowerPC"
115    5 = "ARM"
116    6 = "IA64"
117    9 = "x64"
118}
119
120$Architecture = $ArchitectureOptions[[int](Get-WmiObject -Class Win32_Processor).Architecture]
121$SupportedArchitectures = @("x86", "x64", "arm")
122
123if($SupportedArchitectures -notcontains $Architecture) {
124    Write-Output "This script does not support $($Architecture) based PC's, and no dbx updates are available for this architecture."
125    exit
126}
127
128function Update-DbxVariable {
129    param(
130        $SignaturePath,
131        $ContentPath
132    )
133    Set-SecureBootUefi -Name dbx -SignedFilePath $SignaturePath -ContentFilePath $ContentPath -Time "2010-03-06T19:17:21Z" -AppendWrite
134}
135
136Write-Output "Updating UEFI dbx variable for $($Architecture) architecture."
137
138$UpdateFiles = @{}
139
140if($EmbeddedFiles) {
141    # Use the embedded files in this script
142
143    Write-Output "Using embedded update files, creating files on disk."
144    try {
145    
146        $DbxUpdateFiles.$Architecture.GetEnumerator() | ForEach-Object {
147            $Release = $_.Name
148            $Content = $_.Value.Content
149            $Signature = $_.Value.Signature
150            $TempSigFile = "$($env:temp)\$($Release)_$($Architecture)_signature.p7"
151            $TempContentFile = "$($env:temp)\$($Release)_$($Architecture)_content.bin"
152
153            # Only copy data if signature and content are not null. 
154            if($null -ne $Signature -and $null -ne $Content) {
155                $UpdateFiles.Add($Release, @{"Signature" = $TempSigFile; "Content" = $TempContentFile})
156                
157                Set-Content -Path $TempSigFile -Value ([System.Convert]::FromBase64String($Signature)) -Encoding Byte -ErrorAction Stop
158                Write-Output "Created $($TempSigFile)"
159                
160                Set-Content -Path $TempContentFile -Value ([System.Convert]::FromBase64String($Content)) -Encoding Byte -ErrorAction Stop
161                Write-Output "Created $($TempContentFile)"
162            }
163        }
164    } catch {
165        Write-Output "Error creating temporary files. Check permissions to $($env:temp)."
166        exit
167    }
168}else{
169    # Use the files from a file share location
170
171    Write-Output "Using update files from file share."
172    
173    $DbxUpdatePaths.$Architecture.GetEnumerator() | ForEach-Object {
174        $Release = $_.Name
175        $ContentPath = $_.Value.Content
176        $SignaturePath = $_.Value.Signature
177        
178        # Only add to the queue if both signature and content files are accessible
179        if((Test-Path -Path $ContentPath) -and (Test-Path -Path $SignaturePath)) {
180            Write-Output "`nPreparing: $($Release)`nSignature: $($SignaturePath)`nContent: $($ContentPath)"
181            $UpdateFiles.Add($Release, @{"Signature" = $SignaturePath; "Content" = $ContentPath})
182        }
183    }
184}
185
186$UpdateFiles.GetEnumerator() | ForEach-Object {
187    Write-Output "Applying $($_.Name) update..."
188    Update-DbxVariable -SignaturePath $_.Value.Signature -ContentPath $_.Value.Content
189}   
190
191if($EmbeddedFiles) {
192    $UpdateFiles.Values.Values | ForEach-Object {
193        # Delete file from disk
194        Write-Output "Deleting on disk $($_)"
195        Remove-Item -Path $_ -ErrorAction SilentlyContinue
196    }
197}

References