brunofacca / zen-rails-security-checklist
- суббота, 15 апреля 2017 г. в 03:11:30
Ruby
Checklist of security precautions for Ruby on Rails applications.
This document provides a list of security measures to be implemented when developing a Ruby on Rails application. It is designed to serve as a quick reference and minimize vulnerabilities caused by developer forgetfulness.
This checklist is meant to be a community-driven resource. Your contributions are welcome!
Disclaimer: This document does not cover all possible security vulnerabilities. The authors do not take any legal responsibility for the accuracy or completeness of the information herein.
This document focuses on Rails 4 and 5. Vulnerabilities that were present in earlier versions and fixed in Rails 4 are not included.
Injection attacks are #1 at the OWASP Top10.
#{foo}
) to insert user inputted
strings into ActiveRecord or raw SQL queries. Use the ?
character, named bind
variables or the ActiveRecord::Sanitization
methods
to sanitize user input used in DB queries. Mitigates SQL injection attacks.eval
, system
, syscall
, %x()
, and
exec
. Mitigates command injection attacks.Resources:
Broken Authentication and Session Management are #2 at the OWASP Top 10.
config.reconfirmable = true
in config/initializers/devise.rb
.
Mitigates the creation of bogus accounts with non-existing or third-party
e-mails.config.send_password_change_notification = true
in
config/initializers/devise.rb
. Does not prevent an attacker from changing
the victim's password, but warns the victim so he can contact the system
administrator to revoke the attacker's access.before_acion :authenticate_user!
to ApplicationController
and
skip_before_action :authenticate_user!
to publicly accessible
controllers/actions. Avoid unauthorized access due to developer
forgetfulness.config/routes.rb
by putting
non-public
resources within a authenticate :user do
block (see the Devise
Wiki).
Requiring authentication in both controllers and routes may not be DRY, but
such redundancy provides additional security (see Defense in
depth).Broken Authentication and Session Management are #2 at the OWASP Top 10.
secret_key_base
is set. Strengthens cookie encryption and
mitigates multiple attacks involving cookie tampering.httponly
. Search the project for cookie
accessors and add httponly: true
. Example: cookies[:login] = {value: 'user', httponly: true}
. Restricts cookie access to the Rails server. Mitigates
attackers from using the victim's browser JavaScript to steal cookies after a
successful XSS attack.Resources:
XSS is #3 at the OWASP Top 10.
\Ahttps?
. Mitigates XSS attacks such as entering
javascript:dangerous_stuff()//http://www.some-legit-url.com
as a website URL
that is displayed as a link to other users (e.g., in a user profile page).\A
and \z
to match string
beginning and end. Do not use ^
and $
as anchors. Mitigates XSS
attacks that involve slipping JS code after line breaks, such as
me@example.com\n<script>dangerous_stuff();</script>
.html_safe
or raw
at the view suppresses escaping. Look for calls to these
methods in the entire project, check if you are generating HTML from
user-inputted strings and if those strings are effectively validated. Note that
there are dozens of ways to evade
validation. If
possible, avoid calling html_safe
and raw
altogether. For custom scrubbing,
see
ActionView::Helpers::SanitizeHelper
Mitigates XSS attacks.*Resources:
config.force_ssl = true
in config/environments/production.rb
. May also be
done in a TLS termination point such as a load balancer, Nginx or Passenger
Standalone. Mitigates man-in-the-middle and other attacks.authorize
or policy_scope
method (sample code).
Mitigates forced browsing attacks due to developers forgetting to require
authorization in some controller actions.user.posts
), consider using policy_scope
. See additional details and sample
code. Improves
readability and maintainability of authorization policies.Resources:
../../passwd
./etc/passwd
.Resources:
protect_from_forgery with: :exception
in all controllers used by web views or in
ApplicationController
.config.action_controller.per_form_csrf_tokens = true
.Resources:
config.filter_parameters
at
initializers/filter_parameter_logging.rb
. For added security, consider
converting filter_parameters
into a whitelist. See sample
code. Prevents plain-text storage
of sensitive data in log files.<%# This comment syntax with ERB %>
instead of HTML comments. Avoids exposure of
implementation details.config.consider_all_requests_local = true
in the production
environment. If you need to set config.consider_all_requests_local = true
to
use the better_errors gem, do it
on config/environments/development.rb
. Prevents leakage of exceptions and
other information that should only be accessible to developers.group :development, :test do
block
in the Gemfile
. Prevents leakage of exceptions and even REPL access
if using better_errors + web-console.secret_key_base
, DB, and API
credentials to git repositories. Avoid storing credentials in the source code,
use environment variables instead. If not possible, ensure all sensitive files
such as /config/database.yml
, config/secrets.yml
(and possibly
/db/seeds.rb
if it is used to create seed users for production) are included in
.gitignore
. Mitigates credential leaks/theft.secret_key_base
with over 30 random characters. The rake secret
command generates such strong keys. Strengthens cookie encryption,
mitigating multiple cookie/session related attacks.redirect_to
. If you have no choice, create
a whitelist of acceptable redirect URLs or limit to only redirecting to
paths within your domain (example code).
Mitigates redirection to phishing and malware sites. Prevent attackers from
providing URLs such as
http://www.my-legit-rails-app.com/redirect?to=www.dangeroussite.com
to
victims.match ':controller(/:action(/:id(.:format)))'
and make non-action controller
methods private. Mitigates unintended access to controller methods.Resources:
role
attribute of the User
model for privilege escalation purposes.We may implement password strength validation in Devise by adding the
following code to the User
model.
validate :password_strength
private
def password_strength
minimum_length = 8
# Regex matches at least one lower case letter, one uppercase, and one digit
complexity_regex = /\A(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])/
# When a user is updated but not its password, the password param is nil
if password.present? && password.length < minimum_length || !password.match(complexity_regex)
errors.add :password, 'must be 8 or more characters long, including
at least one lowercase letter, one uppercase
letter, and one digit.'
end
end
Add the following to app/controllers/application_controller.rb
after_action :verify_authorized, except: :index, unless: :devise_controller?
after_action :verify_policy_scoped, only: :index, unless: :devise_controller?
Add the following to controllers that do not require authorization. You may create a concern for DRY purposes.
after_action_skip :verify_authorized
after_action_skip :verify_policy_scoped
Think of a blog-like news site where users with editor
role have access to
specific news categories, and admin
users have access to all categories. The
User
and the Category
models have an HMT relationship. When creating a blog
post, there is a select box for choosing a category. We want editors only to see
their associated categories in the select box, but admins must see all
categories. We could populate that select box with user.categories
. However,
we would have to associate all admin users with all categories (and update these
associations every time a new category is created). A better approach is to use
Pundit Scopes to determine which
categories are visible to each user role and use the policy_scope
method when
populating the select box.
# app/views/posts/_form.html.erb
f.collection_select :category_id, policy_scope(Category), :id, :name
Developers may forget to add one or more parameters that contain sensitive data
to filter_parameters
. Whitelists are usually safer than blacklists as they do
not generate security vulnerabilities in case of developer forgetfulness.
The following code converts filter_parameters
into a whitelist.
# config/initializers/filter_parameter_logging.rb
if Rails.env.production?
# Parameters whose values are allowed to appear in the production logs:
WHITELISTED_KEYS = %w(foo bar baz)
# (^|_)ids? matches the following parameter names: id, *_id, *_ids
WHITELISTED_KEYS_MATCHER = /((^|_)ids?|#{WHITELISTED_KEYS.join('|')})/.freeze
SANITIZED_VALUE = '[FILTERED]'.freeze
Rails.application.config.filter_parameters << lambda do |key, value|
unless key.match(WHITELISTED_KEYS_MATCHER)
value.replace(SANITIZED_VALUE)
end
end
else
# Keep the default blacklist approach in the development environment
Rails.application.config.filter_parameters += [:password]
end
Contributions are welcome. If you would like to correct an error or add new items to the checklist, feel free to create an issue and/or a PR. If you are interested in contributing regularly, drop me a line at the above e-mail to become a collaborator.
Released under the MIT License.