2017-09-29-secure-response-headers.md 9.7 KB


description: > Basic HTTPS response headers security in Ruby.

title: Secure Response Headers

Check out secure web development{:rel="noopener"} and secure response body{:rel="noopener"} for more notes.

This document assumes HTTPS has been enabled. Ruby samples focus on Roda. Check its documentation for further details regarding where/how to use the code samples.

Table of Contents
  • TOC {:toc}

Protect Browser

  • Ensure browsers only access your site through HTTPS to prevent SslStrip attacks.
response["Strict-Transport-Security"] = "max-age=31536000; includeSubdomains; preload"
  • Prevent Clickjacking. Restrict where the response can be embedded to. Don't use the associated meta tag, it has no effect.
response["X-Frame-Options"] = "deny"
  • Control which referrer info should be included with requests made. More granular referrer policies can be set at view level.
response["Referrer-Policy"] = "no-referrer"
  • Deny clients, such as PDF readers, permission to handle data across domains.
response["X-Permitted-Cross-Domain-Policies"] = none

Cross-Site Scripting (XSS)

  • Enable XSS protection and block rendering malicious pages.
response["X-XSS-Protection"] = "1; mode=block"
  • Stop browser from guessing content type in case users manage to store XSS attacks.
response["X-Content-Type-Options"] = "nosniff"

Content Security Policy

Restrict where the browser can load resources from to prevent the execution of unknown scripts.

# Web apps customize these according to their needs
cdns = %w[trusted-cdn.com other-cdn.com]
response["Content-Security-Policy"] = "default-src 'none'; script-src 'self' #{cdns}; connect-src 'self'; img-src 'self'; style-src 'self';"

# For pure JSON APIs
response["Content-Security-Policy"] = "default-src 'none'"

To help prevent web cache deception attacks add the require-sri-for directive.

response["Content-Security-Policy"] = "require-sri-for script style; ..."

The secure response body{:rel="nofollow noreferrer noopener"} cheat sheet has some details on how to add scripts, and styles using the sub-resource integrity (SRI) check.

Cookie Configuration

  • Never store sensitive data in a cookie to help prevent replay attacks.
  • Never hide cryptographic credentials in client software nor on client systems.
  • Serve cookies only over HTTPS using the Secure flag.
  • Limit cookie access level to HttpOnly.
  • Use session cookies over persistent ones.
  • Session secret:
    • Use a different secret token in each environment.
    • Inject secrets using env vars. Avoid hard coding them.
    • Set a securerandom number as a fallback.
require "roda"

class Web < Roda
  use Rack::Session::Cookie, {
    key: "_app_session",
    path: "/",
    httponly: true,
    secure: true,
    expire_after: nil, # Session cookie
    secret: ENV.fetch("COOKIE_KEY") { SecureRandom.hex 64 }
  }
end

Cross-Site Request Forgery (CSRF)

CSRF attacks affect end-points which change state on the server. Which has more to do with a verb's safety than its idempotency.

According to OWASP, there are numerous ways to defend against CSRF. Yet, a session cookie by itself is not enough to prevent this type of attack. Although, when combined with token synchronization it makes a sound defence.

To avoid hidden pitfalls rely in a gem, toolkit, or framework that takes care of CSRF protection. Make sure any non GET requests always require a valid token, though.

require "roda"

class WebSiteWithAjax < Roda

  # cookie configuration here

  plugin :type_routing,
    default_type: :html,
    exclude: :xml,
    types: {
      json: "application/json; charset=utf-8",
      html: "text/html; charset=utf-8"
    }

  plugin :csrf,
    raise: false
end

Cross-Origin Resource Sharing (CORS)

CORS enables communication and resource sharing between trusted partners.

Public Resources

These are dangerous since they allow connections from anywhere...

response["Access-Control-Allow-Origin"] = "*"
response["Access-Control-Allow-Origin"] = "null"

and neutralize credentials.

response["Access-Control-Allow-Credentials"] = true # actually behaves like false

Wildcards aren't partials. They aren't safe to use:

# Invalid
response["Access-Control-Allow-Origin"] = "https://*.example.com"

# Valid but UNSAFE. Accepts connections from http://evil.example.com
response["Access-Control-Allow-Origin"] = "*.example.com"

Single Origin

By default CORS only works with a single origin. Sub-domains aren't included.

response["Access-Control-Allow-Origin"] = "https://www.example.com/"
response["Access-Control-Allow-Credentials"] = true
response["Access-Control-Allow-Methods"] = "GET, POST, PUT, DELETE"

Multiple Origins

A common way of dealing with the single-origin limitation is to dynamically consume the request header. Since that technique depends on input:

  • Avoid trusting multiple origins.
  • Rely on a well tested gem that:
    • Validates URL format.
    • Whitelist sub-domains.
    • Adds the Vary header with Origin value.

Gems & Tools

Gems:

Secure headers tools:

Resources

Guides & Cheat Sheets

Articles