Skip to content

Instantly share code, notes, and snippets.

@mikesamuel
Last active June 19, 2021 04:08
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mikesamuel/f7c7caed42413396e4d3e61dae6f5712 to your computer and use it in GitHub Desktop.
Save mikesamuel/f7c7caed42413396e4d3e61dae6f5712 to your computer and use it in GitHub Desktop.
Golang header safe defaults library proposal

Secure Header Set

tinyurl.com/sec-header-sets

A proposed library that provides safe defaults (with opt-out) for security-relevant HTTP response headers.

Background

A variety of headers have been added over the years to address common security problems. Many of these headers were specified as opt-in to avoid breaking the web.

Type net/http/ResponseWriter provides a Header multimap, and a WriteHeaders() method that commits the header map to the underlying channel.

Goal

When writing new web applications, web application authors should have a way to opt-out of secure defaults based on application requirements instead of having to opt-in.

Relevant HTTP/HTTPS details

Security Relevant Headers

Security scanners (1, 2, 3) seem to have converged on a set of headers that most sites should use barring application-specific needs.

Among the ones that have secure default values which are not the actual default value are:

  1. Frame-Options & X-Frame-Options specifies interactions between frames and affects click-jacking
  2. Referrer-Policy affects leakage of sensitive information via URL parameters included in the referrer header.
  3. Strict-Transport-Security addresses leakage of sensitive information and MITM attacks via HTTPS->HTTP downgrade attacks.
  4. X-Content-Type-Options addresses polyglot attacks by forbidding content-type sniffing.

Out of bounds

Our API does not deal with opt-out security measures or those for which we cannot identify a secure default.

  1. CORS's Access-Control-* headers.

  2. Content-Security-Policy and related headers allow opting into a suite of checks on code and binary content provenance.

    Building a strict content-security policy requires a lot of application specific knowledge, and there is no safe default compatible with current client-side best practices.

  3. Public-Key-Pins similarly is opt-in but has no safe default since it requires detailed knowledge of keys related to the hostname.

  4. X-XSS-Protection controls browser XSS heuristics. It defaults to "1" (filter) so its default is secure but it can be configured to allow collecting telemetry.

  5. Expect-CT has a secure default but is meant for web-host operators, not an application level decision.

  6. Expect-Staple has a secure default but requires coordination with certificate providers so is not an application level decision.

Proposed API

The API below allows creation of a set of headers with safe defaults.

import "net/http"
import "net/http/sec"  // Proposed package name for new code

func handle(rw http.ResponseWriter) {
    // Creates headerset with safe defaults.
    secheaders := sec.SecHeaders{}
    // Writes headers to secheaders or panics if body
    // content has been written
    secheaders.AddTo(rw)    
}

To override a default, assign the appropriately named field. The package defines named constants for common header values.

secheaders := sec.SecHeaders{
    FrameOptions: sec.FrameOptionsSameOrigin
}

SecHeaders's fields are all CamelCasified header names, and each fields' zero value is the safe default.

Related issues in existing code

Header value merging

RFC2616 says of multiple headers with the same name:

It MUST be possible to combine the multiple header fields into one "field-name: field-value" pair without changing the semantics of the message, by appending each subsequent field-value to the first, each separated by a comma.

rw.Header().Add('X-Frame-Options', 'SameOrigin')
...
secheaders.AddTo(rw)  // Adds default value Deny

should not cause an invalid value of SameOrigin,Deny that might unintentionally strip both levels of protection from the document.

ResponseWriter exposes the internal map from header names to value slices so AddTo could detect duplicates and override or decline to set but that would not address .AddTo before .Header().Add(...).

Ideally WriteHeader would contain a blacklist of headers whose values are known not to be comma separated lists and do something reasonable when there are duplicate values. This check could store the failure and later communicate failure via ResponseWriter::Write since that method has an error result.

Server-side content-type guessing

The documentation for ResponseWriter::Write says

// If the Header
// does not contain a Content-Type line, Write adds a Content-Type set
// to the result of passing the initial 512 bytes of written data to
// DetectContentType.

It would be ideal if this resulted in an error when X-Content-Type-Options is not permissive since server-side content-type guessing of proxied content is no less vulnerable to polyglot attacks than client-side content-type guessing.

Forward Compatibility

Since the set of security relevant headers seems to grow over time, an application author might be concerned that the application might break when a new header is introduced in user-agents.

Application end-to-end tests should catch these issues, but if this is a concern, the API could be extended to provide constructor functions like

secheaders := sec.SecureDefaultsAsOf2017()

Should the API change to add SecureDefaultsAsOf2018() older dates could be flagged by code-quality and security-auditing tools and/or a Deprecated: section added to the docs.

Related Concerns

Telemetry

Violation reports allow production engineers to keep an eye out for emerging threats, but when a security policy is applied on the client, server-side logging isn't sufficient.

Several headers provide telemetry on policy violations provide a way to specify a URI to which report violations can be POSTed, and provide a mode in which violations are reported but do not affect user-agent behavior.

  • Content-Security-Policy
  • Expect-CT
  • Expect-Staple
  • Public-Key-Pins
  • X-XSS-Protection (Chrome only)

Given a URI for centralized report collection we could craft a default value with a restrictive policy in report-only mode for headers:

Not collecting telemetry is an insecure default. NIST says

Because performing incident response effectively is a complex undertaking, establishing a successful incident response capability requires substantial planning and resources. Continually monitoring for attacks is essential. Establishing clear procedures for prioritizing the handling of incidents is critical, as is implementing effective methods of collecting, analyzing, and reporting data.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment