The Open Web Application Security Project (OWASP) makes various recommendations about HTTP response headers that should be added, or removed, for security.

This post lists the recommended HTTP response headers for HTML pages and API endpoints, and provides examples of how to configure them in .NET web applications hosted by IIS.

Note: The OWASP pages are the source of the recommendations, but the Mozilla pages (linked to below) generally have better explanations.

HTTP Response Headers

HeaderHTML PagesAPI EndpointsNotes
Content-Security-PolicySet- 
Content-TypeSetSet 
Referrer-PolicySet- 
ServerClearClearThis is a standard HTTP header, so also ok to just send it with no value
Strict-Transport-SecuritySetSet 
X-AspNet-Version HeaderRemoveRemove 
X-AspNetMvc-Version HeaderRemoveRemove 
X-Content-Type-OptionsSetSet 
X-Frame-OptionsSet?This post suggests there are advanced attacks where this is relevant for an API
X-Powered-By HeaderRemoveRemove 
X-XSS-ProtectionSet- 

Setting headers in web.config

Headers that should be set/removed/cleared on every request by IIS, can be specified in web.config

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.web>
    <!-- Suppress "X-AspNet-Version" header -->
    <httpRuntime enableVersionHeader="false" />
  </system.web>  
  <system.webServer>
    <httpProtocol>
    <customHeaders>
        <remove name="X-Powered-By" />
        <add name="Content-Security-Policy" value="default-src 'self'"/>
        <add name="Referrer-Policy" value="no-referrer" />
        <add name="Strict-Transport-Security" value="max-age=31536000"/>
        <add name="X-Content-Type-Options" value="nosniff" />
        <add name="X-Frame-Options" value="SAMEORIGIN" />
        <add name="X-XSS-Protection" value="1; mode=block" />
      </customHeaders>
    </httpProtocol>
    <rewrite>
      <!-- Requires IIS UrlRewrite module to be installed -->
      <outboundRules>
        <rule name="Clear Server header">
          <match serverVariable="RESPONSE_Server" pattern=".+" />
          <action type="Rewrite" value="" />
        </rule>
      </outboundRules>   
    </rewrite>   
  </system.webServer>

Setting headers per page

The meta tag http-equiv attribute is an example of setting Content-Security-Policy on a per-page basis, rather than per-request.

Note: If security-policies are already set in an HTTP header, anything set in a meta tag can only further restrict

This example only allows content from certain external sources:

<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Security-Policy"
          content="script-src 'self' https://www.google-analytics.com; object-src 'none'">
</head>

Setting headers in Code

An example from IdentityServer4 of how to use a custom Attribute on a Controller to set the headers:

[SecurityHeaders]
public class AccountController : Controller
{
    // etc
}
 
public class SecurityHeadersAttribute : ActionFilterAttribute
{
    public override void OnResultExecuting(ResultExecutingContext context)
    {
        var result = context.Result;
        if (result is ViewResult)
        {
            if (!context.HttpContext.Response.Headers.ContainsKey("X-Content-Type-Options"))
            {
                context.HttpContext.Response.Headers.Add("X-Content-Type-Options", "nosniff");
            }
            if (!context.HttpContext.Response.Headers.ContainsKey("X-Frame-Options"))
            {
                context.HttpContext.Response.Headers.Add("X-Frame-Options", "SAMEORIGIN");
            }
 
            var csp = "default-src 'self';";
            // an example if you need client images to be displayed from twitter
            //var csp = "default-src 'self'; img-src 'self' https://pbs.twimg.com";
                 
            // once for standards compliant browsers
            if (!context.HttpContext.Response.Headers.ContainsKey("Content-Security-Policy"))
            {
                context.HttpContext.Response.Headers.Add("Content-Security-Policy", csp);
            }
            // and once again for IE
            if (!context.HttpContext.Response.Headers.ContainsKey("X-Content-Security-Policy"))
            {
                context.HttpContext.Response.Headers.Add("X-Content-Security-Policy", csp);
            }
        }
    }
}

Header Reference

Content-Security-Policy

https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy

Default value: default-src 'self'

