VMware Cloud Community
JDMils_Interact
Enthusiast
Enthusiast

My PowerShell script to disable SLPD service on all hosts (CVE-2021-21974)

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:

  1. Accepts command line parameters:
    1. 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.
    2. 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.
    3. 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.
  2. Checks for a connection to the vCenter
  3. Checks the required PowerShell modules are installed and if not, installs them.
  4. 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!
  5. Once connected to the host, the SLPD service is disabled and before & after details are displayed.
  6. 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

 

 

9 Replies
lamw
Community Manager
Community Manager

Awesome to see that you're automating this but SSH is NOT required to perform these operations nor should it be required ... especially as most customers will have SSH disabled by default

Everything you need is available via vSphere API and can be easily consumed using PowerCLI 🙂

# Check whether slpd service is running on ESXi host

Get-VMHost 192.168.30.5 | Get-VMHostService | where {$_.key -eq "slpd"}

Key                  Label                          Policy     Running  Required
---                  -----                          ------     -------  --------
slpd                 slpd                           off        False    False

 

# Stop slpd service

Get-VMHost 192.168.30.5 | Get-VMHostService | where {$_.key -eq "slpd"} | Stop-VMHostService -Confirm:$false

Key                  Label                          Policy     Running  Required
---                  -----                          ------     -------  --------
slpd                 slpd                           off        False    False

 

This should simplify your automation by quite a bit 😄

Reply
0 Kudos
JDMils_Interact
Enthusiast
Enthusiast

NA

Reply
0 Kudos
JDMils_Interact
Enthusiast
Enthusiast

Thanks for your response. I did see the PowerShell example from VMware stating you can shutdown the SLPD service using PowerCLI, however I initially tested the sample code on my ESXi hosts and some hosts, particularly those which were deemed vulnerable just by their build version, returned no result. Here is my testing results on an ESXi 6.7 host with SLPD service running:

Checked the SLPD service status via SSH:

[root@MyHost:~] /etc/init.d/slpd status

slpd is running

 

[root@MyHost:~] esxcli network firewall ruleset list -r CIMSLP

Name    Enabled

------  -------

CIMSLP     true

 

[root@MyHost:~] chkconfig --list | grep slpd

slpd                    on

 

OK, so the SSH commands all show the SLPD service is running, firewall is allowing connections and the service is set to restart on host restart.

Then I ran the VMware-recommended PowerShell script on the same host:

PS C:\Users\jmilano\Documents\Powershell_Scripts\Vulnerabilities> $VMHost = Get-VMHost -Name 'MyHost.MyDomain.com.au'

PS C:\Users\jmilano\Documents\Powershell_Scripts\Vulnerabilities> $service = $VMHost | Get-VMHostService | Where-Object { $_.Key -eq "slpd" }

PS C:\Users\jmilano\Documents\Powershell_Scripts\Vulnerabilities> $Service

PS C:\Users\jmilano\Documents\Powershell_Scripts\Vulnerabilities>

As you can see from displaying the value of the variable $Service, the PowerShell script returns no results. I checked the web GUI for this host and it only shows the CIM Server as a service, there is no SLPD service in the GUI. The hosts I tested this on are VMware ESXi, 6.7.0, 14320388.

 

I then checked the SLPD service from SSH again and nothing has changed:

[root@MyHost:~] /etc/init.d/slpd status

slpd is running

 

[root@MyHost:~] esxcli network firewall ruleset list -r CIMSLP

Name    Enabled

------  -------

CIMSLP     true

 

[root@MyHost:~] chkconfig --list | grep slpd

slpd                    on

 

From what I can determine, if you cannot see the SLPD service in the web GUI then you cannot turn it off using just straight PowerCLI. And this will most likely be the hosts which are ESXi 6.7 with a very low build value.

 

So if you want to be sure that you mitigate this vulnerability on ALL your hosts, you're better off running the "work around" commands supplied by VMware using SSH from PowerShell. This is for all those admins out there who have not upgraded their hosts to V7 yet. I guess if you have all V7 hosts then the VMware sample code will work fine.

 

*** Interesting note: All my vCenters are now V7 running ESXi 6.7 hosts atm as we are about to upgrade the hosts. No matter if I turn off the SLPD service from an SSH session, as per the VMware workaround instructions, or by using my custom PowerShell script (same thing really!), the service status as displayed in the web GUI does NOT change- so the SLPD service in the web GUI will show "Running" although it is not.

