Response header updates for PCI Compliance

Server and Scripting Response Headers

To meet PCI compliance requirements, four response headers often have to be removed: server, X-Powered-By, and in the case of a .NET (MVC) app, X-AspNet-Version and X-AspNetMvc-Version.  The response headers can be viewed in a browser using dev tools like Chrome Developer Tools, the referenced headers being the last four in the screenshot below.


Removing the headers is fairly straight forward.  With all but X-AspNetMvc-Version being removed via web.config update.  In the case of X-AspNetMvc-Version, it is disabled in a project's global.asax(.cs) file.


Server

If a site is currently on IIS 8, it's recommended to have the site migrated to IIS 10 / Windows Server 2019 by Support.  Where on IIS 10 the server header can be removed via the attribute removeServerHeader

<system.webServer>
  <security>
    <requestFiltering removeServerHeader="true" />
  </security>
</system.webServer>

Otherwise on IIS 8 the header response would be removed via the following rewrite rule for RESPONSE_Server.  Unlike the attribute above, this method still returns a "Server" header, but the response is blank

<system.webServer>
  <rewrite>
    <outboundRules>
      <rule name="Remove RESPONSE_Server" >
        <match serverVariable="RESPONSE_Server" pattern=".+" />
        <action type="Rewrite" value="" />
      </rule>
    </outboundRules>
  </rewrite>
</system.webServer>

X-Powered-By 

The X-Powered-By header is removed via the customHeaders element

<system.webServer>
  <httpProtocol>
    <customHeaders>
      <remove name="X-Powered-By" />
    </customHeaders>
  </httpProtocol>
</system.webServer>

X-AspNet-Version and X-AspNetMvc-Version

ASP.NET version is disabled using the enableVersionHeader attribute

<system.web>
  <httpRuntime enableVersionHeader="false" />
</system.web>

To remove X-AspNetMvc-Version, "MvcHandler.DisableMvcResponseHeader = true" is added to the Application_Start event of the global.asax or global.asax.cs as in the examples below

protected void Application_Start()
{
    MvcHandler.DisableMvcResponseHeader = true;
}
or

    Sub Application_Start()
        MvcHandler.DisableMvcResponseHeader = True
    End Sub

Example web.config and response

An example web.config removing the server, X-Powered-By and X-AspNet-Version would look like

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <system.web>
    <httpRuntime enableVersionHeader="false" />
  </system.web>
  <system.webServer>
    <security>
      <requestFiltering removeServerHeader="true" />
    </security>
    <httpProtocol>
      <customHeaders>
        <remove name="X-Powered-By" />
      </customHeaders>
    </httpProtocol>
  </system.webServer>
</configuration>
Then, along with the project update for MVC, the resulting response header would be



Additional Response Headers

Additional response headers may be required, for example Content-Security-Policy (https://content-security-policy.com/), to set restrictions on how elements are loaded on the site.  Because various headers may be required, a basic syntax example is

<httpProtocol>
  <customHeaders>
    <add name="headerName" value="setting or directive" />
  </customHeaders>
</httpProtocol>
As an example, and not explicitly a guide, nopCommerce has many of these customHeaders set by default, as seen below

<httpProtocol>
  <customHeaders>
    <add name="X-XSS-Protection" value="1; mode=block" />
    <add name="X-Frame-Options" value="SAMEORIGIN" />
    <add name="X-Content-Type-Options" value="nosniff" />
    <add name="Strict-Transport-Security" value="max-age=31536000; includeSubDomains" />
    <add name="Content-Security-Policy" value="default-src 'self'; connect-src *; font-src * data:; frame-src *; img-src * data:; media-src *; object-src *; script-src * 'unsafe-inline' 'unsafe-eval'; style-src * 'unsafe-inline';" />
    <add name="Referrer-Policy" value="same-origin" />
    <add name="Permissions-Policy" value="accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=*, usb=()" />
  </customHeaders>
</httpProtocol>
Note that setting these customHeaders can have unintended consequences on what elements are being loaded on the site.  And extra care and review may be required versus the headers detailed in the first section of the article.

Example conflict

A simple Content Security Policy restriction can be set with default-src.  Description of the directive:  The default-src directive defines the default policy for fetching resources such as JavaScript, Images, CSS, Fonts, AJAX requests, Frames, HTML5 Media.

And basic web.config

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <system.webServer>
    <httpProtocol>
        <customHeaders>
          <add name="Content-Security-Policy" value="default-src 'self';" />
        </customHeaders>
    </httpProtocol>
  </system.webServer>
</configuration>
Which would have the intended effect of preventing images from being loaded from external sources with the message

Refused to load the image 'https://externalDomain/image.png' because it violates the following Content Security Policy directive: "default-src 'self'". Note that 'img-src' was not explicitly set, so 'default-src' is used as a fallback.
However, some Javascript based functions may stop loading on the site with the message

Refused to execute inline event handler because it violates the following Content Security Policy directive: "default-src 'self'". Either the 'unsafe-inline' keyword, a hash ('sha256-...'), or a nonce ('nonce-...') is required to enable inline execution.
Description of unsafe-line being:  Allows use of inline source elements such as style attribute, onclick, or script tag bodies (depends on the context of the source it is applied to) and javascript: URIs

Requiring an update to the Content-Security-Policy above to now read

<add name="Content-Security-Policy" value="default-src 'self'; script-src 'unsafe-inline' *.domain.com domain.com;" />
Adding the unsafe-inline keyword and defining from which domains the Javascript can be loaded.