Check out secure web development and secure response headers 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
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 script
s and link
s. 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 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. noreferrer
is a fallback for browsers 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 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 and secure response headers for more resources.
- HTML Escaping.
- Cross-Site Request Forgery.
- Subresource integrity.
- SRI and CORS
- An Introduction to ERB Templating.
- Testing for CSRF.
- Input Autocomple Attribute.
- rel=”noreferrer noopener” SEO Issues?
- target=”_blank” - The Most Underestimated Vulnerability Ever
- Referrer Policies
- About rel=noopener
- The Performance Benefits of rel=noopener
- BREACH Attack Mitigations