As you know, there's an new (old) vulnerability affecting ESXi hosts, details found here: https://www.vmware.com/security/advisories/VMSA-2021-0002.html. VMware provided a sample PowerShell script o...
See more...
As you know, there's an new (old) vulnerability affecting ESXi hosts, details found here: https://www.vmware.com/security/advisories/VMSA-2021-0002.html. VMware provided a sample PowerShell script on how to deal with section "3b. ESXi OpenSLP heap-overflow vulnerability (CVE-2021-21974)" to disable the SLPD service on the hosts, however I had a lot of difficulties getting this to work as it uses Putty's PLINK executable and this does not auto-accept host keys thus causing issues with the efficient running of the script. I thus set about to create a script using PowerCLI & Posh-SSH which after a day of development, seems to work really well in my environment. I hope this helps you guys out as well. The main script requires the creation of credential XMLfiles which need to be pre-created. The prelim script will ask for your host root logon details and then store these as XML files. Run this script as many times as you have different credentials defined on your vCenter hosts. For example, we have three clusters and in each cluster, the hosts have the same root password however each cluster has different root passwords from the other, so in my case I would create 3 credential files. The main script will loop thru all hosts (the default setting) and try each credential file to logon until it finds one that works. It will then stop the SLPD service on that host and cycle thru to the next host repeating the process. The script performs the following functions: Accepts command line parameters: ESXiHosts: You can add the names of the hosts to run the script on using commas as the separator. If left blank, the script will run on all hosts connected to the vCenter. NumCreds: The number of credential files you wish to use. This is an integer. Credential files need to be named "HostCreds#.xml" where the "#" represents a number from 1. SkipSLPDStatsCheck: A flag which will skip the SLPD stats check on the hosts- When present, this flag causes the host to report on the SLPD statistics showing if the port has been/is being used and takes a while to complete- per host. Checks for a connection to the vCenter Checks the required PowerShell modules are installed and if not, installs them. Loops thru each host, trying to log into the host using the one or more credential files. It auto-accepts the key for the host! Once connected to the host, the SLPD service is disabled and before & after details are displayed. Loops to the next host. I'm by-far the most novice PowerShell author so please let me know if this helps and any improvements you can suggest. Scripts: 1. CreateCredentialsFile.ps1 $Cred = Get-Credential
$fileName = "HostCreds"
$fileExtension = ".xml"
$count = 1
while (Test-Path "$fileName$count$fileExtension")
{
Write-Host "Testing to see if the file [$fileName$count$fileExtension] exists." -ForegroundColor DarkBlue
$count++
}
Export-Clixml -Path "$fileName$count$fileExtension" -InputObject $Cred
Write-Host "Credentials saved to file name [$fileName$count$fileExtension]." -ForegroundColor Green 2. Remove-Vuln-CVE-2021-21974.ps1 <#
.SYNOPSIS
This script disables SLP service on all ESXi hosts.
VMware reference: https://www.vmware.com/security/advisories/VMSA-2021-0002.html
The script attempts to mitigate the following CVEs:
3b. ESXi OpenSLP heap-overflow vulnerability (CVE-2021-21974)
Author: Julian Milano
Version: 1.5
Date: 14th Feb 2023
.DESCRIPTION
This script connects to all ESXi hosts, checks the status of SLP service, and disables it. The status of SLP service is also retrieved before and after disabling the service. You will need to make sure you already have a connection to the vCenter.
.PARAMETER ESXiHosts
The list of ESXi hosts to connect to and run the script on.
.PARAMETER NumCreds
The number of credentials to be used for connecting to the ESXi hosts.You may have different credentials for different hosts in the same vCenter, Thus you can include multiple credential files in the XML format which the Script will use to log into the respective host until one works.
It is thus necessary to have at least one file by the name 'HostCreds1.xml' in the same folder as this script.
The credential files are created using an accompanying script file called 'CreateCredentialsFile.ps1'. This script will ask for credentials and store them in a credentials XML file, creating a new file for each entry stored. You can thus end up with say 4 credentials files looking like this:
HostCreds1.xml
HostCreds2.xml
HostCreds3.xml
HostCreds4.xml
.PARAMETER SkipSLPDStatsCheck
A switch that, when specified, will skip checking the SLPD stats. The ESXi host stores usage statistics on the SLPD service which are retrieved to verfiy if
the service has been and is currently in use. To enable this switch, use the value:
:$True
.EXAMPLE
PS C:\> .\Remove-Vuln_2023-02.ps1 -ESXiHosts (Get-VMHost *)
Connects to all ESXi hosts and disables SLP service on all hosts.
.EXAMPLE
PS C:\> .\Remove-Vuln_2023-02.ps1 -ESXiHosts (Get-VMHost *) -NumCreds 2 -SkipSLPDStatsCheck:$True
Connects to all ESXi hosts using 2 credentials, disables SLP service, and skips checking the SLPD stats.
.EXAMPLE
.\Remove-Vuln_2023-02.ps1 -ESXiHosts 'MyHost01.MyDomain.com.au', 'MyHost02.MyDomain.com.au' -NumCreds 2 -SkipSLPDStatsCheck:$True
Connects to 2 specific hosts, using 2 stored credentials, skips the SLPD Stats check and disables the SLPD service on each.
#>
# ====================================Paramter DEFINITION Start =======================================
Param(
[Parameter(Mandatory=$True)]
[Object]$ESXiHosts = (Get-VMHost *),
[Parameter(Mandatory=$False)]
[INT]$NumCreds = 1,
[Parameter(Mandatory=$False)]
[Switch]$SkipSLPDStatsCheck = $False
)
# ====================================FUNCTION DEFINITION START =======================================
Function CheckModuleInstalled
{
Param(
[STRING]$ModuleName
)
# Check if Module loded.
$ModuleInstalledResult = Get-Module -Name $ModuleName -ListAvailable
If (!$ModuleInstalledResult)
{ # Module is not loaded- load it now.
Install-Module -Name $ModuleName
}
}
Function DisableSLPDService
{
$SLPDCheck_Result = @()
$ItemCheck = New-Object PSObject
$SLPDRun_Result = @()
$ItemRun = New-Object PSObject
# Gather SLPD Service check results from host.
$SLPDCheck1_Result = InvokeESXiCommand -InputCommand $SLPDCheck1
# Check if SLPD Stats check has been disabled by caller.
if (!$SkipSLPDStatsCheck)
{
# It has not. Get result.
$SLPDCheck2_Result = InvokeESXiCommand -InputCommand $SLPDCheck2
$ItemCheck | Add-Member -type NoteProperty -Name 'SLPD_Stats' -Value $($SLPDCheck2_Result.Output -replace '\s+', ' ')
}
Else
{
# Check has been disabled- return 'NA'.
$ItemCheck | Add-Member -type NoteProperty -Name 'SLPD_Stats' -Value "Skipped"
}
$SLPDCheck3_Result = InvokeESXiCommand -InputCommand $SLPDCheck3
$SLPDCheck4_Result = InvokeESXiCommand -InputCommand $SLPDCheck4
# Only retrieve the 3rd part oSf the result showing the Firewall status.
$SLPDCheck3_Result.Output = $SLPDCheck3_Result.Output[2]
$ItemCheck | Add-Member -type NoteProperty -Name 'SLPD_Status' -Value $($SLPDCheck1_Result.Output -replace '\s+', ' ')
$ItemCheck | Add-Member -type NoteProperty -Name 'SLPD_Network_Firewall_Status' -Value $($SLPDCheck3_Result.Output -replace '\s+', ' ')
$ItemCheck | Add-Member -type NoteProperty -Name 'SLPD_AfterBoot_Status' -Value $($SLPDCheck4_Result.Output -replace '\s+', ' ')
# Disable SLPD Service on host.
$SLPDRun_Result1 = InvokeESXiCommand -InputCommand $SLPD1
$SLPDRun_Result2 = InvokeESXiCommand -InputCommand $SLPD2
$SLPDRun_Result3 = InvokeESXiCommand -InputCommand $SLPD3
# If any values are blank, replace them with "NA".
If (!$SLPDRun_Result1.Output) {$SLPDRun_Result1.Output = "NA"}
If (!$SLPDRun_Result2.Output) {$SLPDRun_Result2.Output = "NA"}
If (!$SLPDRun_Result3.Output) {$SLPDRun_Result3.Output = "NA"}
$ItemRun | Add-Member -type NoteProperty -Name 'SLPD_ServiceStop_Status' -Value $($SLPDRun_Result1.Output)
$ItemRun | Add-Member -type NoteProperty -Name 'SLPD_FirewallDisable_Status' -Value $($SLPDRun_Result2.Output)
$ItemRun | Add-Member -type NoteProperty -Name 'SLPD_AfterBoot_Status' -Value $($SLPDRun_Result3.Output)
$SLPDCheck_Result += $ItemCheck
$SLPDRun_Result += $ItemRun
Return $SLPDCheck_Result,$SLPDRun_Result
}
Function InvokeESXiCommand
{
Param(
[STRING]$InputCommand
)
$CommandResult = Invoke-SSHCommand -SSHSession $ssh -Command $InputCommand
Return $CommandResult
}
#
# ====================================FUNCTION DEFINITION END =======================================
# Commands to disable SLPD service.
# https://kb.vmware.com/s/article/76372
# ====================================Code Body Start ===============================================
#
# Check if connected to vCenter server.
#
$ConnectionvCenter = $global:DefaultVIServers
if ($ConnectionvCenter.Count -gt 0)
# Connection exists.
{
Write-Host "Already connected to vCenter server '" $ConnectionvCenter.name "'. Continuing..." -foregroundcolor "Green"
}
Else
{
# No connection to vCenter.
Write-Host "ERROR: Not connected to any vCenter servers. Try the command:" -ForegroundColor "Red"
Write-Host " Connect-ViServer <vCenterServerName>" -ForegroundColor "White"
Write-Host "Stopping script." -ForegroundColor "Red"
Write-Host ""
Throw "Not connected to vCenter server."
Exit
}
# Connection is Live, continue running code.
# Commands to check SLPD service:
$SLPDCheck1 = "/etc/init.d/slpd status"
$SLPDCheck2 = "esxcli system slp stats get"
$SLPDCheck3 = "esxcli network firewall ruleset list -r CIMSLP"
$SLPDCheck4 = "chkconfig --list | grep slpd"
# Commands to disable SLPD Service:
$SLPD1 = "/etc/init.d/slpd stop"
$SLPD2 = "esxcli network firewall ruleset set -r CIMSLP -e 0"
$SLPD3 = "chkconfig slpd off"
# Check if required modules are loaded and ready,
Set-PSRepository -Name PSGallery -InstallationPolicy Trusted
Write-Host "Checking if PowerCLI Module is loaded" -ForegroundColor Yellow
$ModuleCheckPowerCLI = CheckModuleInstalled -ModuleName VMware.PowerCLI
Write-Host "Checking if Posh-SSH Module is loaded" -ForegroundColor Yellow
$ModuleCheckPoshSSH = CheckModuleInstalled -ModuleName Posh-SSH
# Report on modules loaded.
If ($ModuleCheckPowerCLI) {Write-Host "PowerCLI is not installed on this system. The script will now exit."}
If ($ModuleCheckPoshSSH) {Write-Host "POSH-SSH is not installed on this system. The script will now exit."}
if ($ModuleCheckPowerCLI -or $ModuleCheckPoshSSH) {Exit}
if ($SkipSLPDStatsCheck) {Write-Host "SLPD stats check skipped"} Else {Write-Host "SLPD stats check not skipped"}
Write-Host "Modules loaded OK. Now looping thru hosts." -ForegroundColor Green
# Loop through each ESXi host. The $ESXIHosts variable can contain the host names. This line converts the variable contents
# to ESXI host objects.
$oESXiHosts = Get-VMHost $ESXiHosts
# Cycle thru each host object.
$oESXiHosts | ForEach-Object -Process {
$ESXiHostName = $_.Name
# The variable $NumCreds defines how many credential files you have sitting in the same folder as this script.
# The files should be named 'HostCreds#.xml' where # is a number.
# This section will cycle thru each credential file and try to log into the currently selected host using those
# credentials. If the credentials fail, the script will try the next file.
Write-Host "Checking for Credentials file. Should be in the format 'HostCreds#.xml' where # is a number. You can have multiple credential files to test against." -ForegroundColor Cyan
For ($iLoop = 1; $iLoop -le $NumCreds; $iLoop++)
{
$Cred = ""
$CredFileName = -join("HostCreds",$iLoop,".xml")
Write-Host "[$ESXiHostName] Trying credentials file [$CredFileName]." -ForegroundColor Cyan
$Cred = Import-Clixml -Path $CredFileName -ErrorAction SilentlyContinue
# Check if the credentials file exists.
If (!$Cred)
{ #Could not find the next credential file.
Write-Host "[$ESXiHostName] Credential file [$CredFileName] not found. Cannot continue." -ForegroundColor Red
# Exit the host.
Break
}
# Connect to the ESXi host using the SSH protocol and auto-accept the host key.
$ssh = New-SSHSession -ComputerName $ESXiHostName -Credential ($Cred) -AcceptKey:$True
If ($SSH.Connected -eq $True)
{ # Connection was successful, don't try further credential files.
Write-Host "[$ESXiHostName] Credentials file [$CredFileName] selected." -ForegroundColor Cyan
# Exit the credential file loop.
Break
}
}
If (!$SSH.Connected)
{ # Could not connect to the selected host with any credential files, ignore this host and exit the loop.
Write-Host "[$ESXiHostName] Could not establish a connection to the host. Trying the next host."
Continue
}
# Get the host version.
$ESXiVersion = InvokeESXiCommand -InputCommand "vmware -v"
# Dummy check in case you want to add logic as to which hosts to check.
if ($True)
{
# Display host and SLPD service details.
Write-Host "+--------------------------------------------------------------------------------+"
Write-Host "[$ESXiHostName] ESXi Version: $($esxiVersion.Output)" -ForegroundColor Cyan
Write-Host "[$ESXiHostName] Applying workaround. Results will show [BEFORE] -> [AFTER] results." -ForegroundColor DarkCyan
# Disable the SLPD service.
$SLPD_Results = DisableSLPDService
# The function returns 2 variables:
# 1. The service Check results.
# 2. The service After-Run results.
$SLPD_Check_Results = $SLPD_Results[0]
$SLPD_Run_Results = $SLPD_Results[1]
# Show the before and after values.
Write-Host "[$ESXiHostName] SLPD Status [$($SLPD_Check_Results.SLPD_Status)] -> [$($SLPD_Run_Results.SLPD_ServiceStop_Status)]" -ForegroundColor Yellow
Write-Host "[$ESXiHostName] SLPD Stats [$($SLPD_Check_Results.SLPD_Stats)]" -ForegroundColor Yellow
Write-Host "[$ESXiHostName] SLPD Firewall Ruleset [$($SLPD_Check_Results.SLPD_Network_Firewall_Status)] -> [$($SLPD_Run_Results.SLPD_FirewallDisable_Status)]" -ForegroundColor Yellow
Write-Host "[$ESXiHostName] SLPD Reboot Config [$($SLPD_Check_Results.SLPD_AfterBoot_Status)] -> [$($SLPD_Run_Results.SLPD_AfterBoot_Status)]" -ForegroundColor Yellow
Write-Host "+--------------------------------------------------------------------------------+"
Write-Host "|"
}
Else
{
Write-Host "No hosts checked due to not meeting criteria." -ForegroundColor Red
}
# Disconnect from the ESXi host
$HostSSHSessionClosed = Remove-SSHSession -SSHSession $ssh
Write-Host "Removing SSH session from host with result: $HostSSHSessionClosed"
}
Write-Host "Script Finished" -ForegroundColor Green