Skip to main content

If your internal security team require you to harden IIS, specifically in regard to QID 2011827 (a Qualys recommendation), depending upon how your security team requires you to implement QID 2011827 recommendations, you may need to set those "customHeaders" at either the root level of iis (think applicationhost.config file), or at the "Default Web Site" level.

That "usually" isn't an issue, however... IIS doesn't like it when subpages (which is what Management Points use) also have customHeaders defined. And at every upgrade of CM, the Management Point is reinstalled, and if you HAD previously cleaned up those sub-page customHeaders, to accommodate the QID 2011827 settings as required by your internal security team, you either have to perpetually fight with your security team about what might be the "right way" to implement QID 2011827... or accept defeat and then, yourself, manually, cleanup the web.config files on the ManagementPoint sites under "Default Web Site" at every upgrade.

Although this isn't ideal... let's say it's a Saturday at 3am; after you've been up for 18 hours straight fixing something else, and finally the fix was "reinstall the Management Point", and you think, yay, all done, going to go to sleep. But... you forgot about the customHeaders.

What you "could do", and obviously totally optional, is something like the 3 scripts below. This example is for the subsite of "CCM_STS", however, you would need 3 CIs. one each for "CCM_STS", "CMUserService", and "CMUserServiceWindowsAuth". You'd just change any references to CCM_STS to the other ones, once you've created and copied these.


You'd make a Configuration Item, of "application" type.

Application Detection is a script, and it's this (replacing CCM_STS as needed):

<#
.SYNOPSIS
once Custom Header per Qualys QID 2011827 is configured, the CMUser etc. web sites don't like having
set the exact same customHeaders; remove the customHeaders in the sub-sites.
.DESCRIPTION

.NOTES

2020-03-31 Sherry Kissinger
#>
param
(
[string]$VerbosePreference = 'SilentlyContinue',
[string]$ErrorActionPreference = 'SilentlyContinue'
)

Import-Module WebAdministration
if (Test-Path 'IIS:\Sites\Default Web Site\CCM_STS') {
write-host "exists"
}


Detection script is this for CCM_STS, where "what means compliant" is the string Compliant:


<#
.SYNOPSIS
once Custom Header per Qualys QID 2011827 is configured, the CMUser web sites don't like having
set the exact same customHeaders; remove the customHeaders in the sub-sites.
.DESCRIPTION

.NOTES

2020-03-31 Sherry Kissinger
2022-04-19 Sherry Kissinger (Sanitized and cleaned up)
#>


param
(
[string]$VerbosePreference = 'SilentlyContinue',
[string]$ErrorActionPreference = 'SilentlyContinue'
)

Import-Module WebAdministration

#Are any of the root (applicationhost.config, usually in C:\Windows\System32\inetsrv\Config, defined?

$RootOne = (Get-WebConfigurationProperty -Filter "system.webServer/httpProtocol/customHeaders" -Name collection[name='X-Xss-Protection']).Value
$RootTwo = (Get-WebConfigurationProperty -Filter "system.webServer/httpProtocol/customHeaders" -Name collection[name='Content-Security-Policy']).Value
$RootThree = (Get-WebConfigurationProperty -Filter "system.webServer/httpProtocol/customHeaders" -Name collection[name='Strict-Transport-Security']).Value
$RootFour = (Get-WebConfigurationProperty -Filter "system.webServer/httpProtocol/customHeaders" -Name collection[name='X-Content-Type-Options']).Value
$RootFive = (Get-WebConfigurationProperty -Filter "system.webServer/httpProtocol/customHeaders" -Name collection[name='X-Frame-Options']).Value

#Does Default Web Site have any customHeaders?
$FileLocationDefault = (Get-WebConfigFile -PSPath 'Machine/WebRoot/AppHost/Default Web Site')
[xml]$xmld = [xml](Get-Content($FileLocationDefault.FullName))
$CustomHeadersDefault = ($xmld.Configuration.'system.webServer'.httpProtocol.customHeaders).add.name

if (($RootOne) -or ($RootTwo) -or ($RootThree) -or ($RootFour) -or ($RootFive) -or ($CustomHeadersDefault)) {

$FileLocation = (Get-WebConfigFile -PSPath 'Machine/WebRoot/AppHost/Default Web Site/CCM_STS')
[xml]$xml = [xml](Get-Content($FileLocation.FullName))
$CustomHeaders = ($xml.Configuration.'system.webServer'.httpProtocol.customHeaders).add.name

if ($CustomHeaders) {
write-verbose $FileLocation.FullName
$ReturnThisValue = "Custom Headers Exist, and root or default already have custom headers too: $CustomHeaders"
}
else
{
$ReturnThisValue = "Compliant"
write-verbose "Compliant, even though there are custom headers in the root or in default web site, the subsite does not have custom headers"
}

}
Else
{write-verbose 'Compliant by default, there are no root nor default customheaders that might interfere'
$ReturnThisValue = 'Compliant'
}

$ReturnThisValue


Remediation script is this for CCM_STS:


<#
.SYNOPSIS
once Custom Header per Qualys QID 2011827 is configured, the CMUser and STS web sites don't like having
set the exact same customHeaders; remove the customHeaders in the sub-sites.
.DESCRIPTION

.NOTES

2020-03-31 Sherry Kissinger
#>
param
(
[string]$VerbosePreference = 'SilentlyContinue',
[string]$ErrorActionPreference = 'SilentlyContinue'
)

Import-Module WebAdministration
$FileLocation2 = (Get-WebConfigFile -PSPath 'Machine/WebRoot/AppHost/Default Web Site/CCM_STS')
[xml]$xml2 = [xml](Get-Content($FileLocation2.FullName))
$CustomHeaders2 = ($xml2.Configuration.'system.webServer'.httpProtocol.customHeaders).add.name

if ($CustomHeaders2) {
$xml2.Configuration.'system.webServer'.httpProtocol.customHeaders | ForEach-Object { [void]$_.ParentNode.RemoveChild($_) }

$xml2.Save($FileLocation2.Fullname)

write-verbose $FileLocation2.FullName

write-host "Remediation Attempted"

}
else {
write-host "Compliant"
}


Once you've created and tested the one for CCM_STS, clone the CI, and modify all the scripts to instead reference CMUserService. Then clone again, and modify the copy to CMUserServiceWindowsAuth.

Make a Baseline, add the three CIs. When adding the CIs, make sure you say the CIs are OPTIONAL, not REQUIRED, because only targets that actually have CCM_STS, etc., deserve to bother.

End Result:
If you have NOT implemented anything related to customHeaders... compliant by default; whatever happens to be there is fine.

If you HAVE implemented something related to customHeaders, whether that is in applicationhost.config, or in Default Web Site, then the subsites will remediate any locally created customheaders in web.config, allowing those functions to well... function again.

fyi, there are no issues with the Management Point working fine with the QID recommendations... it's just that how it gets implemented and how IIS functions regarding customHeaders can be confusing. It's "top down" and "inherited"... but if at a lower level (like CMUserService) and is locally defined in web.config for THAT site... but also locally configured higher up (in Default Web Site, or in applicationhost.config), then CMUserService just won't work, until that inheritance is 'fixed', which usually means manually cleaning up web.config.

As always, TEST TEST TEST yourself.  Just because this worked for me in my lab doesn't mean it'll work for you in your production environment.