From 4d2d11002391a7e45a1dd163290d6e196d532fca Mon Sep 17 00:00:00 2001 From: David Paulson Date: Thu, 7 Mar 2024 12:09:06 -0600 Subject: [PATCH 01/34] March 2024 SU Release Build Numbers --- Shared/Get-ExchangeBuildVersionInformation.ps1 | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/Shared/Get-ExchangeBuildVersionInformation.ps1 b/Shared/Get-ExchangeBuildVersionInformation.ps1 index 9f14b8d71e..4a9fafb8da 100644 --- a/Shared/Get-ExchangeBuildVersionInformation.ps1 +++ b/Shared/Get-ExchangeBuildVersionInformation.ps1 @@ -126,22 +126,21 @@ function Get-ExchangeBuildVersionInformation { $cuLevel = "CU14" $cuReleaseDate = "02/13/2024" $supportedBuildNumber = $true - $latestSUBuild = $true } + (GetBuildVersion $ex19 "CU14" -SU "Mar24SU") { $latestSUBuild = $true } { $_ -lt (GetBuildVersion $ex19 "CU14") } { $cuLevel = "CU13" $cuReleaseDate = "05/03/2023" $supportedBuildNumber = $true $orgValue = 16761 } - (GetBuildVersion $ex19 "CU13" -SU "Nov23SU") { $latestSUBuild = $true } + (GetBuildVersion $ex19 "CU13" -SU "Mar24SU") { $latestSUBuild = $true } { $_ -lt (GetBuildVersion $ex19 "CU13") } { $cuLevel = "CU12" $cuReleaseDate = "04/20/2022" $supportedBuildNumber = $false $orgValue = 16760 } - (GetBuildVersion $ex19 "CU12" -SU "Nov23SU") { $latestSUBuild = $true } { $_ -lt (GetBuildVersion $ex19 "CU12") } { $cuLevel = "CU11" $cuReleaseDate = "09/28/2021" @@ -227,7 +226,7 @@ function Get-ExchangeBuildVersionInformation { $cuReleaseDate = "04/20/2022" $supportedBuildNumber = $true } - (GetBuildVersion $ex16 "CU23" -SU "Nov23SU") { $latestSUBuild = $true } + (GetBuildVersion $ex16 "CU23" -SU "Mar24SU") { $latestSUBuild = $true } { $_ -lt (GetBuildVersion $ex16 "CU23") } { $cuLevel = "CU22" $cuReleaseDate = "09/28/2021" @@ -711,6 +710,7 @@ function GetExchangeBuildDictionary { "Aug23SUv2" = "15.1.2507.32" "Oct23SU" = "15.1.2507.34" "Nov23SU" = "15.1.2507.35" + "Mar24SU" = "15.1.2507.37" }) } "Exchange2019" = @{ @@ -808,8 +808,11 @@ function GetExchangeBuildDictionary { "Aug23SUv2" = "15.2.1258.25" "Oct23SU" = "15.2.1258.27" "Nov23SU" = "15.2.1258.28" + "Mar24SU" = "15.2.1258.32" + }) + "CU14" = (NewCUAndSUObject "15.2.1544.4" @{ + "Mar24SU" = "15.2.1544.9" }) - "CU14" = (NewCUAndSUObject "15.2.1544.4") } } } From c5e376a1e28eed7805477057a8fe8dc8d6ecadea Mon Sep 17 00:00:00 2001 From: David Paulson Date: Thu, 7 Mar 2024 13:35:13 -0600 Subject: [PATCH 02/34] Add CVE-2024-26198 to HC --- .../Analyzer/Security/Invoke-AnalyzerSecurityCveCheck.ps1 | 1 + .../HealthChecker/Tests/HealthChecker.E16.Main.Tests.ps1 | 4 ++-- .../HealthChecker/Tests/HealthChecker.E19.Main.Tests.ps1 | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Diagnostics/HealthChecker/Analyzer/Security/Invoke-AnalyzerSecurityCveCheck.ps1 b/Diagnostics/HealthChecker/Analyzer/Security/Invoke-AnalyzerSecurityCveCheck.ps1 index 6ca87e5393..a44edb9d86 100644 --- a/Diagnostics/HealthChecker/Analyzer/Security/Invoke-AnalyzerSecurityCveCheck.ps1 +++ b/Diagnostics/HealthChecker/Analyzer/Security/Invoke-AnalyzerSecurityCveCheck.ps1 @@ -132,6 +132,7 @@ function Invoke-AnalyzerSecurityCveCheck { "Aug23SU" = (NewCveEntry @("CVE-2023-38181", "CVE-2023-38182", "CVE-2023-38185", "CVE-2023-35368", "CVE-2023-35388", "CVE-2023-36777", "CVE-2023-36757", "CVE-2023-36756", "CVE-2023-36745", "CVE-2023-36744") @($ex2016, $ex2019)) "Oct23SU" = (NewCveEntry @("CVE-2023-36778") @($ex2016, $ex2019)) "Nov23SU" = (NewCveEntry @("CVE-2023-36050", "CVE-2023-36039", "CVE-2023-36035", "CVE-2023-36439") @($ex2016, $ex2019)) + "Mar24SU" = (NewCveEntry @("CVE-2024-26198") @($ex2016, $ex2019)) } # Need to organize the list so oldest CVEs come out first. diff --git a/Diagnostics/HealthChecker/Tests/HealthChecker.E16.Main.Tests.ps1 b/Diagnostics/HealthChecker/Tests/HealthChecker.E16.Main.Tests.ps1 index b0ffcde76e..028901a482 100644 --- a/Diagnostics/HealthChecker/Tests/HealthChecker.E16.Main.Tests.ps1 +++ b/Diagnostics/HealthChecker/Tests/HealthChecker.E16.Main.Tests.ps1 @@ -141,11 +141,11 @@ Describe "Testing Health Checker by Mock Data Imports - Exchange 2016" { $cveTests = GetObject "Security Vulnerability" $cveTests.Contains("CVE-2020-1147") | Should -Be $true $cveTests.Contains("CVE-2023-36039") | Should -Be $true - $cveTests.Count | Should -Be 49 + $cveTests.Count | Should -Be 50 $downloadDomains = GetObject "CVE-2021-1730" $downloadDomains.DownloadDomainsEnabled | Should -Be "false" - $Script:ActiveGrouping.Count | Should -Be 56 + $Script:ActiveGrouping.Count | Should -Be 57 } } diff --git a/Diagnostics/HealthChecker/Tests/HealthChecker.E19.Main.Tests.ps1 b/Diagnostics/HealthChecker/Tests/HealthChecker.E19.Main.Tests.ps1 index b926686dcf..881d382042 100644 --- a/Diagnostics/HealthChecker/Tests/HealthChecker.E19.Main.Tests.ps1 +++ b/Diagnostics/HealthChecker/Tests/HealthChecker.E19.Main.Tests.ps1 @@ -150,7 +150,7 @@ Describe "Testing Health Checker by Mock Data Imports" { $cveTests.Contains("CVE-2020-1147") | Should -Be $true $cveTests.Contains("CVE-2023-36434") | Should -Be $true $cveTests.Contains("CVE-2023-36039") | Should -Be $true - $cveTests.Count | Should -Be 49 + $cveTests.Count | Should -Be 50 $downloadDomains = GetObject "CVE-2021-1730" $downloadDomains.DownloadDomainsEnabled | Should -Be "False" TestObjectMatch "Extended Protection Vulnerable" "True" -WriteType "Red" From ead75b93bf28c7cb47d0d1d03a37d4b1ad57076c Mon Sep 17 00:00:00 2001 From: David Paulson Date: Thu, 7 Mar 2024 14:53:15 -0600 Subject: [PATCH 03/34] Add ADV24199947 to HC --- .../Invoke-AnalyzerSecurityADV24199947.ps1 | 69 ++++++++++++++++++ .../Invoke-AnalyzerSecurityCveCheck.ps1 | 2 + .../Get-ExchangeInformation.ps1 | 19 ++++- .../Get-ExchangeRegistryValues.ps1 | 7 ++ .../E15/Exchange/Configuration.xml | Bin 0 -> 32974 bytes .../E16/Exchange/Configuration.xml | Bin 0 -> 33214 bytes .../E19/Exchange/Configuration.xml | Bin 0 -> 33288 bytes .../Tests/HealthChecker.E16.Main.Tests.ps1 | 5 +- .../Tests/HealthChecker.E19.Main.Tests.ps1 | 3 +- .../Tests/HealthChecker.MockedCalls.Tests.ps1 | 2 +- ...thCheckerTest.CommonMocks.NotPublished.ps1 | 1 + 11 files changed, 103 insertions(+), 5 deletions(-) create mode 100644 Diagnostics/HealthChecker/Analyzer/Security/Invoke-AnalyzerSecurityADV24199947.ps1 create mode 100644 Diagnostics/HealthChecker/Tests/DataCollection/E15/Exchange/Configuration.xml create mode 100644 Diagnostics/HealthChecker/Tests/DataCollection/E16/Exchange/Configuration.xml create mode 100644 Diagnostics/HealthChecker/Tests/DataCollection/E19/Exchange/Configuration.xml diff --git a/Diagnostics/HealthChecker/Analyzer/Security/Invoke-AnalyzerSecurityADV24199947.ps1 b/Diagnostics/HealthChecker/Analyzer/Security/Invoke-AnalyzerSecurityADV24199947.ps1 new file mode 100644 index 0000000000..7726713738 --- /dev/null +++ b/Diagnostics/HealthChecker/Analyzer/Security/Invoke-AnalyzerSecurityADV24199947.ps1 @@ -0,0 +1,69 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +. $PSScriptRoot\..\Add-AnalyzedResultInformation.ps1 +. $PSScriptRoot\..\..\Helpers\CompareExchangeBuildLevel.ps1 #This is moving in a different PR. + +<# +.DESCRIPTION + Check for ADV24199947 Outside In Module vulnerability + Must be on March 2024 SU and no overrides in place to be considered secure. + Overrides are found in the Configuration.xml file with appending flag of |NO + This only needs to occur on the Mailbox Servers Roles +#> +function Invoke-AnalyzerSecurityADV24199947 { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [ref]$AnalyzeResults, + + [Parameter(Mandatory = $true)] + [object]$SecurityObject, + + [Parameter(Mandatory = $true)] + [object]$DisplayGroupingKey + ) + process { + Write-Verbose "Calling: $($MyInvocation.MyCommand)" + + $params = @{ + AnalyzedInformation = $AnalyzeResults + DisplayGroupingKey = $DisplayGroupingKey + Name = "Security Vulnerability" + DisplayWriteType = "Red" + Details = "{0}" + DisplayTestingValue = "ADV24199947" + } + + if ($SecurityObject.IsEdgeServer) { + Write-Verbose "Skipping over test as this is an edge server." + return + } + + $isVulnerable = (-not (Test-ExchangeBuildGreaterOrEqualThanSecurityPatch -CurrentExchangeBuild $SecurityObject.BuildInformation -SUName "Mar24SU")) + + # if patch is installed, need to check for the override. + if ($isVulnerable -eq $false) { + Write-Verbose "Mar24SU is installed, checking to see if override is set" + # Key for the file content information + $key = [System.IO.Path]::Combine($SecurityObject.ExchangeInformation.RegistryValues.FipFsDatabasePath, "Configuration.xml") + $unknownError = [string]::IsNullOrEmpty($SecurityObject.ExchangeInformation.RegistryValues.FipFsDatabasePath) -or + ($null -eq $SecurityObject.ExchangeInformation.FileContentInformation[$key]) + + if ($unknownError) { + $params.Details += " Unable to determine if override is set due to no data to review." + $params.DisplayWriteType = "Yellow" + $isVulnerable = $true + } else { + $isVulnerable = $null -ne ($SecurityObject.ExchangeInformation.FileContentInformation[$key] | Select-String "\|NO") + } + } + + if ($isVulnerable) { + $params.Details = ("$($params.Details)`r`n`t`tSee: https://portal.msrc.microsoft.com/security-guidance/advisory/{0} for more information." -f "ADV24199947") + Add-AnalyzedResultInformation @params + } else { + Write-Verbose "Not vulnerable to ADV24199947" + } + } +} diff --git a/Diagnostics/HealthChecker/Analyzer/Security/Invoke-AnalyzerSecurityCveCheck.ps1 b/Diagnostics/HealthChecker/Analyzer/Security/Invoke-AnalyzerSecurityCveCheck.ps1 index a44edb9d86..10984b36bc 100644 --- a/Diagnostics/HealthChecker/Analyzer/Security/Invoke-AnalyzerSecurityCveCheck.ps1 +++ b/Diagnostics/HealthChecker/Analyzer/Security/Invoke-AnalyzerSecurityCveCheck.ps1 @@ -1,6 +1,7 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. +. $PSScriptRoot\Invoke-AnalyzerSecurityADV24199947.ps1 . $PSScriptRoot\Invoke-AnalyzerSecurityCve-2020-0796.ps1 . $PSScriptRoot\Invoke-AnalyzerSecurityCve-2020-1147.ps1 . $PSScriptRoot\Invoke-AnalyzerSecurityCve-2021-1730.ps1 @@ -209,6 +210,7 @@ function Invoke-AnalyzerSecurityCveCheck { Invoke-AnalyzerSecurityCve-2023-36434 -AnalyzeResults $AnalyzeResults -SecurityObject $securityObject -DisplayGroupingKey $DisplayGroupingKey Invoke-AnalyzerSecurityCveAddressedBySerializedDataSigning -AnalyzeResults $AnalyzeResults -SecurityObject $securityObject -DisplayGroupingKey $DisplayGroupingKey Invoke-AnalyzerSecurityCve-MarchSuSpecial -AnalyzeResults $AnalyzeResults -SecurityObject $securityObject -DisplayGroupingKey $DisplayGroupingKey + Invoke-AnalyzerSecurityADV24199947 -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/DataCollection/ExchangeInformation/Get-ExchangeInformation.ps1 b/Diagnostics/HealthChecker/DataCollection/ExchangeInformation/Get-ExchangeInformation.ps1 index 795fc6e928..8a3fd05915 100644 --- a/Diagnostics/HealthChecker/DataCollection/ExchangeInformation/Get-ExchangeInformation.ps1 +++ b/Diagnostics/HealthChecker/DataCollection/ExchangeInformation/Get-ExchangeInformation.ps1 @@ -117,7 +117,23 @@ function Get-ExchangeInformation { "$([System.IO.Path]::Combine($serverExchangeBinDirectory, "Search\Ceres\Runtime\1.0\noderunner.exe.config"))") } - $applicationConfigFileStatus = Get-FileContentInformation @configParams + if ($getExchangeServer.IsEdgeServer -eq $false -and + (-not ([string]::IsNullOrEmpty($registryValues.FipFsDatabasePath)))) { + $configParams.FileLocation += "$([System.IO.Path]::Combine($registryValues.FipFsDatabasePath, "Configuration.xml"))" + } + + $getFileContentInformation = Get-FileContentInformation @configParams + $applicationConfigFileStatus = @{} + $fileContentInformation = @{} + + foreach ($key in $getFileContentInformation.Keys) { + if ($key -like "*.exe.config") { + $applicationConfigFileStatus.Add($key, $getFileContentInformation[$key]) + } else { + $fileContentInformation.Add($key, $getFileContentInformation[$key]) + } + } + $serverMaintenance = Get-ExchangeServerMaintenanceState -Server $Server -ComponentsToSkip "ForwardSyncDaemon", "ProvisioningRps" $settingOverrides = Get-ExchangeSettingOverride -Server $Server -CatchActionFunction ${Function:Invoke-CatchActions} @@ -192,6 +208,7 @@ function Get-ExchangeInformation { SettingOverrides = $settingOverrides FIPFSUpdateIssue = $FIPFSUpdateIssue AES256CBCInformation = $aes256CbcDetails + FileContentInformation = $fileContentInformation } } } diff --git a/Diagnostics/HealthChecker/DataCollection/ExchangeInformation/Get-ExchangeRegistryValues.ps1 b/Diagnostics/HealthChecker/DataCollection/ExchangeInformation/Get-ExchangeRegistryValues.ps1 index a10e59c7f8..1caf2912d2 100644 --- a/Diagnostics/HealthChecker/DataCollection/ExchangeInformation/Get-ExchangeRegistryValues.ps1 +++ b/Diagnostics/HealthChecker/DataCollection/ExchangeInformation/Get-ExchangeRegistryValues.ps1 @@ -56,6 +56,12 @@ function Get-ExchangeRegistryValues { ValueType = "String" } + $fipFsDatabasePathParams = $baseParams + @{ + SubKey = "SOFTWARE\Microsoft\ExchangeServer\v15\FIP-FS" + GetValue = "DatabasePath" + ValueType = "String" + } + return [PSCustomObject]@{ DisableBaseTypeCheckForDeserialization = [int](Get-RemoteRegistryValue @baseTypeCheckForDeserializationParams) CtsProcessorAffinityPercentage = [int](Get-RemoteRegistryValue @ctsParams) @@ -65,5 +71,6 @@ function Get-ExchangeRegistryValues { SerializedDataSigning = [int](Get-RemoteRegistryValue @serializedDataSigningParams) MsiInstallPath = [string](Get-RemoteRegistryValue @installDirectoryParams) DisablePreservation = [string](Get-RemoteRegistryValue @disablePreservationParams) + FipFsDatabasePath = [string](Get-RemoteRegistryValue @fipFsDatabasePathParams) } } diff --git a/Diagnostics/HealthChecker/Tests/DataCollection/E15/Exchange/Configuration.xml b/Diagnostics/HealthChecker/Tests/DataCollection/E15/Exchange/Configuration.xml new file mode 100644 index 0000000000000000000000000000000000000000..11ce33ef2d2bd630a3730f625afad2d73901e553 GIT binary patch literal 32974 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 literal 0 HcmV?d00001 diff --git a/Diagnostics/HealthChecker/Tests/DataCollection/E19/Exchange/Configuration.xml b/Diagnostics/HealthChecker/Tests/DataCollection/E19/Exchange/Configuration.xml new file mode 100644 index 0000000000000000000000000000000000000000..f52aa9c191264cc484135d6e0ae5dfa48af6aa99 GIT binary patch literal 33288 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 literal 0 HcmV?d00001 diff --git a/Diagnostics/HealthChecker/Tests/HealthChecker.E16.Main.Tests.ps1 b/Diagnostics/HealthChecker/Tests/HealthChecker.E16.Main.Tests.ps1 index 028901a482..96e3367dd9 100644 --- a/Diagnostics/HealthChecker/Tests/HealthChecker.E16.Main.Tests.ps1 +++ b/Diagnostics/HealthChecker/Tests/HealthChecker.E16.Main.Tests.ps1 @@ -141,11 +141,12 @@ Describe "Testing Health Checker by Mock Data Imports - Exchange 2016" { $cveTests = GetObject "Security Vulnerability" $cveTests.Contains("CVE-2020-1147") | Should -Be $true $cveTests.Contains("CVE-2023-36039") | Should -Be $true - $cveTests.Count | Should -Be 50 + $cveTests.Contains("ADV24199947") | Should -Be $true + $cveTests.Count | Should -Be 51 $downloadDomains = GetObject "CVE-2021-1730" $downloadDomains.DownloadDomainsEnabled | Should -Be "false" - $Script:ActiveGrouping.Count | Should -Be 57 + $Script:ActiveGrouping.Count | Should -Be 58 } } diff --git a/Diagnostics/HealthChecker/Tests/HealthChecker.E19.Main.Tests.ps1 b/Diagnostics/HealthChecker/Tests/HealthChecker.E19.Main.Tests.ps1 index 881d382042..7efb4efbd1 100644 --- a/Diagnostics/HealthChecker/Tests/HealthChecker.E19.Main.Tests.ps1 +++ b/Diagnostics/HealthChecker/Tests/HealthChecker.E19.Main.Tests.ps1 @@ -150,7 +150,8 @@ Describe "Testing Health Checker by Mock Data Imports" { $cveTests.Contains("CVE-2020-1147") | Should -Be $true $cveTests.Contains("CVE-2023-36434") | Should -Be $true $cveTests.Contains("CVE-2023-36039") | Should -Be $true - $cveTests.Count | Should -Be 50 + $cveTests.Contains("ADV24199947") | Should -Be $true + $cveTests.Count | Should -Be 51 $downloadDomains = GetObject "CVE-2021-1730" $downloadDomains.DownloadDomainsEnabled | Should -Be "False" TestObjectMatch "Extended Protection Vulnerable" "True" -WriteType "Red" diff --git a/Diagnostics/HealthChecker/Tests/HealthChecker.MockedCalls.Tests.ps1 b/Diagnostics/HealthChecker/Tests/HealthChecker.MockedCalls.Tests.ps1 index 4f6592f638..686691d0f7 100644 --- a/Diagnostics/HealthChecker/Tests/HealthChecker.MockedCalls.Tests.ps1 +++ b/Diagnostics/HealthChecker/Tests/HealthChecker.MockedCalls.Tests.ps1 @@ -60,7 +60,7 @@ Describe "Testing Health Checker by Mock Data Imports" { Assert-MockCalled Get-WmiObjectHandler -Exactly 6 Assert-MockCalled Invoke-ScriptBlockHandler -Exactly 5 - Assert-MockCalled Get-RemoteRegistryValue -Exactly 23 + Assert-MockCalled Get-RemoteRegistryValue -Exactly 24 Assert-MockCalled Get-NETFrameworkVersion -Exactly 1 Assert-MockCalled Get-DotNetDllFileVersions -Exactly 1 Assert-MockCalled Get-NicPnpCapabilitiesSetting -Exactly 1 diff --git a/Diagnostics/HealthChecker/Tests/HealthCheckerTest.CommonMocks.NotPublished.ps1 b/Diagnostics/HealthChecker/Tests/HealthCheckerTest.CommonMocks.NotPublished.ps1 index 54176f01cd..c616afc645 100644 --- a/Diagnostics/HealthChecker/Tests/HealthCheckerTest.CommonMocks.NotPublished.ps1 +++ b/Diagnostics/HealthChecker/Tests/HealthCheckerTest.CommonMocks.NotPublished.ps1 @@ -121,6 +121,7 @@ Mock Get-RemoteRegistryValue { "DaylightStart" { return @(0, 0, 3, 0, 2, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0) } "DisableBaseTypeCheckForDeserialization" { return $null } "DisablePreservation" { return 0 } + "DatabasePath" { return "$Script:MockDataCollectionRoot\Exchange" } default { throw "Failed to find GetValue: $GetValue" } } } From b6ea87ce67c3f45049d1dfd3ed2510d3f6dbbe6c Mon Sep 17 00:00:00 2001 From: David Paulson Date: Fri, 8 Mar 2024 13:54:55 -0600 Subject: [PATCH 04/34] Display no vulnerabilities on Edge Server --- .../Security/Invoke-AnalyzerSecurityVulnerability.ps1 | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Diagnostics/HealthChecker/Analyzer/Security/Invoke-AnalyzerSecurityVulnerability.ps1 b/Diagnostics/HealthChecker/Analyzer/Security/Invoke-AnalyzerSecurityVulnerability.ps1 index b9189de1bc..e29e6ea06d 100644 --- a/Diagnostics/HealthChecker/Analyzer/Security/Invoke-AnalyzerSecurityVulnerability.ps1 +++ b/Diagnostics/HealthChecker/Analyzer/Security/Invoke-AnalyzerSecurityVulnerability.ps1 @@ -32,8 +32,10 @@ function Invoke-AnalyzerSecurityVulnerability { $buildVersion = $HealthServerObject.ExchangeInformation.BuildInformation.VersionInformation $noLongerSecureExchange = ($buildVersion.ExtendedSupportDate -le ([DateTime]::Now)) -and $buildVersion.LatestSU -eq $false - if ($null -eq $securityVulnerabilities -and - ($null -ne $iisModule -or $iisModule.DisplayValue -eq $false) -and + if ((($null -eq $securityVulnerabilities -and + $HealthServerObject.ExchangeInformation.GetExchangeServer.IsEdgeServer) -or + ($null -eq $securityVulnerabilities -and + ($null -ne $iisModule -or $iisModule.DisplayValue -eq $false))) -and (-not $noLongerSecureExchange)) { $params = $baseParams + @{ Details = "All known security issues in this version of the script passed." From 2c90e5ae2d3174004d9f58f16e74cfb67496445f Mon Sep 17 00:00:00 2001 From: David Paulson Date: Tue, 12 Mar 2024 08:34:15 -0500 Subject: [PATCH 05/34] Add known issue --- .../Analyzer/Invoke-AnalyzerKnownBuildIssues.ps1 | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerKnownBuildIssues.ps1 b/Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerKnownBuildIssues.ps1 index fe9de3db6f..415637e24f 100644 --- a/Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerKnownBuildIssues.ps1 +++ b/Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerKnownBuildIssues.ps1 @@ -232,6 +232,20 @@ function Invoke-AnalyzerKnownBuildIssues { InformationUrl = (GetKnownIssueInformation @infoParams) } TestForKnownBuildIssues @params + + Write-Verbose "Work on March 2024 Security Updates" + $infoParams = @{ + Name = "Known Issues with Mar 2024 Security Updates" + Url = "https://support.microsoft.com/help/5037171" + } + $params = @{ + CurrentVersion = $currentVersion + KnownBuildIssuesToFixes = @((GetKnownIssueBuildInformation "15.2.1544.9" $null), + (GetKnownIssueBuildInformation "15.2.1258.32" $null), + (GetKnownIssueBuildInformation "15.1.2507.37", $null)) + InformationUrl = (GetKnownIssueInformation @infoParams) + } + TestForKnownBuildIssues @params } catch { Write-Verbose "Failed to run TestForKnownBuildIssues" Invoke-CatchActions From c271fea262fda3710c42358e19b88250884f7781 Mon Sep 17 00:00:00 2001 From: lusassl-msft Date: Thu, 1 Feb 2024 21:15:26 +0100 Subject: [PATCH 06/34] OIM Script Initial Creation --- .../src/CVE-2024-xxxxx/CVE-2024-xxxxx.ps1 | 126 +++++++++ .../Invoke-OutsideInModuleAction.ps1 | 265 ++++++++++++++++++ docs/Security/CVE-2024-xxxxx.md | 3 + 3 files changed, 394 insertions(+) create mode 100644 Security/src/CVE-2024-xxxxx/CVE-2024-xxxxx.ps1 create mode 100644 Security/src/CVE-2024-xxxxx/ConfigurationAction/Invoke-OutsideInModuleAction.ps1 create mode 100644 docs/Security/CVE-2024-xxxxx.md diff --git a/Security/src/CVE-2024-xxxxx/CVE-2024-xxxxx.ps1 b/Security/src/CVE-2024-xxxxx/CVE-2024-xxxxx.ps1 new file mode 100644 index 0000000000..b1dded1215 --- /dev/null +++ b/Security/src/CVE-2024-xxxxx/CVE-2024-xxxxx.ps1 @@ -0,0 +1,126 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +<# +.SYNOPSIS + This script can be used to remove vulnerable file types from the FIP-FS configuration.xml file. +.DESCRIPTION + The script removes vulnerable file types from the FIP-FS configuration.xml file. + It can also be used to add these file types back. It also allows you to completely disable the usage of the OutsideInModule + or enable it back. +.PARAMETER Configuration + Use this parameter to specify the configuration that should be changed. + Values that can be passed with this parameter are: ConfigureOutsideIn and ConfigureFileTypes +.PARAMETER Action + Use this parameter to specify the action that should be performed. + Values that can be passed with this parameter are: Allow, Block +.PARAMETER ScriptUpdateOnly + This optional parameter allows you to only update the script without performing any other actions. +.PARAMETER SkipVersionCheck + This optional parameter allows you to skip the automatic version check and script update. +.EXAMPLE + PS C:\> .\CVE-2024-xxxxx.ps1 -Configuration ConfigureFileTypes -Action Block + It will block the vulnerable file types in the FIP-FS configuration file. +.EXAMPLE + PS C:\> .\CVE-2024-xxxxx.ps1 -Configuration ConfigureFileTypes -Action Allow + It will add the vulnerable file types back to the the FIP-FS configuration file. +.EXAMPLE + PS C:\> .\CVE-2024-xxxxx.ps1 -Configuration ConfigureOutsideIn -Action Block + It will disable the OutsideInModule in the FIP-FS configuration file. +.EXAMPLE + PS C:\> .\CVE-2024-xxxxx.ps1 -Configuration ConfigureOutsideIn -Action Allow + It will enable the OutsideInModule in the FIP-FS configuration file. +#> + +[CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess = $true, ConfirmImpact = 'High')] +param( + [Parameter(Mandatory = $false, ParameterSetName = "Default")] + [ValidateSet("ConfigureOutsideIn", "ConfigureFileTypes")] + [string]$Configuration = "ConfigureFileTypes", + + [Parameter(Mandatory = $false, ParameterSetName = "Default")] + [ValidateSet("Allow", "Block")] + [string]$Action = "Block", + + [Parameter(Mandatory = $false, ParameterSetName = "ScriptUpdateOnly")] + [switch]$ScriptUpdateOnly, + + [Parameter(Mandatory = $false, ParameterSetName = "Default")] + [switch]$SkipVersionCheck +) + +begin { + $BuildVersion = "" + + . $PSScriptRoot\ConfigurationAction\Invoke-OutsideInModuleAction.ps1 + . $PSScriptRoot\..\..\..\Shared\OutputOverrides\Write-Host.ps1 + . $PSScriptRoot\..\..\..\Shared\OutputOverrides\Write-Verbose.ps1 + . $PSScriptRoot\..\..\..\Shared\ScriptUpdateFunctions\Test-ScriptVersion.ps1 + . $PSScriptRoot\..\..\..\Shared\Confirm-Administrator.ps1 + . $PSScriptRoot\..\..\..\Shared\LoggerFunctions.ps1 + . $PSScriptRoot\..\..\..\Shared\Show-Disclaimer.ps1 + + function Write-VerboseLog ($Message) { + $Script:Logger = $Script:Logger | Write-LoggerInstance $Message + } + + function Write-HostLog ($Message) { + $Script:Logger = $Script:Logger | Write-LoggerInstance $Message + } + + $loggerInstanceParams = @{ + LogName = "CVE-2024-xxxxx-$((Get-Date).ToString("yyyyMMddhhmmss"))-Debug" + AppendDateTimeToFileName = $false + ErrorAction = "SilentlyContinue" + } + + $Script:Logger = Get-NewLoggerInstance @loggerInstanceParams + + SetWriteHostAction ${Function:Write-HostLog} + SetWriteVerboseAction ${Function:Write-VerboseLog} + + $fileTypesDictionary = New-Object 'System.Collections.Generic.Dictionary[string, array]' + + # Add all vulnerable file types here that should be blocked + $fileTypesDictionary.Add("Excel", @("ExcelStorage")) + $fileTypesDictionary.Add("PreferOutsideIn", @("Html", "Pdf")) +} end { + if (-not(Confirm-Administrator)) { + Write-Host "The script needs to be executed in elevated mode. Start the PowerShell as an administrator." -ForegroundColor Yellow + exit + } + + $versionsUrl = "https://aka.ms/CVE-2024-xxxxx-VersionsUrl" + Write-Host ("CVE-2024-xxxxx script version $($BuildVersion)") -ForegroundColor Green + + if ($ScriptUpdateOnly) { + switch (Test-ScriptVersion -AutoUpdate -VersionsUrl $versionsUrl -Confirm:$false) { + ($true) { Write-Host ("Script was successfully updated") -ForegroundColor Green } + ($false) { Write-Host ("No update of the script performed") -ForegroundColor Yellow } + default { Write-Host ("Unable to perform ScriptUpdateOnly operation") -ForegroundColor Red } + } + return + } + + if ((-not($SkipVersionCheck)) -and + (Test-ScriptVersion -AutoUpdate -VersionsUrl $versionsUrl -Confirm:$false)) { + Write-Host ("Script was updated. Please re-run the command") -ForegroundColor Yellow + return + } + + try { + $invokeOutsideInModuleActionParams = @{ + Configuration = $Configuration + Action = $Action + } + + if ($Configuration -eq "ConfigureFileTypes") { + $invokeOutsideInModuleActionParams.Add("FileTypesDictionary", $fileTypesDictionary) + } + + Invoke-OutsideInModuleAction @invokeOutsideInModuleActionParams + } finally { + Write-Host "" + Write-Host "Do you have feedback regarding the script? Please let us know: ExToolsFeedback@microsoft.com." + } +} diff --git a/Security/src/CVE-2024-xxxxx/ConfigurationAction/Invoke-OutsideInModuleAction.ps1 b/Security/src/CVE-2024-xxxxx/ConfigurationAction/Invoke-OutsideInModuleAction.ps1 new file mode 100644 index 0000000000..30a674e311 --- /dev/null +++ b/Security/src/CVE-2024-xxxxx/ConfigurationAction/Invoke-OutsideInModuleAction.ps1 @@ -0,0 +1,265 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +. $PSScriptRoot\..\..\..\..\Shared\Get-RemoteRegistryValue.ps1 + +function Invoke-OutsideInModuleAction { + [CmdletBinding(DefaultParameterSetName = "ConfigureOutsideIn", ConfirmImpact = 'High')] + param( + [Parameter(Mandatory = $true, ParameterSetName = "ConfigureOutsideIn")] + [Parameter(Mandatory = $true, ParameterSetName = "ConfigureFileTypes")] + [ValidateSet("ConfigureOutsideIn", "ConfigureFileTypes")] + [string]$Configuration, + + [Parameter(Mandatory = $true, ParameterSetName = "ConfigureFileTypes")] + [object]$FileTypesDictionary, + + [Parameter(Mandatory = $true, ParameterSetName = "ConfigureOutsideIn")] + [Parameter(Mandatory = $true, ParameterSetName = "ConfigureFileTypes")] + [ValidateSet("Allow", "Block")] + [string]$Action + ) + + begin { + Write-Verbose "Calling: $($MyInvocation.MyCommand)" + + function GetFipFsConfigurationPath { + param( + [string]$MachineName = $env:COMPUTERNAME + ) + + Write-Verbose "Calling: $($MyInvocation.MyCommand)" + + $fipFsDatabaseParams = @{ + MachineName = $MachineName + SubKey = "SOFTWARE\Microsoft\ExchangeServer\v15\FIP-FS" + GetValue = "DatabasePath" + } + + Write-Verbose "Trying to detect FIP-FS database path for machine: $MachineName" + $fipFsDatabasePath = Get-RemoteRegistryValue @fipFsDatabaseParams + + if (-not[System.String]::IsNullOrWhiteSpace($fipFsDatabasePath)) { + Write-Verbose "FIP-FS database path is: $fipFsDatabasePath" + return (Join-Path $fipFsDatabasePath "Configuration.xml") + } else { + Write-Verbose "Unable to read FIP-FS database path from registry" + return $null + } + } + + function BackupFipFsConfiguration { + param( + [string]$FipFsConfigurationPath + ) + + Write-Verbose "Calling: $($MyInvocation.MyCommand)" + $backupTimestamp = Get-Date -Format yyyyMMddmmss + + if (Test-Path -Path $FipFsConfigurationPath) { + Write-Verbose "FIP-FS configuration file detected" + $configurationBackupPath = $FipFsConfigurationPath + ".$backupTimestamp" + ".bak" + Copy-Item -Path $FipFsConfigurationPath -Destination $configurationBackupPath + + Write-Verbose "Backup configuration is: $configurationBackupPath" + return $true + } else { + Write-Verbose "FIP-FS configuration file doesn't exist" + return $false + } + } + + function StartStopFipFsDependendServices { + param( + [ValidateSet("Start", "Stop")] + [string]$ServiceAction + ) + + Write-Verbose "Calling: $($MyInvocation.MyCommand)" + try { + if ($ServiceAction -eq "Stop") { + Write-Verbose "Stopping MSExchangeTransport and FMS services..." + Stop-Service -Name "FMS" -Force + } else { + Write-Verbose "Starting MSExchangeTransport and FMS services..." + Start-Service -Name "MSExchangeTransport" + Start-Service -Name "FMS" + } + } catch { + Write-Verbose "We hit an exception while performing services action: $ServiceAction" + Write-Verbose "Exception: $_" + + return $false + } + + return $true + } + + function SetConfigurationAttribute { + [CmdletBinding(DefaultParameterSetName = "ConfigureOutsideIn", SupportsShouldProcess = $true, ConfirmImpact = 'High')] + param( + [Parameter(Mandatory = $true, ParameterSetName = "ConfigureOutsideIn")] + [Parameter(Mandatory = $true, ParameterSetName = "ConfigureFileTypes")] + [object[]]$Nodes, + + [Parameter(Mandatory = $false, ParameterSetName = "ConfigureOutsideIn")] + [Parameter(Mandatory = $false, ParameterSetName = "ConfigureFileTypes")] + [ValidateSet("ConfigureOutsideIn", "ConfigureFileTypes")] + [string]$ConfigurationMode = "ConfigureOutsideIn", + + [Parameter(Mandatory = $true, ParameterSetName = "ConfigureFileTypes")] + [object]$FileTypes, + + [Parameter(Mandatory = $false, ParameterSetName = "ConfigureOutsideIn")] + [string]$ModuleToConfigure = "OutsideInModule.dll", + + [Parameter(Mandatory = $false, ParameterSetName = "ConfigureOutsideIn")] + [Parameter(Mandatory = $false, ParameterSetName = "ConfigureFileTypes")] + [bool]$Enabled = $false + ) + + Write-Verbose "Calling: $($MyInvocation.MyCommand)" + # Configuration strings are case sensitive and must be set to lowercase true or false + $modulesEnabledValue = if ($Enabled) { "true" } else { "false" } + + if ($ConfigurationMode -eq "ConfigureFileTypes") { + # We need to clone a node just in case that we need to add file types back to the allowed list + $nodeCloneTemplace = ($Nodes.Node | Where-Object { $null -ne $_.Type } | Select-Object -First 1).CloneNode($true) + } + + foreach ($n in $Nodes) { + $fileTypesEntry = $null + if ($ConfigurationMode -eq "ConfigureFileTypes") { + + $fileTypesEntry = $FileTypes["$($n.Node.Name)"] + if ($Enabled -eq $false) { + # We are removing the specified file types here + if ($null -ne $fileTypesEntry) { + Write-Verbose "AllowedType: $($n.Node.Name) found - checking for FileType entries now..." + $n.Node.Type | Where-Object { + $_.Name -in $fileTypesEntry + } | ForEach-Object { + Write-Verbose "FileType: $($n.Node.Type) is on the allow list and will be removed now" + [void]($_.ParentNode.RemoveChild($_)) + } + } else { + Write-Verbose "AllowedType: $($n.Node.Name) not found and will be skipped" + } + } else { + # We will explicitly enable the specified file types here + if ($null -ne $fileTypesEntry) { + Write-Verbose "AllowedType: $($n.Node.Name) found - checking for FileType entries now..." + $fileTypesEntry | ForEach-Object { + if ($n.Node.Type.Name -notcontains $_) { + Write-Verbose "FileType: $_ will be added to the allow list" + $nodeClone = $nodeCloneTemplace.Type.CloneNode($true) + $nodeClone.Name = "$_" + $n.Node.AppendChild($nodeClone) + } else { + Write-Verbose "FileType: $_ is already on the allow list and will be skipped" + } + } + } + } + } elseif ($ConfigurationMode -eq "ConfigureOutsideIn") { + # We explicitly enable or disable the OutsideInModule here + if ($n.Node.InnerText.StartsWith($ModuleToConfigure)) { + Write-Verbose "Setting module: $($n.Node.InnerText) to Enabled: $Enabled" + $n.Node.Attributes["Enabled"].Value = $modulesEnabledValue + } else { + Write-Verbose "Module: $($n.Node.InnerText) will be skipped as it's not related to: $ModuleToConfigure" + } + } + } + } + + function PerformFipFsConfigurationOperation { + [CmdletBinding(DefaultParameterSetName = "ConfigureOutsideIn", ConfirmImpact = 'High')] + param( + [Parameter(Mandatory = $true, ParameterSetName = "ConfigureOutsideIn")] + [Parameter(Mandatory = $true, ParameterSetName = "ConfigureFileTypes")] + [string]$FipFsConfigurationPath, + + [Parameter(Mandatory = $true, ParameterSetName = "ConfigureOutsideIn")] + [Parameter(Mandatory = $true, ParameterSetName = "ConfigureFileTypes")] + [ValidateSet("DisableOutsideIn", "EnableOutsideIn", "BlockVulnerableFileTypes", "AllowVulnerableFileTypes")] + [string]$Operation, + + [Parameter(Mandatory = $true, ParameterSetName = "ConfigureFileTypes")] + [object]$FileTypesDictionary + ) + + begin { + Write-Verbose "Calling: $($MyInvocation.MyCommand)" + + $configXmlNamespaces = @{ + root = "http://schemas.microsoft.com/forefront/2010/1/fs-configuration" + sys = "http://schemas.microsoft.com/forefront/2010/1/fs-systemconfiguration" + } + $modulePath = "/root:Configuration/sys:System/sys:TextExtractionSettings/sys:ModuleLists/sys:ModuleList/sys:Module" + $typePath = "/root:Configuration/sys:System/sys:TextExtractionSettings/sys:TypeLists/sys:TypeList" + } process { + # Stopping MSExchangeTransport and FMS services + if (StartStopFipFsDependendServices -ServiceAction "Stop") { + # Perform backup of the existing configuration.xml + if (BackupFipFsConfiguration -FipFsConfigurationPath $FipFsConfigurationPath) { + try { + [xml]$configuration = Get-Content -Path $FipFsConfigurationPath + + # Based on how blocking will be done, we need the corresponding path + if ($Operation -eq "DisableOutsideIn" -or + $Operation -eq "EnableOutsideIn") { + + # We use this to completely block the OutsideInModule + $modules = $configuration | Select-Xml -Namespace $configXmlNamespaces -XPath $modulePath + + # Perform the action based on the value that was passed via Operation parameter + $outsideInParams = @{ + Nodes = $modules + Enabled = if ( $Operation -eq "DisableOutsideIn") { $false } else { $true } + } + SetConfigurationAttribute @outsideInParams + } elseif ($Operation -eq "BlockVulnerableFileTypes" -or + $Operation -eq "AllowVulnerableFileTypes") { + + # We use this to partially blocking the vulnerable file types + $types = $configuration | Select-Xml -Namespace $configXmlNamespaces -XPath $typePath + $fileTypeParams = @{ + Nodes = $types + ConfigurationMode = "ConfigureFileTypes" + FileTypes = $FileTypesDictionary + Enabled = if ($Operation -eq "BlockVulnerableFileTypes") { $false } else { $true } + } + SetConfigurationAttribute @fileTypeParams + } + } catch { + Write-Verbose "We hit an exception while processing the change to the configuration.xml. Please run the script again." + Write-Verbose "Exception: $_" + } + } else { + Write-Verbose "We fail to create a backup of the configuration.xml file. Please run the script again." + } + } else { + Write-Verbose "We fail to stop the MSExchangeTransport and FMS services. Please run the script again." + } + } end { + # Save the modified FIP-FS configuration.xml and restart the MSExchangeTransport and FMS services + $configuration.Save($FipFsConfigurationPath) + StartStopFipFsDependendServices -ServiceAction "Start" + } + } + } end { + $fipsOperationParams = @{ + FipFsConfigurationPath = GetFipFsConfigurationPath + } + + if ($Configuration -eq "ConfigureFileTypes") { + $fipsOperationParams.Add("FileTypesDictionary", $FileTypesDictionary) + $fipsOperationParams.Add("Operation", $(if ($Action -eq "Allow") { "AllowVulnerableFileTypes" } else { "BlockVulnerableFileTypes" })) + } else { + $fipsOperationParams.Add("Operation", $(if ($Action -eq "Allow") { "EnableOutsideIn" } else { "DisableOutsideIn" })) + } + + PerformFipFsConfigurationOperation @fipsOperationParams + } +} diff --git a/docs/Security/CVE-2024-xxxxx.md b/docs/Security/CVE-2024-xxxxx.md new file mode 100644 index 0000000000..d80507088f --- /dev/null +++ b/docs/Security/CVE-2024-xxxxx.md @@ -0,0 +1,3 @@ +# CVE-2024-xxxxx + +Placeholder \ No newline at end of file From 04234d08b4fc9b027ce6df25305224101df3346f Mon Sep 17 00:00:00 2001 From: David Paulson Date: Thu, 1 Feb 2024 15:00:16 -0600 Subject: [PATCH 07/34] Common code to handle logging and update logic --- Shared/GenericScriptStartLogging.ps1 | 48 +++++++++++++++++++ .../GenericScriptUpdate.ps1 | 44 +++++++++++++++++ 2 files changed, 92 insertions(+) create mode 100644 Shared/GenericScriptStartLogging.ps1 create mode 100644 Shared/ScriptUpdateFunctions/GenericScriptUpdate.ps1 diff --git a/Shared/GenericScriptStartLogging.ps1 b/Shared/GenericScriptStartLogging.ps1 new file mode 100644 index 0000000000..3ee99b3871 --- /dev/null +++ b/Shared/GenericScriptStartLogging.ps1 @@ -0,0 +1,48 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +<# + This file is designed to inline code that we use to start the scripts and handle the logging. +#> + +. $PSScriptRoot\OutputOverrides\Write-Host.ps1 +. $PSScriptRoot\OutputOverrides\Write-Progress.ps1 +. $PSScriptRoot\OutputOverrides\Write-Verbose.ps1 +. $PSScriptRoot\OutputOverrides\Write-Warning.ps1 +. $PSScriptRoot\Confirm-Administrator.ps1 +. $PSScriptRoot\LoggerFunctions.ps1 +. $PSScriptRoot\Show-Disclaimer.ps1 + +function Write-DebugLog ($Message) { + $Script:DebugLogger = $Script:DebugLogger | Write-LoggerInstance $Message +} + +function Write-HostLogAndDebugLog ($Message) { + $Script:Logger = $Script:Logger | Write-LoggerInstance $Message + Write-DebugLog $Message +} + +$Script:DebugLogger = Get-NewLoggerInstance -LogName "$($script:MyInvocation.MyCommand.Name)-Debug" + +SetWriteVerboseAction ${Function:Write-DebugLog} +SetWriteProgressAction ${Function:Write-DebugLog} +SetWriteWarningAction ${Function:Write-DebugLog} + +# Dual Logging is for when you have a secondary file designed for debug logic and one that is simplified for everything that was displayed to the screen. +Write-Verbose "Dual Logging $(if(-not ($Script:DualLoggingEnabled)){ "NOT "})Enabled." +if ($Script:DualLoggingEnabled) { + $params = @{ + LogName = ([System.IO.Path]::GetFileNameWithoutExtension($Script:DebugLogger.FullPath).Replace("-Debug", "")) + AppendDateTime = $false + AppendDateTimeToFileName = $false + } + $Script:Logger = Get-NewLoggerInstance @params + SetWriteHostAction ${Function:Write-HostLogAndDebugLog} +} else { + SetWriteHostAction ${Write-DebugLog} +} + +if (-not(Confirm-Administrator)) { + Write-Host "The script needs to be executed in elevated mode. Start the PowerShell as an administrator." -ForegroundColor Yellow + exit +} diff --git a/Shared/ScriptUpdateFunctions/GenericScriptUpdate.ps1 b/Shared/ScriptUpdateFunctions/GenericScriptUpdate.ps1 new file mode 100644 index 0000000000..03571f8b37 --- /dev/null +++ b/Shared/ScriptUpdateFunctions/GenericScriptUpdate.ps1 @@ -0,0 +1,44 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +<# + This set of code is designed to handle updating your script as this code is basically the same everywhere, making this a common file to avoid duplication. + Just need to dot load the file to your script and have the correct parameters, then this code does the work for you. + These are the parameters that you should have within your script. + This needs to be done within the main part of the script, not inside a function to work correctly. + + [Parameter(Mandatory = $false, ParameterSetName = "ScriptUpdateOnly")] + [switch]$ScriptUpdateOnly, + + [switch]$SkipVersionCheck +#> + +. $PSScriptRoot\Test-ScriptVersion.ps1 + +$BuildVersion = "" +Write-Host ("$($script:MyInvocation.MyCommand.Name) script version $($BuildVersion)") -ForegroundColor Green + +$scriptVersionParams = @{ + AutoUpdate = $true + Confirm = $false +} + +# This needs to be set prior to injecting this file to other scripts. +if (-not ([string]::IsNullOrEmpty($versionsUrl))) { + $scriptVersionParams.Add("VersionsUrl", $versionsUrl) +} + +if ($ScriptUpdateOnly) { + switch (Test-ScriptVersion @scriptVersionParams) { + ($true) { Write-Host ("Script was successfully updated") -ForegroundColor Green } + ($false) { Write-Host ("No update of the script performed") -ForegroundColor Yellow } + default { Write-Host ("Unable to perform ScriptUpdateOnly operation") -ForegroundColor Red } + } + exit +} + +if ((-not($SkipVersionCheck)) -and + (Test-ScriptVersion @scriptVersionParams)) { + Write-Host ("Script was updated. Please re-run the command") -ForegroundColor Yellow + exit +} From 9059c0e0bd22eb79402f62ddaed5c4a935eb7288 Mon Sep 17 00:00:00 2001 From: David Paulson Date: Thu, 1 Feb 2024 16:50:32 -0600 Subject: [PATCH 08/34] Inline start script logging and update --- .../src/CVE-2024-xxxxx/CVE-2024-xxxxx.ps1 | 51 +------------------ 1 file changed, 2 insertions(+), 49 deletions(-) diff --git a/Security/src/CVE-2024-xxxxx/CVE-2024-xxxxx.ps1 b/Security/src/CVE-2024-xxxxx/CVE-2024-xxxxx.ps1 index b1dded1215..4ec46f1c52 100644 --- a/Security/src/CVE-2024-xxxxx/CVE-2024-xxxxx.ps1 +++ b/Security/src/CVE-2024-xxxxx/CVE-2024-xxxxx.ps1 @@ -50,34 +50,10 @@ param( ) begin { - $BuildVersion = "" . $PSScriptRoot\ConfigurationAction\Invoke-OutsideInModuleAction.ps1 - . $PSScriptRoot\..\..\..\Shared\OutputOverrides\Write-Host.ps1 - . $PSScriptRoot\..\..\..\Shared\OutputOverrides\Write-Verbose.ps1 - . $PSScriptRoot\..\..\..\Shared\ScriptUpdateFunctions\Test-ScriptVersion.ps1 - . $PSScriptRoot\..\..\..\Shared\Confirm-Administrator.ps1 - . $PSScriptRoot\..\..\..\Shared\LoggerFunctions.ps1 - . $PSScriptRoot\..\..\..\Shared\Show-Disclaimer.ps1 - - function Write-VerboseLog ($Message) { - $Script:Logger = $Script:Logger | Write-LoggerInstance $Message - } - - function Write-HostLog ($Message) { - $Script:Logger = $Script:Logger | Write-LoggerInstance $Message - } - - $loggerInstanceParams = @{ - LogName = "CVE-2024-xxxxx-$((Get-Date).ToString("yyyyMMddhhmmss"))-Debug" - AppendDateTimeToFileName = $false - ErrorAction = "SilentlyContinue" - } - - $Script:Logger = Get-NewLoggerInstance @loggerInstanceParams - - SetWriteHostAction ${Function:Write-HostLog} - SetWriteVerboseAction ${Function:Write-VerboseLog} + . $PSScriptRoot\..\..\..\Shared\GenericScriptStartLogging.ps1 + . $PSScriptRoot\..\..\..\Shared\ScriptUpdateFunctions\GenericScriptUpdate.ps1 $fileTypesDictionary = New-Object 'System.Collections.Generic.Dictionary[string, array]' @@ -85,29 +61,6 @@ begin { $fileTypesDictionary.Add("Excel", @("ExcelStorage")) $fileTypesDictionary.Add("PreferOutsideIn", @("Html", "Pdf")) } end { - if (-not(Confirm-Administrator)) { - Write-Host "The script needs to be executed in elevated mode. Start the PowerShell as an administrator." -ForegroundColor Yellow - exit - } - - $versionsUrl = "https://aka.ms/CVE-2024-xxxxx-VersionsUrl" - Write-Host ("CVE-2024-xxxxx script version $($BuildVersion)") -ForegroundColor Green - - if ($ScriptUpdateOnly) { - switch (Test-ScriptVersion -AutoUpdate -VersionsUrl $versionsUrl -Confirm:$false) { - ($true) { Write-Host ("Script was successfully updated") -ForegroundColor Green } - ($false) { Write-Host ("No update of the script performed") -ForegroundColor Yellow } - default { Write-Host ("Unable to perform ScriptUpdateOnly operation") -ForegroundColor Red } - } - return - } - - if ((-not($SkipVersionCheck)) -and - (Test-ScriptVersion -AutoUpdate -VersionsUrl $versionsUrl -Confirm:$false)) { - Write-Host ("Script was updated. Please re-run the command") -ForegroundColor Yellow - return - } - try { $invokeOutsideInModuleActionParams = @{ Configuration = $Configuration From e2771ca26999f506b9eeecd0082d5032690426a1 Mon Sep 17 00:00:00 2001 From: lusassl-msft Date: Fri, 2 Feb 2024 12:03:06 +0100 Subject: [PATCH 09/34] Improvements made to script and documentation --- .build/cspell-words.txt | 3 + .../src/CVE-2024-xxxxx/CVE-2024-xxxxx.ps1 | 133 ++++- .../Invoke-OutsideInModuleAction.ps1 | 518 +++++++++++++++--- docs/Security/CVE-2024-xxxxx.md | 2 +- mkdocs.yml | 1 + 5 files changed, 572 insertions(+), 85 deletions(-) diff --git a/.build/cspell-words.txt b/.build/cspell-words.txt index 5b9a2bff5f..8bb4f8b2dc 100644 --- a/.build/cspell-words.txt +++ b/.build/cspell-words.txt @@ -151,12 +151,15 @@ unconfigured unrequired USERDNSDOMAIN Vianet +Visio vmxnet vssadmin vsstester +Vssx vuln wbxml Webex Weve wevtutil windir +Xlsb diff --git a/Security/src/CVE-2024-xxxxx/CVE-2024-xxxxx.ps1 b/Security/src/CVE-2024-xxxxx/CVE-2024-xxxxx.ps1 index 4ec46f1c52..abfa47df77 100644 --- a/Security/src/CVE-2024-xxxxx/CVE-2024-xxxxx.ps1 +++ b/Security/src/CVE-2024-xxxxx/CVE-2024-xxxxx.ps1 @@ -8,9 +8,18 @@ The script removes vulnerable file types from the FIP-FS configuration.xml file. It can also be used to add these file types back. It also allows you to completely disable the usage of the OutsideInModule or enable it back. -.PARAMETER Configuration - Use this parameter to specify the configuration that should be changed. +.PARAMETER ConfigureMitigation + Use this parameter to specify the mitigation that should be applied. Values that can be passed with this parameter are: ConfigureOutsideIn and ConfigureFileTypes +.PARAMETER ConfigureOverride + Use this parameter to specify the override that should be set. + Note that setting an override works only if the Exchange Server March 2024 security update was installed. + Values that can be passed with this parameter are: OutsideInVersionOverride and FileTypesOverride +.PARAMETER OutsideInEnabledFileTypes + Use this parameter to specify the file types that should be allowed to use the OutsideInModule. + By default, the only file types that are allowed to use the OutsideInModule are: AutoCad, Jpeg and Tiff +.PARAMETER RestoreFileTypeList + Use this parameter if you want to restore the file type list. All existing file type overrides will be removed. .PARAMETER Action Use this parameter to specify the action that should be performed. Values that can be passed with this parameter are: Allow, Block @@ -19,26 +28,49 @@ .PARAMETER SkipVersionCheck This optional parameter allows you to skip the automatic version check and script update. .EXAMPLE - PS C:\> .\CVE-2024-xxxxx.ps1 -Configuration ConfigureFileTypes -Action Block + PS C:\> .\CVE-2024-xxxxx.ps1 -ConfigureMitigation ConfigureFileTypes -Action Block It will block the vulnerable file types in the FIP-FS configuration file. .EXAMPLE - PS C:\> .\CVE-2024-xxxxx.ps1 -Configuration ConfigureFileTypes -Action Allow + PS C:\> .\CVE-2024-xxxxx.ps1 -ConfigureMitigation ConfigureFileTypes -Action Allow It will add the vulnerable file types back to the the FIP-FS configuration file. .EXAMPLE - PS C:\> .\CVE-2024-xxxxx.ps1 -Configuration ConfigureOutsideIn -Action Block + PS C:\> .\CVE-2024-xxxxx.ps1 -ConfigureMitigation ConfigureOutsideIn -Action Block It will disable the OutsideInModule in the FIP-FS configuration file. .EXAMPLE - PS C:\> .\CVE-2024-xxxxx.ps1 -Configuration ConfigureOutsideIn -Action Allow + PS C:\> .\CVE-2024-xxxxx.ps1 -ConfigureMitigation ConfigureOutsideIn -Action Allow It will enable the OutsideInModule in the FIP-FS configuration file. +.EXAMPLE + PS C:\> .\CVE-2024-xxxxx.ps1 -ConfigureOverride OutsideInVersionOverride -Action Allow + It will add the 'NO' override flag to the OutsideInModule.dll which is defined in the 'OutsideInOnly' module list. +.EXAMPLE + PS C:\> .\CVE-2024-xxxxx.ps1 -ConfigureOverride OutsideInVersionOverride -Action Block + It will remove the 'NO' override flag from the OutsideInModule.dll which is defined in the 'OutsideInOnly' module list. +.EXAMPLE + PS C:\> .\CVE-2024-xxxxx.ps1 -ConfigureOverride FileTypesOverride -OutsideInEnabledFileTypes "ExcelStorage" + It will add 'ExcelStorage' file type to the 'OutsideInOnly' file type list and will add the 'NO' flag to the file type. +.EXAMPLE + PS C:\> .\CVE-2024-xxxxx.ps1 -RestoreFileTypeList + It will restore the default file type to file type list mapping and removes any file type override. #> -[CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess = $true, ConfirmImpact = 'High')] +[CmdletBinding(DefaultParameterSetName = "ConfigureMitigation", SupportsShouldProcess = $true, ConfirmImpact = 'High')] param( - [Parameter(Mandatory = $false, ParameterSetName = "Default")] + [Parameter(Mandatory = $true, ParameterSetName = "ConfigureMitigation")] [ValidateSet("ConfigureOutsideIn", "ConfigureFileTypes")] - [string]$Configuration = "ConfigureFileTypes", + [string]$ConfigureMitigation, - [Parameter(Mandatory = $false, ParameterSetName = "Default")] + [Parameter(Mandatory = $true, ParameterSetName = "ConfigureOverride")] + [ValidateSet("OutsideInVersionOverride", "FileTypesOverride")] + [string]$ConfigureOverride, + + [Parameter(Mandatory = $false, ParameterSetName = "ConfigureOverride")] + [object]$OutsideInEnabledFileTypes, + + [Parameter(Mandatory = $true, ParameterSetName = "RestoreFileTypeList")] + [switch]$RestoreFileTypeList, + + [Parameter(Mandatory = $false, ParameterSetName = "ConfigureMitigation")] + [Parameter(Mandatory = $false, ParameterSetName = "ConfigureOverride")] [ValidateSet("Allow", "Block")] [string]$Action = "Block", @@ -50,25 +82,92 @@ param( ) begin { - . $PSScriptRoot\ConfigurationAction\Invoke-OutsideInModuleAction.ps1 . $PSScriptRoot\..\..\..\Shared\GenericScriptStartLogging.ps1 . $PSScriptRoot\..\..\..\Shared\ScriptUpdateFunctions\GenericScriptUpdate.ps1 $fileTypesDictionary = New-Object 'System.Collections.Generic.Dictionary[string, array]' - # Add all vulnerable file types here that should be blocked + # Add all vulnerable file types here that should be removed from the allowed types list $fileTypesDictionary.Add("Excel", @("ExcelStorage")) $fileTypesDictionary.Add("PreferOutsideIn", @("Html", "Pdf")) } end { try { - $invokeOutsideInModuleActionParams = @{ - Configuration = $Configuration - Action = $Action + # TODO adjust the disclaimer wording to match the latest adjustment + $exchangeServicesWording = "Note that each Exchange server's MSExchangeTransport and FMS service will be restarted to backup and apply the setting change action." + $vulnerabilityMoreInformationWording = "More information about the vulnerability can be found here: https://portal.msrc.microsoft.com/security-guidance/advisory/CVE-2024-xxxxx." + + if ($Configuration -eq "ConfigureOutsideIn" -and + $Action -eq "Block") { + $params = @{ + Message = "Display warning about OutsideInModule removal operation" + Target = "Disabling OutsideInModule can be done to mitigate CVE-2024-xxxxx vulnerability. " + + "`r`nRemoval of this module might have impact on xxxxx. " + + "$exchangeServicesWording" + + "`r`n$vulnerabilityMoreInformationWording" + + "`r`nDo you want to proceed?" + Operation = "Disabling FIP-FS OutsideInModule usage" + } + } elseif ($Configuration -eq "ConfigureFileTypes" -and + $Action -eq "Block") { + $params = @{ + Message = "Display warning about ConfigureFileTypes removal operation" + Target = "Configuring file types that can be processed by the OutsideInModule can be done to mitigate CVE-2024-xxxxx vulnerability. " + + "`r`Configuring these file types might have impact on xxxxx. " + + "$exchangeServicesWording" + + "`r`n$vulnerabilityMoreInformationWording" + + "`r`nDo you want to proceed?" + Operation = "Configure file types that can be processed by the FIP-FS OutsideInModule" + } + } else { + $params = @{ + Message = "Display warning about OutsideInModule rollback operation" + Target = "Restoring the previous OutsideInModule configuration state will make your system vulnerable to CVE-2024-xxxxx again. " + + "$exchangeServicesWording" + + "`r`n$vulnerabilityMoreInformationWording" + + "`r`nDo you want to proceed?" + Operation = "Rollback FIP-FS OutsideInModule configuration" + } } - if ($Configuration -eq "ConfigureFileTypes") { - $invokeOutsideInModuleActionParams.Add("FileTypesDictionary", $fileTypesDictionary) + Show-Disclaimer @params + + if (-not([string]::IsNullOrEmpty($ConfigureMitigation))) { + # Mitigation mode was selected. In this mode the script will: + # a) disable the OutsideInModule.dll for all file types + # or + # b) remove vulnerable file types from the file types lists that make use of OutsideInModule.dll + $invokeOutsideInModuleActionParams = @{ + Configuration = $ConfigureMitigation + Action = $Action + } + + if ($ConfigureMitigation -eq "ConfigureFileTypes") { + $invokeOutsideInModuleActionParams.Add("FileTypesDictionary", $fileTypesDictionary) + } + } elseif (-not([string]::IsNullOrEmpty($ConfigureOverride))) { + # Configuration override mode was selected. In this mode the script will: + # a) allows you to add the override flag ('NO') to the OutsideInVersion.dll which is part of the OutsideInOnly module list + # or + # b) allows you to add the override flag to file types that are part of the file type list + # the file type will also moved to the OutsideInOnly file type list (if it's yet part of it) + $invokeOutsideInModuleActionParams = @{ + Configuration = $ConfigureOverride + Action = $Action + } + + if (-not([string]::IsNullOrWhiteSpace($OutsideInEnabledFileTypes))) { + $invokeOutsideInModuleActionParams.Add("FileTypesDictionary", $OutsideInEnabledFileTypes) + } + } elseif ($RestoreFileTypeList) { + # File type list restore mode was selected. In this mode the script will: + # a) restore the file type to file type list mapping + # and + # b) remove the override from any file type that has an override ('NO') set + $invokeOutsideInModuleActionParams = @{ + Configuration = "FileTypesOverride" + Action = "Block" + } } Invoke-OutsideInModuleAction @invokeOutsideInModuleActionParams diff --git a/Security/src/CVE-2024-xxxxx/ConfigurationAction/Invoke-OutsideInModuleAction.ps1 b/Security/src/CVE-2024-xxxxx/ConfigurationAction/Invoke-OutsideInModuleAction.ps1 index 30a674e311..dd4df9c9e6 100644 --- a/Security/src/CVE-2024-xxxxx/ConfigurationAction/Invoke-OutsideInModuleAction.ps1 +++ b/Security/src/CVE-2024-xxxxx/ConfigurationAction/Invoke-OutsideInModuleAction.ps1 @@ -8,7 +8,7 @@ function Invoke-OutsideInModuleAction { param( [Parameter(Mandatory = $true, ParameterSetName = "ConfigureOutsideIn")] [Parameter(Mandatory = $true, ParameterSetName = "ConfigureFileTypes")] - [ValidateSet("ConfigureOutsideIn", "ConfigureFileTypes")] + [ValidateSet("ConfigureOutsideIn", "ConfigureFileTypes", "OutsideInVersionOverride", "FileTypesOverride")] [string]$Configuration, [Parameter(Mandatory = $true, ParameterSetName = "ConfigureFileTypes")] @@ -54,7 +54,11 @@ function Invoke-OutsideInModuleAction { ) Write-Verbose "Calling: $($MyInvocation.MyCommand)" - $backupTimestamp = Get-Date -Format yyyyMMddmmss + $returnObject = @{ + BackupSuccessful = $false + BackupFilePath = $null + } + $backupTimestamp = Get-Date -Format yyyyMMddhhmmss if (Test-Path -Path $FipFsConfigurationPath) { Write-Verbose "FIP-FS configuration file detected" @@ -62,14 +66,16 @@ function Invoke-OutsideInModuleAction { Copy-Item -Path $FipFsConfigurationPath -Destination $configurationBackupPath Write-Verbose "Backup configuration is: $configurationBackupPath" - return $true + $returnObject.BackupFilePath = $configurationBackupPath + $returnObject.BackupSuccessful = $true } else { Write-Verbose "FIP-FS configuration file doesn't exist" - return $false } + + return $returnObject } - function StartStopFipFsDependendServices { + function StartStopFipFsDependentServices { param( [ValidateSet("Start", "Stop")] [string]$ServiceAction @@ -95,79 +101,403 @@ function Invoke-OutsideInModuleAction { return $true } + function MoveFileTypesBetweenNodes { + param( + [object]$Element, + + [string]$FileType, + + [ValidateSet("Text", "Excel", "PreferIFilters", "IFiltersOnly", "PreferOutsideIn", "OutsideInOnly")] + [string]$TargetFileTypeList, + + [switch]$RestoreFileTypeList + ) + + Write-Verbose "Calling: $($MyInvocation.MyCommand)" + + # Mapping of the file types specified in the configuration.xml to the corresponding module list + $fileTypesModuleMapping = @{ + "Text" = "Text" + "XlsbOfficePackage" = "Excel" + "XlsmOfficePackage" = "Excel" + "XlsxOfficePackage" = "Excel" + "ExcelStorage" = "Excel" + "DocmOfficePackage" = "PreferIFilters" + "DocxOfficePackage" = "PreferIFilters" + "PptmOfficePackage" = "PreferIFilters" + "PptxOfficePackage" = "PreferIFilters" + "WordStorage" = "PreferIFilters" + "PowerPointStorage" = "PreferIFilters" + "VisioStorage" = "PreferIFilters" + "Rtf" = "PreferIFilters" + "Xml" = "PreferIFilters" + "OdfTextDocument" = "PreferIFilters" + "OdfSpreadsheet" = "PreferIFilters" + "OdfPresentation" = "PreferIFilters" + "OneNote" = "PreferIFilters" + "VsdmOfficePackage" = "IFiltersOnly" + "VsdxOfficePackage" = "IFiltersOnly" + "VssmOfficePackage" = "IFiltersOnly" + "VssxOfficePackage" = "IFiltersOnly" + "VstmOfficePackage" = "IFiltersOnly" + "VstxOfficePackage" = "IFiltersOnly" + "VisioXml" = "IFiltersOnly" + "PublisherStorage" = "IFiltersOnly" + "Html" = "PreferOutsideIn" + "Pdf" = "PreferOutsideIn" + "AutoCad" = "OutsideInOnly" + "Jpeg" = "OutsideInOnly" + "Tiff" = "OutsideInOnly" + } + + # Clone a node so that we could reuse it to add file types back + $nodeCloneTemplate = ($Element.Node | Where-Object { $null -ne $_.Type } | Select-Object -First 1).CloneNode($true) + + if ($null -eq $nodeCloneTemplate) { + Write-Verbose "Fail to clone a file type node - function cannot continue" + return $false + } + + if (-not($RestoreFileTypeList)) { + Write-Verbose "Function will move file type: $FileType to file type list: $TargetFileTypeList" + + # Get the target node to which the file type should be moved + $targetNode = $Element.node | Where-Object { $_.Name -eq $TargetFileTypeList } + + if ($null -ne $targetNode) { + + # Remove the file type from its current file type list + $Element.Node.Type | Where-Object { + $_.Name.StartsWith("$FileType", "CurrentCultureIgnoreCase") + } | ForEach-Object { + Write-Verbose "FileType: $FileType will be removed from the $($Element.Node.Name) file type list" + [void]($_.ParentNode.RemoveChild($_)) + } + + # Add the file type to the file type list that was passed to the function via TargetFileTypeList parameter + Write-Verbose "FileType: $FileType will be added to the allow list: $TargetFileTypeList" + $nodeClone = $nodeCloneTemplate.Type.CloneNode($true) + $nodeClone.Name = "$FileType" + [void]$targetNode.AppendChild($nodeClone) + } else { + Write-Verbose "Target file type list wasn't found and as a result, the function can't continue" + } + } else { + + Write-Verbose "Restoring the original file type to file type list mapping" + + # Process each file type list node + foreach ($e in $Element) { + + # Process each file type which is assigned to the file type list + foreach ($type in $e.Node.Type.Name) { + + $moveToFileTypeList = [string]::Empty + $fileTypeSearchString = $type + $i = $type.IndexOf("|NO") + + if ($i -ne -1) { + Write-Verbose "File type: $type has an override flag assigned which will be removed" + # Remove the "|NO" override flag for all file types + $resetTypeOverride = @{ + Element = $e.Node + Type = "Type" + TypeName = $type + Action = "Remove" + } + + SetConfigurationOverride @resetTypeOverride + + $fileTypeSearchString = $type.Replace("|NO", "") + } + + # Find the file type list in the mapping table and skip it, if we don't find it in the list + $moveToFileTypeList = $fileTypesModuleMapping["$fileTypeSearchString"] + $targetFileTypeListNode = $Element.Node | Where-Object { $_.Name -eq $moveToFileTypeList } + + if ([string]::IsNullOrEmpty($moveToFileTypeList)) { + Write-Verbose "No mapping exists for file type: $fileTypeSearchString - this file type will be skipped" + continue + } + + # If the file type is already in the original file type list, no further action is required - skip it too + if (($Element.Node | Where-Object { $_.Name -eq $moveToFileTypeList }).Type.Name -contains $fileTypeSearchString) { + Write-Verbose "File type: $fileTypeSearchString is already in the default file type list" + continue + } + + # Remove the file type from its current file type list if a drift to the original mapping was detected + $e.Node.Type | Where-Object { + $_.Name.StartsWith("$fileTypeSearchString", "CurrentCultureIgnoreCase") + } | ForEach-Object { + Write-Verbose "FileType: $fileTypeSearchString will be removed from the $($e.Node.Name) file type list" + [void]($_.ParentNode.RemoveChild($_)) + } + + # Add the file type back to the original file type list based on the original mapping + Write-Verbose "FileType: $fileTypeSearchString will be added back to type list: $moveToFileTypeList" + $nodeClone = $nodeCloneTemplate.Type.CloneNode($true) + $nodeClone.Name = "$fileTypeSearchString" + [void]$targetFileTypeListNode.AppendChild($nodeClone) + } + } + } + } + + function SetConfigurationOverride { + [CmdletBinding(DefaultParameterSetName = "Module")] + [OutputType([boolean])] + param( + [object]$Element, + + [ValidateSet("Module", "Type")] + [string]$Type, + + [string]$TypeName, + + [ValidateSet("Add", "Remove")] + [string]$Action + ) + + Write-Verbose "Calling: $($MyInvocation.MyCommand)" + + # TODO: Optimize this code to remove duplicate code paths + if ($Type -eq "Module") { + $elementName = $Element.Module.InnerText + } else { + $typeObject = $Element.Type | Where-Object { $_.Name.StartsWith("$TypeName", "CurrentCultureIgnoreCase") } + $elementName = $typeObject.Name + } + + if ($elementName.Count -gt 1) { + Write-Verbose "Element contains multiple modules/types which can't be processed by this function" + return $false + } + + if ($Type -eq "Module") { + $index = $Element.Module.InnerText.IndexOf("|NO") + } else { + $index = $elementName.IndexOf("|NO") + } + + if ($Action -eq "Add" -and + $index -eq -1) { + Write-Verbose "Override will be set for: $elementName" + + if ($Type -eq "Module") { + $Element.Module.InnerText = "$elementName|NO" + } else { + $typeObject.Name = "$elementName|NO" + } + + return $true + } elseif ($Action -eq "Remove" -and + $index -ne -1) { + Write-Verbose "Override will be removed for: $elementName" + + if ($Type -eq "Module") { + $Element.Module.InnerText = $Element.Module.InnerText.Substring(0, $index) + } else { + $typeObject.Name = $typeObject.Name.Substring(0, $index) + } + + return $true + } else { + Write-Verbose "Unable to perform the override configuration. This could be because the override exists or was already removed." + } + + return $false + } + + function GetConfigurationOverrideInfo { + param( + [object[]]$Elements + ) + + Write-Verbose "Calling: $($MyInvocation.MyCommand)" + $returnListObject = New-Object 'System.Collections.Generic.List[object]' + + foreach ($e in $Elements) { + $typeListListObject = New-Object 'System.Collections.Generic.List[object]' + + foreach ($type in $e.Type.Name) { + $name = [string]::Empty + $overrideFound = $false + $name = $type + $index = $name.IndexOf("|NO") + + if ($index -eq -1) { + Write-Verbose "No override was detected for $type" + } else { + Write-Verbose "Override was detected for $type" + $overrideFound = $true + } + + $typeListListObject.Add([PSCustomObject]@{ + Name = $type + OverrideExists = $overrideFound + StartIndex = $index + }) + } + + $elementReturnObject = [PSCustomObject]@{ + TypeList = $e.Name + Types = $typeListListObject + } + + $returnListObject.Add($elementReturnObject) + } + + return $returnListObject + } + function SetConfigurationAttribute { [CmdletBinding(DefaultParameterSetName = "ConfigureOutsideIn", SupportsShouldProcess = $true, ConfirmImpact = 'High')] param( - [Parameter(Mandatory = $true, ParameterSetName = "ConfigureOutsideIn")] - [Parameter(Mandatory = $true, ParameterSetName = "ConfigureFileTypes")] [object[]]$Nodes, - [Parameter(Mandatory = $false, ParameterSetName = "ConfigureOutsideIn")] - [Parameter(Mandatory = $false, ParameterSetName = "ConfigureFileTypes")] - [ValidateSet("ConfigureOutsideIn", "ConfigureFileTypes")] + [ValidateSet("ConfigureOutsideIn", "ConfigureFileTypes", "ConfigureOutsideInOverride", "ConfigureFileTypeOverride")] [string]$ConfigurationMode = "ConfigureOutsideIn", - [Parameter(Mandatory = $true, ParameterSetName = "ConfigureFileTypes")] [object]$FileTypes, - [Parameter(Mandatory = $false, ParameterSetName = "ConfigureOutsideIn")] [string]$ModuleToConfigure = "OutsideInModule.dll", - [Parameter(Mandatory = $false, ParameterSetName = "ConfigureOutsideIn")] - [Parameter(Mandatory = $false, ParameterSetName = "ConfigureFileTypes")] [bool]$Enabled = $false ) Write-Verbose "Calling: $($MyInvocation.MyCommand)" + # Configuration strings are case sensitive and must be set to lowercase true or false $modulesEnabledValue = if ($Enabled) { "true" } else { "false" } - if ($ConfigurationMode -eq "ConfigureFileTypes") { - # We need to clone a node just in case that we need to add file types back to the allowed list - $nodeCloneTemplace = ($Nodes.Node | Where-Object { $null -ne $_.Type } | Select-Object -First 1).CloneNode($true) + if ($ConfigurationMode -eq "ConfigureFileTypes" -or + $ConfigurationMode -eq "ConfigureFileTypeOverride") { + + # We need to clone a node just in case that we need to add file types back to an allowed list + $nodeCloneTemplate = ($Nodes.Node | Where-Object { $null -ne $_.Type } | Select-Object -First 1).CloneNode($true) } - foreach ($n in $Nodes) { - $fileTypesEntry = $null - if ($ConfigurationMode -eq "ConfigureFileTypes") { - - $fileTypesEntry = $FileTypes["$($n.Node.Name)"] - if ($Enabled -eq $false) { - # We are removing the specified file types here - if ($null -ne $fileTypesEntry) { - Write-Verbose "AllowedType: $($n.Node.Name) found - checking for FileType entries now..." - $n.Node.Type | Where-Object { - $_.Name -in $fileTypesEntry - } | ForEach-Object { - Write-Verbose "FileType: $($n.Node.Type) is on the allow list and will be removed now" - [void]($_.ParentNode.RemoveChild($_)) + if ($ConfigurationMode -eq "ConfigureFileTypeOverride") { + + # We add or remove the 'NO' flag for a particular file type as this is supported with the March 2024 SU. + # The 'NO' flag will only be considered by Exchange Server for file types which are part of the 'OutsideInOnly' type list. + $fileTypesConfigurationOverrideInfo = GetConfigurationOverrideInfo -Elements $Nodes.Node + + if ($Enabled) { + foreach ($ft in $FileTypes) { + + $fileTypeFoundInLists = $null + + # Validate the current file type status: file type exists? 'NO' override already set? + $fileTypeFoundInLists = $fileTypesConfigurationOverrideInfo | Where-Object { $_.Types.Name -contains $ft } + + if ($null -ne $fileTypeFoundInLists) { + + if ($fileTypeFoundInLists.TypeList.Count -gt 1) { + + Write-Verbose "File type: $ft is assigned to multiple file type lists and therefore ambiguous - it will be skipped" + Write-Verbose "File type lists the file type is assigned to: $([string]::Join(", ", $fileTypeFoundInLists.TypeList))" + continue + } + + # Conditions for a working override are: + # Override flag must be set (e.g., XlsxOfficePackage|NO) + # File type must be part of the 'OutsideInOnly' type list + + # Check if the file type is in the 'OutsideInOnly' type list and move it, if it's not + if ($fileTypeFoundInLists.TypeList -ne "OutsideInOnly") { + + Write-Verbose "File type: $ft is not part of the 'OutsideInOnly' type list and needs to be moved to it" + MoveFileTypesBetweenNodes -Element $Nodes -FileType $ft -TargetFileTypeList OutsideInOnly + } else { + Write-Verbose "File type: $ft is already part of the 'OutsideInOnly' type list and no further action is required" + } + + # Call the SetConfigurationOverride function to set the override - the function will only set if it's not yet in place + $enableTypeOverrideParams = @{ + Element = $Nodes.Node + Type = "Type" + TypeName = $ft + Action = "Add" } + + SetConfigurationOverride @enableTypeOverrideParams } else { - Write-Verbose "AllowedType: $($n.Node.Name) not found and will be skipped" + Write-Verbose "File type: $ft wasn't found on any file type list" } - } else { - # We will explicitly enable the specified file types here - if ($null -ne $fileTypesEntry) { - Write-Verbose "AllowedType: $($n.Node.Name) found - checking for FileType entries now..." - $fileTypesEntry | ForEach-Object { - if ($n.Node.Type.Name -notcontains $_) { - Write-Verbose "FileType: $_ will be added to the allow list" - $nodeClone = $nodeCloneTemplace.Type.CloneNode($true) - $nodeClone.Name = "$_" - $n.Node.AppendChild($nodeClone) - } else { - Write-Verbose "FileType: $_ is already on the allow list and will be skipped" + } + } else { + + # Reset the file types by moving them back to their original file type list and removing the override flag ('NO') + Write-Verbose "Restoring the file type to file type list mapping and removing the override flag" + MoveFileTypesBetweenNodes -Element $Nodes -RestoreFileTypeList + } + } else { + foreach ($n in $Nodes) { + + $fileTypesEntry = $null + + if ($ConfigurationMode -eq "ConfigureFileTypes") { + + $fileTypesEntry = $FileTypes["$($n.Node.Name)"] + + if ($Enabled -eq $false) { + + # Remove the specified file types from the file type list if they exist + if ($null -ne $fileTypesEntry) { + + Write-Verbose "AllowedType: $($n.Node.Name) found - checking for file types entries now..." + $n.Node.Type | Where-Object { + $_.Name -in $fileTypesEntry + } | ForEach-Object { + Write-Verbose "FileType: $($n.Node.Type) is on the allow types list and will be removed now" + [void]($_.ParentNode.RemoveChild($_)) + } + } else { + Write-Verbose "AllowedType: $($n.Node.Name) not found and will be skipped" + } + } else { + + # Add the specified file types back to the file type list + if ($null -ne $fileTypesEntry) { + + Write-Verbose "AllowedType: $($n.Node.Name) found - checking for file types entries now..." + $fileTypesEntry | ForEach-Object { + if ($n.Node.Type.Name -notcontains $_) { + Write-Verbose "FileType: $_ will be added to the allow list" + $nodeClone = $nodeCloneTemplate.Type.CloneNode($true) + $nodeClone.Name = "$_" + [void]$n.Node.AppendChild($nodeClone) + } else { + Write-Verbose "FileType: $_ is already on the allow types list and will be skipped" + } } } } - } - } elseif ($ConfigurationMode -eq "ConfigureOutsideIn") { - # We explicitly enable or disable the OutsideInModule here - if ($n.Node.InnerText.StartsWith($ModuleToConfigure)) { - Write-Verbose "Setting module: $($n.Node.InnerText) to Enabled: $Enabled" - $n.Node.Attributes["Enabled"].Value = $modulesEnabledValue - } else { - Write-Verbose "Module: $($n.Node.InnerText) will be skipped as it's not related to: $ModuleToConfigure" + } elseif ($ConfigurationMode -eq "ConfigureOutsideIn") { + + # OutsideInModule will explicitly enabled or disabled (Enabled=true or Enabled=false - value is case-sensitive and must be lower case) + if ($n.Node.InnerText.StartsWith($ModuleToConfigure, "CurrentCultureIgnoreCase")) { + Write-Verbose "Setting module: $($n.Node.InnerText) to Enabled: $Enabled" + $n.Node.Attributes["Enabled"].Value = $modulesEnabledValue + } else { + Write-Verbose "Module: $($n.Node.InnerText) will be skipped as it's not related to: $ModuleToConfigure" + } + } elseif ($ConfigurationMode -eq "ConfigureOutsideInOverride") { + + # The 'NO' flag for the OutsideInModule.dll in the 'OutsideInOnly' module list will be set or removed + $outsideInOnlyModuleList = $n.Node.ModuleList | Where-Object { $_.TypeList -eq "OutsideInOnly" } + + if ($null -ne $outsideInOnlyModuleList) { + Write-Verbose "OutsideInOnly module list found - override should be added? $Enabled" + $outsideInOnlyParams = @{ + Element = $outsideInOnlyModuleList + Type = "Module" + Action = if ($Enabled) { "Add" } else { "Remove" } + } + SetConfigurationOverride @outsideInOnlyParams + } } } } @@ -176,16 +506,11 @@ function Invoke-OutsideInModuleAction { function PerformFipFsConfigurationOperation { [CmdletBinding(DefaultParameterSetName = "ConfigureOutsideIn", ConfirmImpact = 'High')] param( - [Parameter(Mandatory = $true, ParameterSetName = "ConfigureOutsideIn")] - [Parameter(Mandatory = $true, ParameterSetName = "ConfigureFileTypes")] [string]$FipFsConfigurationPath, - [Parameter(Mandatory = $true, ParameterSetName = "ConfigureOutsideIn")] - [Parameter(Mandatory = $true, ParameterSetName = "ConfigureFileTypes")] - [ValidateSet("DisableOutsideIn", "EnableOutsideIn", "BlockVulnerableFileTypes", "AllowVulnerableFileTypes")] + [ValidateSet("DisableOutsideIn", "EnableOutsideIn", "BlockVulnerableFileTypes", "AllowVulnerableFileTypes", "EnableOitVersionOverride", "DisableOitVersionOverride", "EnableFileTypesOverride", "DisableFileTypesOverride")] [string]$Operation, - [Parameter(Mandatory = $true, ParameterSetName = "ConfigureFileTypes")] [object]$FileTypesDictionary ) @@ -196,21 +521,44 @@ function Invoke-OutsideInModuleAction { root = "http://schemas.microsoft.com/forefront/2010/1/fs-configuration" sys = "http://schemas.microsoft.com/forefront/2010/1/fs-systemconfiguration" } + + $moduleListsPath = "/root:Configuration/sys:System/sys:TextExtractionSettings/sys:ModuleLists" $modulePath = "/root:Configuration/sys:System/sys:TextExtractionSettings/sys:ModuleLists/sys:ModuleList/sys:Module" $typePath = "/root:Configuration/sys:System/sys:TextExtractionSettings/sys:TypeLists/sys:TypeList" } process { + # Stopping MSExchangeTransport and FMS services - if (StartStopFipFsDependendServices -ServiceAction "Stop") { + if (StartStopFipFsDependentServices -ServiceAction "Stop") { + # Perform backup of the existing configuration.xml - if (BackupFipFsConfiguration -FipFsConfigurationPath $FipFsConfigurationPath) { + $fipFsConfigurationBackup = BackupFipFsConfiguration -FipFsConfigurationPath $FipFsConfigurationPath + + if ($fipFsConfigurationBackup.BackupSuccessful) { try { + Write-Verbose "Operation that should be performed is: $Operation" + [xml]$configuration = Get-Content -Path $FipFsConfigurationPath # Based on how blocking will be done, we need the corresponding path - if ($Operation -eq "DisableOutsideIn" -or + if ($Operation -eq "EnableOitVersionOverride" -or + $Operation -eq "DisableOitVersionOverride") { + + # We need the ModuleLists node here as override will only be considered on 'OutsideInOnly' + $moduleLists = $configuration | Select-Xml -Namespace $configXmlNamespaces -XPath $moduleListsPath + + # We use this to add or remove the 'NO' flag to the 'OutsideInOnly' module list. + # The override will work if the March 2024 SU is installed. + $outsideInOverrideParams = @{ + Nodes = $moduleLists + ConfigurationMode = "ConfigureOutsideInOverride" + Enabled = if ( $Operation -eq "DisableOitVersionOverride") { $false } else { $true } + } + + SetConfigurationAttribute @outsideInOverrideParams + } elseif ($Operation -eq "DisableOutsideIn" -or $Operation -eq "EnableOutsideIn") { - # We use this to completely block the OutsideInModule + # We use this to block the OutsideInModule for all module lists $modules = $configuration | Select-Xml -Namespace $configXmlNamespaces -XPath $modulePath # Perform the action based on the value that was passed via Operation parameter @@ -218,7 +566,21 @@ function Invoke-OutsideInModuleAction { Nodes = $modules Enabled = if ( $Operation -eq "DisableOutsideIn") { $false } else { $true } } + SetConfigurationAttribute @outsideInParams + } elseif ($Operation -eq "EnableFileTypesOverride" -or + $Operation -eq "DisableFileTypesOverride") { + + # We call the function to add or remove file type overrides + $types = $configuration | Select-Xml -Namespace $configXmlNamespaces -XPath $typePath + $fileTypesOverrideParams = @{ + Nodes = $types + ConfigurationMode = "ConfigureFileTypeOverride" + FileTypes = $FileTypesDictionary + Enabled = if ($Operation -eq "DisableFileTypesOverride") { $false } else { $true } + } + + SetConfigurationAttribute @fileTypesOverrideParams } elseif ($Operation -eq "BlockVulnerableFileTypes" -or $Operation -eq "AllowVulnerableFileTypes") { @@ -230,22 +592,26 @@ function Invoke-OutsideInModuleAction { FileTypes = $FileTypesDictionary Enabled = if ($Operation -eq "BlockVulnerableFileTypes") { $false } else { $true } } + SetConfigurationAttribute @fileTypeParams } } catch { - Write-Verbose "We hit an exception while processing the change to the configuration.xml. Please run the script again." + Write-Host "We hit an exception while processing the change to the configuration.xml. Please run the script again." Write-Verbose "Exception: $_" } } else { - Write-Verbose "We fail to create a backup of the configuration.xml file. Please run the script again." + Write-Host "We fail to create a backup of the configuration.xml file. Please run the script again." } } else { - Write-Verbose "We fail to stop the MSExchangeTransport and FMS services. Please run the script again." + Write-Host "We fail to stop the MSExchangeTransport and FMS services. Please run the script again." } } end { - # Save the modified FIP-FS configuration.xml and restart the MSExchangeTransport and FMS services + # Save the modified FIP-FS configuration.xml and finally, restart the MSExchangeTransport and FMS services to make them pick up the changes $configuration.Save($FipFsConfigurationPath) - StartStopFipFsDependendServices -ServiceAction "Start" + if (-not(StartStopFipFsDependentServices -ServiceAction "Start")) { + Write-Host "MSExchangeTransport and FMS services couldn't be restarted." + Write-Host "Please try to restart them manually and if it doesn't work, restore the following backup: $($fipFsConfigurationBackup.BackupFilePath)" + } } } } end { @@ -254,9 +620,27 @@ function Invoke-OutsideInModuleAction { } if ($Configuration -eq "ConfigureFileTypes") { + $fipsOperationParams.Add("FileTypesDictionary", $FileTypesDictionary) $fipsOperationParams.Add("Operation", $(if ($Action -eq "Allow") { "AllowVulnerableFileTypes" } else { "BlockVulnerableFileTypes" })) + } elseif ($Configuration -eq "OutsideInVersionOverride") { + + $fipsOperationParams.Add("Operation", $(if ($Action -eq "Allow") { "EnableOitVersionOverride" } else { "DisableOitVersionOverride" })) + } elseif ($Configuration -eq "FileTypesOverride") { + + if ($Action -eq "Allow" -and + $null -ne $FileTypesDictionary) { + + # We must pass the file types here that should be allowed to use OutsideInModule + $fipsOperationParams.Add("Operation", "EnableFileTypesOverride") + $fipsOperationParams.Add("FileTypesDictionary", $FileTypesDictionary) + } elseif ($Action -eq "Block") { + + # File type list will be restored and 'NO' override flags will be removed (in case they exist) + $fipsOperationParams.Add("Operation", "DisableFileTypesOverride") + } } else { + $fipsOperationParams.Add("Operation", $(if ($Action -eq "Allow") { "EnableOutsideIn" } else { "DisableOutsideIn" })) } diff --git a/docs/Security/CVE-2024-xxxxx.md b/docs/Security/CVE-2024-xxxxx.md index d80507088f..a9976a56c2 100644 --- a/docs/Security/CVE-2024-xxxxx.md +++ b/docs/Security/CVE-2024-xxxxx.md @@ -1,3 +1,3 @@ # CVE-2024-xxxxx -Placeholder \ No newline at end of file +Placeholder diff --git a/mkdocs.yml b/mkdocs.yml index 5c19b2d221..18956c1a85 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -92,6 +92,7 @@ nav: - CVE-2023-23397: - Security/CVE-2023-23397/index.md - FAQ: Security/CVE-2023-23397/FAQ.md + - CVE-2024-xxxxx: Security/CVE-2024-xxxxx.md - EOMT: Security/EOMT.md - EOMTv2: Security/EOMTv2.md - Extended Protection: Security/Extended-Protection.md From da096bbc74bc1633e10ab935f362270a3a0c9384 Mon Sep 17 00:00:00 2001 From: lusassl-msft Date: Wed, 21 Feb 2024 19:57:08 +0100 Subject: [PATCH 10/34] Rename to ConfigureFipFsTextExtractionOverrides --- .../Invoke-OutsideInModuleAction.ps1 | 0 ...ConfigureFipFsTextExtractionOverrides.ps1} | 20 ++++--------------- docs/Security/CVE-2024-xxxxx.md | 3 --- .../ConfigureFipFsTextExtractionOverrides.md | 3 +++ mkdocs.yml | 2 +- 5 files changed, 8 insertions(+), 20 deletions(-) rename Security/src/{CVE-2024-xxxxx => ConfigureFipFsTextExtractionOverrides}/ConfigurationAction/Invoke-OutsideInModuleAction.ps1 (100%) rename Security/src/{CVE-2024-xxxxx/CVE-2024-xxxxx.ps1 => ConfigureFipFsTextExtractionOverrides/ConfigureFipFsTextExtractionOverrides.ps1} (89%) delete mode 100644 docs/Security/CVE-2024-xxxxx.md create mode 100644 docs/Security/ConfigureFipFsTextExtractionOverrides.md diff --git a/Security/src/CVE-2024-xxxxx/ConfigurationAction/Invoke-OutsideInModuleAction.ps1 b/Security/src/ConfigureFipFsTextExtractionOverrides/ConfigurationAction/Invoke-OutsideInModuleAction.ps1 similarity index 100% rename from Security/src/CVE-2024-xxxxx/ConfigurationAction/Invoke-OutsideInModuleAction.ps1 rename to Security/src/ConfigureFipFsTextExtractionOverrides/ConfigurationAction/Invoke-OutsideInModuleAction.ps1 diff --git a/Security/src/CVE-2024-xxxxx/CVE-2024-xxxxx.ps1 b/Security/src/ConfigureFipFsTextExtractionOverrides/ConfigureFipFsTextExtractionOverrides.ps1 similarity index 89% rename from Security/src/CVE-2024-xxxxx/CVE-2024-xxxxx.ps1 rename to Security/src/ConfigureFipFsTextExtractionOverrides/ConfigureFipFsTextExtractionOverrides.ps1 index abfa47df77..49626cd4f8 100644 --- a/Security/src/CVE-2024-xxxxx/CVE-2024-xxxxx.ps1 +++ b/Security/src/ConfigureFipFsTextExtractionOverrides/ConfigureFipFsTextExtractionOverrides.ps1 @@ -28,28 +28,16 @@ .PARAMETER SkipVersionCheck This optional parameter allows you to skip the automatic version check and script update. .EXAMPLE - PS C:\> .\CVE-2024-xxxxx.ps1 -ConfigureMitigation ConfigureFileTypes -Action Block - It will block the vulnerable file types in the FIP-FS configuration file. -.EXAMPLE - PS C:\> .\CVE-2024-xxxxx.ps1 -ConfigureMitigation ConfigureFileTypes -Action Allow - It will add the vulnerable file types back to the the FIP-FS configuration file. -.EXAMPLE - PS C:\> .\CVE-2024-xxxxx.ps1 -ConfigureMitigation ConfigureOutsideIn -Action Block - It will disable the OutsideInModule in the FIP-FS configuration file. -.EXAMPLE - PS C:\> .\CVE-2024-xxxxx.ps1 -ConfigureMitigation ConfigureOutsideIn -Action Allow - It will enable the OutsideInModule in the FIP-FS configuration file. -.EXAMPLE - PS C:\> .\CVE-2024-xxxxx.ps1 -ConfigureOverride OutsideInVersionOverride -Action Allow + PS C:\> .\ConfigureFipFsTextExtractionOverrides.ps1 -ConfigureOverride OutsideInVersionOverride -Action Allow It will add the 'NO' override flag to the OutsideInModule.dll which is defined in the 'OutsideInOnly' module list. .EXAMPLE - PS C:\> .\CVE-2024-xxxxx.ps1 -ConfigureOverride OutsideInVersionOverride -Action Block + PS C:\> .\ConfigureFipFsTextExtractionOverrides.ps1 -ConfigureOverride OutsideInVersionOverride -Action Block It will remove the 'NO' override flag from the OutsideInModule.dll which is defined in the 'OutsideInOnly' module list. .EXAMPLE - PS C:\> .\CVE-2024-xxxxx.ps1 -ConfigureOverride FileTypesOverride -OutsideInEnabledFileTypes "ExcelStorage" + PS C:\> .\ConfigureFipFsTextExtractionOverrides.ps1 -ConfigureOverride FileTypesOverride -OutsideInEnabledFileTypes "ExcelStorage" -Action Allow It will add 'ExcelStorage' file type to the 'OutsideInOnly' file type list and will add the 'NO' flag to the file type. .EXAMPLE - PS C:\> .\CVE-2024-xxxxx.ps1 -RestoreFileTypeList + PS C:\> .\ConfigureFipFsTextExtractionOverrides.ps1 -RestoreFileTypeList It will restore the default file type to file type list mapping and removes any file type override. #> diff --git a/docs/Security/CVE-2024-xxxxx.md b/docs/Security/CVE-2024-xxxxx.md deleted file mode 100644 index a9976a56c2..0000000000 --- a/docs/Security/CVE-2024-xxxxx.md +++ /dev/null @@ -1,3 +0,0 @@ -# CVE-2024-xxxxx - -Placeholder diff --git a/docs/Security/ConfigureFipFsTextExtractionOverrides.md b/docs/Security/ConfigureFipFsTextExtractionOverrides.md new file mode 100644 index 0000000000..7870853507 --- /dev/null +++ b/docs/Security/ConfigureFipFsTextExtractionOverrides.md @@ -0,0 +1,3 @@ +# ConfigureOIOverrides + +Placeholder diff --git a/mkdocs.yml b/mkdocs.yml index 18956c1a85..f4efb44e36 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -92,7 +92,7 @@ nav: - CVE-2023-23397: - Security/CVE-2023-23397/index.md - FAQ: Security/CVE-2023-23397/FAQ.md - - CVE-2024-xxxxx: Security/CVE-2024-xxxxx.md + - ConfigureFipFsTextExtractionOverrides: Security/ConfigureFipFsTextExtractionOverrides.md - EOMT: Security/EOMT.md - EOMTv2: Security/EOMTv2.md - Extended Protection: Security/Extended-Protection.md From 3f68a81c61044a07ad92e401a0f4df0cb706b960 Mon Sep 17 00:00:00 2001 From: lusassl-msft Date: Thu, 29 Feb 2024 08:56:21 +0100 Subject: [PATCH 11/34] First draft of the script documentation --- .../ConfigureFipFsTextExtractionOverrides.md | 70 ++++++++++++++++++- 1 file changed, 68 insertions(+), 2 deletions(-) diff --git a/docs/Security/ConfigureFipFsTextExtractionOverrides.md b/docs/Security/ConfigureFipFsTextExtractionOverrides.md index 7870853507..513a7c2bf0 100644 --- a/docs/Security/ConfigureFipFsTextExtractionOverrides.md +++ b/docs/Security/ConfigureFipFsTextExtractionOverrides.md @@ -1,3 +1,69 @@ -# ConfigureOIOverrides +# ConfigureFipFsTextExtractionOverrides -Placeholder +Download the latest release: [ConfigureFipFsTextExtractionOverrides.ps1](https://github.com/microsoft/CSS-Exchange/releases/latest/download/ConfigureFipFsTextExtractionOverrides.ps1) + +!!! warning "Note" + + With the Exchange Server March 2024 security update we disable the use of the OutsideInModule in Microsoft Exchange Server due to multiple security flaws in the module. The OutsideInModule was used by the Microsoft Forefront Filtering Module to extract information from different file types, to perform content inspection as part of the Exchange Server Data Loss Prevention (DLP) feature. + +The `ConfigureFipFsTextExtractionOverrides.ps1` script can be used to enable file types that should be processed by the help of the `Oracle Outside In Technology` (also known as `OutsideInModule`). The module is used by the `Microsoft Forefront Filtering Module` when Exchange Transport Rules (ETR) or Data Loss Prevention (DLP) rules are in place. + +The script can also be used to override the version of the `OutsideInModule` that should be used. After installing the March 2024 Security Update, Exchange Server will use the latest version of the `OutsideInModule`, which is `8.5.7`, if processing for a file type was explicitly enabled by the help of this script. + +Details about the change that was done as part of the March 2024 security update can be found in [KB123456](https://support.microsoft.com/help/123456). + +Details about the security flaw can be found in the [MSRC security advisory](https://portal.msrc.microsoft.com/security-guidance/advisory/ADV123456). + +!!! warning "Warning" + + We strongly recommend to not override the OutsideInModule version as this could make the server vulnerable! Do not use this override unless explicitly advised by Microsoft to do so. + +## 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 enables processing of `Jpeg` and `AutoCad` file types by the help of the `OutsideInModule` on the server where the command was executed. + +```powershell +.\ConfigureFipFsTextExtractionOverrides.ps1 -ConfigureOverride "Jpeg", "AutoCad" -Action "Allow" +``` + +This syntax disables processing of `Jpeg` and `AutoCad` file types by the help of the `OutsideInModule` on the server `ExchangeSrv01` and `ExchangeSrv02`. + +```powershell +.\ConfigureFipFsTextExtractionOverrides.ps1 -ExchangeServerNames ExchangeSrv01, ExchangeSrv02 -ConfigureOverride "Jpeg", "AutoCad" -Action "Block" +``` + +This syntax causes Exchange Server to use the previous version of the `OutsideInModule`. The override will be enabled on the system on which the script was executed. Note that this can make your system vulnerable to known vulnerabilities in the previous version and should not be used unless explicitly advised by Microsoft. + +```powershell +.\ConfigureFipFsTextExtractionOverrides.ps1 -ConfigureOverride "OutsideInModule" -Action "Allow" +``` + +This syntax disables the override of the version of the `OutsideInModule` module on the server `ExchangeSrv01` and `ExchangeSrv02`. + +```powershell +.\ConfigureFipFsTextExtractionOverrides.ps1 -ExchangeServerNames ExchangeSrv01, ExchangeSrv02 -ConfigureOverride "OutsideInModule" -Action "Block" +``` + +This syntax restores the `configuration.xml` from the backup that was created by a previous run of the script on the Exchange server where the script was executed. + +```powershell +.\ConfigureFipFsTextExtractionOverrides.ps1 -Rollback +``` + +## Parameters + +Parameter | Description +----------|------------ +ExchangeServerNames | A list of Exchange servers that you want to run the script against. +SkipExchangeServerNames | A list of Exchange servers that you don't want to execute the configuration action. +ConfigureOverride | A list of file types that should be allowed to be processed by the `OutsideInModule`. It also allows you to override the version of the `OutsideInModule.dll` that should be used by Exchange Server. The following input can be used: `OutsideInModule`, `XlsbOfficePackage`, `XlsmOfficePackage`, `XlsxOfficePackage`, `ExcelStorage`, `DocmOfficePackage`, `DocxOfficePackage`, `PptmOfficePackage`, `PptxOfficePackage`, `WordStorage`, `PowerPointStorage`, `VisioStorage`, `Rtf`, `Xml`, `OdfTextDocument`, `OdfSpreadsheet`, `OdfPresentation`, `OneNote`, `Pdf`, `Html`, `AutoCad`, `Jpeg`, `Tiff`. `OutsideInModule` cannot be used together with other file types. The input is case-sensitive. +Action | String parameter to define the action that should be performed. Input can be `Allow` or `Block`. The default value is: `Block` +Rollback | Switch parameter to restore the `configuration.xml` that was backed-up during a previous run of the script. +ScriptUpdateOnly | Switch parameter to only update the script without performing any other actions. +SkipVersionCheck | Switch parameter to skip the automatic version check and script update. From b95eebbed0f08451ff9b6e38e5e7316e67d4ce5e Mon Sep 17 00:00:00 2001 From: David Paulson Date: Wed, 7 Feb 2024 16:11:57 -0600 Subject: [PATCH 12/34] XmlConfig Framework & StartStop Service --- .../src/Shared/Invoke-StartStopService.ps1 | 42 ++ .../Invoke-XmlConfigurationRemoteAction.ps1 | 527 ++++++++++++++++++ 2 files changed, 569 insertions(+) create mode 100644 Security/src/Shared/Invoke-StartStopService.ps1 create mode 100644 Security/src/Shared/Invoke-XmlConfigurationRemoteAction.ps1 diff --git a/Security/src/Shared/Invoke-StartStopService.ps1 b/Security/src/Shared/Invoke-StartStopService.ps1 new file mode 100644 index 0000000000..385cc6d9f3 --- /dev/null +++ b/Security/src/Shared/Invoke-StartStopService.ps1 @@ -0,0 +1,42 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +<# +.DESCRIPTION +This is used to start or stop services locally on the server. It will return a value of $true if we do not hit an exception. +If an exception does occur, a throw will occur so the caller needs to handle this. +#> +function Invoke-StartStopService { + [CmdletBinding()] + [OutputType([bool])] + param( + [Parameter(Mandatory = $true)] + [string[]]$ServiceName, + + [Parameter(Mandatory = $true)] + [ValidateSet("Start", "Stop")] + [string]$Action + ) + process { + Write-Verbose "Calling: $($MyInvocation.MyCommand)" + try { + if ($Action -eq "Stop") { + Write-Verbose "Stopping Services: $([string]::Join(", ", $ServiceName))" + foreach ($name in $ServiceName) { + Stop-Service -Name $name -Force -ErrorAction Stop + } + } else { + Write-Verbose "Starting Services: $([string]::Join(", ", $ServiceName))" + foreach ($name in $ServiceName) { + Start-Service -Name $name -ErrorAction Stop + } + } + } catch { + Write-Verbose "Unable able to perform $Action action on the server. Inner Exception $_" + # caller should be handling the exceptions that are occurring. So we should throw if we run into an issue. + throw + } + + return $true + } +} diff --git a/Security/src/Shared/Invoke-XmlConfigurationRemoteAction.ps1 b/Security/src/Shared/Invoke-XmlConfigurationRemoteAction.ps1 new file mode 100644 index 0000000000..8fdfd0ba7c --- /dev/null +++ b/Security/src/Shared/Invoke-XmlConfigurationRemoteAction.ps1 @@ -0,0 +1,527 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +<# +TODO List +- Verify the PassWhatIf is needed. +- Provide detail description of process and docs +- Add in Write-Process logic +- Try to use json file instead of xml for restore process +- Determine log logic + +#> + +<# +.DESCRIPTION + Execute the configuration actions on the remote server. This is the script block to be sent to the server. + + InputObject + [string]FilePath + [object[]]Actions + [string]SelectNodesFilter + [string]OperationType AcceptedValues: RemoveNode, SetAttribute, AppendAttribute, MoveNode, ReplaceAttributeValue + [object]Operation + Type = SetAttribute + [string]AttributeName + [string]Value + Type = AppendAttribute + [string]AttributeName + [string]Value + Type = ReplaceAttributeValue + [string]AttributeName + [string]Value + [string]ReplaceValue + Type = MoveNode + [string]MoveToSelectNodesFilter + + # This is only required if the SelectNodesFilter doesn't contain a narrow filtered request where only 1 node is returned. + [string]ParentNodeAttributeNameFilterAdd + [string]BackupFileName + [object]Restore + [string]FileName + [bool]PassedWhatIf ?? need this? +#> +function Invoke-XmlConfigurationRemoteAction { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [object]$InputObject + ) + begin { + Write-Verbose "Calling: $($MyInvocation.MyCommand)" + $isRestoreOption = $null -ne $InputObject.Restore + $errorContext = New-Object System.Collections.Generic.List[object] + $restoreActions = New-Object System.Collections.Generic.List[object] + $originalContent = New-Object System.Collections.Generic.List[object] + $allActionsPerformed = $true + $gatheredAllRestoreActions = $true + $saveRawContent = $false + $restoreActionsSaved = $isRestoreOption -eq $true + $alreadySaveRestoreActions = $null + $backupRestoreFilePath = [string]::Empty + $validationFailed = $false + $rootSavePath = [System.IO.Path]::GetDirectoryName($InputObject.FilePath) + $restoreFileName = "XmlConfigurationRestoreCmdlets-{0}.xml" + + if ($isRestoreOption) { + $fileName = $restoreFileName -f $InputObject.Restore.FileName + $backupRestoreFilePath = [System.IO.Path]::Combine($rootSavePath, $fileName) + } elseif (-not ([string]::IsNullOrEmpty($InputObject.BackupFileName))) { + $fileName = $restoreFileName -f $InputObject.BackupFileName + $backupRestoreFilePath = [System.IO.Path]::Combine($rootSavePath, $fileName) + } + } + process { + <# + Restore Xml Structure + [object[]]OriginalContent + [int]Id + [object]Content + [object[]]Actions + [string]RestoreType AcceptedValues: AppendChild, SetAttribute, MoveNode + [string]SelectNodesFilter This should always be the location where we want to handle actions in the main configuration file. + [object]Operation + Type = AppendChild + [int]ContentId + [string]OriginalSelectNodesFilter + Type = SetAttribute + [string]AttributeName + [string]RestoreValue + Type = MoveNode + [string]MoveToSelectNodesFilter + #> + try { + Write-Verbose "-------------------------------------------------" + Write-Verbose "Starting Xml Configuration$(if($isRestoreOption){ " Restore" }) Action: $([DateTime]::Now)" + Write-Verbose "-------------------------------------------------" + + # Verify all the actions to make sure they are valid. + foreach ($action in $InputObject.Actions) { + try { + if ([string]::IsNullOrEmpty($action.SelectNodesFilter)) { + throw "Failed to provide action SelectNodesFilter value." + } + + if ($null -eq $action.OperationType -or + ($action.OperationType -ne "RemoveNode" -and + $action.OperationType -ne "SetAttribute" -and + $action.OperationType -ne "AppendAttribute" -and + $action.OperationType -ne "ReplaceAttributeValue" -and + $action.OperationType -ne "MoveNode")) { + throw "Failed to provide valid action OperationType." + } + + if (($null -eq $action.Operation -and $action.OperationType -ne "RemoveNode") -or + (($action.OperationType -eq "SetAttribute" -or + $action.OperationType -eq "AppendAttribute") -and + ([string]::IsNullOrEmpty($action.Operation.AttributeName) -or + [string]::IsNullOrEmpty($action.Operation.Value))) -or + ($action.OperationType -eq "ReplaceAttributeValue" -and + ([string]::IsNullOrEmpty($action.Operation.AttributeName) -or + [string]::IsNullOrEmpty($action.Operation.Value) -or + $null -eq $action.Operation.ReplaceValue)) -or + ($action.OperationType -eq "MoveNode" -and + ([string]::IsNullOrEmpty($action.Operation.MoveToSelectNodesFilter)))) { + throw "Failed to provide correct Operation values for OperationType '$($action.OperationType)'" + } + } catch { + Write-Verbose "Failed to provide valid Actions object structure. Inner Exception: $_" + $errorContext.Add($_) + $validationFailed = $true + } + } + + if (-not (Test-Path $InputObject.FilePath)) { + $validationFailed = $true + Write-Verbose "Incorrect FilePath provided. '$($InputObject.FilePath)'" + $errorContext.Add((New-Object -TypeName System.IO.FileNotFoundException -ArgumentList "Configuration File Path Not Found: '$($InputObject.FilePath)'")) + } + + if (-not $isRestoreOption -and [string]::IsNullOrEmpty($InputObject.BackupFileName)) { + $validationFailed = $true + Write-Verbose "BackupFileName was not set." + $errorContext.Add((New-Object -TypeName System.Exception -ArgumentList "BackupFileName not set on input object.")) + } + + if ($validationFailed) { return } + + try { + $contentRaw = Get-Content $InputObject.FilePath -ErrorAction Stop -Raw + [xml]$contentXml = $contentRaw + } catch { + Write-Verbose "Failed to load the configuration file. Inner Exception: $_" + $errorContext.Add($_) + return + } + + # attempt to load the current backup file if it exists + if (-not ([string]::IsNullOrEmpty($backupRestoreFilePath))) { + if ((Test-Path $backupRestoreFilePath)) { + Write-Verbose "Backup/Restore file already exists. Attempting to load it." + + try { + $alreadySaveRestoreActions = Import-Clixml $backupRestoreFilePath -ErrorAction Stop + + # Should really look to see if there are multiple matches already. + Write-Verbose "Adding pre-saved restore actions to memory" + foreach ($value in $alreadySaveRestoreActions.Actions) { + Write-Verbose "RestoreType: $($value.RestoreType) SelectNodesFilter: $($value.SelectNodesFilter)" + $restoreActions.Add($value) + } + + Write-Verbose "Loading previous configuration files" + foreach ($content in $alreadySaveRestoreActions.OriginalContent) { + Write-Verbose "Loading Content ID $($content.Id)" + $originalContent.Add($content) + } + } catch { + Write-Verbose "Failed to load the current backup file '$backupRestoreFilePath'." + $errorContext.Add($_) + throw "Failed to load the current backup file. Inner Exception: $_" + } + } else { + Write-Verbose "No Backup/Restore file exists at: '$($backupRestoreFilePath)'" + + if ($isRestoreOption) { + Write-Error "Unable to restore due to no restore file. '$backupRestoreFilePath'" + # Must throw to break out and prevent from moving forward + throw "No restore file exists: '$($backupRestoreFilePath)'" + } + } + } + + if ($isRestoreOption) { + # Don't need to worry about if the restore file wasn't there. This was already handled + Write-Verbose "Starting Restore Process" + foreach ($action in $restoreActions) { + try { + Write-Verbose "Trying to find nodes based off filter: '$($action.SelectNodesFilter)'" + $selectNodes = $contentXml.SelectNodes($action.SelectNodesFilter) + Write-Verbose "Found $($selectNodes.Count) node(s)" + + if ($null -eq $selectNodes) { + Write-Verbose "No nodes were found with the current filter. Unable to perform restore action. Filter: $($action.SelectNodesFilter)" + # TODO: Determine how to handle + continue + } + + if ($selectNodes.Count -gt 1) { + throw "Multiple nodes found in restore process for filter '$($action.SelectNodesFilter)'. Unable to continue." + } else { + $selectNode = $selectNodes[0] # This is required to be able to do AppendChild + } + + if ($action.RestoreType -eq "AppendChild") { + # This restore type we have to find the node to restore from the saved configuration content. + Write-Verbose "Attempting to find the original content by Id $($action.Operation.ContentId)" + $content = ($originalContent | Where-Object { $_.Id -eq $action.Operation.ContentId }).Content + + if ($null -eq $content) { + throw "No Restore Content Found for the AppendChild to restore." + } + + # Now we need to find the node again. + $restoreSelectNodes = ([xml]$content).SelectNodes($action.Operation.OriginalSelectNodesFilter) + + if ($null -eq $restoreSelectNodes) { + throw "Failed to find the OriginalSelectNodesFilter: '$($action.Operation.OriginalSelectNodesFilter)'" + } + + # Possible multiple nodes? Need to look into this + foreach ($node in $restoreSelectNodes) { + $importNode = $contentXml.ImportNode($node, $true) + [void]$selectNode.AppendChild($importNode) + } + } elseif ($action.RestoreType -eq "SetAttribute") { + if ($null -eq $selectNode.($action.Operation.AttributeName)) { + throw "Attribute '$($action.Operation.AttributeName)' currently doesn't exist on node." + } + + Write-Verbose "Setting attribute '$($action.Operation.AttributeName)' with value of '$($action.Operation.RestoreValue)'" + $selectNode.($action.Operation.AttributeName) = $action.Operation.RestoreValue + } elseif ($action.RestoreType -eq "MoveNode") { + $moveToNodeLocation = $contentXml.SelectNodes($action.Operation.MoveToSelectNodesFilter) + + if ($null -eq $moveToNodeLocation) { + throw "Failed to find node selection to move to" + } + + if ($moveToNodeLocation.Count -gt 1) { + throw "Found multiple node locations to move to. This is unsupported." + } + + [void]$selectNode.ParentNode.RemoveChild($selectNode) + [void]$moveToNodeLocation.AppendChild($selectNode) + } + } catch { + $allActionsPerformed = $false + Write-Verbose "Failed to restore a setting. Inner Exception: $_" + $errorContext.Add($_) + } + } + + if ($errorContext.Count -gt 0) { + Write-Warning "Errors occurred preventing the restore from completing." + return + } + + try { + # Now try to save out the file + $contentXml.Save($InputObject.FilePath) + } catch { + $allActionsPerformed = $false + Write-Verbose "Failed to save configuration file. Inner exception: $_" + $errorContext.Add($_) + return + } + + try { + Remove-Item $backupRestoreFilePath -Force -ErrorAction Stop + Write-Verbose "Successfully removed the restore file." + } catch { + $allActionsPerformed = $false + Write-Verbose "Failed to remove the restore file. Inner Exception: $_" + $errorContext.Add($_) + return + } + + return + } + + # for each action provided, do the action. + foreach ($action in $InputObject.Actions) { + Write-Verbose "Trying to find SelectNodes based off filter: '$($action.SelectNodesFilter)'" + $selectNodes = $contentXml.SelectNodes($action.SelectNodesFilter) + + if ($null -eq $selectNodes) { + # This shouldn't be treated as an error. + Write-Verbose "No nodes were found with the current filter. This could be the action was already taken or doesn't exist." + continue + } + + <# + It is ideal to always narrow down your filter so only 1 item is returned. + This is going to be a requirement if the Action is to set anything other than RemoveNode. + However, if the calculated parent node select filter would return multiple nodes, then we will also throw an issue. + This is to prevent any issues with the restore process and making sure that only the correct setting gets added back to the correct location. + #> + Write-Verbose "Found $($selectNodes.Count) Node(s)" + + if ($selectNodes.Count -gt 1 -and + $action.OperationType -ne "RemoveNode") { + throw "Multiple Nodes found with filter '$($action.SelectNodesFilter)'. This breaks the restore logic and are unable to continue." + } + + foreach ($node in $selectNodes) { + + try { + + $currentRestoreAction = [PSCustomObject]@{ + RestoreType = "NotSet" + SelectNodesFilter = [string]::Empty + Operation = $null + } + + if ($action.OperationType -eq "RemoveNode") { + + $lastIndexOf = $action.SelectNodesFilter.LastIndexOf("/") + + if ($lastIndexOf -eq -1) { + throw "Failed to provide a filter that would have a parent node to restore to." + } + + $parentSelectNodesFilter = $action.SelectNodesFilter.Substring(0, $lastIndexOf) + $testSelectNodesResults = $contentXml.SelectNodes($parentSelectNodesFilter) + + if ($testSelectNodesResults.Count -gt 1) { + throw "Multiple nodes returned for parent node which will result in restore process failure. Unable to continue." + } + + Write-Verbose "Parent Select Nodes Filter Passed: $($parentSelectNodesFilter)" + $currentRestoreAction.RestoreType = "AppendChild" + $currentRestoreAction.SelectNodesFilter = $parentSelectNodesFilter + $currentRestoreAction.Operation = [PSCustomObject]@{ + ContentId = $originalContent.Count + OriginalSelectNodesFilter = $action.SelectNodesFilter + } + # Need to handle What if scenario here + [void]$node.ParentNode.RemoveChild($node) + Write-Verbose "Successfully removed node." + } elseif ($action.OperationType -eq "MoveNode") { + $lastChildNode = $action.SelectNodesFilter.Substring($action.SelectNodesFilter.LastIndexOf("/")) + $moveToNodeLocation = $contentXml.SelectNodes($action.Operation.MoveToSelectNodesFilter) + + if ($null -eq $moveToNodeLocation) { + throw "Failed to find node selection to move to" + } + + if ($moveToNodeLocation.Count -gt 1) { + throw "Found multiple node locations to move to. This is unsupported." + } + + if ([string]::IsNullOrEmpty($action.Operation.ParentNodeAttributeNameFilterAdd)) { + $moveToSelectNodesFilter = $action.SelectNodesFilter.Replace($lastChildNode, "") + } else { + $moveToSelectNodesFilter = $action.SelectNodesFilter.Replace($lastChildNode, "") + + "[@$($action.Operation.ParentNodeAttributeNameFilterAdd)='$($node.ParentNode.($action.Operation.ParentNodeAttributeNameFilterAdd))']" + } + + $currentRestoreAction.RestoreType = "MoveNode" + $currentRestoreAction.SelectNodesFilter = $action.Operation.MoveToSelectNodesFilter + $lastChildNode + $currentRestoreAction.Operation = [PSCustomObject]@{ + MoveToSelectNodesFilter = $moveToSelectNodesFilter + } + + # Now verify that we can move it back for the restore process. + Write-Verbose "Verifying possible restore process with filter: $($currentRestoreAction.Operation.MoveToSelectNodesFilter)" + $verifyMoveBack = $contentXml.SelectNodes($currentRestoreAction.Operation.MoveToSelectNodesFilter) + + if ($null -eq $verifyMoveBack -or + $verifyMoveBack.Count -gt 1) { + throw "Found multiple node locations for the move back. Since we are unable to restore, preventing move from occurring." + } + + [void]$node.ParentNode.RemoveChild($node) + [void]$moveToNodeLocation.AppendChild($node) + } elseif ($action.OperationType -eq "SetAttribute" -or + $action.OperationType -eq "AppendAttribute" -or + $action.OperationType -eq "ReplaceAttributeValue") { + + if ($null -eq $node.($action.Operation.AttributeName)) { + throw "Attribute '$($action.Operation.AttributeName)' doesn't exist on this node" + } + + $currentRestoreAction.RestoreType = "SetAttribute" + $currentRestoreAction.SelectNodesFilter = $action.SelectNodesFilter + $currentRestoreAction.Operation = [PSCustomObject]@{ + AttributeName = $action.Operation.AttributeName + RestoreValue = $node.($action.Operation.AttributeName) + } + Write-Verbose "Stored the current value of the attribute. '$($currentRestoreAction.Operation.RestoreValue)'" + + if ($action.OperationType -eq "AppendAttribute") { + $currentValue = $node.($action.Operation.AttributeName) + $newAppendValue = $node.($action.Operation.AttributeName) + $action.Operation.Value + # during restore process, we can run into issues with action type of AppendAttribute if the SelectNodesFilter contains + # the attribute that you are appending a value for. + $lastChildNode = $action.SelectNodesFilter.Substring($action.SelectNodesFilter.LastIndexOf("/")) + $splitResults = $lastChildNode.Split("[").Split("]") + if ($splitResults -contains "@$($action.Operation.AttributeName)='$($currentValue)'" -or + $splitResults -contains "@$($action.Operation.AttributeName)=`"$($currentValue)`"") { + + if ($lastChildNode.IndexOf($currentValue) -ne $lastChildNode.LastIndexOf($currentValue)) { + throw "Last child node contains multiple entries for the current value. Unable to determine new filter to use on restore." + } + + $updatedReplaceChildNode = $lastChildNode.Replace($currentValue, $newAppendValue) + $currentRestoreAction.SelectNodesFilter = $currentRestoreAction.SelectNodesFilter.Replace($lastChildNode, $updatedReplaceChildNode) + Write-Verbose "Updated SelectNodesFilter to: $($currentRestoreAction.SelectNodesFilter)" + } + + $node.($action.Operation.AttributeName) = $newAppendValue + } elseif ($action.OperationType -eq "ReplaceAttributeValue") { + # With this operation, we need to treat this similar as AppendAttribute value with handling the restore process + $currentValue = $node.($action.Operation.AttributeName) + $newReplaceValue = $currentValue.Replace($action.Operation.Value, $action.Operation.ReplaceValue) + # TODO: Replace current code with function + $lastChildNode = $action.SelectNodesFilter.Substring($action.SelectNodesFilter.LastIndexOf("/")) + $splitResults = $lastChildNode.Split("[").Split("]") + if ($splitResults -contains "@$($action.Operation.AttributeName)='$($currentValue)'" -or + $splitResults -contains "@$($action.Operation.AttributeName)=`"$($currentValue)`"") { + + if ($lastChildNode.IndexOf($currentValue) -ne $lastChildNode.LastIndexOf($currentValue)) { + throw "Last child node contains multiple entries for the current value. Unable to determine new filter to use on restore." + } + + $updatedReplaceChildNode = $lastChildNode.Replace($currentValue, $newReplaceValue) + $currentRestoreAction.SelectNodesFilter = $currentRestoreAction.SelectNodesFilter.Replace($lastChildNode, $updatedReplaceChildNode) + Write-Verbose "Updated SelectNodesFilter to: $($currentRestoreAction.SelectNodesFilter)" + } + + $node.($action.Operation.AttributeName) = $newReplaceValue + } else { + # Need to handle what if scenario here + $node.($action.Operation.AttributeName) = $action.Operation.Value + Write-Verbose "Successfully reset the value to '$($action.Operation.Value)'" + } + } + + # Add Current Restore to list if needed. + if ($null -ne $alreadySaveRestoreActions) { + $matchFound = $null -ne ($restoreActions | + Where-Object { $_.RestoreType -eq $currentRestoreAction.RestoreType -and + $_.SelectNodesFilter -eq $currentRestoreAction.SelectNodesFilter }) + } + + if ($null -eq $alreadySaveRestoreActions -or $matchFound -eq $false) { + Write-Verbose "Adding new restore action" + $restoreActions.Add($currentRestoreAction) + + # Since we are adding a new restore action, we need to check to see if the action requires you to save the original content + if ($currentRestoreAction.RestoreType -eq "AppendChild") { + $saveRawContent = $true + } + } else { + Write-Verbose "Found match, don't overwrite setting. Not adding to restore action" + } + } catch { + Write-Verbose "Ran into an exception while executing the actions. Inner Exception: $_" + $errorContext.Add($_) + # Determine if we want to break out of here. + } + } + } + + # If there has been an error, we don't want to continue. + if ($errorContext.Count -gt 0) { return } + + try { + if ($saveRawContent) { + $originalContent.Add(([PSCustomObject]@{ + Id = $originalContent.Count + Content = $contentRaw + })) + } + $restoreActionResults = [PSCustomObject]@{ + OriginalContent = $originalContent + Actions = $restoreActions + } + # Maybe we don't want to save if nothing new was added. + # Save out the restore action prior to saving the configuration file. + $restoreActionResults | Export-Clixml -Path $backupRestoreFilePath -Encoding utf8 -Force -ErrorAction Stop + Write-Verbose "Successfully saved out the restore actions to path: $backupRestoreFilePath" + $restoreActionsSaved = $true + } catch { + Write-Verbose "Unable to export Restore Actions. Inner Exception: $_" + $errorContext.Add($_) + return + } + + try { + $contentXml.Save($InputObject.FilePath) + Write-Verbose "Successfully saved out the configuration file to path: $($InputObject.FilePath)" + } catch { + Write-Verbose "Failed to save the updated configuration file. Inner Exception: $_" + $errorContext.Add($_) + } + } catch { + Write-Verbose "Failed to compete Xml Configuration Execution. Inner Exception: $_" + $errorContext.Add($_) + return + } + } + end { + Write-Verbose "Ending Xml Configuration$(if($isRestoreOption) { " Restore"}) Action: $([DateTime]::Now)" + Write-Verbose "-------------------------------------------------" + + return [PSCustomObject]@{ + ComputerName = $env:COMPUTERNAME + AllActionsPerformed = $allActionsPerformed + GatheredAllRestoreActions = $gatheredAllRestoreActions + RestoreActions = $restoreActions + RestoreActionsSaved = $restoreActionsSaved + SuccessfulExecution = $allActionsPerformed -and $gatheredAllRestoreActions -and $restoreActionsSaved -and $errorContext.Count -eq 0 + ErrorContext = $errorContext + } + } +} From 22adf5e566e668d7e749839b7d2fe1daa84201e3 Mon Sep 17 00:00:00 2001 From: David Paulson Date: Thu, 22 Feb 2024 15:35:11 -0600 Subject: [PATCH 13/34] Invoke-TextExtractionOverride logic added --- .../Invoke-TextExtractionOverride.ps1 | 193 ++++++++++++++++++ .../ConfigureFipFsTextExtractionOverrides.ps1 | 53 +++-- 2 files changed, 227 insertions(+), 19 deletions(-) create mode 100644 Security/src/ConfigureFipFsTextExtractionOverrides/ConfigurationAction/Invoke-TextExtractionOverride.ps1 diff --git a/Security/src/ConfigureFipFsTextExtractionOverrides/ConfigurationAction/Invoke-TextExtractionOverride.ps1 b/Security/src/ConfigureFipFsTextExtractionOverrides/ConfigurationAction/Invoke-TextExtractionOverride.ps1 new file mode 100644 index 0000000000..173b703fe2 --- /dev/null +++ b/Security/src/ConfigureFipFsTextExtractionOverrides/ConfigurationAction/Invoke-TextExtractionOverride.ps1 @@ -0,0 +1,193 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +function Invoke-TextExtractionOverride { + [CmdletBinding()] + param( + [Parameter(Mandatory)] + [string[]]$ComputerName, + + [Parameter(Mandatory = $false)] + [string[]]$ConfigureOverride, + + [string]$Action, + + [switch]$Rollback + ) + begin { + $remoteScriptBlockExecute = { + param($ArgumentList) + . $PSScriptRoot\..\..\..\..\Shared\Get-RemoteRegistryValue.ps1 + . $PSScriptRoot\..\..\Shared\Invoke-StartStopService.ps1 + . $PSScriptRoot\..\..\Shared\Invoke-XmlConfigurationRemoteAction.ps1 + + $VerbosePreference = $Using:VerbosePreference # To be able to write back to the host screen if -Verbose is used. + + if ($null -eq $ArgumentList -or + (($null -eq $ArgumentList.Rollback -or $false -eq $ArgumentList.Rollback) -and + ($null -eq $ArgumentList.ConfigureOverride -or $null -eq $ArgumentList.Action))) { + throw "Invalid ArgumentList provided to remote execution." + } + + # We need to hard code this, which isn't ideal. But this is the best option that we have at the moment. + $defaultTypeLocations = @{ + "XlsbOfficePackage" = "Excel" + "XlsmOfficePackage" = "Excel" + "XlsxOfficePackage" = "Excel" + "ExcelStorage" = "Excel" + "DocmOfficePackage" = "PreferIFilters" + "DocxOfficePackage" = "PreferIFilters" + "PptmOfficePackage" = "PreferIFilters" + "PptxOfficePackage" = "PreferIFilters" + "WordStorage" = "PreferIFilters" + "PowerPointStorage" = "PreferIFilters" + "VisioStorage" = "PreferIFilters" + "Rtf" = "PreferIFilters" + "Xml" = "PreferIFilters" + "OdfTextDocument" = "PreferIFilters" + "OdfSpreadsheet" = "PreferIFilters" + "OdfPresentation" = "PreferIFilters" + "OneNote" = "PreferIFilters" + "Pdf" = "PreferOutsideIn" + "Html" = "PreferOutsideIn" + "AutoCad" = "OutsideInOnly" + "Jpeg" = "OutsideInOnly" + "Tiff" = "OutsideInOnly" + } + + $baseXPathFilter = "//*[local-name()='Configuration']/*[local-name()='System']/*[local-name()='TextExtractionSettings']" + $outsideInOnlyModuleXPathFilter = $baseXPathFilter + + "/*[local-name()='ModuleLists']/*[local-name()='ModuleList'][@TypeList='OutsideInOnly']/*[local-name()='Module'][contains(., 'OutsideInModule.dll')]" + $typeListBaseXPathFilter = $baseXPathFilter + "/*[local-name()='TypeLists']/*[local-name()='TypeList'][@Name='{0}']" + $getTypeBaseTypeListXPathFilter = $baseXPathFilter + + "/*[local-name()='TypeLists']/*[local-name()='TypeList']/*[local-name()='Type'][contains(@Name, '{0}')]" + + $fipFsDatabaseParams = @{ + MachineName = $env:COMPUTERNAME + SubKey = "SOFTWARE\Microsoft\ExchangeServer\v15\FIP-FS" + GetValue = "DatabasePath" + } + $fipFsDatabasePath = Get-RemoteRegistryValue @fipFsDatabaseParams + + if (([string]::IsNullOrEmpty($fipFsDatabasePath))) { + throw "Unable to find FIP FS Database Path" + } + + $path = (Join-Path $fipFsDatabasePath "Configuration.xml") + Write-Verbose "Using the database path of '$path' to adjust" + + $xmlConfigurationRemoteAction = [PSCustomObject]@{ + FilePath = $path + BackupFileName = "TextExtractionOverride" + Actions = (New-Object System.Collections.Generic.List[object]) + } + + # Always Stop the services first + # TODO: Determine if we need to stop the services or need to restart + $serviceResult = Invoke-StartStopService -ServiceName "MSExchangeTransport", "FMS" -Action "Stop" + + if ($true -eq $serviceResult) { + + if (-not $ArgumentList.Rollback) { + # If we got a true result, we stopped the service + # Now create the actions list + foreach ($configureActionOverride in $ArgumentList.ConfigureOverride) { + if ($configureActionOverride -eq "OutsideInModule") { + # If configureActionOverride is OutsideInModule then we are setting that path only. + $actionOperation = [PSCustomObject]@{ + SelectNodesFilter = $outsideInOnlyModuleXPathFilter + OperationType = [string]::Empty + Operation = [PSCustomObject]@{ + AttributeName = "#text" + Value = "|NO" + ReplaceValue = [string]::Empty + } + } + + if ($ArgumentList.Action -eq "Allow") { + $actionOperation.OperationType = "AppendAttribute" + $xmlConfigurationRemoteAction.Actions.Add($actionOperation) + } elseif ($ArgumentList.Action -eq "Block") { + $actionOperation.OperationType = "ReplaceAttributeValue" + $xmlConfigurationRemoteAction.Actions.Add($actionOperation) + } + } else { + # Now everything else is attempting to do the following on the Type: + # Either set or remove the |NO flag + # Move the Type to the TypeList OutsideInOnly as that is the only location where the |NO flag is honored + $baseFilter = $getTypeBaseTypeListXPathFilter -f $configureActionOverride + + if ($ArgumentList.Action -eq "Allow") { + + $xmlConfigurationRemoteAction.Actions.Add(([PSCustomObject]@{ + SelectNodesFilter = $baseFilter + OperationType = "MoveNode" + Operation = [PSCustomObject]@{ + MoveToSelectNodesFilter = ($typeListBaseXPathFilter -f "OutsideInOnly") + ParentNodeAttributeNameFilterAdd = "Name" + } + })) + + $xmlConfigurationRemoteAction.Actions.Add(([PSCustomObject]@{ + SelectNodesFilter = $baseFilter + OperationType = "AppendAttribute" + Operation = [PSCustomObject]@{ + AttributeName = "Name" + Value = "|NO" + } + })) + } elseif ($ArgumentList.Action -eq "Block") { + $xmlConfigurationRemoteAction.Actions.Add(([PSCustomObject]@{ + SelectNodesFilter = $baseFilter + OperationType = "ReplaceAttributeValue" + Operation = [PSCustomObject]@{ + AttributeName = "Name" + Value = "|NO" + ReplaceValue = [string]::Empty + } + })) + + $xmlConfigurationRemoteAction.Actions.Add(([PSCustomObject]@{ + SelectNodesFilter = $baseFilter + OperationType = "MoveNode" + Operation = [PSCustomObject]@{ + MoveToSelectNodesFilter = ($typeListBaseXPathFilter -f $defaultTypeLocations[$configureActionOverride]) + ParentNodeAttributeNameFilterAdd = "Name" + } + })) + } + } + } + } else { + $xmlConfigurationRemoteAction | Add-Member -MemberType NoteProperty -Name "Restore" -Value ([PSCustomObject]@{ + FileName = $xmlConfigurationRemoteAction.BackupFileName + }) + } + + # Now that we have the list of actions, we need to execute the results then determine if we were successful or not. + $results = Invoke-XmlConfigurationRemoteAction -InputObject $xmlConfigurationRemoteAction + Write-Host "" + + if ($results.SuccessfulExecution) { + Write-Host "[$env:COMPUTERNAME] Successfully completed the configuration for FIP FS Text Extraction Override" + } else { + Write-Warning "$env:COMPUTERNAME Failed to execution configuration action for FIP FS Text Extraction Override" + } + + Write-Host "" + } + + # Attempt to start the service again, even if we failed to stop. One could have worked. + $serviceResult = Invoke-StartStopService -ServiceName "MSExchangeTransport", "FMS" -Action "Start" + + # TODO Add a return object here to better process + } + } + process { + Invoke-Command -ComputerName $ComputerName -ScriptBlock $remoteScriptBlockExecute -ArgumentList ([PSCustomObject]@{ + ConfigureOverride = $ConfigureOverride + Action = $Action + Rollback = $Rollback + }) + } +} diff --git a/Security/src/ConfigureFipFsTextExtractionOverrides/ConfigureFipFsTextExtractionOverrides.ps1 b/Security/src/ConfigureFipFsTextExtractionOverrides/ConfigureFipFsTextExtractionOverrides.ps1 index 49626cd4f8..1a4e9686b2 100644 --- a/Security/src/ConfigureFipFsTextExtractionOverrides/ConfigureFipFsTextExtractionOverrides.ps1 +++ b/Security/src/ConfigureFipFsTextExtractionOverrides/ConfigureFipFsTextExtractionOverrides.ps1 @@ -41,50 +41,58 @@ It will restore the default file type to file type list mapping and removes any file type override. #> -[CmdletBinding(DefaultParameterSetName = "ConfigureMitigation", SupportsShouldProcess = $true, ConfirmImpact = 'High')] +[CmdletBinding(DefaultParameterSetName = "ConfigureOverride", SupportsShouldProcess = $true, ConfirmImpact = 'High')] param( - [Parameter(Mandatory = $true, ParameterSetName = "ConfigureMitigation")] - [ValidateSet("ConfigureOutsideIn", "ConfigureFileTypes")] - [string]$ConfigureMitigation, - - [Parameter(Mandatory = $true, ParameterSetName = "ConfigureOverride")] - [ValidateSet("OutsideInVersionOverride", "FileTypesOverride")] - [string]$ConfigureOverride, + [Parameter(Mandatory = $false, ValueFromPipeline, ParameterSetName = "ConfigureOverride")] + [Parameter(Mandatory = $false, ValueFromPipeline, ParameterSetName = "Rollback")] + [string[]]$ExchangeServerNames, [Parameter(Mandatory = $false, ParameterSetName = "ConfigureOverride")] - [object]$OutsideInEnabledFileTypes, + [Parameter(Mandatory = $false, ParameterSetName = "Rollback")] + [string[]]$SkipExchangeServerNames, - [Parameter(Mandatory = $true, ParameterSetName = "RestoreFileTypeList")] - [switch]$RestoreFileTypeList, + [Parameter(Mandatory = $true, ParameterSetName = "ConfigureOverride")] + [ValidateSet("OutsideInModule", "XlsbOfficePackage", "XlsmOfficePackage", "XlsxOfficePackage", "ExcelStorage" , "DocmOfficePackage", + "DocxOfficePackage", "PptmOfficePackage", "PptxOfficePackage", "WordStorage", "PowerPointStorage", "VisioStorage", "Rtf", + "Xml", "OdfTextDocument", "OdfSpreadsheet", "OdfPresentation", "OneNote", "Pdf", "Html", "AutoCad", "Jpeg", "Tiff", IgnoreCase = $false)] + [string[]]$ConfigureOverride, - [Parameter(Mandatory = $false, ParameterSetName = "ConfigureMitigation")] [Parameter(Mandatory = $false, ParameterSetName = "ConfigureOverride")] [ValidateSet("Allow", "Block")] [string]$Action = "Block", + [Parameter(Mandatory = $true, ParameterSetName = "Rollback")] + [switch]$Rollback, + [Parameter(Mandatory = $false, ParameterSetName = "ScriptUpdateOnly")] [switch]$ScriptUpdateOnly, - [Parameter(Mandatory = $false, ParameterSetName = "Default")] [switch]$SkipVersionCheck ) begin { - . $PSScriptRoot\ConfigurationAction\Invoke-OutsideInModuleAction.ps1 + . $PSScriptRoot\ConfigurationAction\Invoke-TextExtractionOverride.ps1 . $PSScriptRoot\..\..\..\Shared\GenericScriptStartLogging.ps1 . $PSScriptRoot\..\..\..\Shared\ScriptUpdateFunctions\GenericScriptUpdate.ps1 - $fileTypesDictionary = New-Object 'System.Collections.Generic.Dictionary[string, array]' + if ($ConfigureOverride.Count -gt 1 -and $ConfigureOverride -contains "OutsideInModule") { + Write-Error "OutsideInModule ConfigureOverride can only be processed by itself." + exit + } - # Add all vulnerable file types here that should be removed from the allowed types list - $fileTypesDictionary.Add("Excel", @("ExcelStorage")) - $fileTypesDictionary.Add("PreferOutsideIn", @("Html", "Pdf")) + $includeExchangeServerNames = New-Object System.Collections.Generic.List[string] +} process { + foreach ($server in $ExchangeServerNames) { + $includeExchangeServerNames.Add($server) + } } end { try { # TODO adjust the disclaimer wording to match the latest adjustment $exchangeServicesWording = "Note that each Exchange server's MSExchangeTransport and FMS service will be restarted to backup and apply the setting change action." $vulnerabilityMoreInformationWording = "More information about the vulnerability can be found here: https://portal.msrc.microsoft.com/security-guidance/advisory/CVE-2024-xxxxx." + # TODO: Update Disclaimer section. + if ($Configuration -eq "ConfigureOutsideIn" -and $Action -eq "Block") { $params = @{ @@ -158,7 +166,14 @@ begin { } } - Invoke-OutsideInModuleAction @invokeOutsideInModuleActionParams + $params = @{ + ComputerName = $includeExchangeServerNames + ConfigureOverride = $ConfigureOverride + Action = $Action + Rollback = $Rollback + } + + Invoke-TextExtractionOverride @params } finally { Write-Host "" Write-Host "Do you have feedback regarding the script? Please let us know: ExToolsFeedback@microsoft.com." From 75a89eb40e8dad6415b816604c40732ce3665cd9 Mon Sep 17 00:00:00 2001 From: David Paulson Date: Wed, 28 Feb 2024 12:22:53 -0600 Subject: [PATCH 14/34] Add server processing list logic --- .../ConfigureFipFsTextExtractionOverrides.ps1 | 54 +++------- .../src/Shared/Get-ProcessedServerList.ps1 | 101 ++++++++++++++++++ 2 files changed, 118 insertions(+), 37 deletions(-) create mode 100644 Security/src/Shared/Get-ProcessedServerList.ps1 diff --git a/Security/src/ConfigureFipFsTextExtractionOverrides/ConfigureFipFsTextExtractionOverrides.ps1 b/Security/src/ConfigureFipFsTextExtractionOverrides/ConfigureFipFsTextExtractionOverrides.ps1 index 1a4e9686b2..ce78fc4e63 100644 --- a/Security/src/ConfigureFipFsTextExtractionOverrides/ConfigureFipFsTextExtractionOverrides.ps1 +++ b/Security/src/ConfigureFipFsTextExtractionOverrides/ConfigureFipFsTextExtractionOverrides.ps1 @@ -72,6 +72,7 @@ param( begin { . $PSScriptRoot\ConfigurationAction\Invoke-TextExtractionOverride.ps1 + . $PSScriptRoot\..\Shared\Get-ProcessedServerList.ps1 . $PSScriptRoot\..\..\..\Shared\GenericScriptStartLogging.ps1 . $PSScriptRoot\..\..\..\Shared\ScriptUpdateFunctions\GenericScriptUpdate.ps1 @@ -87,6 +88,13 @@ begin { } } end { try { + + if ($includeExchangeServerNames.Count -eq 0 -and + ($null -eq $SkipExchangeServerNames -or $SkipExchangeServerNames.Count -eq 0)) { + Write-Host "Only going to attempt to run against the local server '$($env:COMPUTERNAME)' since no servers were provided." + $includeExchangeServerNames.Add($env:COMPUTERNAME) + } + # TODO adjust the disclaimer wording to match the latest adjustment $exchangeServicesWording = "Note that each Exchange server's MSExchangeTransport and FMS service will be restarted to backup and apply the setting change action." $vulnerabilityMoreInformationWording = "More information about the vulnerability can be found here: https://portal.msrc.microsoft.com/security-guidance/advisory/CVE-2024-xxxxx." @@ -128,51 +136,23 @@ begin { Show-Disclaimer @params - if (-not([string]::IsNullOrEmpty($ConfigureMitigation))) { - # Mitigation mode was selected. In this mode the script will: - # a) disable the OutsideInModule.dll for all file types - # or - # b) remove vulnerable file types from the file types lists that make use of OutsideInModule.dll - $invokeOutsideInModuleActionParams = @{ - Configuration = $ConfigureMitigation - Action = $Action - } - - if ($ConfigureMitigation -eq "ConfigureFileTypes") { - $invokeOutsideInModuleActionParams.Add("FileTypesDictionary", $fileTypesDictionary) - } - } elseif (-not([string]::IsNullOrEmpty($ConfigureOverride))) { - # Configuration override mode was selected. In this mode the script will: - # a) allows you to add the override flag ('NO') to the OutsideInVersion.dll which is part of the OutsideInOnly module list - # or - # b) allows you to add the override flag to file types that are part of the file type list - # the file type will also moved to the OutsideInOnly file type list (if it's yet part of it) - $invokeOutsideInModuleActionParams = @{ - Configuration = $ConfigureOverride - Action = $Action - } - - if (-not([string]::IsNullOrWhiteSpace($OutsideInEnabledFileTypes))) { - $invokeOutsideInModuleActionParams.Add("FileTypesDictionary", $OutsideInEnabledFileTypes) - } - } elseif ($RestoreFileTypeList) { - # File type list restore mode was selected. In this mode the script will: - # a) restore the file type to file type list mapping - # and - # b) remove the override from any file type that has an override ('NO') set - $invokeOutsideInModuleActionParams = @{ - Configuration = "FileTypesOverride" - Action = "Block" - } + $processParams = @{ + ExchangeServerNames = $includeExchangeServerNames + SkipExchangeServerNames = $SkipExchangeServerNames + CheckOnline = $true + DisableGetExchangeServerFullList = $includeExchangeServerNames.Count -gt 0 # if we pass a list, we shouldn't need to get all the servers in the org. } + $processedExchangeServers = Get-ProcessedServerList @processParams + $params = @{ - ComputerName = $includeExchangeServerNames + ComputerName = $processedExchangeServers.OnlineExchangeServerFqdn ConfigureOverride = $ConfigureOverride Action = $Action Rollback = $Rollback } + Write-Host "Running the configuration change against the following server(s): $([string]::Join(", ", $params.ComputerName))" Invoke-TextExtractionOverride @params } finally { Write-Host "" diff --git a/Security/src/Shared/Get-ProcessedServerList.ps1 b/Security/src/Shared/Get-ProcessedServerList.ps1 new file mode 100644 index 0000000000..64f36b7880 --- /dev/null +++ b/Security/src/Shared/Get-ProcessedServerList.ps1 @@ -0,0 +1,101 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +. $PSScriptRoot\..\..\..\Shared\Invoke-ScriptBlockHandler.ps1 +function Get-ProcessedServerList { + [CmdletBinding()] + param( + [string[]]$ExchangeServerNames, + + [string[]]$SkipExchangeServerNames, + + [bool]$CheckOnline, + + [bool]$DisableGetExchangeServerFullList + ) + begin { + Write-Verbose "Calling: $($MyInvocation.MyCommand)" + $getExchangeServer = New-Object System.Collections.Generic.List[object] + $validExchangeServer = New-Object System.Collections.Generic.List[object] + $validExchangeServerFqdn = New-Object System.Collections.Generic.List[string] + $onlineExchangeServer = New-Object System.Collections.Generic.List[object] + $onlineExchangeServerFqdn = New-Object System.Collections.Generic.List[string] + } + process { + if ($DisableGetExchangeServerFullList) { + # If we don't want to get all the Exchange Servers, then we need to make sure the list of Servers are Exchange Server + if ($null -eq $ExchangeServerNames -or + $ExchangeServerNames.Count -eq 0) { + throw "Must provide servers to process when DisableGetExchangeServerFullList is set." + } + + Write-Verbose "Getting the result of the Exchange Servers individually" + foreach ($server in $ExchangeServerNames) { + try { + $result = Get-ExchangeServer $server -ErrorAction Stop + $getExchangeServer.Add($result) + } catch { + Write-Verbose "Failed to run Get-ExchangeServer for server '$server'. Inner Exception $_" + throw + } + } + } else { + Write-Verbose "Getting all the Exchange Servers in the organization" + $result = @(Get-ExchangeServer) + $getExchangeServer.AddRange($result) + } + + if ($null -ne $ExchangeServerNames -and $ExchangeServerNames.Count -gt 0) { + $getExchangeServer | + Where-Object { ($_.Name -in $ExchangeServerNames) -or ($_.FQDN -in $ExchangeServerNames) } | + ForEach-Object { + if ($null -ne $SkipExchangeServerNames -and $SkipExchangeServerNames.Count -gt 0) { + if (($_.Name -notin $SkipExchangeServerNames) -and ($_.FQDN -notin $SkipExchangeServerNames)) { + Write-Verbose "Adding Server $($_.Name) to the valid server list" + $validExchangeServer.Add($_) + } + } else { + Write-Verbose "Adding Server $($_.Name) to the valid server list" + $validExchangeServer.Add($_) + } + } + } else { + if ($null -ne $SkipExchangeServerNames -and $SkipExchangeServerNames.Count -gt 0) { + $getExchangeServer | + Where-Object { ($_.Name -notin $SkipExchangeServerNames) -and ($_.FQDN -notin $SkipExchangeServerNames) } | + ForEach-Object { + Write-Verbose "Adding Server $($_.Name) to the valid server list" + $validExchangeServer.Add($_) + } + } else { + Write-Verbose "Adding Server $($_.Name) to the valid server list" + $validExchangeServer.AddRange($getExchangeServer) + } + } + + $validExchangeServer | ForEach-Object { $validExchangeServerFqdn.Add($_.FQDN ) } + + if ($CheckOnline) { + Write-Verbose "Will check to see if the servers are online" + foreach ($server in $validExchangeServer) { + $result = Invoke-ScriptBlockHandler -ComputerName $server -ScriptBlock { return $env:COMPUTERNAME } + + if ($null -ne $result) { + $onlineExchangeServer.Add($server) + $onlineExchangeServerFqdn.Add($Server.FQDN) + } else { + Write-Verbose "Server $($server.Name) not online" + } + } + } + } + end { + return [PSCustomObject]@{ + ValidExchangeServer = $validExchangeServer + ValidExchangeServerFqdn = $validExchangeServerFqdn + GetExchangeServer = $getExchangeServer + OnlineExchangeServer = $onlineExchangeServer + OnlineExchangeServerFqdn = $onlineExchangeServerFqdn + } + } +} From a2862f88801c3442a3138e364afb8c8e2b08bef7 Mon Sep 17 00:00:00 2001 From: David Paulson Date: Thu, 29 Feb 2024 12:54:29 -0600 Subject: [PATCH 15/34] Remove Invoke-OutsideInModuleAction file --- .../Invoke-OutsideInModuleAction.ps1 | 649 ------------------ 1 file changed, 649 deletions(-) delete mode 100644 Security/src/ConfigureFipFsTextExtractionOverrides/ConfigurationAction/Invoke-OutsideInModuleAction.ps1 diff --git a/Security/src/ConfigureFipFsTextExtractionOverrides/ConfigurationAction/Invoke-OutsideInModuleAction.ps1 b/Security/src/ConfigureFipFsTextExtractionOverrides/ConfigurationAction/Invoke-OutsideInModuleAction.ps1 deleted file mode 100644 index dd4df9c9e6..0000000000 --- a/Security/src/ConfigureFipFsTextExtractionOverrides/ConfigurationAction/Invoke-OutsideInModuleAction.ps1 +++ /dev/null @@ -1,649 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. - -. $PSScriptRoot\..\..\..\..\Shared\Get-RemoteRegistryValue.ps1 - -function Invoke-OutsideInModuleAction { - [CmdletBinding(DefaultParameterSetName = "ConfigureOutsideIn", ConfirmImpact = 'High')] - param( - [Parameter(Mandatory = $true, ParameterSetName = "ConfigureOutsideIn")] - [Parameter(Mandatory = $true, ParameterSetName = "ConfigureFileTypes")] - [ValidateSet("ConfigureOutsideIn", "ConfigureFileTypes", "OutsideInVersionOverride", "FileTypesOverride")] - [string]$Configuration, - - [Parameter(Mandatory = $true, ParameterSetName = "ConfigureFileTypes")] - [object]$FileTypesDictionary, - - [Parameter(Mandatory = $true, ParameterSetName = "ConfigureOutsideIn")] - [Parameter(Mandatory = $true, ParameterSetName = "ConfigureFileTypes")] - [ValidateSet("Allow", "Block")] - [string]$Action - ) - - begin { - Write-Verbose "Calling: $($MyInvocation.MyCommand)" - - function GetFipFsConfigurationPath { - param( - [string]$MachineName = $env:COMPUTERNAME - ) - - Write-Verbose "Calling: $($MyInvocation.MyCommand)" - - $fipFsDatabaseParams = @{ - MachineName = $MachineName - SubKey = "SOFTWARE\Microsoft\ExchangeServer\v15\FIP-FS" - GetValue = "DatabasePath" - } - - Write-Verbose "Trying to detect FIP-FS database path for machine: $MachineName" - $fipFsDatabasePath = Get-RemoteRegistryValue @fipFsDatabaseParams - - if (-not[System.String]::IsNullOrWhiteSpace($fipFsDatabasePath)) { - Write-Verbose "FIP-FS database path is: $fipFsDatabasePath" - return (Join-Path $fipFsDatabasePath "Configuration.xml") - } else { - Write-Verbose "Unable to read FIP-FS database path from registry" - return $null - } - } - - function BackupFipFsConfiguration { - param( - [string]$FipFsConfigurationPath - ) - - Write-Verbose "Calling: $($MyInvocation.MyCommand)" - $returnObject = @{ - BackupSuccessful = $false - BackupFilePath = $null - } - $backupTimestamp = Get-Date -Format yyyyMMddhhmmss - - if (Test-Path -Path $FipFsConfigurationPath) { - Write-Verbose "FIP-FS configuration file detected" - $configurationBackupPath = $FipFsConfigurationPath + ".$backupTimestamp" + ".bak" - Copy-Item -Path $FipFsConfigurationPath -Destination $configurationBackupPath - - Write-Verbose "Backup configuration is: $configurationBackupPath" - $returnObject.BackupFilePath = $configurationBackupPath - $returnObject.BackupSuccessful = $true - } else { - Write-Verbose "FIP-FS configuration file doesn't exist" - } - - return $returnObject - } - - function StartStopFipFsDependentServices { - param( - [ValidateSet("Start", "Stop")] - [string]$ServiceAction - ) - - Write-Verbose "Calling: $($MyInvocation.MyCommand)" - try { - if ($ServiceAction -eq "Stop") { - Write-Verbose "Stopping MSExchangeTransport and FMS services..." - Stop-Service -Name "FMS" -Force - } else { - Write-Verbose "Starting MSExchangeTransport and FMS services..." - Start-Service -Name "MSExchangeTransport" - Start-Service -Name "FMS" - } - } catch { - Write-Verbose "We hit an exception while performing services action: $ServiceAction" - Write-Verbose "Exception: $_" - - return $false - } - - return $true - } - - function MoveFileTypesBetweenNodes { - param( - [object]$Element, - - [string]$FileType, - - [ValidateSet("Text", "Excel", "PreferIFilters", "IFiltersOnly", "PreferOutsideIn", "OutsideInOnly")] - [string]$TargetFileTypeList, - - [switch]$RestoreFileTypeList - ) - - Write-Verbose "Calling: $($MyInvocation.MyCommand)" - - # Mapping of the file types specified in the configuration.xml to the corresponding module list - $fileTypesModuleMapping = @{ - "Text" = "Text" - "XlsbOfficePackage" = "Excel" - "XlsmOfficePackage" = "Excel" - "XlsxOfficePackage" = "Excel" - "ExcelStorage" = "Excel" - "DocmOfficePackage" = "PreferIFilters" - "DocxOfficePackage" = "PreferIFilters" - "PptmOfficePackage" = "PreferIFilters" - "PptxOfficePackage" = "PreferIFilters" - "WordStorage" = "PreferIFilters" - "PowerPointStorage" = "PreferIFilters" - "VisioStorage" = "PreferIFilters" - "Rtf" = "PreferIFilters" - "Xml" = "PreferIFilters" - "OdfTextDocument" = "PreferIFilters" - "OdfSpreadsheet" = "PreferIFilters" - "OdfPresentation" = "PreferIFilters" - "OneNote" = "PreferIFilters" - "VsdmOfficePackage" = "IFiltersOnly" - "VsdxOfficePackage" = "IFiltersOnly" - "VssmOfficePackage" = "IFiltersOnly" - "VssxOfficePackage" = "IFiltersOnly" - "VstmOfficePackage" = "IFiltersOnly" - "VstxOfficePackage" = "IFiltersOnly" - "VisioXml" = "IFiltersOnly" - "PublisherStorage" = "IFiltersOnly" - "Html" = "PreferOutsideIn" - "Pdf" = "PreferOutsideIn" - "AutoCad" = "OutsideInOnly" - "Jpeg" = "OutsideInOnly" - "Tiff" = "OutsideInOnly" - } - - # Clone a node so that we could reuse it to add file types back - $nodeCloneTemplate = ($Element.Node | Where-Object { $null -ne $_.Type } | Select-Object -First 1).CloneNode($true) - - if ($null -eq $nodeCloneTemplate) { - Write-Verbose "Fail to clone a file type node - function cannot continue" - return $false - } - - if (-not($RestoreFileTypeList)) { - Write-Verbose "Function will move file type: $FileType to file type list: $TargetFileTypeList" - - # Get the target node to which the file type should be moved - $targetNode = $Element.node | Where-Object { $_.Name -eq $TargetFileTypeList } - - if ($null -ne $targetNode) { - - # Remove the file type from its current file type list - $Element.Node.Type | Where-Object { - $_.Name.StartsWith("$FileType", "CurrentCultureIgnoreCase") - } | ForEach-Object { - Write-Verbose "FileType: $FileType will be removed from the $($Element.Node.Name) file type list" - [void]($_.ParentNode.RemoveChild($_)) - } - - # Add the file type to the file type list that was passed to the function via TargetFileTypeList parameter - Write-Verbose "FileType: $FileType will be added to the allow list: $TargetFileTypeList" - $nodeClone = $nodeCloneTemplate.Type.CloneNode($true) - $nodeClone.Name = "$FileType" - [void]$targetNode.AppendChild($nodeClone) - } else { - Write-Verbose "Target file type list wasn't found and as a result, the function can't continue" - } - } else { - - Write-Verbose "Restoring the original file type to file type list mapping" - - # Process each file type list node - foreach ($e in $Element) { - - # Process each file type which is assigned to the file type list - foreach ($type in $e.Node.Type.Name) { - - $moveToFileTypeList = [string]::Empty - $fileTypeSearchString = $type - $i = $type.IndexOf("|NO") - - if ($i -ne -1) { - Write-Verbose "File type: $type has an override flag assigned which will be removed" - # Remove the "|NO" override flag for all file types - $resetTypeOverride = @{ - Element = $e.Node - Type = "Type" - TypeName = $type - Action = "Remove" - } - - SetConfigurationOverride @resetTypeOverride - - $fileTypeSearchString = $type.Replace("|NO", "") - } - - # Find the file type list in the mapping table and skip it, if we don't find it in the list - $moveToFileTypeList = $fileTypesModuleMapping["$fileTypeSearchString"] - $targetFileTypeListNode = $Element.Node | Where-Object { $_.Name -eq $moveToFileTypeList } - - if ([string]::IsNullOrEmpty($moveToFileTypeList)) { - Write-Verbose "No mapping exists for file type: $fileTypeSearchString - this file type will be skipped" - continue - } - - # If the file type is already in the original file type list, no further action is required - skip it too - if (($Element.Node | Where-Object { $_.Name -eq $moveToFileTypeList }).Type.Name -contains $fileTypeSearchString) { - Write-Verbose "File type: $fileTypeSearchString is already in the default file type list" - continue - } - - # Remove the file type from its current file type list if a drift to the original mapping was detected - $e.Node.Type | Where-Object { - $_.Name.StartsWith("$fileTypeSearchString", "CurrentCultureIgnoreCase") - } | ForEach-Object { - Write-Verbose "FileType: $fileTypeSearchString will be removed from the $($e.Node.Name) file type list" - [void]($_.ParentNode.RemoveChild($_)) - } - - # Add the file type back to the original file type list based on the original mapping - Write-Verbose "FileType: $fileTypeSearchString will be added back to type list: $moveToFileTypeList" - $nodeClone = $nodeCloneTemplate.Type.CloneNode($true) - $nodeClone.Name = "$fileTypeSearchString" - [void]$targetFileTypeListNode.AppendChild($nodeClone) - } - } - } - } - - function SetConfigurationOverride { - [CmdletBinding(DefaultParameterSetName = "Module")] - [OutputType([boolean])] - param( - [object]$Element, - - [ValidateSet("Module", "Type")] - [string]$Type, - - [string]$TypeName, - - [ValidateSet("Add", "Remove")] - [string]$Action - ) - - Write-Verbose "Calling: $($MyInvocation.MyCommand)" - - # TODO: Optimize this code to remove duplicate code paths - if ($Type -eq "Module") { - $elementName = $Element.Module.InnerText - } else { - $typeObject = $Element.Type | Where-Object { $_.Name.StartsWith("$TypeName", "CurrentCultureIgnoreCase") } - $elementName = $typeObject.Name - } - - if ($elementName.Count -gt 1) { - Write-Verbose "Element contains multiple modules/types which can't be processed by this function" - return $false - } - - if ($Type -eq "Module") { - $index = $Element.Module.InnerText.IndexOf("|NO") - } else { - $index = $elementName.IndexOf("|NO") - } - - if ($Action -eq "Add" -and - $index -eq -1) { - Write-Verbose "Override will be set for: $elementName" - - if ($Type -eq "Module") { - $Element.Module.InnerText = "$elementName|NO" - } else { - $typeObject.Name = "$elementName|NO" - } - - return $true - } elseif ($Action -eq "Remove" -and - $index -ne -1) { - Write-Verbose "Override will be removed for: $elementName" - - if ($Type -eq "Module") { - $Element.Module.InnerText = $Element.Module.InnerText.Substring(0, $index) - } else { - $typeObject.Name = $typeObject.Name.Substring(0, $index) - } - - return $true - } else { - Write-Verbose "Unable to perform the override configuration. This could be because the override exists or was already removed." - } - - return $false - } - - function GetConfigurationOverrideInfo { - param( - [object[]]$Elements - ) - - Write-Verbose "Calling: $($MyInvocation.MyCommand)" - $returnListObject = New-Object 'System.Collections.Generic.List[object]' - - foreach ($e in $Elements) { - $typeListListObject = New-Object 'System.Collections.Generic.List[object]' - - foreach ($type in $e.Type.Name) { - $name = [string]::Empty - $overrideFound = $false - $name = $type - $index = $name.IndexOf("|NO") - - if ($index -eq -1) { - Write-Verbose "No override was detected for $type" - } else { - Write-Verbose "Override was detected for $type" - $overrideFound = $true - } - - $typeListListObject.Add([PSCustomObject]@{ - Name = $type - OverrideExists = $overrideFound - StartIndex = $index - }) - } - - $elementReturnObject = [PSCustomObject]@{ - TypeList = $e.Name - Types = $typeListListObject - } - - $returnListObject.Add($elementReturnObject) - } - - return $returnListObject - } - - function SetConfigurationAttribute { - [CmdletBinding(DefaultParameterSetName = "ConfigureOutsideIn", SupportsShouldProcess = $true, ConfirmImpact = 'High')] - param( - [object[]]$Nodes, - - [ValidateSet("ConfigureOutsideIn", "ConfigureFileTypes", "ConfigureOutsideInOverride", "ConfigureFileTypeOverride")] - [string]$ConfigurationMode = "ConfigureOutsideIn", - - [object]$FileTypes, - - [string]$ModuleToConfigure = "OutsideInModule.dll", - - [bool]$Enabled = $false - ) - - Write-Verbose "Calling: $($MyInvocation.MyCommand)" - - # Configuration strings are case sensitive and must be set to lowercase true or false - $modulesEnabledValue = if ($Enabled) { "true" } else { "false" } - - if ($ConfigurationMode -eq "ConfigureFileTypes" -or - $ConfigurationMode -eq "ConfigureFileTypeOverride") { - - # We need to clone a node just in case that we need to add file types back to an allowed list - $nodeCloneTemplate = ($Nodes.Node | Where-Object { $null -ne $_.Type } | Select-Object -First 1).CloneNode($true) - } - - if ($ConfigurationMode -eq "ConfigureFileTypeOverride") { - - # We add or remove the 'NO' flag for a particular file type as this is supported with the March 2024 SU. - # The 'NO' flag will only be considered by Exchange Server for file types which are part of the 'OutsideInOnly' type list. - $fileTypesConfigurationOverrideInfo = GetConfigurationOverrideInfo -Elements $Nodes.Node - - if ($Enabled) { - foreach ($ft in $FileTypes) { - - $fileTypeFoundInLists = $null - - # Validate the current file type status: file type exists? 'NO' override already set? - $fileTypeFoundInLists = $fileTypesConfigurationOverrideInfo | Where-Object { $_.Types.Name -contains $ft } - - if ($null -ne $fileTypeFoundInLists) { - - if ($fileTypeFoundInLists.TypeList.Count -gt 1) { - - Write-Verbose "File type: $ft is assigned to multiple file type lists and therefore ambiguous - it will be skipped" - Write-Verbose "File type lists the file type is assigned to: $([string]::Join(", ", $fileTypeFoundInLists.TypeList))" - continue - } - - # Conditions for a working override are: - # Override flag must be set (e.g., XlsxOfficePackage|NO) - # File type must be part of the 'OutsideInOnly' type list - - # Check if the file type is in the 'OutsideInOnly' type list and move it, if it's not - if ($fileTypeFoundInLists.TypeList -ne "OutsideInOnly") { - - Write-Verbose "File type: $ft is not part of the 'OutsideInOnly' type list and needs to be moved to it" - MoveFileTypesBetweenNodes -Element $Nodes -FileType $ft -TargetFileTypeList OutsideInOnly - } else { - Write-Verbose "File type: $ft is already part of the 'OutsideInOnly' type list and no further action is required" - } - - # Call the SetConfigurationOverride function to set the override - the function will only set if it's not yet in place - $enableTypeOverrideParams = @{ - Element = $Nodes.Node - Type = "Type" - TypeName = $ft - Action = "Add" - } - - SetConfigurationOverride @enableTypeOverrideParams - } else { - Write-Verbose "File type: $ft wasn't found on any file type list" - } - } - } else { - - # Reset the file types by moving them back to their original file type list and removing the override flag ('NO') - Write-Verbose "Restoring the file type to file type list mapping and removing the override flag" - MoveFileTypesBetweenNodes -Element $Nodes -RestoreFileTypeList - } - } else { - foreach ($n in $Nodes) { - - $fileTypesEntry = $null - - if ($ConfigurationMode -eq "ConfigureFileTypes") { - - $fileTypesEntry = $FileTypes["$($n.Node.Name)"] - - if ($Enabled -eq $false) { - - # Remove the specified file types from the file type list if they exist - if ($null -ne $fileTypesEntry) { - - Write-Verbose "AllowedType: $($n.Node.Name) found - checking for file types entries now..." - $n.Node.Type | Where-Object { - $_.Name -in $fileTypesEntry - } | ForEach-Object { - Write-Verbose "FileType: $($n.Node.Type) is on the allow types list and will be removed now" - [void]($_.ParentNode.RemoveChild($_)) - } - } else { - Write-Verbose "AllowedType: $($n.Node.Name) not found and will be skipped" - } - } else { - - # Add the specified file types back to the file type list - if ($null -ne $fileTypesEntry) { - - Write-Verbose "AllowedType: $($n.Node.Name) found - checking for file types entries now..." - $fileTypesEntry | ForEach-Object { - if ($n.Node.Type.Name -notcontains $_) { - Write-Verbose "FileType: $_ will be added to the allow list" - $nodeClone = $nodeCloneTemplate.Type.CloneNode($true) - $nodeClone.Name = "$_" - [void]$n.Node.AppendChild($nodeClone) - } else { - Write-Verbose "FileType: $_ is already on the allow types list and will be skipped" - } - } - } - } - } elseif ($ConfigurationMode -eq "ConfigureOutsideIn") { - - # OutsideInModule will explicitly enabled or disabled (Enabled=true or Enabled=false - value is case-sensitive and must be lower case) - if ($n.Node.InnerText.StartsWith($ModuleToConfigure, "CurrentCultureIgnoreCase")) { - Write-Verbose "Setting module: $($n.Node.InnerText) to Enabled: $Enabled" - $n.Node.Attributes["Enabled"].Value = $modulesEnabledValue - } else { - Write-Verbose "Module: $($n.Node.InnerText) will be skipped as it's not related to: $ModuleToConfigure" - } - } elseif ($ConfigurationMode -eq "ConfigureOutsideInOverride") { - - # The 'NO' flag for the OutsideInModule.dll in the 'OutsideInOnly' module list will be set or removed - $outsideInOnlyModuleList = $n.Node.ModuleList | Where-Object { $_.TypeList -eq "OutsideInOnly" } - - if ($null -ne $outsideInOnlyModuleList) { - Write-Verbose "OutsideInOnly module list found - override should be added? $Enabled" - $outsideInOnlyParams = @{ - Element = $outsideInOnlyModuleList - Type = "Module" - Action = if ($Enabled) { "Add" } else { "Remove" } - } - SetConfigurationOverride @outsideInOnlyParams - } - } - } - } - } - - function PerformFipFsConfigurationOperation { - [CmdletBinding(DefaultParameterSetName = "ConfigureOutsideIn", ConfirmImpact = 'High')] - param( - [string]$FipFsConfigurationPath, - - [ValidateSet("DisableOutsideIn", "EnableOutsideIn", "BlockVulnerableFileTypes", "AllowVulnerableFileTypes", "EnableOitVersionOverride", "DisableOitVersionOverride", "EnableFileTypesOverride", "DisableFileTypesOverride")] - [string]$Operation, - - [object]$FileTypesDictionary - ) - - begin { - Write-Verbose "Calling: $($MyInvocation.MyCommand)" - - $configXmlNamespaces = @{ - root = "http://schemas.microsoft.com/forefront/2010/1/fs-configuration" - sys = "http://schemas.microsoft.com/forefront/2010/1/fs-systemconfiguration" - } - - $moduleListsPath = "/root:Configuration/sys:System/sys:TextExtractionSettings/sys:ModuleLists" - $modulePath = "/root:Configuration/sys:System/sys:TextExtractionSettings/sys:ModuleLists/sys:ModuleList/sys:Module" - $typePath = "/root:Configuration/sys:System/sys:TextExtractionSettings/sys:TypeLists/sys:TypeList" - } process { - - # Stopping MSExchangeTransport and FMS services - if (StartStopFipFsDependentServices -ServiceAction "Stop") { - - # Perform backup of the existing configuration.xml - $fipFsConfigurationBackup = BackupFipFsConfiguration -FipFsConfigurationPath $FipFsConfigurationPath - - if ($fipFsConfigurationBackup.BackupSuccessful) { - try { - Write-Verbose "Operation that should be performed is: $Operation" - - [xml]$configuration = Get-Content -Path $FipFsConfigurationPath - - # Based on how blocking will be done, we need the corresponding path - if ($Operation -eq "EnableOitVersionOverride" -or - $Operation -eq "DisableOitVersionOverride") { - - # We need the ModuleLists node here as override will only be considered on 'OutsideInOnly' - $moduleLists = $configuration | Select-Xml -Namespace $configXmlNamespaces -XPath $moduleListsPath - - # We use this to add or remove the 'NO' flag to the 'OutsideInOnly' module list. - # The override will work if the March 2024 SU is installed. - $outsideInOverrideParams = @{ - Nodes = $moduleLists - ConfigurationMode = "ConfigureOutsideInOverride" - Enabled = if ( $Operation -eq "DisableOitVersionOverride") { $false } else { $true } - } - - SetConfigurationAttribute @outsideInOverrideParams - } elseif ($Operation -eq "DisableOutsideIn" -or - $Operation -eq "EnableOutsideIn") { - - # We use this to block the OutsideInModule for all module lists - $modules = $configuration | Select-Xml -Namespace $configXmlNamespaces -XPath $modulePath - - # Perform the action based on the value that was passed via Operation parameter - $outsideInParams = @{ - Nodes = $modules - Enabled = if ( $Operation -eq "DisableOutsideIn") { $false } else { $true } - } - - SetConfigurationAttribute @outsideInParams - } elseif ($Operation -eq "EnableFileTypesOverride" -or - $Operation -eq "DisableFileTypesOverride") { - - # We call the function to add or remove file type overrides - $types = $configuration | Select-Xml -Namespace $configXmlNamespaces -XPath $typePath - $fileTypesOverrideParams = @{ - Nodes = $types - ConfigurationMode = "ConfigureFileTypeOverride" - FileTypes = $FileTypesDictionary - Enabled = if ($Operation -eq "DisableFileTypesOverride") { $false } else { $true } - } - - SetConfigurationAttribute @fileTypesOverrideParams - } elseif ($Operation -eq "BlockVulnerableFileTypes" -or - $Operation -eq "AllowVulnerableFileTypes") { - - # We use this to partially blocking the vulnerable file types - $types = $configuration | Select-Xml -Namespace $configXmlNamespaces -XPath $typePath - $fileTypeParams = @{ - Nodes = $types - ConfigurationMode = "ConfigureFileTypes" - FileTypes = $FileTypesDictionary - Enabled = if ($Operation -eq "BlockVulnerableFileTypes") { $false } else { $true } - } - - SetConfigurationAttribute @fileTypeParams - } - } catch { - Write-Host "We hit an exception while processing the change to the configuration.xml. Please run the script again." - Write-Verbose "Exception: $_" - } - } else { - Write-Host "We fail to create a backup of the configuration.xml file. Please run the script again." - } - } else { - Write-Host "We fail to stop the MSExchangeTransport and FMS services. Please run the script again." - } - } end { - # Save the modified FIP-FS configuration.xml and finally, restart the MSExchangeTransport and FMS services to make them pick up the changes - $configuration.Save($FipFsConfigurationPath) - if (-not(StartStopFipFsDependentServices -ServiceAction "Start")) { - Write-Host "MSExchangeTransport and FMS services couldn't be restarted." - Write-Host "Please try to restart them manually and if it doesn't work, restore the following backup: $($fipFsConfigurationBackup.BackupFilePath)" - } - } - } - } end { - $fipsOperationParams = @{ - FipFsConfigurationPath = GetFipFsConfigurationPath - } - - if ($Configuration -eq "ConfigureFileTypes") { - - $fipsOperationParams.Add("FileTypesDictionary", $FileTypesDictionary) - $fipsOperationParams.Add("Operation", $(if ($Action -eq "Allow") { "AllowVulnerableFileTypes" } else { "BlockVulnerableFileTypes" })) - } elseif ($Configuration -eq "OutsideInVersionOverride") { - - $fipsOperationParams.Add("Operation", $(if ($Action -eq "Allow") { "EnableOitVersionOverride" } else { "DisableOitVersionOverride" })) - } elseif ($Configuration -eq "FileTypesOverride") { - - if ($Action -eq "Allow" -and - $null -ne $FileTypesDictionary) { - - # We must pass the file types here that should be allowed to use OutsideInModule - $fipsOperationParams.Add("Operation", "EnableFileTypesOverride") - $fipsOperationParams.Add("FileTypesDictionary", $FileTypesDictionary) - } elseif ($Action -eq "Block") { - - # File type list will be restored and 'NO' override flags will be removed (in case they exist) - $fipsOperationParams.Add("Operation", "DisableFileTypesOverride") - } - } else { - - $fipsOperationParams.Add("Operation", $(if ($Action -eq "Allow") { "EnableOutsideIn" } else { "DisableOutsideIn" })) - } - - PerformFipFsConfigurationOperation @fipsOperationParams - } -} From ef5e884ff40544f1db3d047318dde10648c44bdf Mon Sep 17 00:00:00 2001 From: David Paulson Date: Thu, 29 Feb 2024 16:25:43 -0600 Subject: [PATCH 16/34] Avoid repeat appends --- Security/src/Shared/Invoke-XmlConfigurationRemoteAction.ps1 | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Security/src/Shared/Invoke-XmlConfigurationRemoteAction.ps1 b/Security/src/Shared/Invoke-XmlConfigurationRemoteAction.ps1 index 8fdfd0ba7c..9510b4b8d5 100644 --- a/Security/src/Shared/Invoke-XmlConfigurationRemoteAction.ps1 +++ b/Security/src/Shared/Invoke-XmlConfigurationRemoteAction.ps1 @@ -401,6 +401,11 @@ function Invoke-XmlConfigurationRemoteAction { if ($action.OperationType -eq "AppendAttribute") { $currentValue = $node.($action.Operation.AttributeName) + # If currentValue already has what we are trying to append with, don't do anything. + if ($currentValue.EndsWith($action.Operation.Value)) { + Write-Verbose "Already have the appended value, skipping over action" + continue + } $newAppendValue = $node.($action.Operation.AttributeName) + $action.Operation.Value # during restore process, we can run into issues with action type of AppendAttribute if the SelectNodesFilter contains # the attribute that you are appending a value for. From 8b1d73cc80a6996fcc1e0a0f50ebea8bdaecc144 Mon Sep 17 00:00:00 2001 From: David Paulson Date: Fri, 1 Mar 2024 12:41:23 -0600 Subject: [PATCH 17/34] Reverse the order for the restore process --- Security/src/Shared/Invoke-XmlConfigurationRemoteAction.ps1 | 1 + 1 file changed, 1 insertion(+) diff --git a/Security/src/Shared/Invoke-XmlConfigurationRemoteAction.ps1 b/Security/src/Shared/Invoke-XmlConfigurationRemoteAction.ps1 index 9510b4b8d5..7adf390cba 100644 --- a/Security/src/Shared/Invoke-XmlConfigurationRemoteAction.ps1 +++ b/Security/src/Shared/Invoke-XmlConfigurationRemoteAction.ps1 @@ -193,6 +193,7 @@ function Invoke-XmlConfigurationRemoteAction { if ($isRestoreOption) { # Don't need to worry about if the restore file wasn't there. This was already handled Write-Verbose "Starting Restore Process" + $restoreActions.Reverse() # Reverse the order to make sure that the restore process should always work, unless manually modifying the files to where we can't find the nodes. foreach ($action in $restoreActions) { try { Write-Verbose "Trying to find nodes based off filter: '$($action.SelectNodesFilter)'" From 778c9581c211d37594d09ce5c0407b96a38a007f Mon Sep 17 00:00:00 2001 From: David Paulson Date: Fri, 1 Mar 2024 13:42:25 -0600 Subject: [PATCH 18/34] Add function for common code --- .../Invoke-XmlConfigurationRemoteAction.ps1 | 80 ++++++++++++------- 1 file changed, 51 insertions(+), 29 deletions(-) diff --git a/Security/src/Shared/Invoke-XmlConfigurationRemoteAction.ps1 b/Security/src/Shared/Invoke-XmlConfigurationRemoteAction.ps1 index 7adf390cba..01ff7abd64 100644 --- a/Security/src/Shared/Invoke-XmlConfigurationRemoteAction.ps1 +++ b/Security/src/Shared/Invoke-XmlConfigurationRemoteAction.ps1 @@ -48,6 +48,42 @@ function Invoke-XmlConfigurationRemoteAction { [object]$InputObject ) begin { + + function TestLastChildNodeRestoreAction { + [CmdletBinding()] + param( + [Parameter(Mandatory)] + [string]$LastChildNode, + + [Parameter(Mandatory)] + [string]$AttributeName, + + [Parameter(Mandatory)] + [string]$CurrentValue, + + [Parameter(Mandatory)] + [string]$NewValue, + + [Parameter(Mandatory)] + [ref]$RestoreAction + ) + + # If the Current SelectNodesFilter that we are using to track down the Node contains a filter for an exact match for the attribute that we are manipulating + # We need to properly process the change for the restore process to work. + $splitResults = $LastChildNode.Split("[").Split("]") + + if ($splitResults -contains "@$AttributeName='$CurrentValue'" -or + $splitResults -contains "@$AttributeName=`"$CurrentValue`"") { + if ($LastChildNode.IndexOf($CurrentValue) -ne $LastChildNode.LastIndexOf($CurrentValue)) { + throw "Last child node contains multiple entries for the current value. Unable to determine new filter to use on restore." + } + + $updatedReplaceChildNode = $LastChildNode.Replace($CurrentValue, $NewValue) + $RestoreAction.Value.SelectNodesFilter = $RestoreAction.Value.SelectNodesFilter.Replace($LastChildNode, $updatedReplaceChildNode) + Write-Verbose "Updated SelectNodesFilter to: $($RestoreAction.Value.SelectNodesFilter)" + } + } + Write-Verbose "Calling: $($MyInvocation.MyCommand)" $isRestoreOption = $null -ne $InputObject.Restore $errorContext = New-Object System.Collections.Generic.List[object] @@ -408,42 +444,28 @@ function Invoke-XmlConfigurationRemoteAction { continue } $newAppendValue = $node.($action.Operation.AttributeName) + $action.Operation.Value - # during restore process, we can run into issues with action type of AppendAttribute if the SelectNodesFilter contains - # the attribute that you are appending a value for. - $lastChildNode = $action.SelectNodesFilter.Substring($action.SelectNodesFilter.LastIndexOf("/")) - $splitResults = $lastChildNode.Split("[").Split("]") - if ($splitResults -contains "@$($action.Operation.AttributeName)='$($currentValue)'" -or - $splitResults -contains "@$($action.Operation.AttributeName)=`"$($currentValue)`"") { - - if ($lastChildNode.IndexOf($currentValue) -ne $lastChildNode.LastIndexOf($currentValue)) { - throw "Last child node contains multiple entries for the current value. Unable to determine new filter to use on restore." - } - - $updatedReplaceChildNode = $lastChildNode.Replace($currentValue, $newAppendValue) - $currentRestoreAction.SelectNodesFilter = $currentRestoreAction.SelectNodesFilter.Replace($lastChildNode, $updatedReplaceChildNode) - Write-Verbose "Updated SelectNodesFilter to: $($currentRestoreAction.SelectNodesFilter)" + $params = @{ + LastChildNode = $action.SelectNodesFilter.Substring($action.SelectNodesFilter.LastIndexOf("/")) + AttributeName = $action.Operation.AttributeName + CurrentValue = $currentValue + NewValue = $newAppendValue + RestoreAction = [ref]$currentRestoreAction } - + TestLastChildNodeRestoreAction @params $node.($action.Operation.AttributeName) = $newAppendValue } elseif ($action.OperationType -eq "ReplaceAttributeValue") { # With this operation, we need to treat this similar as AppendAttribute value with handling the restore process $currentValue = $node.($action.Operation.AttributeName) $newReplaceValue = $currentValue.Replace($action.Operation.Value, $action.Operation.ReplaceValue) - # TODO: Replace current code with function - $lastChildNode = $action.SelectNodesFilter.Substring($action.SelectNodesFilter.LastIndexOf("/")) - $splitResults = $lastChildNode.Split("[").Split("]") - if ($splitResults -contains "@$($action.Operation.AttributeName)='$($currentValue)'" -or - $splitResults -contains "@$($action.Operation.AttributeName)=`"$($currentValue)`"") { - - if ($lastChildNode.IndexOf($currentValue) -ne $lastChildNode.LastIndexOf($currentValue)) { - throw "Last child node contains multiple entries for the current value. Unable to determine new filter to use on restore." - } - - $updatedReplaceChildNode = $lastChildNode.Replace($currentValue, $newReplaceValue) - $currentRestoreAction.SelectNodesFilter = $currentRestoreAction.SelectNodesFilter.Replace($lastChildNode, $updatedReplaceChildNode) - Write-Verbose "Updated SelectNodesFilter to: $($currentRestoreAction.SelectNodesFilter)" - } + $params = @{ + LastChildNode = $action.SelectNodesFilter.Substring($action.SelectNodesFilter.LastIndexOf("/")) + AttributeName = $action.Operation.AttributeName + CurrentValue = $currentValue + NewValue = $newReplaceValue + RestoreAction = [ref]$currentRestoreAction + } + TestLastChildNodeRestoreAction @params $node.($action.Operation.AttributeName) = $newReplaceValue } else { # Need to handle what if scenario here From 9e88d81fe5d78eda5875a333fc82d3da90b001b4 Mon Sep 17 00:00:00 2001 From: David Paulson Date: Fri, 1 Mar 2024 15:07:54 -0600 Subject: [PATCH 19/34] Add return object and display successful config changes --- .../Invoke-TextExtractionOverride.ps1 | 324 ++++++++++-------- 1 file changed, 181 insertions(+), 143 deletions(-) diff --git a/Security/src/ConfigureFipFsTextExtractionOverrides/ConfigurationAction/Invoke-TextExtractionOverride.ps1 b/Security/src/ConfigureFipFsTextExtractionOverrides/ConfigurationAction/Invoke-TextExtractionOverride.ps1 index 173b703fe2..06c06c7549 100644 --- a/Security/src/ConfigureFipFsTextExtractionOverrides/ConfigurationAction/Invoke-TextExtractionOverride.ps1 +++ b/Security/src/ConfigureFipFsTextExtractionOverrides/ConfigurationAction/Invoke-TextExtractionOverride.ps1 @@ -22,172 +22,210 @@ function Invoke-TextExtractionOverride { . $PSScriptRoot\..\..\Shared\Invoke-XmlConfigurationRemoteAction.ps1 $VerbosePreference = $Using:VerbosePreference # To be able to write back to the host screen if -Verbose is used. + $successfulExecution = $false + $errorContext = New-Object System.Collections.Generic.List[object] - if ($null -eq $ArgumentList -or - (($null -eq $ArgumentList.Rollback -or $false -eq $ArgumentList.Rollback) -and - ($null -eq $ArgumentList.ConfigureOverride -or $null -eq $ArgumentList.Action))) { - throw "Invalid ArgumentList provided to remote execution." - } + try { - # We need to hard code this, which isn't ideal. But this is the best option that we have at the moment. - $defaultTypeLocations = @{ - "XlsbOfficePackage" = "Excel" - "XlsmOfficePackage" = "Excel" - "XlsxOfficePackage" = "Excel" - "ExcelStorage" = "Excel" - "DocmOfficePackage" = "PreferIFilters" - "DocxOfficePackage" = "PreferIFilters" - "PptmOfficePackage" = "PreferIFilters" - "PptxOfficePackage" = "PreferIFilters" - "WordStorage" = "PreferIFilters" - "PowerPointStorage" = "PreferIFilters" - "VisioStorage" = "PreferIFilters" - "Rtf" = "PreferIFilters" - "Xml" = "PreferIFilters" - "OdfTextDocument" = "PreferIFilters" - "OdfSpreadsheet" = "PreferIFilters" - "OdfPresentation" = "PreferIFilters" - "OneNote" = "PreferIFilters" - "Pdf" = "PreferOutsideIn" - "Html" = "PreferOutsideIn" - "AutoCad" = "OutsideInOnly" - "Jpeg" = "OutsideInOnly" - "Tiff" = "OutsideInOnly" - } + if ($null -eq $ArgumentList -or + (($null -eq $ArgumentList.Rollback -or $false -eq $ArgumentList.Rollback) -and + ($null -eq $ArgumentList.ConfigureOverride -or $null -eq $ArgumentList.Action))) { + throw "Invalid ArgumentList provided to remote execution." + } - $baseXPathFilter = "//*[local-name()='Configuration']/*[local-name()='System']/*[local-name()='TextExtractionSettings']" - $outsideInOnlyModuleXPathFilter = $baseXPathFilter + - "/*[local-name()='ModuleLists']/*[local-name()='ModuleList'][@TypeList='OutsideInOnly']/*[local-name()='Module'][contains(., 'OutsideInModule.dll')]" - $typeListBaseXPathFilter = $baseXPathFilter + "/*[local-name()='TypeLists']/*[local-name()='TypeList'][@Name='{0}']" - $getTypeBaseTypeListXPathFilter = $baseXPathFilter + - "/*[local-name()='TypeLists']/*[local-name()='TypeList']/*[local-name()='Type'][contains(@Name, '{0}')]" - - $fipFsDatabaseParams = @{ - MachineName = $env:COMPUTERNAME - SubKey = "SOFTWARE\Microsoft\ExchangeServer\v15\FIP-FS" - GetValue = "DatabasePath" - } - $fipFsDatabasePath = Get-RemoteRegistryValue @fipFsDatabaseParams + # We need to hard code this, which isn't ideal. But this is the best option that we have at the moment. + $defaultTypeLocations = @{ + "XlsbOfficePackage" = "Excel" + "XlsmOfficePackage" = "Excel" + "XlsxOfficePackage" = "Excel" + "ExcelStorage" = "Excel" + "DocmOfficePackage" = "PreferIFilters" + "DocxOfficePackage" = "PreferIFilters" + "PptmOfficePackage" = "PreferIFilters" + "PptxOfficePackage" = "PreferIFilters" + "WordStorage" = "PreferIFilters" + "PowerPointStorage" = "PreferIFilters" + "VisioStorage" = "PreferIFilters" + "Rtf" = "PreferIFilters" + "Xml" = "PreferIFilters" + "OdfTextDocument" = "PreferIFilters" + "OdfSpreadsheet" = "PreferIFilters" + "OdfPresentation" = "PreferIFilters" + "OneNote" = "PreferIFilters" + "Pdf" = "PreferOutsideIn" + "Html" = "PreferOutsideIn" + "AutoCad" = "OutsideInOnly" + "Jpeg" = "OutsideInOnly" + "Tiff" = "OutsideInOnly" + } - if (([string]::IsNullOrEmpty($fipFsDatabasePath))) { - throw "Unable to find FIP FS Database Path" - } + $baseXPathFilter = "//*[local-name()='Configuration']/*[local-name()='System']/*[local-name()='TextExtractionSettings']" + $outsideInOnlyModuleXPathFilter = $baseXPathFilter + + "/*[local-name()='ModuleLists']/*[local-name()='ModuleList'][@TypeList='OutsideInOnly']/*[local-name()='Module'][contains(., 'OutsideInModule.dll')]" + $typeListBaseXPathFilter = $baseXPathFilter + "/*[local-name()='TypeLists']/*[local-name()='TypeList'][@Name='{0}']" + $getTypeBaseTypeListXPathFilter = $baseXPathFilter + + "/*[local-name()='TypeLists']/*[local-name()='TypeList']/*[local-name()='Type'][contains(@Name, '{0}')]" + + $fipFsDatabaseParams = @{ + MachineName = $env:COMPUTERNAME + SubKey = "SOFTWARE\Microsoft\ExchangeServer\v15\FIP-FS" + GetValue = "DatabasePath" + } + $fipFsDatabasePath = Get-RemoteRegistryValue @fipFsDatabaseParams - $path = (Join-Path $fipFsDatabasePath "Configuration.xml") - Write-Verbose "Using the database path of '$path' to adjust" + if (([string]::IsNullOrEmpty($fipFsDatabasePath))) { + throw "Unable to find FIP FS Database Path" + } - $xmlConfigurationRemoteAction = [PSCustomObject]@{ - FilePath = $path - BackupFileName = "TextExtractionOverride" - Actions = (New-Object System.Collections.Generic.List[object]) - } + $path = (Join-Path $fipFsDatabasePath "Configuration.xml") + Write-Verbose "Using the database path of '$path' to adjust" + + $xmlConfigurationRemoteAction = [PSCustomObject]@{ + FilePath = $path + BackupFileName = "TextExtractionOverride" + Actions = (New-Object System.Collections.Generic.List[object]) + } - # Always Stop the services first - # TODO: Determine if we need to stop the services or need to restart - $serviceResult = Invoke-StartStopService -ServiceName "MSExchangeTransport", "FMS" -Action "Stop" - - if ($true -eq $serviceResult) { - - if (-not $ArgumentList.Rollback) { - # If we got a true result, we stopped the service - # Now create the actions list - foreach ($configureActionOverride in $ArgumentList.ConfigureOverride) { - if ($configureActionOverride -eq "OutsideInModule") { - # If configureActionOverride is OutsideInModule then we are setting that path only. - $actionOperation = [PSCustomObject]@{ - SelectNodesFilter = $outsideInOnlyModuleXPathFilter - OperationType = [string]::Empty - Operation = [PSCustomObject]@{ - AttributeName = "#text" - Value = "|NO" - ReplaceValue = [string]::Empty + # Always Stop the services first + # TODO: Determine if we need to stop the services or need to restart + $serviceResult = Invoke-StartStopService -ServiceName "MSExchangeTransport", "FMS" -Action "Stop" + + if ($true -eq $serviceResult) { + + if (-not $ArgumentList.Rollback) { + # If we got a true result, we stopped the service + # Now create the actions list + foreach ($configureActionOverride in $ArgumentList.ConfigureOverride) { + if ($configureActionOverride -eq "OutsideInModule") { + # If configureActionOverride is OutsideInModule then we are setting that path only. + $actionOperation = [PSCustomObject]@{ + SelectNodesFilter = $outsideInOnlyModuleXPathFilter + OperationType = [string]::Empty + Operation = [PSCustomObject]@{ + AttributeName = "#text" + Value = "|NO" + ReplaceValue = [string]::Empty + } } - } - if ($ArgumentList.Action -eq "Allow") { - $actionOperation.OperationType = "AppendAttribute" - $xmlConfigurationRemoteAction.Actions.Add($actionOperation) - } elseif ($ArgumentList.Action -eq "Block") { - $actionOperation.OperationType = "ReplaceAttributeValue" - $xmlConfigurationRemoteAction.Actions.Add($actionOperation) - } - } else { - # Now everything else is attempting to do the following on the Type: - # Either set or remove the |NO flag - # Move the Type to the TypeList OutsideInOnly as that is the only location where the |NO flag is honored - $baseFilter = $getTypeBaseTypeListXPathFilter -f $configureActionOverride - - if ($ArgumentList.Action -eq "Allow") { - - $xmlConfigurationRemoteAction.Actions.Add(([PSCustomObject]@{ - SelectNodesFilter = $baseFilter - OperationType = "MoveNode" - Operation = [PSCustomObject]@{ - MoveToSelectNodesFilter = ($typeListBaseXPathFilter -f "OutsideInOnly") - ParentNodeAttributeNameFilterAdd = "Name" - } - })) - - $xmlConfigurationRemoteAction.Actions.Add(([PSCustomObject]@{ - SelectNodesFilter = $baseFilter - OperationType = "AppendAttribute" - Operation = [PSCustomObject]@{ - AttributeName = "Name" - Value = "|NO" - } - })) - } elseif ($ArgumentList.Action -eq "Block") { - $xmlConfigurationRemoteAction.Actions.Add(([PSCustomObject]@{ - SelectNodesFilter = $baseFilter - OperationType = "ReplaceAttributeValue" - Operation = [PSCustomObject]@{ - AttributeName = "Name" - Value = "|NO" - ReplaceValue = [string]::Empty - } - })) - - $xmlConfigurationRemoteAction.Actions.Add(([PSCustomObject]@{ - SelectNodesFilter = $baseFilter - OperationType = "MoveNode" - Operation = [PSCustomObject]@{ - MoveToSelectNodesFilter = ($typeListBaseXPathFilter -f $defaultTypeLocations[$configureActionOverride]) - ParentNodeAttributeNameFilterAdd = "Name" - } - })) + if ($ArgumentList.Action -eq "Allow") { + $actionOperation.OperationType = "AppendAttribute" + $xmlConfigurationRemoteAction.Actions.Add($actionOperation) + } elseif ($ArgumentList.Action -eq "Block") { + $actionOperation.OperationType = "ReplaceAttributeValue" + $xmlConfigurationRemoteAction.Actions.Add($actionOperation) + } + } else { + # Now everything else is attempting to do the following on the Type: + # Either set or remove the |NO flag + # Move the Type to the TypeList OutsideInOnly as that is the only location where the |NO flag is honored + $baseFilter = $getTypeBaseTypeListXPathFilter -f $configureActionOverride + + if ($ArgumentList.Action -eq "Allow") { + + $xmlConfigurationRemoteAction.Actions.Add(([PSCustomObject]@{ + SelectNodesFilter = $baseFilter + OperationType = "MoveNode" + Operation = [PSCustomObject]@{ + MoveToSelectNodesFilter = ($typeListBaseXPathFilter -f "OutsideInOnly") + ParentNodeAttributeNameFilterAdd = "Name" + } + })) + + $xmlConfigurationRemoteAction.Actions.Add(([PSCustomObject]@{ + SelectNodesFilter = $baseFilter + OperationType = "AppendAttribute" + Operation = [PSCustomObject]@{ + AttributeName = "Name" + Value = "|NO" + } + })) + } elseif ($ArgumentList.Action -eq "Block") { + $xmlConfigurationRemoteAction.Actions.Add(([PSCustomObject]@{ + SelectNodesFilter = $baseFilter + OperationType = "ReplaceAttributeValue" + Operation = [PSCustomObject]@{ + AttributeName = "Name" + Value = "|NO" + ReplaceValue = [string]::Empty + } + })) + + $xmlConfigurationRemoteAction.Actions.Add(([PSCustomObject]@{ + SelectNodesFilter = $baseFilter + OperationType = "MoveNode" + Operation = [PSCustomObject]@{ + MoveToSelectNodesFilter = ($typeListBaseXPathFilter -f $defaultTypeLocations[$configureActionOverride]) + ParentNodeAttributeNameFilterAdd = "Name" + } + })) + } } } + } else { + $xmlConfigurationRemoteAction | Add-Member -MemberType NoteProperty -Name "Restore" -Value ([PSCustomObject]@{ + FileName = $xmlConfigurationRemoteAction.BackupFileName + }) } - } else { - $xmlConfigurationRemoteAction | Add-Member -MemberType NoteProperty -Name "Restore" -Value ([PSCustomObject]@{ - FileName = $xmlConfigurationRemoteAction.BackupFileName - }) - } - # Now that we have the list of actions, we need to execute the results then determine if we were successful or not. - $results = Invoke-XmlConfigurationRemoteAction -InputObject $xmlConfigurationRemoteAction - Write-Host "" + # Now that we have the list of actions, we need to execute the results then determine if we were successful or not. + $results = Invoke-XmlConfigurationRemoteAction -InputObject $xmlConfigurationRemoteAction + Write-Host "" + + if ($results.SuccessfulExecution) { + Write-Host "[$env:COMPUTERNAME] Successfully completed the configuration for FIP FS Text Extraction Override" + $successfulExecution = $true + } else { + Write-Warning "$env:COMPUTERNAME Failed to execution configuration action for FIP FS Text Extraction Override" + } - if ($results.SuccessfulExecution) { - Write-Host "[$env:COMPUTERNAME] Successfully completed the configuration for FIP FS Text Extraction Override" - } else { - Write-Warning "$env:COMPUTERNAME Failed to execution configuration action for FIP FS Text Extraction Override" + Write-Host "" } - Write-Host "" + # Attempt to start the service again, even if we failed to stop. One could have worked. + $serviceResult = Invoke-StartStopService -ServiceName "MSExchangeTransport", "FMS" -Action "Start" + } catch { + Write-Verbose "Caught an exception while trying to execute actions for Text EXtraction Override. Inner Exception: $_" + $errorContext.Add($_) + } finally { + [PSCustomObject]@{ + ServerName = $env:COMPUTERNAME + SuccessfulExecution = $successfulExecution + ErrorContext = $errorContext + ServicesStarted = $true -eq $serviceResult + } } - - # Attempt to start the service again, even if we failed to stop. One could have worked. - $serviceResult = Invoke-StartStopService -ServiceName "MSExchangeTransport", "FMS" -Action "Start" - - # TODO Add a return object here to better process } } process { - Invoke-Command -ComputerName $ComputerName -ScriptBlock $remoteScriptBlockExecute -ArgumentList ([PSCustomObject]@{ + $results = Invoke-Command -ComputerName $ComputerName -ScriptBlock $remoteScriptBlockExecute -ArgumentList ([PSCustomObject]@{ ConfigureOverride = $ConfigureOverride Action = $Action Rollback = $Rollback }) + + $successServers = @($results | Where-Object { $_.SuccessfulExecution -eq $true -and $_.ErrorContext.Count -eq 0 }) + $failedServers = @($results | Where-Object { $_.SuccessfulExecution -eq $false -or $_.ErrorContext.Count -ne 0 }) + $failedServiceStart = @($results | Where-Object { $_.ServicesStarted -eq $false }) + + Write-Host "" + Write-Host "" + + if ($null -ne $failedServers -and + $failedServers.Count -gt 0) { + Write-Warning "Failed to complete Text Extraction Override on the following servers: $([string]::Join(", ", $failedServers.ServerName))" + } + + if ($null -ne $failedServiceStart -and + $failedServiceStart.Count -gt 0) { + Write-Warning "Failed to start the MSExchangeTransport and/or FMS services on the following servers: $([string]::Join(", ", $failedServiceStart.ServerName))" + } + + if ($null -ne $successServers -and + $successServers.Count -gt 0) { + Write-Host "Successfully completed Text Extraction Override on the following servers: $([string]::Join(", ", $successServers.ServerName))" -ForegroundColor "Green" + } + + Write-Host "" } } From ebece4fe46fef5e1784ccbbfd95f86308a0a324b Mon Sep 17 00:00:00 2001 From: David Paulson Date: Fri, 1 Mar 2024 15:30:01 -0600 Subject: [PATCH 20/34] Add Write-Progress to Text Extraction Override --- .../Invoke-TextExtractionOverride.ps1 | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Security/src/ConfigureFipFsTextExtractionOverrides/ConfigurationAction/Invoke-TextExtractionOverride.ps1 b/Security/src/ConfigureFipFsTextExtractionOverrides/ConfigurationAction/Invoke-TextExtractionOverride.ps1 index 06c06c7549..fa08bb2bcc 100644 --- a/Security/src/ConfigureFipFsTextExtractionOverrides/ConfigurationAction/Invoke-TextExtractionOverride.ps1 +++ b/Security/src/ConfigureFipFsTextExtractionOverrides/ConfigurationAction/Invoke-TextExtractionOverride.ps1 @@ -24,6 +24,11 @@ function Invoke-TextExtractionOverride { $VerbosePreference = $Using:VerbosePreference # To be able to write back to the host screen if -Verbose is used. $successfulExecution = $false $errorContext = New-Object System.Collections.Generic.List[object] + $activityBase = "[$env:COMPUTERNAME]" + $writeProgressParams = @{ + Activity = "$activityBase Getting FIP FS Database Path" + Id = [Math]::Abs(($env:COMPUTERNAME).GetHashCode()) + } try { @@ -65,6 +70,7 @@ function Invoke-TextExtractionOverride { $typeListBaseXPathFilter = $baseXPathFilter + "/*[local-name()='TypeLists']/*[local-name()='TypeList'][@Name='{0}']" $getTypeBaseTypeListXPathFilter = $baseXPathFilter + "/*[local-name()='TypeLists']/*[local-name()='TypeList']/*[local-name()='Type'][contains(@Name, '{0}')]" + Write-Progress @writeProgressParams $fipFsDatabaseParams = @{ MachineName = $env:COMPUTERNAME @@ -86,6 +92,8 @@ function Invoke-TextExtractionOverride { Actions = (New-Object System.Collections.Generic.List[object]) } + $writeProgressParams.Activity = $activityBase + " Stopping MSExchangeTransport and FMS Services" + Write-Progress @writeProgressParams # Always Stop the services first # TODO: Determine if we need to stop the services or need to restart $serviceResult = Invoke-StartStopService -ServiceName "MSExchangeTransport", "FMS" -Action "Stop" @@ -169,6 +177,8 @@ function Invoke-TextExtractionOverride { } # Now that we have the list of actions, we need to execute the results then determine if we were successful or not. + $writeProgressParams.Activity = $activityBase + " updating the Xml Configuration file" + Write-Progress @writeProgressParams $results = Invoke-XmlConfigurationRemoteAction -InputObject $xmlConfigurationRemoteAction Write-Host "" @@ -183,11 +193,14 @@ function Invoke-TextExtractionOverride { } # Attempt to start the service again, even if we failed to stop. One could have worked. + $writeProgressParams.Activity = $activityBase + " Starting MSExchangeTransport and FMS Services" + Write-Progress @writeProgressParams $serviceResult = Invoke-StartStopService -ServiceName "MSExchangeTransport", "FMS" -Action "Start" } catch { Write-Verbose "Caught an exception while trying to execute actions for Text EXtraction Override. Inner Exception: $_" $errorContext.Add($_) } finally { + Write-Progress @writeProgressParams -Completed [PSCustomObject]@{ ServerName = $env:COMPUTERNAME SuccessfulExecution = $successfulExecution From 362deadab515f0a13b48f183f45653c19cb1693f Mon Sep 17 00:00:00 2001 From: lusassl-msft Date: Wed, 6 Mar 2024 09:24:04 +0100 Subject: [PATCH 21/34] Final KB and Advisory link added --- docs/Security/ConfigureFipFsTextExtractionOverrides.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Security/ConfigureFipFsTextExtractionOverrides.md b/docs/Security/ConfigureFipFsTextExtractionOverrides.md index 513a7c2bf0..f6a4390cf7 100644 --- a/docs/Security/ConfigureFipFsTextExtractionOverrides.md +++ b/docs/Security/ConfigureFipFsTextExtractionOverrides.md @@ -10,9 +10,9 @@ The `ConfigureFipFsTextExtractionOverrides.ps1` script can be used to enable fil The script can also be used to override the version of the `OutsideInModule` that should be used. After installing the March 2024 Security Update, Exchange Server will use the latest version of the `OutsideInModule`, which is `8.5.7`, if processing for a file type was explicitly enabled by the help of this script. -Details about the change that was done as part of the March 2024 security update can be found in [KB123456](https://support.microsoft.com/help/123456). +Details about the change that was done as part of the March 2024 security update can be found in [KB5037191](https://support.microsoft.com/topic/5037191). -Details about the security flaw can be found in the [MSRC security advisory](https://portal.msrc.microsoft.com/security-guidance/advisory/ADV123456). +Details about the security flaw can be found in the [MSRC security advisory](https://portal.msrc.microsoft.com/security-guidance/advisory/ADV24199947). !!! warning "Warning" From c85ad5300f45d0affa67329811cd09fb79ca2df2 Mon Sep 17 00:00:00 2001 From: lusassl-msft Date: Wed, 6 Mar 2024 09:52:25 +0100 Subject: [PATCH 22/34] Update Description comments and display disclaimer --- .../ConfigureFipFsTextExtractionOverrides.ps1 | 123 ++++++++++-------- 1 file changed, 69 insertions(+), 54 deletions(-) diff --git a/Security/src/ConfigureFipFsTextExtractionOverrides/ConfigureFipFsTextExtractionOverrides.ps1 b/Security/src/ConfigureFipFsTextExtractionOverrides/ConfigureFipFsTextExtractionOverrides.ps1 index ce78fc4e63..5c84d0f088 100644 --- a/Security/src/ConfigureFipFsTextExtractionOverrides/ConfigureFipFsTextExtractionOverrides.ps1 +++ b/Security/src/ConfigureFipFsTextExtractionOverrides/ConfigureFipFsTextExtractionOverrides.ps1 @@ -3,42 +3,54 @@ <# .SYNOPSIS - This script can be used to remove vulnerable file types from the FIP-FS configuration.xml file. + This script can be used revert the 'secure by default' change which was introduced as part of the Exchange Server March 2024 security update. + More information can be found in https://support.microsoft.com/help/5036795 .DESCRIPTION - The script removes vulnerable file types from the FIP-FS configuration.xml file. - It can also be used to add these file types back. It also allows you to completely disable the usage of the OutsideInModule - or enable it back. -.PARAMETER ConfigureMitigation - Use this parameter to specify the mitigation that should be applied. - Values that can be passed with this parameter are: ConfigureOutsideIn and ConfigureFileTypes + The script can be used to add overrides to the FIP-FS configuration.xml file. + This can be done to reactivate the use of the OutsideInModule for file types, which are no longer processed by the help of this module. + An override can also be done to the version of the OutsideInModule.dll. After the March 2024 security update was installed, + Exchange Server uses OutsideInModule version 8.5.7 by default, which is the latest version that was available at the time the SU was published. + By the help of the script, usage of the previous version 8.5.3 can be enforced. +.PARAMETER ExchangeServerNames + Use this parameter to specify the Exchange Server on which the change to the configuration should be done. +.PARAMETER SkipExchangeServerNames + Use this parameter to specify the Exchange Server, which should be excluded from the configuration action. .PARAMETER ConfigureOverride - Use this parameter to specify the override that should be set. - Note that setting an override works only if the Exchange Server March 2024 security update was installed. - Values that can be passed with this parameter are: OutsideInVersionOverride and FileTypesOverride -.PARAMETER OutsideInEnabledFileTypes - Use this parameter to specify the file types that should be allowed to use the OutsideInModule. - By default, the only file types that are allowed to use the OutsideInModule are: AutoCad, Jpeg and Tiff -.PARAMETER RestoreFileTypeList - Use this parameter if you want to restore the file type list. All existing file type overrides will be removed. + Use this parameter to specify the file types for which the override should be added or from which the override should be removed. + You can also use this parameter to configure the override of the OutsideInModule version. + The values are case sensitive. Values that can be used with this parameter are: + OutsideInModule, XlsbOfficePackage, XlsmOfficePackage, XlsxOfficePackage, ExcelStorage , DocmOfficePackage, + DocxOfficePackage, PptmOfficePackage, PptxOfficePackage, WordStorage, PowerPointStorage, VisioStorage, Rtf, + Xml, OdfTextDocument, OdfSpreadsheet, OdfPresentation, OneNote, Pdf, Html, AutoCad, Jpeg, Tiff .PARAMETER Action - Use this parameter to specify the action that should be performed. - Values that can be passed with this parameter are: Allow, Block + Use this parameter to specify the action that should be performed. The override flag will be added if the Allow value was used. + Values that can be passed are: Allow, Block + The default value is: Block +.PARAMETER Rollback + Use this parameter to restore the configuration.xml based on the backup that was automatically created during a previous run of the script. + The restore operation will fail if no backup file can be found. .PARAMETER ScriptUpdateOnly This optional parameter allows you to only update the script without performing any other actions. .PARAMETER SkipVersionCheck This optional parameter allows you to skip the automatic version check and script update. .EXAMPLE - PS C:\> .\ConfigureFipFsTextExtractionOverrides.ps1 -ConfigureOverride OutsideInVersionOverride -Action Allow - It will add the 'NO' override flag to the OutsideInModule.dll which is defined in the 'OutsideInOnly' module list. + PS C:\> .\ConfigureFipFsTextExtractionOverrides.ps1 -ConfigureOverride OutsideInModule -Action Allow + It will add the override flag to the OutsideInModule.dll which is defined in the 'OutsideInOnly' module list. The action will be performed on + the machine where the script was executed. .EXAMPLE - PS C:\> .\ConfigureFipFsTextExtractionOverrides.ps1 -ConfigureOverride OutsideInVersionOverride -Action Block - It will remove the 'NO' override flag from the OutsideInModule.dll which is defined in the 'OutsideInOnly' module list. + PS C:\> .\ConfigureFipFsTextExtractionOverrides.ps1 -ConfigureOverride OutsideInModule -Action Block + It will remove the override flag from the OutsideInModule.dll which is defined in the 'OutsideInOnly' module list. The action will be performed on + the machine where the script was executed. .EXAMPLE - PS C:\> .\ConfigureFipFsTextExtractionOverrides.ps1 -ConfigureOverride FileTypesOverride -OutsideInEnabledFileTypes "ExcelStorage" -Action Allow - It will add 'ExcelStorage' file type to the 'OutsideInOnly' file type list and will add the 'NO' flag to the file type. + PS C:\> .\ConfigureFipFsTextExtractionOverrides.ps1 -ConfigureOverride AutoCad -Action Allow + It will add the override flag to the 'AutoCad' file type. The action will be performed on the machine where the script was executed. .EXAMPLE - PS C:\> .\ConfigureFipFsTextExtractionOverrides.ps1 -RestoreFileTypeList - It will restore the default file type to file type list mapping and removes any file type override. + PS C:\> Get-ExchangeServer | .\ConfigureFipFsTextExtractionOverrides.ps1 -ConfigureOverride AutoCad -Action Allow + It will add the override flag to the 'AutoCad' file type. The action will be performed on all Exchange servers. +.EXAMPLE + PS C:\> Get-ExchangeServer | .\ConfigureFipFsTextExtractionOverrides.ps1 -Rollback -SkipExchangeServerNames "ExchSrv02" + It will restore the configuration.xml from the backup file that was created during a previous run of the script. + The action will be performed on all Exchange servers except ExchSrv02. #> [CmdletBinding(DefaultParameterSetName = "ConfigureOverride", SupportsShouldProcess = $true, ConfirmImpact = 'High')] @@ -71,16 +83,14 @@ param( ) begin { + $versionsUrl = "https://aka.ms/ConfigureFipFsTextExtractionOverrides-VersionsURL" + . $PSScriptRoot\ConfigurationAction\Invoke-TextExtractionOverride.ps1 . $PSScriptRoot\..\Shared\Get-ProcessedServerList.ps1 + . $PSScriptRoot\..\..\..\Shared\Confirm-ExchangeManagementShell.ps1 . $PSScriptRoot\..\..\..\Shared\GenericScriptStartLogging.ps1 . $PSScriptRoot\..\..\..\Shared\ScriptUpdateFunctions\GenericScriptUpdate.ps1 - if ($ConfigureOverride.Count -gt 1 -and $ConfigureOverride -contains "OutsideInModule") { - Write-Error "OutsideInModule ConfigureOverride can only be processed by itself." - exit - } - $includeExchangeServerNames = New-Object System.Collections.Generic.List[string] } process { foreach ($server in $ExchangeServerNames) { @@ -88,6 +98,17 @@ begin { } } end { try { + Write-Verbose "Url to check for new versions of the script is: $versionsUrl" + + if (-not (Confirm-ExchangeManagementShell)) { + Write-Error "This script must be run from Exchange Management Shell." + exit + } + + if ($ConfigureOverride.Count -gt 1 -and $ConfigureOverride -contains "OutsideInModule") { + Write-Error "OutsideInModule ConfigureOverride can only be processed by itself." + exit + } if ($includeExchangeServerNames.Count -eq 0 -and ($null -eq $SkipExchangeServerNames -or $SkipExchangeServerNames.Count -eq 0)) { @@ -95,42 +116,36 @@ begin { $includeExchangeServerNames.Add($env:COMPUTERNAME) } - # TODO adjust the disclaimer wording to match the latest adjustment - $exchangeServicesWording = "Note that each Exchange server's MSExchangeTransport and FMS service will be restarted to backup and apply the setting change action." - $vulnerabilityMoreInformationWording = "More information about the vulnerability can be found here: https://portal.msrc.microsoft.com/security-guidance/advisory/CVE-2024-xxxxx." + $exchangeServicesWording = "Each Exchange server's MSExchangeTransport and FMS service will be restarted to backup and apply the configuration change." + $vulnerabilityMoreInformationWording = "More information about the security vulnerability can be found here: https://portal.msrc.microsoft.com/security-guidance/advisory/ADV24199947." - # TODO: Update Disclaimer section. - - if ($Configuration -eq "ConfigureOutsideIn" -and - $Action -eq "Block") { + if ($ConfigureOverride -eq "OutsideInModule" -and + $Action -eq "Allow") { $params = @{ - Message = "Display warning about OutsideInModule removal operation" - Target = "Disabling OutsideInModule can be done to mitigate CVE-2024-xxxxx vulnerability. " + - "`r`nRemoval of this module might have impact on xxxxx. " + - "$exchangeServicesWording" + + Message = "Display warning about OutsideInModule override operation" + Target = "This operation enables an outdate version of the OutsideInModule which is known to be vulnerable." + + "`r`n$exchangeServicesWording" + "`r`n$vulnerabilityMoreInformationWording" + "`r`nDo you want to proceed?" - Operation = "Disabling FIP-FS OutsideInModule usage" + Operation = "Enabling usage of an outdated OutsideInModule version" } - } elseif ($Configuration -eq "ConfigureFileTypes" -and - $Action -eq "Block") { + } elseif ($ConfigureOverride.Count -ge 1 -and + $Action -eq "Allow") { $params = @{ - Message = "Display warning about ConfigureFileTypes removal operation" - Target = "Configuring file types that can be processed by the OutsideInModule can be done to mitigate CVE-2024-xxxxx vulnerability. " + - "`r`Configuring these file types might have impact on xxxxx. " + - "$exchangeServicesWording" + + Message = "Display warning about file type override operation" + Target = "This operation enables OutsideInModule usage for the following file types:" + + "`r`n$([string]::Join(", ", $ConfigureOverride))" + + "`r`n$exchangeServicesWording" + "`r`n$vulnerabilityMoreInformationWording" + "`r`nDo you want to proceed?" - Operation = "Configure file types that can be processed by the FIP-FS OutsideInModule" + Operation = "Configure file types that should be processed by the OutsideInModule" } } else { $params = @{ - Message = "Display warning about OutsideInModule rollback operation" - Target = "Restoring the previous OutsideInModule configuration state will make your system vulnerable to CVE-2024-xxxxx again. " + - "$exchangeServicesWording" + - "`r`n$vulnerabilityMoreInformationWording" + + Message = "Display warning about service restart operation" + Target = "$exchangeServicesWording" + "`r`nDo you want to proceed?" - Operation = "Rollback FIP-FS OutsideInModule configuration" + Operation = "Performing OutsideInModule configuration action" } } From fe430ac8aa8f5d5f9a565d5744a4e9c5c5a4d550 Mon Sep 17 00:00:00 2001 From: lusassl-msft Date: Wed, 6 Mar 2024 18:45:57 +0100 Subject: [PATCH 23/34] Skip servers that are not running Mar24SU --- .../ConfigureFipFsTextExtractionOverrides.ps1 | 13 ++++- .../src/Shared/Get-ProcessedServerList.ps1 | 56 +++++++++++++++++-- 2 files changed, 61 insertions(+), 8 deletions(-) diff --git a/Security/src/ConfigureFipFsTextExtractionOverrides/ConfigureFipFsTextExtractionOverrides.ps1 b/Security/src/ConfigureFipFsTextExtractionOverrides/ConfigureFipFsTextExtractionOverrides.ps1 index 5c84d0f088..554f39a350 100644 --- a/Security/src/ConfigureFipFsTextExtractionOverrides/ConfigureFipFsTextExtractionOverrides.ps1 +++ b/Security/src/ConfigureFipFsTextExtractionOverrides/ConfigureFipFsTextExtractionOverrides.ps1 @@ -156,6 +156,7 @@ begin { SkipExchangeServerNames = $SkipExchangeServerNames CheckOnline = $true DisableGetExchangeServerFullList = $includeExchangeServerNames.Count -gt 0 # if we pass a list, we shouldn't need to get all the servers in the org. + MinimumSU = "Mar24SU" } $processedExchangeServers = Get-ProcessedServerList @processParams @@ -167,8 +168,16 @@ begin { Rollback = $Rollback } - Write-Host "Running the configuration change against the following server(s): $([string]::Join(", ", $params.ComputerName))" - Invoke-TextExtractionOverride @params + if ($processedExchangeServers.OutdatedBuildExchangeServerFqdn.Count -ge 1) { + Write-Host "Excluded the following server(s) because build is too old: $([string]::Join(", ", $processedExchangeServers.OutdatedBuildExchangeServerFqdn))" + } + + if ($processedExchangeServers.OnlineExchangeServerFqdn.Count -ge 1) { + Write-Host "Running the configuration change against the following server(s): $([string]::Join(", ", $params.ComputerName))" + Invoke-TextExtractionOverride @params + } else { + Write-Host "None of the server(s) passed to the script do support OutsideInModule overrides" + } } finally { Write-Host "" Write-Host "Do you have feedback regarding the script? Please let us know: ExToolsFeedback@microsoft.com." diff --git a/Security/src/Shared/Get-ProcessedServerList.ps1 b/Security/src/Shared/Get-ProcessedServerList.ps1 index 64f36b7880..978ab8b132 100644 --- a/Security/src/Shared/Get-ProcessedServerList.ps1 +++ b/Security/src/Shared/Get-ProcessedServerList.ps1 @@ -1,7 +1,10 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. +. $PSScriptRoot\..\..\..\Shared\Get-ExchangeBuildVersionInformation.ps1 . $PSScriptRoot\..\..\..\Shared\Invoke-ScriptBlockHandler.ps1 +. $PSScriptRoot\..\..\..\Diagnostics\HealthChecker\Helpers\CompareExchangeBuildLevel.ps1 # TODO: Make this a shared function +. $PSScriptRoot\..\..\..\Diagnostics\HealthChecker\DataCollection\ExchangeInformation\Get-ExSetupDetails.ps1 # TODO: Make this a shared function function Get-ProcessedServerList { [CmdletBinding()] param( @@ -11,7 +14,9 @@ function Get-ProcessedServerList { [bool]$CheckOnline, - [bool]$DisableGetExchangeServerFullList + [bool]$DisableGetExchangeServerFullList, + + [string]$MinimumSU ) begin { Write-Verbose "Calling: $($MyInvocation.MyCommand)" @@ -20,6 +25,7 @@ function Get-ProcessedServerList { $validExchangeServerFqdn = New-Object System.Collections.Generic.List[string] $onlineExchangeServer = New-Object System.Collections.Generic.List[object] $onlineExchangeServerFqdn = New-Object System.Collections.Generic.List[string] + $outdatedBuildExchangeServerFqdn = New-Object System.Collections.Generic.List[string] } process { if ($DisableGetExchangeServerFullList) { @@ -45,6 +51,43 @@ function Get-ProcessedServerList { $getExchangeServer.AddRange($result) } + # Remove any Exchange Server from the list that doesn't run the expected security update build + if (-not([system.string]::IsNullOrWhiteSpace($MinimumSU))) { + Write-Verbose "Removing all Exchange Servers from the list that are running the expected build" + foreach ($server in $getExchangeServer) { + Write-Verbose "Validating Exchange Server: $($server.FQDN)" + $exSetupDetails = $null + $exVersionInformation = $null + + $exSetupDetails = Get-ExSetupDetails -Server $server.FQDN + $exVersionInformation = Get-ExchangeBuildVersionInformation -FileVersion $exSetupDetails.FileVersion + + if ($null -eq $exSetupDetails) { + Write-Verbose "Build cannot be validated due to unreachable Exchange Server - removing the server from the list" + $outdatedBuildExchangeServerFqdn.Add($server.FQDN) + continue + } + + if (-not(Test-ExchangeBuildGreaterOrEqualThanSecurityPatch -CurrentExchangeBuild $exVersionInformation -SU $MinimumSU)) { + Write-Verbose "Build is older than the expected security update: '$MinimumSU' - removing the server from the list" + $outdatedBuildExchangeServerFqdn.Add($server.FQDN) + continue + } + + Write-Verbose "Build is equal or newer than the expected security update: '$MinimumSU'" + } + + # Perform remove operation after foreach to avoid 'Collection was modified; enumeration operation may not execute' exception + $getExchangeServerClone = $getExchangeServer.Clone() + foreach ($server in $getExchangeServerClone) { + if ($outdatedBuildExchangeServerFqdn -contains $server.FQDN) { + $index = $getExchangeServer.FindIndex({ $args[0].FQDN -eq "$($server.FQDN)" }) + Write-Verbose "Removing server: $($server.FQDN) at index: $index from List" + $getExchangeServer.RemoveAt($index) + } + } + } + if ($null -ne $ExchangeServerNames -and $ExchangeServerNames.Count -gt 0) { $getExchangeServer | Where-Object { ($_.Name -in $ExchangeServerNames) -or ($_.FQDN -in $ExchangeServerNames) } | @@ -91,11 +134,12 @@ function Get-ProcessedServerList { } end { return [PSCustomObject]@{ - ValidExchangeServer = $validExchangeServer - ValidExchangeServerFqdn = $validExchangeServerFqdn - GetExchangeServer = $getExchangeServer - OnlineExchangeServer = $onlineExchangeServer - OnlineExchangeServerFqdn = $onlineExchangeServerFqdn + ValidExchangeServer = $validExchangeServer + ValidExchangeServerFqdn = $validExchangeServerFqdn + GetExchangeServer = $getExchangeServer + OnlineExchangeServer = $onlineExchangeServer + OnlineExchangeServerFqdn = $onlineExchangeServerFqdn + OutdatedBuildExchangeServerFqdn = $outdatedBuildExchangeServerFqdn } } } From ff38c7bde7837e4187e36edd8fd7b887a30e4bb4 Mon Sep 17 00:00:00 2001 From: lusassl-msft Date: Mon, 11 Mar 2024 14:34:24 +0100 Subject: [PATCH 24/34] Documentation updated after review --- .../ConfigureFipFsTextExtractionOverrides.md | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/docs/Security/ConfigureFipFsTextExtractionOverrides.md b/docs/Security/ConfigureFipFsTextExtractionOverrides.md index f6a4390cf7..f8fa71ed7c 100644 --- a/docs/Security/ConfigureFipFsTextExtractionOverrides.md +++ b/docs/Security/ConfigureFipFsTextExtractionOverrides.md @@ -4,19 +4,22 @@ Download the latest release: [ConfigureFipFsTextExtractionOverrides.ps1](https:/ !!! warning "Note" - With the Exchange Server March 2024 security update we disable the use of the OutsideInModule in Microsoft Exchange Server due to multiple security flaws in the module. The OutsideInModule was used by the Microsoft Forefront Filtering Module to extract information from different file types, to perform content inspection as part of the Exchange Server Data Loss Prevention (DLP) feature. + Starting in the Exchange Server March 2024 security update we disable the use of the Oracle Outside In Technology (also known as OutsideInModule or OIT) in Microsoft Exchange Server due to multiple security vulnerabilities in the module. The OutsideInModule was used by the Microsoft Forefront Filtering Module to extract information from different file types, to perform content inspection as part of the Exchange Server Data Loss Prevention (DLP) or Exchange Transport Rules (ETR) features. -The `ConfigureFipFsTextExtractionOverrides.ps1` script can be used to enable file types that should be processed by the help of the `Oracle Outside In Technology` (also known as `OutsideInModule`). The module is used by the `Microsoft Forefront Filtering Module` when Exchange Transport Rules (ETR) or Data Loss Prevention (DLP) rules are in place. +The `ConfigureFipFsTextExtractionOverrides.ps1` script can be used to manipulate the usage of `OutsideInModule` that is disabled by default in the Exchange Server March 2024 security update. -The script can also be used to override the version of the `OutsideInModule` that should be used. After installing the March 2024 Security Update, Exchange Server will use the latest version of the `OutsideInModule`, which is `8.5.7`, if processing for a file type was explicitly enabled by the help of this script. +There are two scenarios in which the script could be used: + +- It can be used to explicitly enable file types that should be processed by the help of the `OutsideInModule`. +- It can be used to override the version of the `OutsideInModule` that should be used for processing file types, which were explicitly enabled to be processed by the `OutsideInModule`. After installing the March 2024 security update, Exchange Server uses the latest version of the `OutsideInModule` version `8.5.7` by default. By activating this override, `OutsideInModule` version `8.5.3` will be used. Details about the change that was done as part of the March 2024 security update can be found in [KB5037191](https://support.microsoft.com/topic/5037191). -Details about the security flaw can be found in the [MSRC security advisory](https://portal.msrc.microsoft.com/security-guidance/advisory/ADV24199947). +Details about the security vulnerability can be found in the [MSRC security advisory](https://portal.msrc.microsoft.com/security-guidance/advisory/ADV24199947). !!! warning "Warning" - We strongly recommend to not override the OutsideInModule version as this could make the server vulnerable! Do not use this override unless explicitly advised by Microsoft to do so. + Microsoft strongly recommends not overriding the default behavior that was introduced with the March 2024 security update if there are no functional issues that affect your organization's mail flow. ## Requirements @@ -62,7 +65,7 @@ Parameter | Description ----------|------------ ExchangeServerNames | A list of Exchange servers that you want to run the script against. SkipExchangeServerNames | A list of Exchange servers that you don't want to execute the configuration action. -ConfigureOverride | A list of file types that should be allowed to be processed by the `OutsideInModule`. It also allows you to override the version of the `OutsideInModule.dll` that should be used by Exchange Server. The following input can be used: `OutsideInModule`, `XlsbOfficePackage`, `XlsmOfficePackage`, `XlsxOfficePackage`, `ExcelStorage`, `DocmOfficePackage`, `DocxOfficePackage`, `PptmOfficePackage`, `PptxOfficePackage`, `WordStorage`, `PowerPointStorage`, `VisioStorage`, `Rtf`, `Xml`, `OdfTextDocument`, `OdfSpreadsheet`, `OdfPresentation`, `OneNote`, `Pdf`, `Html`, `AutoCad`, `Jpeg`, `Tiff`. `OutsideInModule` cannot be used together with other file types. The input is case-sensitive. +ConfigureOverride | A list of file types that should be allowed to be processed by the `OutsideInModule`. The following input can be used: `XlsbOfficePackage`, `XlsmOfficePackage`, `XlsxOfficePackage`, `ExcelStorage`, `DocmOfficePackage`, `DocxOfficePackage`, `PptmOfficePackage`, `PptxOfficePackage`, `WordStorage`, `PowerPointStorage`, `VisioStorage`, `Rtf`, `Xml`, `OdfTextDocument`, `OdfSpreadsheet`, `OdfPresentation`, `OneNote`, `Pdf`, `Html`, `AutoCad`, `Jpeg`, `Tiff`.

