Security HTTP response headers for .NET websites and APIs
Steve Moss
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
Header | HTML Pages | API Endpoints | Notes |
---|---|---|---|
Content-Security-Policy | Set | - | |
Content-Type | Set | Set | |
Referrer-Policy | Set | - | |
Server | Clear | Clear | This is a standard HTTP header, so also ok to just send it with no value |
Strict-Transport-Security | Set | Set | |
X-AspNet-Version Header | Remove | Remove | |
X-AspNetMvc-Version Header | Remove | Remove | |
X-Content-Type-Options | Set | Set | |
X-Frame-Options | Set | ? | This post suggests there are advanced attacks where this is relevant for an API |
X-Powered-By Header | Remove | Remove | |
X-XSS-Protection | Set | - |
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