Notes:

  • If multiple security-policies are set, subsequent policies can only further restrict
  • This policy can also be set instead on a per-page basis in meta tags (see below)
  • Policy violations should be reported to a CSP endpoint, so we have visibility of attacks (but we don't show that here)

Setting in web.config

<system.webServer>
  <httpProtocol>
    <customHeaders>         
      <add name="Content-Security-Policy" value="default-src 'self'"/>

Setting per page

The meta tag http-equiv attribute allows Content-Security-Policy to be specified on a per-page basis, rather than per-request.

Note: If security-policies are also set in an HTTP header, anything set in a meta tag can only further restrict

This example uses this instead of an HTTP header to apply specific exceptions:

<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Security-Policy"
          content="script-src 'self' https://www.google-analytics.com; object-src 'none'">
</head>

Referrer-Policy

https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy

Default value: no-referrer

HTTP Strict-Transport-Security (HSTS)

https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security

Default value: max-age=31536000; includeSubDomains

Setting in web.config

<system.webServer>
  <httpProtocol>
    <customHeaders>         
      <add name="Strict-Transport-Security" value="max-age=31536000"/>

Setting in web.config with UrlRewrite

I've also seen this header added using UrlRewrite.  This only adds the header, when HTTPS is in use.  But if you send the header when using HTTP the browser just ignores it, so I prefer the simpler syntax above:

<rewrite>
  <outboundRules>
    <rule name="Add_HSTS_Header" preCondition="USING_HTTPS" patternSyntax="Wildcard">
      <match serverVariable="RESPONSE_Strict-Transport-Security" pattern="*" />
      <action type="Rewrite" value="max-age=31536000" />
    </rule>
    <preConditions>
      <preCondition name="USING_HTTPS">
        <add input="{HTTPS}" pattern="^ON$" />
      </preCondition>
    </preConditions>
  </outboundRules>

X-Content-Type-Options

https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options

Default value: nosniff

Setting in web.config

<system.webServer>
  <httpProtocol>
    <customHeaders>         
      <add name="X-Content-Type-Options" value="nosniff"/>

X-Frame-Options

https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options

Default value: SAMEORIGIN

Setting in web.config

<system.webServer>
  <httpProtocol>
    <customHeaders>         
      <add name="X-Frame-Options" value="SAMEORIGIN"/>

Ensuring header is set

To fix-up a website where some pages already added the header, and some didn’t, I used the following code in global.asax:

protected void Application_BeginRequest()
{
    HttpContext.Current.Response.AddOnSendingHeaders(httpContext =>
    {
        if (isHtmlResponse() && hasNoFrameOptionsHeader())
        {
            httpContext.Response.AddHeader("x-frame-options", "SAMEORIGIN");
        }

        bool isHtmlResponse () {
            var contentTypeValue = httpContext.Response.Headers["content-type"];
            return contentTypeValue is null ? false : contentTypeValue.ToLower().Contains("text/html");
        }
        bool hasNoFrameOptionsHeader () => httpContext.Response.Headers["x-frame-options"] is null;
    });            
}

Removing HTTP Response Headers

Idea here is not to reveal any information about our infrastructure (which could help an attacker).

Server Header

Added by IIS, eg `server:Microsoft-IIS/10.0`

To clear for all requests, we can configure IIS's UrlRewrite module (which has to be manually installed) in web.config.  This clears the value of the header, to hide the sensitive information:

<rewrite>
  <outboundRules>
    <rule name="Clear Server header">
      <match serverVariable="RESPONSE_Server" pattern=".+" />
      <action type="Rewrite" value="" />
    </rule>

This header can be removed in code, but would not apply to any requests going direct to static files (eg CSS, images):

protected void Application_BeginRequest(object sender, EventArgs e)
{
    var application = sender as HttpApplication;
    if (application != null && application.Context != null)
    {
        application.Context.Response.Headers.Remove("Server");
    }
}

Note: this approach to removing headers in web.config has no effect:

<!-- DO NOT DO THIS --> 
<system.webServer>
    <httpProtocol>
      <customHeaders>
        <remove name="Server" />

X-AspNet-Version Header

To stop the header being added in the first place, set property in web.config:

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

Or if have to remove later, can use:

<system.webServer>
  <httpProtocol>
    <customHeaders>
      <remove name="X-AspNet-Version" />

X-AspNetMvc-Version Header

Can stop this being added using:

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

X-Powered-By Header

To remove:-

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

Comments


Comments are closed