2017-09-30-secure-response-body.md 8.0 KB


description: > Basic security for HTML and JSON response bodies.

title: Secure Response Body

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

This cheat sheet only covers HTML and JSON response bodies. Sample code uses a combination of slim, and pseudo code. Single letter methods u, j escape their argument's contents.

Table of Contents
  • TOC {:toc}

Handling input

Display input, and user-controlled data securely:

  • Only inject data in whitelisted locations.
  • Ensure the Content-Type header matches the body.
  • Interpolate sanitized data when building up output.
  • Escape user-controlled, and input-based data according to its output type.
  • Only escape data in an output context for data integrity.
src="https://#{ u(uri.host + uri.path) }"

JSON response

  • Avoid embedding scripts.
  • Escape all values:
    • Input.
    • User-controlled.
= j hsh.to_json

HTML Response

Head

  • Set more granular robot rules.
meta name="robots" content="noindex, follow"
  • Set a more detailed referrer policy.
meta name="referrer" content="origin"
  • Expose CSRF tokens to use in secure Ajax calls.
= csrf_metatag

/ short for:

meta name="_csrf" content="#{ csrf_token }"

Avoid caching pages with csrf-token named meta tags. Instead, retrieve the token when users perform the first non GET request as a pre-flight GET request to session/csrf.

Subresource Integrity

Secure CDN loaded assets by adding the integrity attribute to scripts and links. To prevent attackers from reading data cross-origin we must add the crossorigin attribute with either anonymous or use-credentials value.

script[
    src="https://cdn.example.com/in-vogue.js"
    integrity="sha256-oqV...= sha512-zM0...=="
    crossorigin="anonymous"
  ]

If our CDN provider doesn't provide integrity hashes we can generate them using httpie{:rel="nofollow noreferrer noopener"} and openssl:

$ http https://trusted.cdn.com/path/to/asset.css | openssl dgst -sha512 -binary | openssl enc -base64 -A

In the first example we included both the sha512, and the less secured sha256 for compatibility with older browsers. Modern browsers will check against the most secured version by default.

Body

Links

  • Add privacy settings on all links (a elements) pointing to external services.
a href="https://example.com" rel="nofollow noreferrer noopener"

If for SEO the site requires links (a) to be followed merely remove nofollow. Keeping noopener to help prevent tabnabbing attacks, won't affect SEO{:rel="nofollow noreferrer noopener"}. noreferrer is a fallback for browsers{:rel="nofollow noreferrer noopener"} that still don't support noopener.

  • Use even more detailed referrer policies.
a href="http://example.com/" referrerPolicy="origin"
  | click me

Form AutoComplete

  • Turn autocomplete off for sensitive input.
input type="text" name="passport-number" autocomplete="off"

Modern browsers{:rel="nofollow noreferrer noopener"} ignore the off option for inputs type username, password, current-password so credential managers can auto fill them.

Cross Site Request Forgery

  • Add security tokens in forms and links that trigger changes to prevent CSRF.
form action="/replace" id="replace" method="post"
  input name="_method" type="hidden" value="put"
  = csrf_tag
  • Test unique identifiers are appended to:
    • non-idempotent requests.
    • links.
    • forms.

Cross-Site Scripting (XSS)

When for legitimate reasons we allow input and/or end-users to manipulate data into structured, and styled content:

  • Escape both according to their output type.
HTML
  • Support input in other markup languages. eg. markdown, liquid.
  • Escape these before injecting them into HTML elements:
    • User-controlled content.
    • Input-generated content.
h1
  = user_controlled.content
div
  = input_generated.content
  • Escape input, and user-manipulated content before using it as an attribute's value.
p attr="#{ attribute.value }"
  | content

Some elements can NEVER be consider safe enough to inject input and/or user-manipulated content to.

script
  = inline_scripts
/!
  = inline_comments
div "#{ as_attribute_name }"=test
div
  "#{ as_tag_name }".some-class
style
  = inline_styling
CSS
  • Use security-focused gems to escape CSS before injecting it.

Some CSS contexts can NEVER be consider to save to use with input even if properly escaped. (Pseudo code)

{ background-url: "URL_TO_MALICIOUS_JS"; } // same for other attributes that can take URLs
{ text-size: "expression(MALICIOUS_JS)"; } // only in IE
ECMAScript (JS)
  • Avoid embedding input-based ECMAScript (JS) to prevent malicious scripts.
  • Escape input based, and user-controlled scripts with help of specialized libraries when it can't be avoid.

Some expressions that will NEVER handle input safely:

window.setInterval('HERE_WE_ALWAYS_GET_XSSED');

Special Considerations

BREACH

This category of attacks doesn't affect a specific piece of software. Web apps compressing HTTP body responses (eg. gzip, brotli), whilst reflecting user provided content, and secrets (eg. CSRF tokens, sensitive data) are likely to be vulnerable.

The recommended mitigation practices, in order of effectiveness, are:

  • Disable HTTP compression.
  • Separate secrets from user input.
  • Randomize secrets per request.
  • Mask secrets.
    • XORG w/random request-secret.
  • Use CSRF tokens.
  • Randomly byte-pad responses to hide their length.
  • Rate-limit requests.

Keep in mind that the practicality of each mitigation varies from one app to another.

Resources

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