Using the VMware PowerShell script samples DOES change the Service status in the web GUI, and from what I can see it is because the PowerCLI commands create a task in vCenter/ESXi hosts, you can see the task popup in the Recent Tasks pane at the bottom of the web GUI when you run the VMware PowerShell samples, so this seems to trigger the vCenter server to update the web GUI service status.

Strange!

Reply
0 Kudos
lamw
Community Manager
Community Manager

Ah, I just pulled up 6.0uX and it does look like the Services API was limited to a subset of services 😕

In terms of seeing reflected service status, you may want to try refreshing services endpoint using https://vdc-repo.vmware.com/vmwb-repository/dcr-public/c476b64b-c93c-4b21-9d76-be14da0148f9/04ca12ad... which should reflect whatever the actual state of the system is

JDMils_Interact
Enthusiast
Enthusiast

Can you help me out here? I can access the vCenter & Host MOBs however I cannot find the option you mentioned. How do I navigate there?

 

Is there a MOB tree online where you can search for what you want and find a path to get there by navigating the MOB?

Reply
0 Kudos
lamw
Community Manager
Community Manager

MOB is nothing more than a "visual debugging" interface to underlying vSphere API ... whats more important to understand IS the vSphere API and its modeling and inventory structure, so you can navigate it and the MOB can help but if you don't understand how API actually works, then simply browsing the MOB won't help either 🙂

As I've already shared in my previous reply, the API reference gives you all the info you need. The RefreshServices() method is available under HostServiceSystem managed object, which you will need to retrieve for the given ESXi host before you can all it

https://vdc-repo.vmware.com/vmwb-repository/dcr-public/c476b64b-c93c-4b21-9d76-be14da0148f9/04ca12ad...

Here's is PowerCLI way of interacting with vSphere API and obtaining a reference to ServiceSystem and then calling the method

$ss = Get-View (Get-VMHost 192.168.30.5).ExtensionData.ConfigManager.ServiceSystem
$ss.RefreshServices()

 

Reply
0 Kudos
JDMils_Interact
Enthusiast
Enthusiast

OK, so the MOB is a generic view of some APIs, and the APIs are the core of the communications- so not all APIs will appear in the MOB. Is that correct?

I added the code to refresh the services view on the vCenter so that changes to the services are now reflecting on the vCenter Services section of the hosts' web GUI. However, some hosts are showing this error when the code runs:

Get-View : Cannot validate argument on parameter 'VIObject'. The argument is null or empty. Provide an argument that is not null or empty, and then try the command again.
At C:\Users\jmilano\Documents\PowerShell_Scripts\Vulnerabilities\Remove-Vuln_2023-02.ps1:157 char:20
+ ...  = Get-View (Get-VMHost $ESXiHostName).ExtensionData.ConfigManager.Se ...
+                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidData: (:) [Get-View], ParameterBindingValidationException
    + FullyQualifiedErrorId : ParameterArgumentValidationError,VMware.VimAutomation.ViCore.Cmdlets.Commands.DotNetInterop.GetVIView

You cannot call a method on a null-valued expression.
At C:\Users\jmilano\Documents\PowerShell_Scripts\Vulnerabilities\Remove-Vuln_2023-02.ps1:158 char:5
+     $ss.RefreshServices()
+     ~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (:) [], RuntimeException
    + FullyQualifiedErrorId : InvokeMethodOnNull

The error happens when this command is executed:

$ss = Get-View (Get-VMHost 192.168.30.5).ExtensionData.ConfigManager.ServiceSystem

Thanks for your help so far.

Reply
0 Kudos
JDMils_Interact
Enthusiast
Enthusiast

Sorry for the BUMP. Does anyone know why this error happens on some hosts?

Reply
0 Kudos
StephenMoll
Expert
Expert

We had a similar issue when developing our system security strategy and settings.

Following the VMware Security Guidance and the example PowerCLI contained within it, we initially believed SLPD and CIM services were safe. The snippets of script in the guidance not spitting out a list of non-compliant hosts. Only the systems were failing IT Health Checks (pen-tests).

When we went to the ESXi CLI and checked the services there we found the services were indeed still active.

On digging we found that if the SLPD service in particular, would only be manageable if it was visible in the webclient GUI. If it wasn't the PowerCLI commands given in the guidance would in actually give you an invalid response. We fed this back to VMware via our TAM along with other observations about the security guidance which apparently caused a bit of a ruckus.

I think (but not 100% certain) that the security guidance is now OK on this point for ESXi 7.0u3, for versions older than that, I wouldn't trust the guidance, and would resort to using scripting over SSH as JDMils_Interact did.