Project Name | Stars | Downloads | Repos Using This | Packages Using This | Most Recent Commit | Total Releases | Latest Release | Open Issues | License | Language |
---|---|---|---|---|---|---|---|---|---|---|
Api Security Checklist | 21,061 | 3 months ago | 12 | mit | ||||||
Checklist of the most important security countermeasures when designing, testing, and releasing your API | ||||||||||
Osquery | 20,663 | 2 days ago | 3 | March 31, 2018 | 592 | other | C++ | |||
SQL powered operating system instrumentation, monitoring, and analytics. | ||||||||||
Personal Security Checklist | 12,553 | 11 days ago | 25 | other | ||||||
🔒 A compiled checklist of 300+ tips for protecting digital security and privacy in 2023 | ||||||||||
The Practical Linux Hardening Guide | 8,217 | 3 years ago | 3 | mit | ||||||
This guide details creating a secure Linux production system. OpenSCAP (C2S/CIS, STIG). | ||||||||||
Devops Resources | 7,566 | 2 months ago | 14 | Groovy | ||||||
DevOps resources - Linux, Jenkins, AWS, SRE, Prometheus, Docker, Python, Ansible, Git, Kubernetes, Terraform, OpenStack, SQL, NoSQL, Azure, GCP | ||||||||||
Checklist Checklist | 2,529 | 7 months ago | 6 | JavaScript | ||||||
🌈 A Curated List of Checklists ✔︎✔︎ | ||||||||||
Windows_hardening | 1,963 | 11 days ago | 11 | mit | PowerShell | |||||
HardeningKitty and Windows Hardening settings and configurations | ||||||||||
Zen Rails Security Checklist | 1,632 | 4 years ago | 1 | mit | Ruby | |||||
Checklist of security precautions for Ruby on Rails applications. | ||||||||||
Owasp Web Checklist | 1,323 | a year ago | ||||||||
OWASP Web Application Security Testing Checklist | ||||||||||
Rails Security Checklist | 1,305 | a year ago | 65 | Ruby | ||||||
:key: Community-driven Rails Security Checklist (see our GitHub Issues for the newest checks that aren't yet in the README) |
This document provides a not necessarily comprehensive 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. It does not replace developer training on secure coding principles and how they can be applied.
Describing how each security vulnerability works is outside the scope of this document. Links to external resources containing further information are provided in the corresponding sections of the checklist. Please apply only the suggestions you thoroughly understand.
Please keep in mind that security is a moving target. New vulnerabilities and attack vectors are discovered every day. We suggest you try to keep up to date, for instance, by subscribing to security mailing lists related to the software and libraries you are using.
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.
Table of contents generated by DocToc.
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()
,
open
, popen<n>
, File.read
, File.write
, and exec
. Using regular
expressions is a good way to sanitize it (code sample).
Mitigates command injection attacks.
Resources:
Broken Authentication and Session Management are #2 at the OWASP Top 10.
config.password_length = 8..128
in
config/initializers/devise.rb
.config.reconfirmable = true
in config/initializers/devise.rb
.config.send_password_change_notification = true
in
config/initializers/devise.rb
.config.paranoid = true
in
config/initializers/devise.rb
will protect the confirmable
,
recoverable
and unlockable
modules against user enumeration. To protect
the registerable
module, add a captcha to the registration page (see
instructions in the Devise
Wiki).before_action :authenticate_user!
to
ApplicationController
and
skip_before_action :authenticate_user!
to publicly accessible
controllers/actions.config/routes.rb
. Requiring
authentication in both controllers and routes may not be DRY, but such
redundancy provides additional security (see Defense in
depth).
authenticate :user do
block
(see the Devise
Wiki).devise_parameter_sanitizer.permit
.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.
link_to
(the second argument) will be
HTML escaped. However, link_to
allows any scheme for the URL. If using regex,
ensure that the string begins with the expected protocol(s), as in
\Ahttps?
. Mitigates XSS attacks such as entering
javascript:dangerous_stuff()//http://www.some-legit-url.com
as a website URL
or a dangerous data:
payload that is displayed 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
[email protected]\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. Most templating
libraries also provide a way of skipping escaping. ERB uses the double ==
:
<%== params[:query] %>
. For custom scrubbing, see
ActionView::Helpers::SanitizeHelper
Mitigates XSS attacks.
html_safe
, it is possible to introduce cross-site scripting into templates
with unquoted attributes. In the following code
<p class=<%= params[:style] %>...</p>
, an attacker can insert a space into
the style parameter and suddenly the payload is outside the attribute value and
they can insert their own payload. And when a victim mouses over the paragraph,
the XSS payload will fire. Mitigates XSS attacks.
</script>
tag as HTML no matter
where it is. The Rails documentation recommends always using json_escape
just in case to_json
is overridden or the value is not valid JSON.
Mitigates XSS attacks.
render inline: ...
. The value passed in will be
treated like an ERB template by default. Take a look at this code:
render inline: "Thanks #{@user.name}!"
. Assuming users can set their own
name, an attacker might set their name to <%= rm -rf / %>
which will execute
rm -rf /
on the server! This is called Server Side Template Injection and it
allows arbitrary code execution (RCE) on the server. If you must use an inline
template treat all input the same as you would in a regular ERB template:
render inline: "Thanks <%= @user.name %>"
. Mitigates XSS attacks.
_html
, it will automatically be marked as html safe while the key interpolations will be escaped! See (example code).!=
in Haml and it should be made sure that no
user data is rendered unescaped. The !=
notation in Haml works the way
<%= raw(…) %>
works in ERB. See (example code).Resources:
Resources:
"/get/post/6"
, for example, but not "/get/post/9"
but the system does not
properly check those permissions. And if we change "6" in the URL, what happens?
We can see the data of all users. This may be due to the fact that the data was
generated as follows: @user = User.find_by(id: params[:user_id])
– which is
basically getting the ID from the GET parameter in the URL. Instead a more
secure way of doing this is setting the @user
parameter based on the
"current_user"
session variable like this: @user = current_user
.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.
127.0.0.1
. For availability
reasons, you shouldn't have 1 server in production anyway. For staging and test
environments, follow this rule. For production setups where you have multiple
Rails servers that need to connect to memcached, use the private IP of the
server. This is something like 192.168.0.1
, 172.16.0.1
, or 10.0.0.1
. When
you start memcached, use --listen 127.0.0.1
or --listen 192.168.0.1
.-U 0
when
starting memcached.Resources:
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:
You can use rack-cors
gem and in config/application.rb
specify your
configuration (code sample).
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.
404 Not Found
status code instead of 403 Forbidden
for authorization errors.
Prevents leakage of attribute values used to generate the slugs. For instance,
visiting www.myapp.com/users/john-doe
and getting a 403
return status
indicates the application has a user named John Doe.*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.
config/master.key
is created when you run
rails new
. It's also added to .gitignore
so it doesn't get committed to your
repository. Mitigates credential leaks/theft.
config/credentials.yml.enc
file directly. To add
credentials, run bin/rails credentials:edit
. Use a flat format which means you
don't have to put development or production anymore. Mitigates credential
leaks/theft.
bin/rails secret
and
add that to your credentials by running bin/rails credentials:edit
.master.key
securely. You can scp or sftp the file. Upload the key
to a shared directory. Shared here means shared between releases, not a shared
filesystem. On each deploy, you symlink config/master.key
to
/path/to/shared/config/master.key
.RAILS_MASTER_KEY
environment variable. In some cases
where you can't upload a file, this is the only option. Even though this is
convenient, make sure you know the risks of using environment variables. The
risks can be mitigated, but if you can upload master.key then use that option.Resources:
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:
MediumSecurity
is a good way to stop
malicious gems getting installed on the server. For example,
bundle --trust-policy MediumSecurity
.rubocop
gem and enables security-related rules in the
.rubocop.yml
configuration file.Resources:
role
attribute of the User
model for privilege escalation purposes.
# User input
params[:shop][:items_ids] # Maybe you expect this to be an array inside a string.
# But it can contain something very dangerous like:
# "Kernel.exec('Whatever OS command you want')"
# Vulnerable code
evil_string = params[:shop][:items_ids]
eval(evil_string)
If you see a call to eval you must be very sure that you are properly sanitizing it. Using regular expressions is a good way to accomplish that.
# Secure code
evil_string = params[:shop][:items_ids]
secure_string = /\[\d*,?\d*,?\d*\]/.match(evil_string).to_s
eval(secure_string)
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
module Sample
class Application < Rails::Application
config.middleware.use Rack::Cors do
allow do
origins 'someserver.example.com'
resource %r{/users/\d+.json},
headers: ['Origin', 'Accept', 'Content-Type'],
methods: [:post, :get]
end
end
end
end
On some pages like the login page, you'll want to throttle your users to a few requests per minute. This prevents bots from trying thousands of passwords quickly.
Rack Attack is a Rack middleware that provides throttling among other features.
Rack::Attack.throttle('logins/email', :limit => 6, :period => 60.seconds) do |req|
req.params['email'] if req.path == '/login' && req.post?
end
Instead of the following example:
# en.yml
en:
hello: "Welcome <strong>%{user_name}</strong>!"
<%= t('hello', user_name: current_user.first_name).html_safe %>
Use the next one:
# en.yml
en:
hello_html: "Welcome <strong>%{user_name}</strong>!"
<%= t('hello_html', user_name: current_user.first_name) %>
By default,
="<em>emphasized<em>"
!= "<em>emphasized<em>"
compiles to:
<em>emphasized</em>
<em>emphasized<em>
Contributions are welcome. If you would like to correct an error or add new items to the checklist, feel free to create an issue followed by a PR. See the TODO section for contribution suggestions.
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.