If you want to enable the previous version of the `OutsideInModule` (`8.5.3`) to process file types, you must specify `OutsideInModule` as file type. Note that the `OutsideInModule` value cannot be used together with other file type values.

The input is case-sensitive. Action | String parameter to define the action that should be performed. Input can be `Allow` or `Block`. The default value is: `Block` Rollback | Switch parameter to restore the `configuration.xml` that was backed-up during a previous run of the script. ScriptUpdateOnly | Switch parameter to only update the script without performing any other actions. From 3af4df27cfd3718e8f3ed1df59c925d92a2753c0 Mon Sep 17 00:00:00 2001 From: David Paulson Date: Wed, 6 Mar 2024 14:57:56 -0600 Subject: [PATCH 25/34] Improve Get-ProcessedServerList to handle Min SU requirement --- .../src/Shared/Get-ProcessedServerList.ps1 | 91 +++++++++---------- 1 file changed, 44 insertions(+), 47 deletions(-) diff --git a/Security/src/Shared/Get-ProcessedServerList.ps1 b/Security/src/Shared/Get-ProcessedServerList.ps1 index 978ab8b132..15e0bbc048 100644 --- a/Security/src/Shared/Get-ProcessedServerList.ps1 +++ b/Security/src/Shared/Get-ProcessedServerList.ps1 @@ -20,11 +20,19 @@ function Get-ProcessedServerList { ) begin { Write-Verbose "Calling: $($MyInvocation.MyCommand)" + # The complete list of all the Exchange Servers that we ran Get-ExchangeServer against. $getExchangeServer = New-Object System.Collections.Generic.List[object] + # The list of possible validExchangeServers prior to completing the list. + $possibleValidExchangeServer = New-Object System.Collections.Generic.List[object] + # The Get-ExchangeServer object for all the servers that are either in ExchangeServerNames or not in SkipExchangeServerNames and are within the correct SU build. $validExchangeServer = New-Object System.Collections.Generic.List[object] + # The FQDN of the servers in the validExchangeServer list $validExchangeServerFqdn = New-Object System.Collections.Generic.List[string] + # Servers that are online within the validExchangeServer list. $onlineExchangeServer = New-Object System.Collections.Generic.List[object] + # The FQDN of the servers that are in the onlineExchangeServer list $onlineExchangeServerFqdn = New-Object System.Collections.Generic.List[string] + # The list of servers that are outside min required SU $outdatedBuildExchangeServerFqdn = New-Object System.Collections.Generic.List[string] } process { @@ -51,43 +59,6 @@ function Get-ProcessedServerList { $getExchangeServer.AddRange($result) } - # Remove any Exchange Server from the list that doesn't run the expected security update build - if (-not([system.string]::IsNullOrWhiteSpace($MinimumSU))) { - Write-Verbose "Removing all Exchange Servers from the list that are running the expected build" - foreach ($server in $getExchangeServer) { - Write-Verbose "Validating Exchange Server: $($server.FQDN)" - $exSetupDetails = $null - $exVersionInformation = $null - - $exSetupDetails = Get-ExSetupDetails -Server $server.FQDN - $exVersionInformation = Get-ExchangeBuildVersionInformation -FileVersion $exSetupDetails.FileVersion - - if ($null -eq $exSetupDetails) { - Write-Verbose "Build cannot be validated due to unreachable Exchange Server - removing the server from the list" - $outdatedBuildExchangeServerFqdn.Add($server.FQDN) - continue - } - - if (-not(Test-ExchangeBuildGreaterOrEqualThanSecurityPatch -CurrentExchangeBuild $exVersionInformation -SU $MinimumSU)) { - Write-Verbose "Build is older than the expected security update: '$MinimumSU' - removing the server from the list" - $outdatedBuildExchangeServerFqdn.Add($server.FQDN) - continue - } - - Write-Verbose "Build is equal or newer than the expected security update: '$MinimumSU'" - } - - # Perform remove operation after foreach to avoid 'Collection was modified; enumeration operation may not execute' exception - $getExchangeServerClone = $getExchangeServer.Clone() - foreach ($server in $getExchangeServerClone) { - if ($outdatedBuildExchangeServerFqdn -contains $server.FQDN) { - $index = $getExchangeServer.FindIndex({ $args[0].FQDN -eq "$($server.FQDN)" }) - Write-Verbose "Removing server: $($server.FQDN) at index: $index from List" - $getExchangeServer.RemoveAt($index) - } - } - } - if ($null -ne $ExchangeServerNames -and $ExchangeServerNames.Count -gt 0) { $getExchangeServer | Where-Object { ($_.Name -in $ExchangeServerNames) -or ($_.FQDN -in $ExchangeServerNames) } | @@ -95,11 +66,11 @@ function Get-ProcessedServerList { if ($null -ne $SkipExchangeServerNames -and $SkipExchangeServerNames.Count -gt 0) { if (($_.Name -notin $SkipExchangeServerNames) -and ($_.FQDN -notin $SkipExchangeServerNames)) { Write-Verbose "Adding Server $($_.Name) to the valid server list" - $validExchangeServer.Add($_) + $possibleValidExchangeServer.Add($_) } } else { Write-Verbose "Adding Server $($_.Name) to the valid server list" - $validExchangeServer.Add($_) + $possibleValidExchangeServer.Add($_) } } } else { @@ -108,28 +79,54 @@ function Get-ProcessedServerList { Where-Object { ($_.Name -notin $SkipExchangeServerNames) -and ($_.FQDN -notin $SkipExchangeServerNames) } | ForEach-Object { Write-Verbose "Adding Server $($_.Name) to the valid server list" - $validExchangeServer.Add($_) + $possibleValidExchangeServer.Add($_) } } else { Write-Verbose "Adding Server $($_.Name) to the valid server list" - $validExchangeServer.AddRange($getExchangeServer) + $possibleValidExchangeServer.AddRange($getExchangeServer) } } - $validExchangeServer | ForEach-Object { $validExchangeServerFqdn.Add($_.FQDN ) } - - if ($CheckOnline) { + if ($CheckOnline -or (-not ([string]::IsNullOrEmpty($MinimumSU)))) { Write-Verbose "Will check to see if the servers are online" - foreach ($server in $validExchangeServer) { - $result = Invoke-ScriptBlockHandler -ComputerName $server -ScriptBlock { return $env:COMPUTERNAME } + foreach ($server in $possibleValidExchangeServer) { + $exSetupDetails = Get-ExSetupDetails -Server $server.FQDN - if ($null -ne $result) { + if ($null -ne $exSetupDetails -and + (-not ([string]::IsNullOrEmpty($exSetupDetails)))) { + # Got some results back, they are online. $onlineExchangeServer.Add($server) $onlineExchangeServerFqdn.Add($Server.FQDN) + + if (-not ([string]::IsNullOrEmpty($MinimumSU))) { + $params = @{ + CurrentExchangeBuild = (Get-ExchangeBuildVersionInformation -FileVersion $exSetupDetails.FileVersion) + SU = $MinimumSU + } + if ((Test-ExchangeBuildGreaterOrEqualThanSecurityPatch @params)) { + $validExchangeServer.Add($server) + } else { + Write-Verbose "Server $($server.Name) build is older than our expected min SU build. Build Number: $($exSetupDetails.FileVersion)" + $outdatedBuildExchangeServerFqdn.Add($server.FQDN) + } + } else { + $validExchangeServer.Add($server) + } } else { Write-Verbose "Server $($server.Name) not online" } } + } else { + $validExchangeServer.AddRange($possibleValidExchangeServer) + } + + $validExchangeServer | ForEach-Object { $validExchangeServerFqdn.Add($_.FQDN) } + + # If we have servers in the outdatedBuildExchangeServerFqdn list, the default response should be to display that we are removing them from the list. + if ($outdatedBuildExchangeServerFqdn.Count -gt 0) { + Write-Host "" + Write-Host "Excluded the following server(s) because the build is older than what is required to make a change: $([string]::Join(", ", $outdatedBuildExchangeServerFqdn))" + Write-Host "" } } end { From afe3488845861a13fb9fb187a35eeb2c08133de2 Mon Sep 17 00:00:00 2001 From: David Paulson Date: Wed, 6 Mar 2024 14:59:17 -0600 Subject: [PATCH 26/34] Don't duplicate display out of date Exchange Servers --- .../ConfigureFipFsTextExtractionOverrides.ps1 | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Security/src/ConfigureFipFsTextExtractionOverrides/ConfigureFipFsTextExtractionOverrides.ps1 b/Security/src/ConfigureFipFsTextExtractionOverrides/ConfigureFipFsTextExtractionOverrides.ps1 index 554f39a350..8614de35ce 100644 --- a/Security/src/ConfigureFipFsTextExtractionOverrides/ConfigureFipFsTextExtractionOverrides.ps1 +++ b/Security/src/ConfigureFipFsTextExtractionOverrides/ConfigureFipFsTextExtractionOverrides.ps1 @@ -168,10 +168,6 @@ begin { Rollback = $Rollback } - if ($processedExchangeServers.OutdatedBuildExchangeServerFqdn.Count -ge 1) { - Write-Host "Excluded the following server(s) because build is too old: $([string]::Join(", ", $processedExchangeServers.OutdatedBuildExchangeServerFqdn))" - } - if ($processedExchangeServers.OnlineExchangeServerFqdn.Count -ge 1) { Write-Host "Running the configuration change against the following server(s): $([string]::Join(", ", $params.ComputerName))" Invoke-TextExtractionOverride @params From 0500be43ceee818fa9d4a5743abbc070282ab2e3 Mon Sep 17 00:00:00 2001 From: David Paulson Date: Wed, 6 Mar 2024 15:04:45 -0600 Subject: [PATCH 27/34] Moved Get-ExSetupDetails to shared --- .../ExchangeInformation/Get-ExchangeInformation.ps1 | 4 ++-- .../Tests/HealthChecker.E19.Main.Tests.ps1 | 2 +- .../Tests/HealthChecker.E19.Scenarios.Tests.ps1 | 4 ++-- .../Tests/HealthChecker.MockedCalls.Tests.ps1 | 4 ++-- .../HealthCheckerTest.CommonMocks.NotPublished.ps1 | 2 +- Security/src/Shared/Get-ProcessedServerList.ps1 | 4 ++-- .../Get-ExSetupFileVersionInfo.ps1 | 11 +++++++---- 7 files changed, 17 insertions(+), 14 deletions(-) rename Diagnostics/HealthChecker/DataCollection/ExchangeInformation/Get-ExSetupDetails.ps1 => Shared/Get-ExSetupFileVersionInfo.ps1 (85%) diff --git a/Diagnostics/HealthChecker/DataCollection/ExchangeInformation/Get-ExchangeInformation.ps1 b/Diagnostics/HealthChecker/DataCollection/ExchangeInformation/Get-ExchangeInformation.ps1 index 8a3fd05915..9d5abe474d 100644 --- a/Diagnostics/HealthChecker/DataCollection/ExchangeInformation/Get-ExchangeInformation.ps1 +++ b/Diagnostics/HealthChecker/DataCollection/ExchangeInformation/Get-ExchangeInformation.ps1 @@ -5,6 +5,7 @@ . $PSScriptRoot\..\..\..\..\Shared\ErrorMonitorFunctions.ps1 . $PSScriptRoot\..\..\..\..\Shared\Get-ExchangeBuildVersionInformation.ps1 . $PSScriptRoot\..\..\..\..\Shared\Get-ExchangeSettingOverride.ps1 +. $PSScriptRoot\..\..\..\..\Shared\Get-ExSetupFileVersionInfo.ps1 . $PSScriptRoot\..\..\..\..\Shared\Get-FileContentInformation.ps1 . $PSScriptRoot\IISInformation\Get-ExchangeAppPoolsInformation.ps1 . $PSScriptRoot\IISInformation\Get-ExchangeServerIISSettings.ps1 @@ -16,7 +17,6 @@ . $PSScriptRoot\Get-ExchangeServerMaintenanceState.ps1 . $PSScriptRoot\Get-ExchangeUpdates.ps1 . $PSScriptRoot\Get-ExchangeVirtualDirectories.ps1 -. $PSScriptRoot\Get-ExSetupDetails.ps1 . $PSScriptRoot\Get-FIPFSScanEngineVersionState.ps1 . $PSScriptRoot\Get-ServerRole.ps1 function Get-ExchangeInformation { @@ -35,7 +35,7 @@ function Get-ExchangeInformation { $windows2016OrGreater = Invoke-ScriptBlockHandler @params $getExchangeServer = (Get-ExchangeServer -Identity $Server -Status) $exchangeCertificates = Get-ExchangeServerCertificates -Server $Server - $exSetupDetails = Get-ExSetupDetails -Server $Server + $exSetupDetails = Get-ExSetupFileVersionInfo -Server $Server -CatchActionFunction ${Function:Invoke-CatchActions} if ($null -eq $exSetupDetails) { # couldn't find ExSetup.exe this should be rare so we are just going to handle this by displaying the AdminDisplayVersion from Get-ExchangeServer diff --git a/Diagnostics/HealthChecker/Tests/HealthChecker.E19.Main.Tests.ps1 b/Diagnostics/HealthChecker/Tests/HealthChecker.E19.Main.Tests.ps1 index 7efb4efbd1..29b4722c78 100644 --- a/Diagnostics/HealthChecker/Tests/HealthChecker.E19.Main.Tests.ps1 +++ b/Diagnostics/HealthChecker/Tests/HealthChecker.E19.Main.Tests.ps1 @@ -174,7 +174,7 @@ Describe "Testing Health Checker by Mock Data Imports" { -MockWith { return Import-Clixml "$Script:MockDataCollectionRoot\Hardware\Physical_Win32_PhysicalMemory.xml" } Mock Get-WmiObjectHandler -ParameterFilter { $Class -eq "Win32_Processor" } ` -MockWith { return Import-Clixml "$Script:MockDataCollectionRoot\Hardware\Physical_Win32_Processor.xml" } - Mock Get-ExSetupDetails { return Import-Clixml "$Script:MockDataCollectionRoot\Exchange\ExSetup1.xml" } + Mock Get-ExSetupFileVersionInfo { return Import-Clixml "$Script:MockDataCollectionRoot\Exchange\ExSetup1.xml" } Mock Get-WebSite -ParameterFilter { $Name -eq "Default Web Site" } -MockWith { return Import-Clixml "$Script:MockDataCollectionRoot\Exchange\IIS\GetWebSite_DefaultWebSite1.xml" } Mock Get-WebConfigFile -ParameterFilter { $PSPath -eq "IIS:\Sites\Default Web Site" } -MockWith { return [PSCustomObject]@{ FullName = "$Script:MockDataCollectionRoot\Exchange\IIS\DefaultWebSite_web2.config" } } Mock Invoke-ScriptBlockHandler -ParameterFilter { $ScriptBlockDescription -eq "Getting applicationHost.config" } -MockWith { return Get-Content "$Script:MockDataCollectionRoot\Exchange\IIS\applicationHost2.config" -Raw } diff --git a/Diagnostics/HealthChecker/Tests/HealthChecker.E19.Scenarios.Tests.ps1 b/Diagnostics/HealthChecker/Tests/HealthChecker.E19.Scenarios.Tests.ps1 index a36ad08e03..c0fc07e9cd 100644 --- a/Diagnostics/HealthChecker/Tests/HealthChecker.E19.Scenarios.Tests.ps1 +++ b/Diagnostics/HealthChecker/Tests/HealthChecker.E19.Scenarios.Tests.ps1 @@ -220,7 +220,7 @@ Describe "Testing Health Checker by Mock Data Imports" { Mock Get-OwaVirtualDirectory { return Import-Clixml "$Script:MockDataCollectionRoot\Exchange\GetOwaVirtualDirectory2.xml" } Mock Get-AcceptedDomain { return Import-Clixml "$Script:MockDataCollectionRoot\Exchange\GetAcceptedDomain_Bad.xml" } Mock Get-DnsClient { return Import-Clixml "$Script:MockDataCollectionRoot\OS\GetDnsClient1.xml" } - Mock Get-ExSetupDetails { return Import-Clixml "$Script:MockDataCollectionRoot\Exchange\ExSetup1.xml" } + Mock Get-ExSetupFileVersionInfo { return Import-Clixml "$Script:MockDataCollectionRoot\Exchange\ExSetup1.xml" } Mock Invoke-ScriptBlockHandler -ParameterFilter { $ScriptBlockDescription -eq "Getting applicationHost.config" } -MockWith { return Get-Content "$Script:MockDataCollectionRoot\Exchange\IIS\applicationHost1.config" -Raw } Mock Get-Content -ParameterFilter { $Path -eq "C:\Program Files\Microsoft\Exchange Server\V15\Bin\Search\Ceres\Runtime\1.0\noderunner.exe.config" } -MockWith { Get-Content "$Script:MockDataCollectionRoot\Exchange\noderunner.exe1.config" -Raw } Mock Get-Content -ParameterFilter { $Path -eq "C:\Program Files\Microsoft\Exchange Server\V15\Bin\EdgeTransport.exe.config" } -MockWith { Get-Content "$Script:MockDataCollectionRoot\Exchange\EdgeTransport.exe1.config" -Raw } @@ -308,7 +308,7 @@ Describe "Testing Health Checker by Mock Data Imports" { -MockWith { return Import-Clixml "$Script:MockDataCollectionRoot\Hardware\Physical_Win32_PhysicalMemory.xml" } Mock Get-WmiObjectHandler -ParameterFilter { $Class -eq "Win32_Processor" } ` -MockWith { return Import-Clixml "$Script:MockDataCollectionRoot\Hardware\Physical_Win32_Processor1.xml" } - Mock Get-ExSetupDetails { return Import-Clixml "$Script:MockDataCollectionRoot\Exchange\ExSetup1.xml" } + Mock Get-ExSetupFileVersionInfo { return Import-Clixml "$Script:MockDataCollectionRoot\Exchange\ExSetup1.xml" } Mock Invoke-ScriptBlockHandler -ParameterFilter { $ScriptBlockDescription -eq "Getting applicationHost.config" } -MockWith { return Get-Content "$Script:MockDataCollectionRoot\Exchange\IIS\applicationHost2.config" -Raw } SetDefaultRunOfHealthChecker "Debug_Scenario3_Physical_Results.xml" diff --git a/Diagnostics/HealthChecker/Tests/HealthChecker.MockedCalls.Tests.ps1 b/Diagnostics/HealthChecker/Tests/HealthChecker.MockedCalls.Tests.ps1 index 686691d0f7..aeb7b399ab 100644 --- a/Diagnostics/HealthChecker/Tests/HealthChecker.MockedCalls.Tests.ps1 +++ b/Diagnostics/HealthChecker/Tests/HealthChecker.MockedCalls.Tests.ps1 @@ -24,7 +24,7 @@ Describe "Testing Health Checker by Mock Data Imports" { Mock Get-ExchangeServer { return Import-Clixml "$Script:MockDataCollectionRoot\Exchange\GetExchangeServer.xml" } Mock Get-ExchangeCertificate { return Import-Clixml "$Script:MockDataCollectionRoot\Exchange\GetExchangeCertificate.xml" } Mock Get-AuthConfig { return Import-Clixml "$Script:MockDataCollectionRoot\Exchange\GetAuthConfig.xml" } - Mock Get-ExSetupDetails { return Import-Clixml "$Script:MockDataCollectionRoot\Exchange\ExSetup.xml" } + Mock Get-ExSetupFileVersionInfo { return Import-Clixml "$Script:MockDataCollectionRoot\Exchange\ExSetup.xml" } Mock Get-MailboxServer { return Import-Clixml "$Script:MockDataCollectionRoot\Exchange\GetMailboxServer.xml" } Mock Get-OwaVirtualDirectory { return Import-Clixml "$Script:MockDataCollectionRoot\Exchange\GetOwaVirtualDirectory.xml" } Mock Get-WebServicesVirtualDirectory { return Import-Clixml "$Script:MockDataCollectionRoot\Exchange\GetWebServicesVirtualDirectory.xml" } @@ -79,7 +79,7 @@ Describe "Testing Health Checker by Mock Data Imports" { Assert-MockCalled Get-ExchangeServer -Exactly 1 Assert-MockCalled Get-ExchangeCertificate -Exactly 1 Assert-MockCalled Get-AuthConfig -Exactly 1 - Assert-MockCalled Get-ExSetupDetails -Exactly 1 + Assert-MockCalled Get-ExSetupFileVersionInfo -Exactly 1 Assert-MockCalled Get-MailboxServer -Exactly 1 Assert-MockCalled Get-OwaVirtualDirectory -Exactly 1 Assert-MockCalled Get-WebServicesVirtualDirectory -Exactly 1 diff --git a/Diagnostics/HealthChecker/Tests/HealthCheckerTest.CommonMocks.NotPublished.ps1 b/Diagnostics/HealthChecker/Tests/HealthCheckerTest.CommonMocks.NotPublished.ps1 index c616afc645..e1e76593dc 100644 --- a/Diagnostics/HealthChecker/Tests/HealthCheckerTest.CommonMocks.NotPublished.ps1 +++ b/Diagnostics/HealthChecker/Tests/HealthCheckerTest.CommonMocks.NotPublished.ps1 @@ -221,7 +221,7 @@ Mock Get-ExchangeADSplitPermissionsEnabled { return $false } -Mock Get-ExSetupDetails { +Mock Get-ExSetupFileVersionInfo { return Import-Clixml "$Script:MockDataCollectionRoot\Exchange\ExSetup.xml" } diff --git a/Security/src/Shared/Get-ProcessedServerList.ps1 b/Security/src/Shared/Get-ProcessedServerList.ps1 index 15e0bbc048..e0dd70c329 100644 --- a/Security/src/Shared/Get-ProcessedServerList.ps1 +++ b/Security/src/Shared/Get-ProcessedServerList.ps1 @@ -2,9 +2,9 @@ # Licensed under the MIT License. . $PSScriptRoot\..\..\..\Shared\Get-ExchangeBuildVersionInformation.ps1 +. $PSScriptRoot\..\..\..\Shared\Get-ExSetupFileVersionInfo.ps1 . $PSScriptRoot\..\..\..\Shared\Invoke-ScriptBlockHandler.ps1 . $PSScriptRoot\..\..\..\Diagnostics\HealthChecker\Helpers\CompareExchangeBuildLevel.ps1 # TODO: Make this a shared function -. $PSScriptRoot\..\..\..\Diagnostics\HealthChecker\DataCollection\ExchangeInformation\Get-ExSetupDetails.ps1 # TODO: Make this a shared function function Get-ProcessedServerList { [CmdletBinding()] param( @@ -90,7 +90,7 @@ function Get-ProcessedServerList { if ($CheckOnline -or (-not ([string]::IsNullOrEmpty($MinimumSU)))) { Write-Verbose "Will check to see if the servers are online" foreach ($server in $possibleValidExchangeServer) { - $exSetupDetails = Get-ExSetupDetails -Server $server.FQDN + $exSetupDetails = Get-ExSetupFileVersionInfo -Server $server.FQDN if ($null -ne $exSetupDetails -and (-not ([string]::IsNullOrEmpty($exSetupDetails)))) { diff --git a/Diagnostics/HealthChecker/DataCollection/ExchangeInformation/Get-ExSetupDetails.ps1 b/Shared/Get-ExSetupFileVersionInfo.ps1 similarity index 85% rename from Diagnostics/HealthChecker/DataCollection/ExchangeInformation/Get-ExSetupDetails.ps1 rename to Shared/Get-ExSetupFileVersionInfo.ps1 index ea82172f3f..d2ee7d0230 100644 --- a/Diagnostics/HealthChecker/DataCollection/ExchangeInformation/Get-ExSetupDetails.ps1 +++ b/Shared/Get-ExSetupFileVersionInfo.ps1 @@ -1,11 +1,14 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. -. $PSScriptRoot\..\..\..\..\Shared\Invoke-ScriptBlockHandler.ps1 -function Get-ExSetupDetails { +. $PSScriptRoot\Invoke-ScriptBlockHandler.ps1 +function Get-ExSetupFileVersionInfo { param( [Parameter(Mandatory = $true)] - [string]$Server + [string]$Server, + + [Parameter(Mandatory = $false)] + [ScriptBlock]$CatchActionFunction ) Write-Verbose "Calling: $($MyInvocation.MyCommand)" @@ -27,7 +30,7 @@ function Get-ExSetupDetails { } } - $exSetupDetails = Invoke-ScriptBlockHandler -ComputerName $Server -ScriptBlock ${Function:Get-ExSetupDetailsScriptBlock} -ScriptBlockDescription "Getting ExSetup remotely" -CatchActionFunction ${Function:Invoke-CatchActions} + $exSetupDetails = Invoke-ScriptBlockHandler -ComputerName $Server -ScriptBlock ${Function:Get-ExSetupDetailsScriptBlock} -ScriptBlockDescription "Getting ExSetup remotely" -CatchActionFunction $CatchActionFunction Write-Verbose "Exiting: $($MyInvocation.MyCommand)" return $exSetupDetails } From 103f368289f0b70abb5d154a33042e9b520844b1 Mon Sep 17 00:00:00 2001 From: David Paulson Date: Wed, 6 Mar 2024 15:26:37 -0600 Subject: [PATCH 28/34] Moved CompareExchangeBuildLevel to shared --- .../Analyzer/Invoke-AnalyzerFrequentConfigurationIssues.ps1 | 2 +- .../HealthChecker/Analyzer/Invoke-AnalyzerOsInformation.ps1 | 2 +- .../Analyzer/Security/Get-SerializedDataSigningState.ps1 | 2 +- .../Analyzer/Security/Invoke-AnalyzerSecurityADV24199947.ps1 | 2 +- .../Security/Invoke-AnalyzerSecurityAMSIConfigState.ps1 | 2 +- .../Analyzer/Security/Invoke-AnalyzerSecurityCve-2021-1730.ps1 | 2 +- .../Security/Invoke-AnalyzerSecurityCve-2021-34470.ps1 | 2 +- .../Security/Invoke-AnalyzerSecurityCve-MarchSuSpecial.ps1 | 2 +- .../Analyzer/Security/Invoke-AnalyzerSecurityCveCheck.ps1 | 2 +- .../Security/Invoke-AnalyzerSecurityMitigationService.ps1 | 2 +- .../Analyzer/Security/Invoke-AnalyzerSecurityOverrides.ps1 | 2 +- .../ExchangeInformation/Get-ExchangeAES256CBCDetails.ps1 | 2 +- Security/src/Shared/Get-ProcessedServerList.ps1 | 3 ++- .../Helpers => Shared}/CompareExchangeBuildLevel.ps1 | 2 +- 14 files changed, 15 insertions(+), 14 deletions(-) rename {Diagnostics/HealthChecker/Helpers => Shared}/CompareExchangeBuildLevel.ps1 (98%) diff --git a/Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerFrequentConfigurationIssues.ps1 b/Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerFrequentConfigurationIssues.ps1 index 772f22bb6c..b8426c1ec1 100644 --- a/Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerFrequentConfigurationIssues.ps1 +++ b/Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerFrequentConfigurationIssues.ps1 @@ -3,7 +3,7 @@ . $PSScriptRoot\Add-AnalyzedResultInformation.ps1 . $PSScriptRoot\Get-DisplayResultsGroupingKey.ps1 -. $PSScriptRoot\..\Helpers\CompareExchangeBuildLevel.ps1 +. $PSScriptRoot\..\..\..\Shared\CompareExchangeBuildLevel.ps1 function Invoke-AnalyzerFrequentConfigurationIssues { [CmdletBinding()] param( diff --git a/Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerOsInformation.ps1 b/Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerOsInformation.ps1 index 5ee4ae2827..42a856d01e 100644 --- a/Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerOsInformation.ps1 +++ b/Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerOsInformation.ps1 @@ -3,7 +3,7 @@ . $PSScriptRoot\Add-AnalyzedResultInformation.ps1 . $PSScriptRoot\Get-DisplayResultsGroupingKey.ps1 -. $PSScriptRoot\..\Helpers\CompareExchangeBuildLevel.ps1 +. $PSScriptRoot\..\..\..\Shared\CompareExchangeBuildLevel.ps1 . $PSScriptRoot\..\..\..\Shared\VisualCRedistributableVersionFunctions.ps1 . $PSScriptRoot\..\..\..\Shared\Get-NETFrameworkVersion.ps1 function Invoke-AnalyzerOsInformation { diff --git a/Diagnostics/HealthChecker/Analyzer/Security/Get-SerializedDataSigningState.ps1 b/Diagnostics/HealthChecker/Analyzer/Security/Get-SerializedDataSigningState.ps1 index 555d389cdc..bfd18147ec 100644 --- a/Diagnostics/HealthChecker/Analyzer/Security/Get-SerializedDataSigningState.ps1 +++ b/Diagnostics/HealthChecker/Analyzer/Security/Get-SerializedDataSigningState.ps1 @@ -2,7 +2,7 @@ # Licensed under the MIT License. . $PSScriptRoot\..\Get-FilteredSettingOverrideInformation.ps1 -. $PSScriptRoot\..\..\Helpers\CompareExchangeBuildLevel.ps1 +. $PSScriptRoot\..\..\..\..\Shared\CompareExchangeBuildLevel.ps1 # Used to determine the state of the Serialized Data Signing on the server. function Get-SerializedDataSigningState { [CmdletBinding()] diff --git a/Diagnostics/HealthChecker/Analyzer/Security/Invoke-AnalyzerSecurityADV24199947.ps1 b/Diagnostics/HealthChecker/Analyzer/Security/Invoke-AnalyzerSecurityADV24199947.ps1 index 7726713738..b6f24ef412 100644 --- a/Diagnostics/HealthChecker/Analyzer/Security/Invoke-AnalyzerSecurityADV24199947.ps1 +++ b/Diagnostics/HealthChecker/Analyzer/Security/Invoke-AnalyzerSecurityADV24199947.ps1 @@ -2,7 +2,7 @@ # Licensed under the MIT License. . $PSScriptRoot\..\Add-AnalyzedResultInformation.ps1 -. $PSScriptRoot\..\..\Helpers\CompareExchangeBuildLevel.ps1 #This is moving in a different PR. +. $PSScriptRoot\..\..\..\..\Shared\CompareExchangeBuildLevel.ps1 <# .DESCRIPTION diff --git a/Diagnostics/HealthChecker/Analyzer/Security/Invoke-AnalyzerSecurityAMSIConfigState.ps1 b/Diagnostics/HealthChecker/Analyzer/Security/Invoke-AnalyzerSecurityAMSIConfigState.ps1 index 94e95c97d4..4402fb9d85 100644 --- a/Diagnostics/HealthChecker/Analyzer/Security/Invoke-AnalyzerSecurityAMSIConfigState.ps1 +++ b/Diagnostics/HealthChecker/Analyzer/Security/Invoke-AnalyzerSecurityAMSIConfigState.ps1 @@ -1,7 +1,7 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. . $PSScriptRoot\..\Get-FilteredSettingOverrideInformation.ps1 -. $PSScriptRoot\..\..\Helpers\CompareExchangeBuildLevel.ps1 +. $PSScriptRoot\..\..\..\..\Shared\CompareExchangeBuildLevel.ps1 function Invoke-AnalyzerSecurityAMSIConfigState { [CmdletBinding()] diff --git a/Diagnostics/HealthChecker/Analyzer/Security/Invoke-AnalyzerSecurityCve-2021-1730.ps1 b/Diagnostics/HealthChecker/Analyzer/Security/Invoke-AnalyzerSecurityCve-2021-1730.ps1 index 5a29b178c4..a83b2b145a 100644 --- a/Diagnostics/HealthChecker/Analyzer/Security/Invoke-AnalyzerSecurityCve-2021-1730.ps1 +++ b/Diagnostics/HealthChecker/Analyzer/Security/Invoke-AnalyzerSecurityCve-2021-1730.ps1 @@ -2,7 +2,7 @@ # Licensed under the MIT License. . $PSScriptRoot\..\Add-AnalyzedResultInformation.ps1 -. $PSScriptRoot\..\..\Helpers\CompareExchangeBuildLevel.ps1 +. $PSScriptRoot\..\..\..\..\Shared\CompareExchangeBuildLevel.ps1 function Invoke-AnalyzerSecurityCve-2021-1730 { [CmdletBinding()] param( diff --git a/Diagnostics/HealthChecker/Analyzer/Security/Invoke-AnalyzerSecurityCve-2021-34470.ps1 b/Diagnostics/HealthChecker/Analyzer/Security/Invoke-AnalyzerSecurityCve-2021-34470.ps1 index 18d55dde70..23d10c34e9 100644 --- a/Diagnostics/HealthChecker/Analyzer/Security/Invoke-AnalyzerSecurityCve-2021-34470.ps1 +++ b/Diagnostics/HealthChecker/Analyzer/Security/Invoke-AnalyzerSecurityCve-2021-34470.ps1 @@ -2,7 +2,7 @@ # Licensed under the MIT License. . $PSScriptRoot\..\Add-AnalyzedResultInformation.ps1 -. $PSScriptRoot\..\..\Helpers\CompareExchangeBuildLevel.ps1 +. $PSScriptRoot\..\..\..\..\Shared\CompareExchangeBuildLevel.ps1 function Invoke-AnalyzerSecurityCve-2021-34470 { [CmdletBinding()] param( diff --git a/Diagnostics/HealthChecker/Analyzer/Security/Invoke-AnalyzerSecurityCve-MarchSuSpecial.ps1 b/Diagnostics/HealthChecker/Analyzer/Security/Invoke-AnalyzerSecurityCve-MarchSuSpecial.ps1 index 096874f709..632eb8df0f 100644 --- a/Diagnostics/HealthChecker/Analyzer/Security/Invoke-AnalyzerSecurityCve-MarchSuSpecial.ps1 +++ b/Diagnostics/HealthChecker/Analyzer/Security/Invoke-AnalyzerSecurityCve-MarchSuSpecial.ps1 @@ -2,7 +2,7 @@ # Licensed under the MIT License. . $PSScriptRoot\..\Add-AnalyzedResultInformation.ps1 -. $PSScriptRoot\..\..\Helpers\CompareExchangeBuildLevel.ps1 +. $PSScriptRoot\..\..\..\..\Shared\CompareExchangeBuildLevel.ps1 function Invoke-AnalyzerSecurityCve-MarchSuSpecial { [CmdletBinding()] param( diff --git a/Diagnostics/HealthChecker/Analyzer/Security/Invoke-AnalyzerSecurityCveCheck.ps1 b/Diagnostics/HealthChecker/Analyzer/Security/Invoke-AnalyzerSecurityCveCheck.ps1 index 10984b36bc..c4bb99ed89 100644 --- a/Diagnostics/HealthChecker/Analyzer/Security/Invoke-AnalyzerSecurityCveCheck.ps1 +++ b/Diagnostics/HealthChecker/Analyzer/Security/Invoke-AnalyzerSecurityCveCheck.ps1 @@ -13,7 +13,7 @@ . $PSScriptRoot\Invoke-AnalyzerSecurityExtendedProtectionConfigState.ps1 . $PSScriptRoot\Invoke-AnalyzerSecurityIISModules.ps1 . $PSScriptRoot\..\Add-AnalyzedResultInformation.ps1 -. $PSScriptRoot\..\..\Helpers\CompareExchangeBuildLevel.ps1 +. $PSScriptRoot\..\..\..\..\Shared\CompareExchangeBuildLevel.ps1 function Invoke-AnalyzerSecurityCveCheck { [CmdletBinding()] param( diff --git a/Diagnostics/HealthChecker/Analyzer/Security/Invoke-AnalyzerSecurityMitigationService.ps1 b/Diagnostics/HealthChecker/Analyzer/Security/Invoke-AnalyzerSecurityMitigationService.ps1 index 2bfb62bbc7..81b8bbb03b 100644 --- a/Diagnostics/HealthChecker/Analyzer/Security/Invoke-AnalyzerSecurityMitigationService.ps1 +++ b/Diagnostics/HealthChecker/Analyzer/Security/Invoke-AnalyzerSecurityMitigationService.ps1 @@ -2,7 +2,7 @@ # Licensed under the MIT License. . $PSScriptRoot\..\Add-AnalyzedResultInformation.ps1 -. $PSScriptRoot\..\..\Helpers\CompareExchangeBuildLevel.ps1 +. $PSScriptRoot\..\..\..\..\Shared\CompareExchangeBuildLevel.ps1 function Invoke-AnalyzerSecurityMitigationService { [CmdletBinding()] param( diff --git a/Diagnostics/HealthChecker/Analyzer/Security/Invoke-AnalyzerSecurityOverrides.ps1 b/Diagnostics/HealthChecker/Analyzer/Security/Invoke-AnalyzerSecurityOverrides.ps1 index 3408181194..b8a75e272f 100644 --- a/Diagnostics/HealthChecker/Analyzer/Security/Invoke-AnalyzerSecurityOverrides.ps1 +++ b/Diagnostics/HealthChecker/Analyzer/Security/Invoke-AnalyzerSecurityOverrides.ps1 @@ -3,7 +3,7 @@ . $PSScriptRoot\..\Add-AnalyzedResultInformation.ps1 . $PSScriptRoot\..\Get-FilteredSettingOverrideInformation.ps1 -. $PSScriptRoot\..\..\Helpers\CompareExchangeBuildLevel.ps1 +. $PSScriptRoot\..\..\..\..\Shared\CompareExchangeBuildLevel.ps1 function Invoke-AnalyzerSecurityOverrides { [CmdletBinding()] param( diff --git a/Diagnostics/HealthChecker/DataCollection/ExchangeInformation/Get-ExchangeAES256CBCDetails.ps1 b/Diagnostics/HealthChecker/DataCollection/ExchangeInformation/Get-ExchangeAES256CBCDetails.ps1 index 08270470cd..fbbdd7de2d 100644 --- a/Diagnostics/HealthChecker/DataCollection/ExchangeInformation/Get-ExchangeAES256CBCDetails.ps1 +++ b/Diagnostics/HealthChecker/DataCollection/ExchangeInformation/Get-ExchangeAES256CBCDetails.ps1 @@ -1,7 +1,7 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. -. $PSScriptRoot\..\..\Helpers\CompareExchangeBuildLevel.ps1 +. $PSScriptRoot\..\..\..\..\Shared\CompareExchangeBuildLevel.ps1 . $PSScriptRoot\..\..\..\..\Shared\Invoke-ScriptBlockHandler.ps1 . $PSScriptRoot\..\..\..\..\Shared\ErrorMonitorFunctions.ps1 function Get-ExchangeAES256CBCDetails { diff --git a/Security/src/Shared/Get-ProcessedServerList.ps1 b/Security/src/Shared/Get-ProcessedServerList.ps1 index e0dd70c329..d41f261cd9 100644 --- a/Security/src/Shared/Get-ProcessedServerList.ps1 +++ b/Security/src/Shared/Get-ProcessedServerList.ps1 @@ -1,10 +1,11 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. +. $PSScriptRoot\..\..\..\Shared\CompareExchangeBuildLevel.ps1 . $PSScriptRoot\..\..\..\Shared\Get-ExchangeBuildVersionInformation.ps1 . $PSScriptRoot\..\..\..\Shared\Get-ExSetupFileVersionInfo.ps1 . $PSScriptRoot\..\..\..\Shared\Invoke-ScriptBlockHandler.ps1 -. $PSScriptRoot\..\..\..\Diagnostics\HealthChecker\Helpers\CompareExchangeBuildLevel.ps1 # TODO: Make this a shared function + function Get-ProcessedServerList { [CmdletBinding()] param( diff --git a/Diagnostics/HealthChecker/Helpers/CompareExchangeBuildLevel.ps1 b/Shared/CompareExchangeBuildLevel.ps1 similarity index 98% rename from Diagnostics/HealthChecker/Helpers/CompareExchangeBuildLevel.ps1 rename to Shared/CompareExchangeBuildLevel.ps1 index 9fa467159f..9062b3f577 100644 --- a/Diagnostics/HealthChecker/Helpers/CompareExchangeBuildLevel.ps1 +++ b/Shared/CompareExchangeBuildLevel.ps1 @@ -1,7 +1,7 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. -. $PSScriptRoot\..\..\..\Shared\Get-ExchangeBuildVersionInformation.ps1 +. $PSScriptRoot\Get-ExchangeBuildVersionInformation.ps1 function Test-ExchangeBuildGreaterOrEqualThanBuild { [CmdletBinding()] [OutputType([bool])] From 92270c95fce4838051f8b584569ed7e0cf37ce61 Mon Sep 17 00:00:00 2001 From: David Paulson Date: Sun, 10 Mar 2024 13:08:04 -0500 Subject: [PATCH 29/34] Use starts-with vs contains --- .../ConfigurationAction/Invoke-TextExtractionOverride.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Security/src/ConfigureFipFsTextExtractionOverrides/ConfigurationAction/Invoke-TextExtractionOverride.ps1 b/Security/src/ConfigureFipFsTextExtractionOverrides/ConfigurationAction/Invoke-TextExtractionOverride.ps1 index fa08bb2bcc..f57e98403e 100644 --- a/Security/src/ConfigureFipFsTextExtractionOverrides/ConfigurationAction/Invoke-TextExtractionOverride.ps1 +++ b/Security/src/ConfigureFipFsTextExtractionOverrides/ConfigurationAction/Invoke-TextExtractionOverride.ps1 @@ -69,7 +69,7 @@ function Invoke-TextExtractionOverride { "/*[local-name()='ModuleLists']/*[local-name()='ModuleList'][@TypeList='OutsideInOnly']/*[local-name()='Module'][contains(., 'OutsideInModule.dll')]" $typeListBaseXPathFilter = $baseXPathFilter + "/*[local-name()='TypeLists']/*[local-name()='TypeList'][@Name='{0}']" $getTypeBaseTypeListXPathFilter = $baseXPathFilter + - "/*[local-name()='TypeLists']/*[local-name()='TypeList']/*[local-name()='Type'][contains(@Name, '{0}')]" + "/*[local-name()='TypeLists']/*[local-name()='TypeList']/*[local-name()='Type'][starts-with(@Name, '{0}')]" Write-Progress @writeProgressParams $fipFsDatabaseParams = @{ From 5b9596630f078a8eae9bdbd5a2da6aad8650f859 Mon Sep 17 00:00:00 2001 From: lusassl-msft Date: Mon, 11 Mar 2024 11:43:37 +0100 Subject: [PATCH 30/34] Improved null-value check in restore operation --- Security/src/Shared/Invoke-XmlConfigurationRemoteAction.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Security/src/Shared/Invoke-XmlConfigurationRemoteAction.ps1 b/Security/src/Shared/Invoke-XmlConfigurationRemoteAction.ps1 index 01ff7abd64..aca10514e1 100644 --- a/Security/src/Shared/Invoke-XmlConfigurationRemoteAction.ps1 +++ b/Security/src/Shared/Invoke-XmlConfigurationRemoteAction.ps1 @@ -236,7 +236,7 @@ function Invoke-XmlConfigurationRemoteAction { $selectNodes = $contentXml.SelectNodes($action.SelectNodesFilter) Write-Verbose "Found $($selectNodes.Count) node(s)" - if ($null -eq $selectNodes) { + if ($selectNodes.Count -eq 0) { Write-Verbose "No nodes were found with the current filter. Unable to perform restore action. Filter: $($action.SelectNodesFilter)" # TODO: Determine how to handle continue From 21d283872cea1361dfe67a3745f067ce595b9951 Mon Sep 17 00:00:00 2001 From: David Paulson Date: Mon, 11 Mar 2024 12:32:04 -0500 Subject: [PATCH 31/34] After SelectNodes need to use Count -eq 0 to determine no result --- .../Shared/Invoke-XmlConfigurationRemoteAction.ps1 | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/Security/src/Shared/Invoke-XmlConfigurationRemoteAction.ps1 b/Security/src/Shared/Invoke-XmlConfigurationRemoteAction.ps1 index aca10514e1..bf10ed817a 100644 --- a/Security/src/Shared/Invoke-XmlConfigurationRemoteAction.ps1 +++ b/Security/src/Shared/Invoke-XmlConfigurationRemoteAction.ps1 @@ -279,7 +279,7 @@ function Invoke-XmlConfigurationRemoteAction { } elseif ($action.RestoreType -eq "MoveNode") { $moveToNodeLocation = $contentXml.SelectNodes($action.Operation.MoveToSelectNodesFilter) - if ($null -eq $moveToNodeLocation) { + if ($moveToNodeLocation.Count -eq 0) { throw "Failed to find node selection to move to" } @@ -330,7 +330,7 @@ function Invoke-XmlConfigurationRemoteAction { Write-Verbose "Trying to find SelectNodes based off filter: '$($action.SelectNodesFilter)'" $selectNodes = $contentXml.SelectNodes($action.SelectNodesFilter) - if ($null -eq $selectNodes) { + if ($selectNodes.Count -eq 0) { # This shouldn't be treated as an error. Write-Verbose "No nodes were found with the current filter. This could be the action was already taken or doesn't exist." continue @@ -370,6 +370,10 @@ function Invoke-XmlConfigurationRemoteAction { $parentSelectNodesFilter = $action.SelectNodesFilter.Substring(0, $lastIndexOf) $testSelectNodesResults = $contentXml.SelectNodes($parentSelectNodesFilter) + if ($testSelectNodesResults.Count -eq 0) { + throw "No parent nodes where found. This shouldn't occur. Unable to continue." + } + if ($testSelectNodesResults.Count -gt 1) { throw "Multiple nodes returned for parent node which will result in restore process failure. Unable to continue." } @@ -388,7 +392,7 @@ function Invoke-XmlConfigurationRemoteAction { $lastChildNode = $action.SelectNodesFilter.Substring($action.SelectNodesFilter.LastIndexOf("/")) $moveToNodeLocation = $contentXml.SelectNodes($action.Operation.MoveToSelectNodesFilter) - if ($null -eq $moveToNodeLocation) { + if ($moveToNodeLocation.Count -eq 0) { throw "Failed to find node selection to move to" } @@ -413,8 +417,7 @@ function Invoke-XmlConfigurationRemoteAction { Write-Verbose "Verifying possible restore process with filter: $($currentRestoreAction.Operation.MoveToSelectNodesFilter)" $verifyMoveBack = $contentXml.SelectNodes($currentRestoreAction.Operation.MoveToSelectNodesFilter) - if ($null -eq $verifyMoveBack -or - $verifyMoveBack.Count -gt 1) { + if ($verifyMoveBack.Count -ne 1) { throw "Found multiple node locations for the move back. Since we are unable to restore, preventing move from occurring." } From 655373b09fe2803f755465a9a976ddb09ac1f59e Mon Sep 17 00:00:00 2001 From: David Paulson Date: Mon, 11 Mar 2024 13:31:14 -0500 Subject: [PATCH 32/34] Block Pre March SU builds --- .../ConfigureFipFsTextExtractionOverrides.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Security/src/ConfigureFipFsTextExtractionOverrides/ConfigureFipFsTextExtractionOverrides.ps1 b/Security/src/ConfigureFipFsTextExtractionOverrides/ConfigureFipFsTextExtractionOverrides.ps1 index 8614de35ce..098df8a2d0 100644 --- a/Security/src/ConfigureFipFsTextExtractionOverrides/ConfigureFipFsTextExtractionOverrides.ps1 +++ b/Security/src/ConfigureFipFsTextExtractionOverrides/ConfigureFipFsTextExtractionOverrides.ps1 @@ -162,13 +162,13 @@ begin { $processedExchangeServers = Get-ProcessedServerList @processParams $params = @{ - ComputerName = $processedExchangeServers.OnlineExchangeServerFqdn + ComputerName = $processedExchangeServers.ValidExchangeServerFqdn ConfigureOverride = $ConfigureOverride Action = $Action Rollback = $Rollback } - if ($processedExchangeServers.OnlineExchangeServerFqdn.Count -ge 1) { + if ($processedExchangeServers.ValidExchangeServerFqdn.Count -ge 1) { Write-Host "Running the configuration change against the following server(s): $([string]::Join(", ", $params.ComputerName))" Invoke-TextExtractionOverride @params } else { From a4e483ffce5ac1195ab42e0067794c51a06e0afb Mon Sep 17 00:00:00 2001 From: David Paulson Date: Mon, 11 Mar 2024 13:49:00 -0500 Subject: [PATCH 33/34] Allow Rollback to work on Pre March SU --- .../ConfigureFipFsTextExtractionOverrides.ps1 | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Security/src/ConfigureFipFsTextExtractionOverrides/ConfigureFipFsTextExtractionOverrides.ps1 b/Security/src/ConfigureFipFsTextExtractionOverrides/ConfigureFipFsTextExtractionOverrides.ps1 index 098df8a2d0..d50592bf96 100644 --- a/Security/src/ConfigureFipFsTextExtractionOverrides/ConfigureFipFsTextExtractionOverrides.ps1 +++ b/Security/src/ConfigureFipFsTextExtractionOverrides/ConfigureFipFsTextExtractionOverrides.ps1 @@ -168,7 +168,12 @@ begin { Rollback = $Rollback } - if ($processedExchangeServers.ValidExchangeServerFqdn.Count -ge 1) { + if ($Rollback -and $processedExchangeServers.OutdatedBuildExchangeServerFqdn.Count -gt 0) { + Write-Host "Adding the Server(s) back into the list to process because we are attempting to rollback: $([string]::Join(", ", $processedExchangeServers.OutdatedBuildExchangeServerFqdn))" + $params.ComputerName = $processedExchangeServers.OnlineExchangeServerFqdn + } + + if ($params.ComputerName.Count -ge 1) { Write-Host "Running the configuration change against the following server(s): $([string]::Join(", ", $params.ComputerName))" Invoke-TextExtractionOverride @params } else { From 0d73dc114ed5782fb6ec9413c57199a2859ac6c5 Mon Sep 17 00:00:00 2001 From: David Paulson Date: Tue, 12 Mar 2024 11:27:16 -0500 Subject: [PATCH 34/34] Remove multi server support for now --- .../ConfigureFipFsTextExtractionOverrides.ps1 | 8 -------- .../ConfigureFipFsTextExtractionOverrides.md | 14 -------------- 2 files changed, 22 deletions(-) diff --git a/Security/src/ConfigureFipFsTextExtractionOverrides/ConfigureFipFsTextExtractionOverrides.ps1 b/Security/src/ConfigureFipFsTextExtractionOverrides/ConfigureFipFsTextExtractionOverrides.ps1 index d50592bf96..4e13651d72 100644 --- a/Security/src/ConfigureFipFsTextExtractionOverrides/ConfigureFipFsTextExtractionOverrides.ps1 +++ b/Security/src/ConfigureFipFsTextExtractionOverrides/ConfigureFipFsTextExtractionOverrides.ps1 @@ -55,14 +55,6 @@ [CmdletBinding(DefaultParameterSetName = "ConfigureOverride", SupportsShouldProcess = $true, ConfirmImpact = 'High')] param( - [Parameter(Mandatory = $false, ValueFromPipeline, ParameterSetName = "ConfigureOverride")] - [Parameter(Mandatory = $false, ValueFromPipeline, ParameterSetName = "Rollback")] - [string[]]$ExchangeServerNames, - - [Parameter(Mandatory = $false, ParameterSetName = "ConfigureOverride")] - [Parameter(Mandatory = $false, ParameterSetName = "Rollback")] - [string[]]$SkipExchangeServerNames, - [Parameter(Mandatory = $true, ParameterSetName = "ConfigureOverride")] [ValidateSet("OutsideInModule", "XlsbOfficePackage", "XlsmOfficePackage", "XlsxOfficePackage", "ExcelStorage" , "DocmOfficePackage", "DocxOfficePackage", "PptmOfficePackage", "PptxOfficePackage", "WordStorage", "PowerPointStorage", "VisioStorage", "Rtf", diff --git a/docs/Security/ConfigureFipFsTextExtractionOverrides.md b/docs/Security/ConfigureFipFsTextExtractionOverrides.md index f8fa71ed7c..ccd1d3715f 100644 --- a/docs/Security/ConfigureFipFsTextExtractionOverrides.md +++ b/docs/Security/ConfigureFipFsTextExtractionOverrides.md @@ -35,24 +35,12 @@ This syntax enables processing of `Jpeg` and `AutoCad` file types by the help of .\ConfigureFipFsTextExtractionOverrides.ps1 -ConfigureOverride "Jpeg", "AutoCad" -Action "Allow" ``` -This syntax disables processing of `Jpeg` and `AutoCad` file types by the help of the `OutsideInModule` on the server `ExchangeSrv01` and `ExchangeSrv02`. - -```powershell -.\ConfigureFipFsTextExtractionOverrides.ps1 -ExchangeServerNames ExchangeSrv01, ExchangeSrv02 -ConfigureOverride "Jpeg", "AutoCad" -Action "Block" -``` - This syntax causes Exchange Server to use the previous version of the `OutsideInModule`. The override will be enabled on the system on which the script was executed. Note that this can make your system vulnerable to known vulnerabilities in the previous version and should not be used unless explicitly advised by Microsoft. ```powershell .\ConfigureFipFsTextExtractionOverrides.ps1 -ConfigureOverride "OutsideInModule" -Action "Allow" ``` -This syntax disables the override of the version of the `OutsideInModule` module on the server `ExchangeSrv01` and `ExchangeSrv02`. - -```powershell -.\ConfigureFipFsTextExtractionOverrides.ps1 -ExchangeServerNames ExchangeSrv01, ExchangeSrv02 -ConfigureOverride "OutsideInModule" -Action "Block" -``` - This syntax restores the `configuration.xml` from the backup that was created by a previous run of the script on the Exchange server where the script was executed. ```powershell @@ -63,8 +51,6 @@ This syntax restores the `configuration.xml` from the backup that was created by Parameter | Description ----------|------------ -ExchangeServerNames | A list of Exchange servers that you want to run the script against. -SkipExchangeServerNames | A list of Exchange servers that you don't want to execute the configuration action. ConfigureOverride | A list of file types that should be allowed to be processed by the `OutsideInModule`. The following input can be used: `XlsbOfficePackage`, `XlsmOfficePackage`, `XlsxOfficePackage`, `ExcelStorage`, `DocmOfficePackage`, `DocxOfficePackage`, `PptmOfficePackage`, `PptxOfficePackage`, `WordStorage`, `PowerPointStorage`, `VisioStorage`, `Rtf`, `Xml`, `OdfTextDocument`, `OdfSpreadsheet`, `OdfPresentation`, `OneNote`, `Pdf`, `Html`, `AutoCad`, `Jpeg`, `Tiff`.

If you want to enable the previous version of the `OutsideInModule` (`8.5.3`) to process file types, you must specify `OutsideInModule` as file type. Note that the `OutsideInModule` value cannot be used together with other file type values.

The input is case-sensitive. Action | String parameter to define the action that should be performed. Input can be `Allow` or `Block`. The default value is: `Block` Rollback | Switch parameter to restore the `configuration.xml` that was backed-up during a previous run of the script.