Check out secure web development and secure response body 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
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 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 withOrigin
value.
Gems & Tools
Gems:
- rack-secure_headers.
- rack-protection. Help against typical web attacks.
- rack-cors.
- rack-attack. Request throttler.
Secure headers tools:
- Observatory. Header configuration checker.
- Laboratory. CSP generator. Firefox web extension.
- HSTS Preload Submission Form.
Resources
Guides & Cheat Sheets
- Cross-Site Request Forgery Prevention Cheat Sheet
- Testing for CSRF.
- OWASP Secure Headers
- Content Security Policy Reference
- OWASP Content Security Policy Cheat Sheet
Articles
- Everything you need to know about HTTP security headers
- Mozilla Web Security Guidelines
- How HSTS’ includeSubdomains works
- The Curious Case of TLS and the Missing Referrers
- W3C Referrer Policy
- Content Security Policy
- CSP: require-sri-for
- Block mixed content
- Web Application Security Working Group
- X-Frame-Options
- X-XSS-Protection
- Stric Transport Security Cheat Sheet
- HSTS Preload List Submition
- I’m Giving Up on HPKP.
- XSS Prevention Cheat Sheet
- One Line of Code that Compromises Your Server.
- What are idempotent and/or safe methods?
- Reviewing Code for CSRF Issues.
- Cross-domain Ajax with Cross-Origin Resource Sharing.
- HTTP Access Control (CORS).