Secure Web Development

Check out secure response headers and secure response body for more notes.

Table of Contents

Secure Coding

From the fragments of source code mentioned in the Advisory, I felt that with such coding style there should still be security issues remained in FTA if I kept looking. — Orange Tsai

  • Verify for security early.
  • Keep a consistent code style.
  • Review code frequently. Security is never done.
  • Security concerns over development convenience.
    • Review dependencies’ security practices.
    • Use redundant protection mechanism.
    • Forbid everything by default. ie. whitelists, least privilege principle.
    • Validate all input.
    • Sanitize valid input.
    • Interpolate sanitized input.
    • Encrypt sensitive data. eg. User identifiable data, business classified data.
    • Encode all output according to its type and delivery mechanism.
    • Consider possible exceptions, errors, and bugs.
    • Ensure exceptions don’t reveal too much as to create security holes.
    • Rescue specific exceptions rather than generic ones.
    • Don’t rely on implementation secrecy to keep security.
  • Test Security.
    • Add settings tests to prevent:
      • Configuration tampering.
      • Unexpected changes to dependency defaults.
    • Mimic attacker actions.
  • Keep tools up-to-date for bug and security patches.
  • Never commit private credentials to a repo.
  • Change private credentials when team members leave.
  • Use environments to maintain a single codebase.

Environments

  • Never store production data in other environments.
    • Use fake data.
    • Scrub data from tests reproducing bugs.
  • Store production credentials only in env vars.
  • Manage environment credentials independently. Avoid grouping by environment.
  • Never reuse credentials from any environment.
  • Limit production environment access to white-listed IPs/users.
  • Check no genuine transactions can be executed in staging/demo environments.

Assuming Bundler for gem management.

Gemfile

  • Set runtime dependencies in the main body.
  • Group & install other gems by environment.
  • Configure the patch-level upgrade for each gem.
# Gemfile

gem "roda", "~> 3.0"

group :development do
  gem "minitest", "~> 5.10"
end

Maintenance

Keep a maintenance schedule to:

  • Audit the codebase.
  • Audit dependencies.
  • Update gems.
    • update gems individually.
    • run tests before and after an update.
    • commit each gem update individually.
  • Audit Logs.
    • review own log files.
    • review data captured by error monitoring services.
    • purge old logs.
  • DB backups.
  • Renew private keys before expiration.

Input Handling

Proper input handling prevents code injection. In general:

  • Ensure client validations are also done on the server.
  • Validate input as soon as it’s received.
  • Validate semantics.
    • Presence.
    • Length.
    • Type.
    • Format.
  • Sanitize according to syntactical validations.
    • Enforce structure.
    • Whitelist safe elements.
    • Encode special elements.
  • Never escape special characters. It always depends on the output type.

Regex

When validating format:

  • Use \A and \z anchors to help prevent code injection.
greetings = "hello world\n<script>malicious_js();</script>"

/\Ahello world\z/.match? greetings # => false
/^hello world$/.match? greetings # => true

Unicode

Given unicode’s range of characters:

  • Normalize input.
    • Ensure canonical encoding. ie. "\u00e9" and "\u0065\u0301" should be equivalent.
    • Handle invalid characters.
  • Whitelist by character category. eg. CJK ideographs, Tibetan, etc.
  • Whitelist individual characters.

XSS

Cross-Site Scripting attacks embed and trigger malicious scripts on either server or client. A sound XSS defence covers both input and output.

Check out secure response headers and secure response body for their respective XSS prevention practices.

  • Encode all data controlled by end-users.
  • Sanitize valid input according to its type. eg. HTML, CSS.
    • Whitelist elements.
    • Encode valid elements.
  • Encode using security-focused gems to avoid pitfalls.

URLs

  • Validate URL schemes. eg. HTTPS, FTPS.
  • Escape special characters. eg. URI.escape.
  • Whitelist URLs meant to be used as src.
  • Sanitize params values.
  • Test for malicious params values.
  • Interpolate URL fragments rather than passing it in full.

Database

  • Avoid saving null.
  • Add DB constraints to prevent data corruption.
  • Escape special characters before storing input.
  • Prevent mass assignment. Whitelist mutable attributes.

SQLi

To prevent SQL injection:

  • Interpolate input when building SQL queries.
  • Type cast attribute values to match DB data types.
  • Test common SQLi attacks to ensure basic coverage.

Test ORM for SQLi to prevent unexpected changes.

Files

  • Ensure uploads to the app server are not allowed.
  • Use security-focused gems and/or services that:
    • Validate files whitelisting:
      • Name’s character set.
      • Names to prevent overwrites.
      • File extensions.
    • Validate MIME types.
    • Scan files for viruses/malware.
    • Upload directly to a storing server.
    • Process media files asynchronously.

CMDi

Avoid passing input as system call arguments to prevent command injection.

# Kernel
exec
system
`echo 'backticks'`
%x{}
syscall
command
open

Whenever it can’t be avoided:

  • Use security-oriented command wrapping gems.
  • Add tests to ensure secure versions of system libraries.
  • Keep an allowed_commands list.

Pass commands as string fragments rather than a single string. Optionally, pass env vars as a hash.

def run flag:
  system { "ENV_VAR" => "value" },
    "command", "argument", "--option #{allowed_flags[flag]}"
end

Paths

  • Ensure this kind of input can’t be avoided.
  • Turn paths into their canonical version to check their absolute path.
  • Limit access to paths within the app.
    • Whitelist safe paths.
    • Interpolate path segments.

In ruby, consider using classes such as Pathname, or methods such as Dir.chroot before allowing users to pass paths as arguments.

Ruby dangerous methods

