diff --git a/.build/cspell-words.txt b/.build/cspell-words.txt index b9db29bb36..bbbacd3166 100644 --- a/.build/cspell-words.txt +++ b/.build/cspell-words.txt @@ -61,6 +61,7 @@ hsts httperr ietf imap +inetsrv Infoworker Inproc INSUFF @@ -90,6 +91,7 @@ msdcs MSDTC MSERT msert +msipc msrc Multiplexor nato diff --git a/.build/cspell.json b/.build/cspell.json index fbd443d9c5..358e63c59e 100644 --- a/.build/cspell.json +++ b/.build/cspell.json @@ -15,6 +15,13 @@ ".vscode", "/cspell-words.txt" ], + "patterns": [ + { + "name": "return-newline-tab-tab", + "pattern": "\\`r\\`n\\`t\\`t" + } + ], + "ignoreRegExpList": ["return-newline-tab-tab"], "caseSensitive": true, "overrides": [ { diff --git a/Diagnostics/HealthChecker/Analyzer/Security/Invoke-AnalyzerSecurityCve-2023-21709.ps1 b/Diagnostics/HealthChecker/Analyzer/Security/Invoke-AnalyzerSecurityCve-2023-21709.ps1 new file mode 100644 index 0000000000..1f936ceff1 --- /dev/null +++ b/Diagnostics/HealthChecker/Analyzer/Security/Invoke-AnalyzerSecurityCve-2023-21709.ps1 @@ -0,0 +1,48 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +. $PSScriptRoot\..\Add-AnalyzedResultInformation.ps1 +function Invoke-AnalyzerSecurityCve-2023-21709 { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [ref]$AnalyzeResults, + + [Parameter(Mandatory = $true)] + [object]$SecurityObject, + + [Parameter(Mandatory = $true)] + [object]$DisplayGroupingKey + ) + + Write-Verbose "Calling: $($MyInvocation.MyCommand)" + + <# + Description: Check for CVE-2023-21709 vulnerability + Affected Exchange versions: 2016, 2019 + Fix: Remove TokenCacheModule from IIS + Workaround: N/A + #> + + if ($SecurityObject.IsEdgeServer -eq $false) { + Write-Verbose "Testing CVE: CVE-2023-21709" + + if ($SecurityObject.ExchangeInformation.IISSettings.IISModulesInformation.ModuleList.Name -contains "TokenCacheModule") { + Write-Verbose "TokenCacheModule detected - system is vulnerable to CVE-2023-21709 vulnerability" + $params = @{ + AnalyzedInformation = $AnalyzeResults + DisplayGroupingKey = $DisplayGroupingKey + Name = "Security Vulnerability" + Details = ("{0}`r`n`t`tSee: https://portal.msrc.microsoft.com/security-guidance/advisory/{0} for more information." -f "CVE-2023-21709") + DisplayWriteType = "Red" + DisplayTestingValue = "CVE-2023-21709" + AddHtmlDetailRow = $false + } + Add-AnalyzedResultInformation @params + } else { + Write-Verbose "TokenCacheModule wasn't detected and as a result, system is not vulnerable to CVE-2023-21709 vulnerability" + } + } else { + Write-Verbose "Edge Server Role is not affected by this vulnerability as it has no IIS installed" + } +} diff --git a/Diagnostics/HealthChecker/Analyzer/Security/Invoke-AnalyzerSecurityCveCheck.ps1 b/Diagnostics/HealthChecker/Analyzer/Security/Invoke-AnalyzerSecurityCveCheck.ps1 index 5d5ab10d1f..6955c120cf 100644 --- a/Diagnostics/HealthChecker/Analyzer/Security/Invoke-AnalyzerSecurityCveCheck.ps1 +++ b/Diagnostics/HealthChecker/Analyzer/Security/Invoke-AnalyzerSecurityCveCheck.ps1 @@ -6,6 +6,7 @@ . $PSScriptRoot\Invoke-AnalyzerSecurityCve-2021-1730.ps1 . $PSScriptRoot\Invoke-AnalyzerSecurityCve-2021-34470.ps1 . $PSScriptRoot\Invoke-AnalyzerSecurityCve-2022-21978.ps1 +. $PSScriptRoot\Invoke-AnalyzerSecurityCve-2023-21709.ps1 . $PSScriptRoot\Invoke-AnalyzerSecurityCve-MarchSuSpecial.ps1 . $PSScriptRoot\Invoke-AnalyzerSecurityExtendedProtectionConfigState.ps1 . $PSScriptRoot\Invoke-AnalyzerSecurityIISModules.ps1 @@ -127,6 +128,7 @@ function Invoke-AnalyzerSecurityCveCheck { "Feb23SU" = (@(NewCveEntry @("CVE-2023-21529", "CVE-2023-21706", "CVE-2023-21707") $ex131619) + (NewCveEntry "CVE-2023-21710" @($ex2016, $ex2019))) "Mar23SU" = (@(NewCveEntry ("CVE-2023-21707") $ex131619)) "Jun23SU" = (NewCveEntry @("CVE-2023-28310", "CVE-2023-32031") @($ex2016, $ex2019)) + "Aug23SU" = (NewCveEntry @("CVE-2023-38181", "CVE-2023-38182", "CVE-2023-38185", "CVE-2023-35368", "CVE-2023-35388") @($ex2016, $ex2019)) } # Need to organize the list so oldest CVEs come out first. @@ -200,6 +202,7 @@ function Invoke-AnalyzerSecurityCveCheck { Invoke-AnalyzerSecurityCve-2021-1730 -AnalyzeResults $AnalyzeResults -SecurityObject $securityObject -DisplayGroupingKey $DisplayGroupingKey Invoke-AnalyzerSecurityCve-2021-34470 -AnalyzeResults $AnalyzeResults -SecurityObject $securityObject -DisplayGroupingKey $DisplayGroupingKey Invoke-AnalyzerSecurityCve-2022-21978 -AnalyzeResults $AnalyzeResults -SecurityObject $securityObject -DisplayGroupingKey $DisplayGroupingKey + Invoke-AnalyzerSecurityCve-2023-21709 -AnalyzeResults $AnalyzeResults -SecurityObject $securityObject -DisplayGroupingKey $DisplayGroupingKey Invoke-AnalyzerSecurityCve-MarchSuSpecial -AnalyzeResults $AnalyzeResults -SecurityObject $securityObject -DisplayGroupingKey $DisplayGroupingKey # Make sure that these stay as the last one to keep the output more readable Invoke-AnalyzerSecurityExtendedProtectionConfigState -AnalyzeResults $AnalyzeResults -SecurityObject $securityObject -DisplayGroupingKey $DisplayGroupingKey diff --git a/Diagnostics/HealthChecker/Analyzer/Security/Invoke-AnalyzerSecuritySettings.ps1 b/Diagnostics/HealthChecker/Analyzer/Security/Invoke-AnalyzerSecuritySettings.ps1 index f7e0cca7ea..91a0d196bb 100644 --- a/Diagnostics/HealthChecker/Analyzer/Security/Invoke-AnalyzerSecuritySettings.ps1 +++ b/Diagnostics/HealthChecker/Analyzer/Security/Invoke-AnalyzerSecuritySettings.ps1 @@ -23,6 +23,7 @@ function Invoke-AnalyzerSecuritySettings { Write-Verbose "Calling: $($MyInvocation.MyCommand)" $osInformation = $HealthServerObject.OSInformation + $aes256CbcInformation = $HealthServerObject.ExchangeInformation.AES256CBCInformation $keySecuritySettings = (Get-DisplayResultsGroupingKey -Name "Security Settings" -DisplayOrder $Order) $baseParams = @{ AnalyzedInformation = $AnalyzeResults @@ -331,6 +332,30 @@ function Invoke-AnalyzerSecuritySettings { } Add-AnalyzedResultInformation @params + # AES256-CBC encryption support check + $params = $baseParams + @{ + Name = "AES256-CBC Protected Content Support" + Details = $true + DisplayWriteType = "Green" + } + + if (($aes256CbcInformation.AES256CBCSupportedBuild) -and + ($aes256CbcInformation.ValidAESConfiguration -eq $false)) { + $params.Details = ("True" + + "`r`n`t`tThis build supports AES256-CBC protected content, but the configuration is not complete. Exchange Server is not able to decrypt" + + "`r`n`t`tprotected messages which could impact eDiscovery and Journaling tasks. If you use Rights Management Service (RMS) on-premises," + + "`r`n`t`tplease follow the instructions as outlined in the documentation: https://aka.ms/ExchangeCBCKB") + $params.DisplayWriteType = "Yellow" + } elseif ($aes256CbcInformation.AES256CBCSupportedBuild -eq $false) { + $params.Details = ("False" + + "`r`n`t`tThis could lead to scenarios where Exchange Server is no longer able to decrypt protected messages," + + "`r`n`t`tfor example, when sending rights management protected messages using AES256-CBC encryption algorithm," + + "`r`n`t`tor when performing eDiscovery and Journaling tasks." + + "`r`n`t`tMore Information: https://aka.ms/Purview/CBCDetails") + $params.DisplayWriteType = "Red" + } + Add-AnalyzedResultInformation @params + $additionalDisplayValue = [string]::Empty $smb1Settings = $osInformation.Smb1ServerSettings diff --git a/Diagnostics/HealthChecker/DataCollection/ExchangeInformation/Get-ExchangeAES256CBCDetails.ps1 b/Diagnostics/HealthChecker/DataCollection/ExchangeInformation/Get-ExchangeAES256CBCDetails.ps1 new file mode 100644 index 0000000000..6c134bd685 --- /dev/null +++ b/Diagnostics/HealthChecker/DataCollection/ExchangeInformation/Get-ExchangeAES256CBCDetails.ps1 @@ -0,0 +1,84 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +. $PSScriptRoot\..\..\Helpers\CompareExchangeBuildLevel.ps1 +. $PSScriptRoot\..\..\..\..\Shared\Invoke-ScriptBlockHandler.ps1 +. $PSScriptRoot\..\..\..\..\Shared\ErrorMonitorFunctions.ps1 +function Get-ExchangeAES256CBCDetails { + param( + [Parameter(Mandatory = $false)] + [String]$Server = $env:COMPUTERNAME, + + [Parameter(Mandatory = $true)] + [System.Object]$VersionInformation + ) + + <# + AES256-CBC encryption support check + https://techcommunity.microsoft.com/t5/security-compliance-and-identity/encryption-algorithm-changes-in-microsoft-purview-information/ba-p/3831909 + #> + + begin { + Write-Verbose "Calling: $($MyInvocation.MyCommand)" + $aes256CBCSupported = $false + $msipcRegistryAclAsExpected = $false + $regPathToCheck = "HKLM:\SOFTWARE\Microsoft\MSIPC\Server" + # Translates to: "NetworkService", "FullControl", "ContainerInherit, ObjectInherit", "None", "Allow" + # See: https://learn.microsoft.com/dotnet/api/system.security.accesscontrol.registryaccessrule.-ctor?view=net-7.0#system-security-accesscontrol-registryaccessrule-ctor(system-security-principal-identityreference-system-security-accesscontrol-registryrights-system-security-accesscontrol-inheritanceflags-system-security-accesscontrol-propagationflags-system-security-accesscontrol-accesscontroltype) + $networkServiceAcl = New-Object System.Security.AccessControl.RegistryAccessRule( + [System.Security.Principal.SecurityIdentifier]::new("S-1-5-20"), 983103, 3, 0, 0 + ) + } process { + # First, check if the build running on the server supports AES256-CBC + if (Test-ExchangeBuildGreaterOrEqualThanSecurityPatch -CurrentExchangeBuild $VersionInformation -SU "Aug23SU") { + + Write-Verbose "AES256-CBC encryption for information protection is supported by this Exchange Server build" + $aes256CBCSupported = $true + + # As build supports AES256-CBC, next step is to check that the registry key exists and permissions are set as expected + $params = @{ + ComputerName = $Server + ScriptBlock = { Test-Path -Path $regPathToCheck } + CatchActionFunction = ${Function:Invoke-CatchActions} + } + + if ((Invoke-ScriptBlockHandler @params) -eq $false) { + Write-Verbose "Unable to query Acl of registry key $regPathToCheck assuming that the key doesn't exist" + } else { + $params.ScriptBlock = { Get-Acl -Path $regPathToCheck } + $acl = Invoke-ScriptBlockHandler @params + + # ToDo: As we have multiple places in HC where we query acls, we should consider creating a function + # that can be used to do the acl call, similar to what we do in Get-ExchangeRegistryValues.ps1. + Write-Verbose "Registry key exists and Acl was successfully queried - validating Acl now" + try { + $aclMatch = $acl.Access.Where({ + ($_.RegistryRights -eq $networkServiceAcl.RegistryRights) -and + ($_.AccessControlType -eq $networkServiceAcl.AccessControlType) -and + ($_.IdentityReference.Translate([System.Security.Principal.SecurityIdentifier]) -eq $networkServiceAcl.IdentityReference) -and + ($_.InheritanceFlags -eq $networkServiceAcl.InheritanceFlags) -and + ($_.PropagationFlags -eq $networkServiceAcl.PropagationFlags) + }) + + if (@($aclMatch).Count -ge 1) { + Write-Verbose "Acl for NetworkService is as expected" + $msipcRegistryAclAsExpected = $true + } else { + Write-Verbose "Acl for NetworkService was not found or is not as expected" + } + } catch { + Write-Verbose "Unable to verify Acl on registry key $regPathToCheck" + Invoke-CatchActions + } + } + } else { + Write-Verbose "AES256-CBC encryption for information protection is not supported by this Exchange Server build" + } + } end { + return [PSCustomObject]@{ + AES256CBCSupportedBuild = $aes256CBCSupported + RegistryKeyConfiguredAsExpected = $msipcRegistryAclAsExpected + ValidAESConfiguration = (($aes256CBCSupported) -and ($msipcRegistryAclAsExpected)) + } + } +} diff --git a/Diagnostics/HealthChecker/DataCollection/ExchangeInformation/Get-ExchangeInformation.ps1 b/Diagnostics/HealthChecker/DataCollection/ExchangeInformation/Get-ExchangeInformation.ps1 index bb54a7ad8b..2341114bf3 100644 --- a/Diagnostics/HealthChecker/DataCollection/ExchangeInformation/Get-ExchangeInformation.ps1 +++ b/Diagnostics/HealthChecker/DataCollection/ExchangeInformation/Get-ExchangeInformation.ps1 @@ -7,6 +7,7 @@ . $PSScriptRoot\..\..\..\..\Shared\Get-ExchangeSettingOverride.ps1 . $PSScriptRoot\IISInformation\Get-ExchangeAppPoolsInformation.ps1 . $PSScriptRoot\IISInformation\Get-ExchangeServerIISSettings.ps1 +. $PSScriptRoot\Get-ExchangeAES256CBCDetails.ps1 . $PSScriptRoot\Get-ExchangeApplicationConfigurationFileValidation.ps1 . $PSScriptRoot\Get-ExchangeConnectors.ps1 . $PSScriptRoot\Get-ExchangeDependentServices.ps1 @@ -147,6 +148,13 @@ function Get-ExchangeInformation { } } $eemsEndpointResults = Invoke-ScriptBlockHandler @eemsEndpointParams + + Write-Verbose "Checking AES256-CBC information protection readiness and configuration" + $aes256CbcParams = @{ + Server = $Server + VersionInformation = $versionInformation + } + $aes256CbcDetails = Get-ExchangeAES256CBCDetails @aes256CbcParams } end { Write-Verbose "Exiting: Get-ExchangeInformation" @@ -169,6 +177,7 @@ function Get-ExchangeInformation { IISSettings = $iisSettings SettingOverrides = $settingOverrides FIPFSUpdateIssue = $FIPFSUpdateIssue + AES256CBCInformation = $aes256CbcDetails } } } diff --git a/Diagnostics/HealthChecker/Tests/HealthChecker.E15.Main.Tests.ps1 b/Diagnostics/HealthChecker/Tests/HealthChecker.E15.Main.Tests.ps1 index 5637ae0bc3..5dbeaa1a57 100644 --- a/Diagnostics/HealthChecker/Tests/HealthChecker.E15.Main.Tests.ps1 +++ b/Diagnostics/HealthChecker/Tests/HealthChecker.E15.Main.Tests.ps1 @@ -127,7 +127,7 @@ Describe "Testing Health Checker by Mock Data Imports - Exchange 2013" { TestObjectMatch "SMB1 Installed" "True" -WriteType "Red" TestObjectMatch "SMB1 Blocked" "False" -WriteType "Red" - $Script:ActiveGrouping.Count | Should -Be 82 + $Script:ActiveGrouping.Count | Should -Be 83 } It "Display Results - Security Vulnerability" { @@ -135,7 +135,7 @@ Describe "Testing Health Checker by Mock Data Imports - Exchange 2013" { $cveTests = $Script:ActiveGrouping.TestingValue | Where-Object { ($_.GetType().Name -eq "String") -and ($_.StartsWith("CVE")) } $cveTests.Contains("CVE-2020-1147") | Should -Be $true - $cveTests.Count | Should -Be 53 + $cveTests.Count | Should -Be 54 } } } diff --git a/Diagnostics/HealthChecker/Tests/HealthChecker.E16.Main.Tests.ps1 b/Diagnostics/HealthChecker/Tests/HealthChecker.E16.Main.Tests.ps1 index 24d85ee544..635736a9ac 100644 --- a/Diagnostics/HealthChecker/Tests/HealthChecker.E16.Main.Tests.ps1 +++ b/Diagnostics/HealthChecker/Tests/HealthChecker.E16.Main.Tests.ps1 @@ -130,7 +130,7 @@ Describe "Testing Health Checker by Mock Data Imports - Exchange 2016" { TestObjectMatch "Pattern service" "Unreachable`r`n`t`tMore information: https://aka.ms/HelpConnectivityEEMS" -WriteType "Yellow" TestObjectMatch "Telemetry enabled" "False" - $Script:ActiveGrouping.Count | Should -Be 95 + $Script:ActiveGrouping.Count | Should -Be 96 } It "Display Results - Security Vulnerability" { @@ -138,11 +138,11 @@ Describe "Testing Health Checker by Mock Data Imports - Exchange 2016" { $cveTests = GetObject "Security Vulnerability" $cveTests.Contains("CVE-2020-1147") | Should -Be $true - $cveTests.Count | Should -Be 33 + $cveTests.Count | Should -Be 39 $downloadDomains = GetObject "CVE-2021-1730" $downloadDomains.DownloadDomainsEnabled | Should -Be "false" - $Script:ActiveGrouping.Count | Should -Be 40 + $Script:ActiveGrouping.Count | Should -Be 46 } } diff --git a/Diagnostics/HealthChecker/Tests/HealthChecker.E19.Main.Tests.ps1 b/Diagnostics/HealthChecker/Tests/HealthChecker.E19.Main.Tests.ps1 index f2fef4fc31..7153f31080 100644 --- a/Diagnostics/HealthChecker/Tests/HealthChecker.E19.Main.Tests.ps1 +++ b/Diagnostics/HealthChecker/Tests/HealthChecker.E19.Main.Tests.ps1 @@ -135,7 +135,7 @@ Describe "Testing Health Checker by Mock Data Imports" { TestObjectMatch "Strict Mode disabled" "False" -WriteType "Green" TestObjectMatch "BaseTypeCheckForDeserialization disabled" "False" -WriteType "Green" - $Script:ActiveGrouping.Count | Should -Be 78 + $Script:ActiveGrouping.Count | Should -Be 79 } It "Display Results - Security Vulnerability" { @@ -143,7 +143,7 @@ Describe "Testing Health Checker by Mock Data Imports" { $cveTests = GetObject "Security Vulnerability" $cveTests.Contains("CVE-2020-1147") | Should -Be $true - $cveTests.Count | Should -Be 33 + $cveTests.Count | Should -Be 39 $downloadDomains = GetObject "CVE-2021-1730" $downloadDomains.DownloadDomainsEnabled | Should -Be "False" TestObjectMatch "Extended Protection Vulnerable" "True" -WriteType "Red" diff --git a/Security/src/CVE-2023-21709/CVE-2023-21709.ps1 b/Security/src/CVE-2023-21709/CVE-2023-21709.ps1 new file mode 100644 index 0000000000..2a6f4ac850 --- /dev/null +++ b/Security/src/CVE-2023-21709/CVE-2023-21709.ps1 @@ -0,0 +1,134 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +<# +.SYNOPSIS + This script removes the TokenCacheModule from IIS to protect Exchange Server against CVE-2023-21709. +.DESCRIPTION + The script removes the TokenCacheModule from IIS to protect Exchange Server against CVE-2023-21709. + It comes with a parameter that allows you to explicitly specify a subset of Exchange servers on which the TokenCacheModule + should be removed or restored (ExchangeServerNames). + It's also possible to exclude a subset of Exchange servers from the operation performed by the script (SkipExchangeServerNames). +.PARAMETER ExchangeServerNames + Use this parameter to explicitly specify the Exchange servers on which the TokenCacheModule should be removed or restored. +.PARAMETER SkipExchangeServerNames + Use this parameter to explicitly exclude Exchange servers from removing or restoring the TokenCacheModule. +.PARAMETER Rollback + Use this parameter rollback the CVE-2023-21709 solution and add the TokenCacheModule back to IIS. +.EXAMPLE + PS C:\> .\CVE-2023-21709.ps1 + It will remove the TokenCacheModule from all of the Exchange servers in the organization. +.EXAMPLE + PS C:\> .\CVE-2023-21709.ps1 -ExchangeServerNames + It will remove the TokenCacheModule from all of the Exchange servers provided via -ExchangeServerNames parameter. +.EXAMPLE + PS C:\> .\CVE-2023-21709.ps1 -SkipExchangeServerNames + It will remove the TokenCacheModule from all of the Exchange servers in the organization except the Exchange servers provided via -SkipExchangeServerNames parameter. +.EXAMPLE + PS C:\> .\CVE-2023-21709.ps1 -Rollback + It will restore the TokenCacheModule on all Exchange servers within the organization. +#> +[CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')] + +param( + [string[]]$ExchangeServerNames = $null, + + [string[]]$SkipExchangeServerNames = $null, + + [switch]$Rollback +) + +begin { + $BuildVersion = "" + + . $PSScriptRoot\WriteFunctions.ps1 + . $PSScriptRoot\ConfigurationAction\Invoke-ConfigureMitigation.ps1 + . $PSScriptRoot\ConfigurationAction\Invoke-Rollback.ps1 + . $PSScriptRoot\..\..\..\Shared\OutputOverrides\Write-Host.ps1 + . $PSScriptRoot\..\..\..\Shared\OutputOverrides\Write-Progress.ps1 + . $PSScriptRoot\..\..\..\Shared\OutputOverrides\Write-Verbose.ps1 + . $PSScriptRoot\..\..\..\Shared\OutputOverrides\Write-Warning.ps1 + . $PSScriptRoot\..\..\..\Shared\ScriptUpdateFunctions\Test-ScriptVersion.ps1 + . $PSScriptRoot\..\..\..\Shared\Confirm-Administrator.ps1 + . $PSScriptRoot\..\..\..\Shared\Confirm-ExchangeShell.ps1 + . $PSScriptRoot\..\..\..\Shared\LoggerFunctions.ps1 + . $PSScriptRoot\..\..\..\Shared\Out-Columns.ps1 + . $PSScriptRoot\..\..\..\Shared\Show-Disclaimer.ps1 + + $Script:Logger = Get-NewLoggerInstance -LogName "CVE-2023-21709-$((Get-Date).ToString("yyyyMMddhhmmss"))-Debug" ` + -AppendDateTimeToFileName $false ` + -ErrorAction SilentlyContinue + + SetWriteHostAction ${Function:Write-HostLog} + SetWriteVerboseAction ${Function:Write-VerboseLog} + SetWriteWarningAction ${Function:Write-HostLog} + SetWriteProgressAction ${Function:Write-HostLog} +} end { + if (-not (Confirm-Administrator)) { + Write-Warning "The script needs to be executed in elevated mode. Start the shell as an Administrator." + exit + } + + Write-Host ("CVE-2023-21709 script version $($BuildVersion)") -ForegroundColor Green + $versionsUrl = "https://aka.ms/CVE-2023-21709-VersionsUrl" + if (Test-ScriptVersion -AutoUpdate -VersionsUrl $versionsUrl -Confirm:$false) { + Write-Host ("Script was updated. Please re-run the command") -ForegroundColor Yellow + return + } + + $exchangeShell = Confirm-ExchangeShell + if (-not($exchangeShell.ShellLoaded)) { + Write-Warning "Failed to load the Exchange Management Shell. Start the script using the Exchange Management Shell." + exit + } elseif (-not($exchangeShell.EMS)) { + Write-Warning "This script requires to be run inside of Exchange Management Shell. Please run on an Exchange Management Server or an Exchange Server with Exchange Management Shell." + exit + } + + try { + if (-not $Rollback) { + $params = @{ + Message = "Display Warning about removing TokenCacheModule" + Target = "TokenCacheModule is recommended to be removed from IIS for security reasons. " + + "Removal of this module might have performance impact on Outlook For Web and Active-Sync" + + "`r`nYou can find more information on: https://portal.msrc.microsoft.com/security-guidance/advisory/CVE-2023-21709. Do you want to proceed?" + Operation = "Removing TokenCacheModule from IIS" + } + + Show-Disclaimer @params + } + + Write-Verbose ("Running Get-ExchangeServer to get list of all exchange servers") + Set-ADServerSettings -ViewEntireForest $true + $ExchangeServers = Get-ExchangeServer | Where-Object { $_.AdminDisplayVersion -like "Version 15*" -and $_.ServerRole -ne "Edge" } + + if ($null -ne $ExchangeServerNames -and $ExchangeServerNames.Count -gt 0) { + Write-Verbose "Running only on servers: $([string]::Join(", " ,$ExchangeServerNames))" + $ExchangeServers = $ExchangeServers | Where-Object { ($_.Name -in $ExchangeServerNames) -or ($_.FQDN -in $ExchangeServerNames) } + } + + if ($null -ne $SkipExchangeServerNames -and $SkipExchangeServerNames.Count -gt 0) { + Write-Verbose "Skipping servers: $([string]::Join(", ", $SkipExchangeServerNames))" + + # Remove all the servers present in the SkipExchangeServerNames list + $ExchangeServers = $ExchangeServers | Where-Object { ($_.Name -notin $SkipExchangeServerNames) -and ($_.FQDN -notin $SkipExchangeServerNames) } + } + + if ($null -eq $ExchangeServers) { + Write-Host "No exchange servers to process. Please specify server filters correctly" + exit + } + + if ($Rollback) { + Invoke-Rollback -ExchangeServers $ExchangeServers + return + } + + Invoke-ConfigureMitigation -ExchangeServers $ExchangeServers + return + } finally { + Write-Host "" + Write-Host "Script Completed successfully!" + Write-Host "Do you have feedback regarding the script? Please email ExToolsFeedback@microsoft.com." + } +} diff --git a/Security/src/CVE-2023-21709/ConfigurationAction/Invoke-ConfigureMitigation.ps1 b/Security/src/CVE-2023-21709/ConfigurationAction/Invoke-ConfigureMitigation.ps1 new file mode 100644 index 0000000000..b2d349b7d8 --- /dev/null +++ b/Security/src/CVE-2023-21709/ConfigurationAction/Invoke-ConfigureMitigation.ps1 @@ -0,0 +1,107 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +. $PSScriptRoot\..\..\..\..\Shared\Invoke-ScriptBlockHandler.ps1 +. $PSScriptRoot\..\..\..\..\Shared\Write-ErrorInformation.ps1 + +function Invoke-ConfigureMitigation { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [string[]]$ExchangeServers + ) + + begin { + $FailedServers = New-Object 'System.Collections.Generic.List[string]' + $NoImpactServers = New-Object 'System.Collections.Generic.List[string]' + + $progressParams = @{ + Activity = "Removing TokenCachingModule" + Status = [string]::Empty + PercentComplete = 0 + } + + Write-Verbose "Calling: $($MyInvocation.MyCommand)" + + $ConfigureMitigation = { + $results = @{ + IsMitigationRequired = $true + IsSuccessful = $false + ErrorContext = $null + } + + try { + if ($null -eq (Get-WebGlobalModule -Name "TokenCacheModule")) { + $results.IsMitigationRequired = $false + return $results + } + + Clear-WebConfiguration -Filter "/system.webServer/globalModules/add[@name='TokenCacheModule']" -PSPath "IIS:\" -ErrorAction Stop + if (-not $WhatIfPreference) { + if ($null -eq (Get-WebGlobalModule -Name "TokenCacheModule")) { + $results.IsSuccessful = $true + } + } + } catch { + $results.ErrorContext = $_ + } + + return $results + } + } process { + $counter = 0 + $totalCount = $ExchangeServers.Count + + if ($WhatIfPreference) { + Write-Host ("What if: Will perform the below actions on the following servers: {0}" -f [string]::Join(", ", $ExchangeServers)) + } + + foreach ($Server in $ExchangeServers) { + $baseStatus = "Processing: $Server -" + $progressParams.PercentComplete = ($counter / $totalCount * 100) + $progressParams.Status = "$baseStatus Applying mitigation" + Write-Progress @progressParams + + $counter ++; + + if (-not $WhatIfPreference -or ($Server).Split(".")[0] -eq $env:COMPUTERNAME) { + Write-Verbose ("Calling Invoke-ScriptBlockHandler on Server {0}" -f $Server) + $resultsInvoke = Invoke-ScriptBlockHandler -ComputerName $Server -ScriptBlock $ConfigureMitigation + } + + if (-not $WhatIfPreference) { + if ($null -eq $resultsInvoke) { + Write-Warning ("Server {0} is unavailable. Skipping it!" -f $Server) + $FailedServers += $Server + continue; + } + + if (-not $resultsInvoke.IsMitigationRequired) { + Write-Verbose ("Mitigation is not required on server {0}" -f $Server) + $NoImpactServers += $Server + continue + } + + if ($resultsInvoke.IsSuccessful) { + Write-Verbose ("Successfully applied mitigation on server {0}" -f $Server) + } else { + Write-Host ("Script failed to apply mitigation on server {0}" -f $Server) -ForegroundColor Red + $FailedServers += $Server + continue + } + } + } + } end { + Write-Progress @progressParams -Completed + + if (-not $WhatIfPreference) { + if ($FailedServers.Length -gt 0) { + Write-Host ("Unable to apply mitigation of following servers: {0}" -f [string]::Join(", ", $FailedServers)) -ForegroundColor Red + } + + if ($NoImpactServers.Length -gt 0) { + Write-Host ("No mitigation required for the following servers : {0}. These servers have already been mitigated." -f [string]::Join(", ", $NoImpactServers)) + } + } + } +} diff --git a/Security/src/CVE-2023-21709/ConfigurationAction/Invoke-Rollback.ps1 b/Security/src/CVE-2023-21709/ConfigurationAction/Invoke-Rollback.ps1 new file mode 100644 index 0000000000..9d1cadbaf8 --- /dev/null +++ b/Security/src/CVE-2023-21709/ConfigurationAction/Invoke-Rollback.ps1 @@ -0,0 +1,108 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +. $PSScriptRoot\..\..\..\..\Shared\Invoke-ScriptBlockHandler.ps1 +. $PSScriptRoot\..\..\..\..\Shared\Write-ErrorInformation.ps1 + +function Invoke-Rollback { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [string[]]$ExchangeServers + ) + + begin { + $FailedServers = New-Object 'System.Collections.Generic.List[string]' + $NoImpactServers = New-Object 'System.Collections.Generic.List[string]' + + $progressParams = @{ + Activity = "Adding TokenCachingModule" + Status = [string]::Empty + PercentComplete = 0 + } + + Write-Verbose "Calling: $($MyInvocation.MyCommand)" + + $ConfigureRollback = { + $results = @{ + IsRollbackRequired = $true + IsSuccessful = $false + ErrorContext = $null + } + + try { + if ($null -ne (Get-WebGlobalModule -Name "TokenCacheModule")) { + $results.IsRollbackRequired = $false + return $results + } + + New-WebGlobalModule -Name "TokenCacheModule" -Image "%windir%\System32\inetsrv\cachtokn.dll" -ErrorAction Stop + + if (-not $WhatIfPreference) { + if ($null -ne (Get-WebGlobalModule -Name "TokenCacheModule")) { + $results.IsSuccessful = $true + } + } + } catch { + $results.ErrorContext = $_ + } + + return $results + } + } process { + $counter = 0 + $totalCount = $ExchangeServers.Count + + if ($WhatIfPreference) { + Write-Host ("What if: Will perform the below actions on the following servers: {0}" -f [string]::Join(", ", $ExchangeServers)) + } + + foreach ($Server in $ExchangeServers) { + $baseStatus = "Processing: $Server -" + $progressParams.PercentComplete = ($counter / $totalCount * 100) + $progressParams.Status = "$baseStatus Rollback mitigation" + Write-Progress @progressParams + + $counter ++; + + if (-not $WhatIfPreference -or ($Server).Split(".")[0] -eq $env:COMPUTERNAME) { + Write-Verbose ("Calling Invoke-ScriptBlockHandler on Server {0}" -f $Server) + $resultsInvoke = Invoke-ScriptBlockHandler -ComputerName $Server -ScriptBlock $ConfigureRollback + } + + if (-not $WhatIfPreference) { + if ($null -eq $resultsInvoke) { + Write-Warning ("Server {0} is unavailable. Skipping it!" -f $Server) + $FailedServers += $Server + continue; + } + + if (-not $resultsInvoke.IsRollbackRequired) { + Write-Verbose ("Rollback is not required on server {0}" -f $Server) + $NoImpactServers += $Server + continue + } + + if ($resultsInvoke.IsSuccessful) { + Write-Verbose ("Successfully rollback mitigation on server {0}" -f $Server) + } else { + Write-Host ("Script failed to rollback mitigation on server {0}" -f $Server) -ForegroundColor Red + $FailedServers += $Server + continue + } + } + } + } end { + Write-Progress @progressParams -Completed + + if (-not $WhatIfPreference) { + if ($FailedServers.Length -gt 0) { + Write-Host ("Unable to rollback mitigation of following servers: {0}" -f [string]::Join(", ", $FailedServers)) -ForegroundColor Red + } + + if ($NoImpactServers.Length -gt 0) { + Write-Host ("No rollback required for the following servers : {0}" -f [string]::Join(", ", $NoImpactServers)) + } + } + } +} diff --git a/Security/src/CVE-2023-21709/WriteFunctions.ps1 b/Security/src/CVE-2023-21709/WriteFunctions.ps1 new file mode 100644 index 0000000000..f46cf6343b --- /dev/null +++ b/Security/src/CVE-2023-21709/WriteFunctions.ps1 @@ -0,0 +1,10 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +function Write-VerboseLog ($Message) { + $Script:Logger = $Script:Logger | Write-LoggerInstance $Message +} + +function Write-HostLog ($Message) { + $Script:Logger = $Script:Logger | Write-LoggerInstance $Message +} diff --git a/Shared/Get-ExchangeBuildVersionInformation.ps1 b/Shared/Get-ExchangeBuildVersionInformation.ps1 index ab03ecbd33..f56292d33f 100644 --- a/Shared/Get-ExchangeBuildVersionInformation.ps1 +++ b/Shared/Get-ExchangeBuildVersionInformation.ps1 @@ -127,14 +127,14 @@ function Get-ExchangeBuildVersionInformation { $cuReleaseDate = "05/03/2023" $supportedBuildNumber = $true } - (GetBuildVersion $ex19 "CU13" -SU "Jun23SU") { $latestSUBuild = $true } + (GetBuildVersion $ex19 "CU13" -SU "Aug23SU") { $latestSUBuild = $true } { $_ -lt (GetBuildVersion $ex19 "CU13") } { $cuLevel = "CU12" $cuReleaseDate = "04/20/2022" $supportedBuildNumber = $true $orgValue = 16760 } - (GetBuildVersion $ex19 "CU12" -SU "Jun23SU") { $latestSUBuild = $true } + (GetBuildVersion $ex19 "CU12" -SU "Aug23SU") { $latestSUBuild = $true } { $_ -lt (GetBuildVersion $ex19 "CU12") } { $cuLevel = "CU11" $cuReleaseDate = "09/28/2021" @@ -221,7 +221,7 @@ function Get-ExchangeBuildVersionInformation { $cuReleaseDate = "04/20/2022" $supportedBuildNumber = $true } - (GetBuildVersion $ex16 "CU23" -SU "Jun23SU") { $latestSUBuild = $true } + (GetBuildVersion $ex16 "CU23" -SU "Aug23SU") { $latestSUBuild = $true } { $_ -lt (GetBuildVersion $ex16 "CU23") } { $cuLevel = "CU22" $cuReleaseDate = "09/28/2021" @@ -701,6 +701,7 @@ function GetExchangeBuildDictionary { "Feb23SU" = "15.1.2507.21" "Mar23SU" = "15.1.2507.23" "Jun23SU" = "15.1.2507.27" + "Aug23SU" = "15.1.2507.31" }) } "Exchange2019" = @{ @@ -787,9 +788,11 @@ function GetExchangeBuildDictionary { "Feb23SU" = "15.2.1118.25" "Mar23SU" = "15.2.1118.26" "Jun23SU" = "15.2.1118.30" + "Aug23SU" = "15.2.1118.36" }) "CU13" = (NewCUAndSUObject "15.2.1258.12" @{ "Jun23SU" = "15.2.1258.16" + "Aug23SU" = "15.2.1258.23" }) } } diff --git a/docs/Security/CVE-2023-21709.md b/docs/Security/CVE-2023-21709.md new file mode 100644 index 0000000000..51dd8be640 --- /dev/null +++ b/docs/Security/CVE-2023-21709.md @@ -0,0 +1,46 @@ +# CVE-2023-21709 + +Download the latest release: [CVE-2023-21709.ps1](https://github.com/microsoft/CSS-Exchange/releases/latest/download/CVE-2023-21709.ps1) + +The `CVE-2023-21709.ps1` script can be used to address the Exchange Server vulnerability `CVE-2023-21709` by removing the `TokenCacheModule` from IIS. It can also be used to restore a previously removed `TokenCacheModule`. +The script allows you to explicitly specify a subset of Exchange servers on which the `TokenCacheModule` should be removed or restored. It's also possible to exclude a subset of Exchange servers from the operation performed by the script. + +## Requirements + +This script **must** be run as Administrator in `Exchange Management Shell (EMS)`. The user must be a member of the `Organization Management` role group. + +## How To Run + +### Examples: + +This syntax removes the `TokenCacheModule` from all Exchange servers within the organization. + +```powershell +.\CVE-2023-21709.ps1 +``` + +This syntax removes the `TokenCacheModule` from `ExchangeSrv01` and `ExchangeSrv02`. + +```powershell +.\CVE-2023-21709.ps1 -ExchangeServerNames ExchangeSrv01, ExchangeSrv02 +``` + +This syntax removes the `TokenCacheModule` from all Exchange servers within the organization except `ExchangeSrv02`. + +```powershell +.\CVE-2023-21709.ps1 -SkipExchangeServerNames ExchangeSrv02 +``` + +This syntax restores the `TokenCacheModule` on all Exchange servers within the organization. + +```powershell +.\CVE-2023-21709.ps1 -Rollback +``` + +## Parameters + +Parameter | Description +----------|------------ +ExchangeServerNames | A list of Exchange servers that you want to run the script against. This can be used for applying or rollback the `CVE-2023-21709` configuration change. +SkipExchangeServerNames | A list of Exchange servers that you don't want to execute the `TokenCacheModule` configuration action. +Rollback | Switch parameter to rollback the `CVE-2023-21709` configuration change and add the `TokenCacheModule` back to IIS. diff --git a/mkdocs.yml b/mkdocs.yml index 346192e939..7cfd2193ea 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -83,6 +83,7 @@ nav: - Search: - Troubleshoot-ModernSearch: Search/Troubleshoot-ModernSearch.md - Security: + - CVE-2023-21709: Security/CVE-2023-21709.md - CVE-2023-23397: - Security/CVE-2023-23397/index.md - FAQ: Security/CVE-2023-23397/FAQ.md