In Ruby, we can help prevent remote code execution when we avoid passing user controlled/generated input to:

Subprocesses

# Kernel
fork
spawn

Dynamic code loading methods

# Kernel
load
autoload
require
require_relative

Metaprogramming methods

# Object
send
__send__
public_send

# BasicObject
instance_eval
instance_exec

# Module
eval
class_eval
class_exec
module_eval
module_exec
alias_method
binding.eval

Furthermore, avoid passing input to any method in Fiddle module.

Deserialization

Stick to YAML, and JSON when serializing data. To avoid arbitrary code execution, make sure to deserialize it using default settings.

require "yaml"
require "json"

YAML.safe_load
JSON.parse

HTTPS

The HTTPS protocol is used to securely exchange data with clients. The server must obtain a valid TLS (aka SSL) certificate to ensure others it can securely exchange data. We can get free certificates from Let’s Encrypt. However, how to setup HTTPS is beyond the scope of this cheat sheet.

Authentication & Authorization

  • Enforce authentication using well tested gems.
  • Test authentication for every restricted action.
  • Grant each role enough privileges to get their job done.
  • Test authorization scope. eg. Users can’t change each other’s details.
  • Never send passwords through notification channels. eg. email.

Authentication Details

Rely on well designed gems that cover as many of these features as possible:

  • Encourage high entropy passwords.
    • Don’t limit character set.
    • Configurable max length. Opt-in 128 characters length.
    • Prevent empty passwords. Opt-in min. 10 characters.
    • Prevent password reuse.
    • Prevent using common passwords.
    • Prevent using pwoned passwords.
  • Password hashing using either:
    • Argon2i
    • Scrypt
    • Bcrypt
  • Manage session tokens to prevent session fixation.
  • Password reset:
    • Display generic message eg. we’ve sent more details to the registered email.
    • Never lock users out immediately. Send a reset-or-ignore message.
    • Send reset password message even when user doesn’t exist but w/link to create account.
  • Limit amount of password resets per hr/days.
  • Verify password before editing sensitive data.
  • Prevent user enumeration and guessable accounts.
    • Respond failed logins with status code 400.
    • Use generic error message. ie. Authentication failed.
    • If 2FA, pass 1st w/invalid token. Fail 2nd.
    • cron emails eg. password reset, 2FA, so on.
  • Prevent timing attacks.
    • Increase report time with each failed login attempt.
    • Limit the number of login attempts per second.
  • Role based access control (RBAC).
  • Opt-in store password hashes in a restricted table.
  • Opt-in multi-factor authentication (FA).
  • Opt-in timeout sessions.
  • Opt-in limit simultaneous sessions per user.

Registration

  • Require unique identifier, eg. username.
  • Verify account.
  • Opt-in multiple contact channels. ie. email, signal, wire.
  • Opt-in multi-factor authentication.
  • Opt-in notifications when user logs in from new IP.

User Privacy

  • Avoid using identifiable user data, such as email, as user identifiers.
  • Use indirect object references. eg. unique displayable id.
  • Request email only for verification.
  • Ask the least amount of identifiable data.
  • Encrypt identifiable data before storing it.

Router

  • Prevent open redirect attacks. Redirect only to:
    • Internal paths, and services.
    • Trusted 3rd party services.
  • Sanitize params.
    • Whitelist keys.
    • Validate & sanitize values.
  • Turn off detailed error reports in production.
    • Return 400 with a generic error page.
    • Remove sensitive data before logging errors.
  • Throttle requests to prevent DDOS attacks.
  • Help prevent CSRF validating requests headers:
    • Origin.
    • Referrer.

Open redirection

If for legitimate reasons, we allow input-generated redirection:

  • Whitelist sites to redirect to.
  • Confirm forwarding. Clearly state destination.
  • Add tests to:
    • Prevent whitelist tampering.
    • Refute location header can be an invalid url.

Cache

  • Prevent web cache deception attacks.
    • Only cache assets with a verifiable caching HTTP header.
    • Cache resource by contents, not extension.
  • Store all static resources in a designated directory.
  • Ensure when /public/non-existent.css is requested by authenticated user:
    • Never respond with 202.
    • Never render a page, eg. home, as fallback.
    • Return 404 response.
    • Redirection via 302 is ok.

Robots

Robots usually categorize (index) websites and/or extract (crawl) their data.

Beware, robot rules are merely advisory. A robot can simply ignore them.

  • Include index/crawling rules in robots.txt.
  • Include robots.txt in the root route.
  • Add authentication to prevent index/crawling:
    • Private, and sensitive data.
    • Hosted non-production environments.
    • Restricted areas.
  • For granular rules use:
    • the X-Robots-Tag response header.
    • meta-tags.

A robots.txt which doesn’t allow any robot to index/crawl a site looks like:

User-agent: *
Disallow: /

Logs

  • Filter before logging:
    • Identifiable data.
    • Private data.
    • Restricted actions.
  • Ensure logging services only get filtered data.

Gems & Tools

Code quality:

Security test:

Encryption:

Credential Management:

  • git secrets. Prevent committing AWS private credentials.

Input handling:

  • Sanitize. Whitelist-based HTML and CSS sanitizer.
  • Loofah. HTML/XML manipulation and sanitzation gem.
  • Pathname. Ruby standard library for handling paths.

HTTPS scanners:

Rack solutions to generate/renew Let’s Encrypt certificates:

Rack compatible authentication & authorization gems:

Logging:

Recommendations

Although outside the scope of this cheat sheet, consider:

  • Disable compressed responses to prevent compression based attacks.
  • Require latest browser versions.
  • Require browser never accepts 3rd party cookies to prevent Heist attack.

Resources

Guides & Cheat Sheets

Articles

Presentations