├── empty_file.rb ├── LICENSE └── README.md /empty_file.rb: -------------------------------------------------------------------------------- 1 | puts "Please ignore this file. Its only purpose is to make GitHub recognize 2 | this repo as a Ruby project." -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Bruno Facca 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Zen Rails Security Checklist 2 | 3 | ## Summary 4 | This document provides a not necessarily comprehensive list of security measures 5 | to be implemented when developing a Ruby on Rails application. It is designed to 6 | serve as a quick reference and minimize vulnerabilities caused by developer 7 | forgetfulness. It does not replace developer training on secure coding 8 | principles and how they can be applied. 9 | 10 | Describing how each security vulnerability works is outside the scope of this 11 | document. Links to external resources containing further information are 12 | provided in the corresponding sections of the checklist. Please apply only the 13 | suggestions you thoroughly understand. 14 | 15 | Please keep in mind that security is a moving target. New vulnerabilities and 16 | attack vectors are discovered every day. We suggest you try to keep up to date, 17 | for instance, by subscribing to security mailing lists related to the software 18 | and libraries you are using. 19 | 20 | This checklist is meant to be a community-driven resource. Your 21 | [contributions](#contributing) are welcome! 22 | 23 | **Disclaimer**: This document does not cover all possible security 24 | vulnerabilities. The authors do not take any legal responsibility for the 25 | accuracy or completeness of the information herein. 26 | 27 | ## Supported Rails Versions 28 | This document focuses on Rails 4 and 5. Vulnerabilities that were present in 29 | earlier versions and fixed in Rails 4 are not included. 30 | 31 | 32 | 33 | ## Table of Contents 34 | 35 | - [The Checklist](#the-checklist) 36 | - [Injection](#injection) 37 | - [Authentication](#authentication) 38 | - [Sessions & Cookies](#sessions--cookies) 39 | - [Cross-Site Scripting (XSS)](#cross-site-scripting-xss) 40 | - [Handling User Input](#handling-user-input) 41 | - [Output Escaping & Sanitization](#output-escaping--sanitization) 42 | - [XSS protection in HAML templates](#xss-protection-in-haml-templates) 43 | - [Content Security Policy (CSP)](#content-security-policy-csp) 44 | - [Insecure Direct Object Reference](#insecure-direct-object-reference) 45 | - [HTTP & TLS](#http--tls) 46 | - [Security-related headers](#security-related-headers) 47 | - [Memcached Security](#memcached-security) 48 | - [Authorization (Pundit)](#authorization-pundit) 49 | - [Files](#files) 50 | - [File Uploads](#file-uploads) 51 | - [File Downloads](#file-downloads) 52 | - [Cross-Site Request Forgery (CSRF)](#cross-site-request-forgery-csrf) 53 | - [Cross Origin Resource Sharing (CORS)](#cross-origin-resource-sharing-cors) 54 | - [Sensitive Data Exposure](#sensitive-data-exposure) 55 | - [Credentials](#credentials) 56 | - [Routing, Template Selection, and Redirection](#routing-template-selection-and-redirection) 57 | - [Third-party Software](#third-party-software) 58 | - [Security Tools](#security-tools) 59 | - [Testing](#testing) 60 | - [Others](#others) 61 | - [Details and Code Samples](#details-and-code-samples) 62 | - [Command Injection example](#command-injection-example) 63 | - [Password validation regex](#password-validation-regex) 64 | - [Pundit: ensure all actions are authorized](#pundit-ensure-all-actions-are-authorized) 65 | - [Pundit: only display appropriate records in select boxes](#pundit-only-display-appropriate-records-in-select-boxes) 66 | - [rack-cors configuration](#rack-cors-configuration) 67 | - [Convert filter_parameters into a whitelist](#convert-filter_parameters-into-a-whitelist) 68 | - [Throttling Requests](#throttling-requests) 69 | - [HAML: XSS protection](#haml-xss-protection) 70 | - [Authors](#authors) 71 | - [Contributing](#contributing) 72 | - [TODO](#todo) 73 | - [References and Further Reading](#references-and-further-reading) 74 | - [License](#license) 75 | 76 | 77 | 78 | Table of contents generated by [DocToc](https://github.com/thlorenz/doctoc). 79 | 80 | ## The Checklist 81 | 82 | #### Injection 83 | Injection attacks are #1 at the [OWASP Top10](https://www.owasp.org/index.php/Top_10_2013-Top_10). 84 | - [ ] Don’t use standard Ruby interpolation (`#{foo}`) to insert user inputted 85 | strings into ActiveRecord or raw SQL queries. Use the `?` character, named bind 86 | variables or the [ActiveRecord::Sanitization 87 | methods](http://api.rubyonrails.org/classes/ActiveRecord/Sanitization/ClassMethods.html#method-i-sanitize_conditions) 88 | to sanitize user input used in DB queries. *Mitigates SQL injection attacks.* 89 | - [ ] Don't pass user inputted strings to methods capable of evaluating 90 | code or running O.S. commands such as `eval`, `system`, `syscall`, `%x()`, 91 | `open`, `popen`, `File.read`, `File.write`, and `exec`. Using regular 92 | expressions is a good way to sanitize it ([code sample](#command-injection-example)). 93 | *Mitigates command injection attacks.* 94 | 95 | Resources: 96 | - [Ruby on Rails Security Guide - SQL Injection](http://guides.rubyonrails.org/security.html#sql-injection) 97 | - [Rails SQL Injection Examples](https://rails-sqli.org/) 98 | - [Step Up the Security of Your Rails App | Part 1](https://www.ombulabs.com/blog/rails/security/rails-security.html) 99 | 100 | #### Authentication 101 | Broken Authentication and Session Management are #2 at the [OWASP Top 10](https://www.owasp.org/index.php/Top_10_2013-Top_10). 102 | - [ ] Avoid rolling your own authentication unless you know **exactly** what you 103 | are doing. Consider using a gem such as 104 | [Devise](https://github.com/plataformatec/devise), 105 | [Authlogic](https://github.com/binarylogic/authlogic) or 106 | [Clearance](https://github.com/thoughtbot/clearance). *Mitigates dozens of 107 | potential vulnerabilities.* 108 | - [ ] Enforce a minimum password length of 8 characters or more. *Mitigates 109 | brute-force attacks.* 110 | - Devise: set `config.password_length = 8..128` in 111 | `config/initializers/devise.rb`. 112 | - [ ] Consider validating passwords against: 113 | - Dictionary words. Since passwords have a minimum length requirement, the 114 | dictionary need only include words meeting that requirement. 115 | - A list of commonly used passwords such as 116 | [these](https://github.com/danielmiessler/SecLists/tree/master/Passwords). 117 | The [StrongPassword](https://github.com/bdmac/strong_password) gem provide 118 | such feature. 119 | - A leaked password database such as [PasswordPing](https://www.passwordping.com/docs-passwords-api/). 120 | - Context-specific words, such as the name of the application, the 121 | username, and derivatives thereof. 122 | - [ ] Consider the pros and cons of enforcing password complexity rules such as 123 | mixtures of different character types. Most applications use it. However, the 124 | latest [NIST Guidelines](https://pages.nist.gov/800-63-3/sp800-63b.html) advise 125 | against it. An alternative is to increase the minimum length requirement and 126 | encourage the usage of passphrases. *Mitigate brute-force attacks.* 127 | - Devise: use a Devise-specific gem such as 128 | [devise-security](https://github.com/devise-security/devise-security), 129 | [devise_zxcvbn](https://github.com/bitzesty/devise_zxcvbn) or one of the 130 | following authentication-agnostic solutions. 131 | - The [StrongPassword](https://github.com/bdmac/strong_password) gem or a 132 | regex validation ([code sample](#password-validation-regex)) should work 133 | with most authentication setups. 134 | - [ ] Lock the account after multiple failed login attempts. *Mitigates 135 | brute-force attacks.* 136 | - Devise: activate the [lockable 137 | module](https://github.com/plataformatec/devise/wiki/How-To:-Add-:lockable-to-Users). 138 | - [ ] Require users to confirm their e-mail addresses on sign-up and when the 139 | e-mail address is changed. *Mitigates the creation of bogus accounts with 140 | non-existing or third-party e-mails.* 141 | - Devise: use the [confirmable 142 | module](https://github.com/plataformatec/devise/wiki/How-To:-Add-:confirmable-to-Users) 143 | and set `config.reconfirmable = true` in `config/initializers/devise.rb`. 144 | - [ ] Require users to input their old password on password change. *Mitigates 145 | unauthorized password changes on session hijacking, CSRF or when a user forgets 146 | to log out and leaves the PC or mobile device unattended.* 147 | - Devise: does that by default 148 | - [ ] Expire the session at log out and expire old sessions at every successful 149 | login. *Mitigates CSRF, session hijacking and session fixation attacks by 150 | reducing their time-frame.* 151 | - Devise: does that by default. 152 | - [ ] Expire sessions after a period of inactivity (e.g., 30 minutes). 153 | *Mitigates CSRF, session hijacking and session fixation attacks by reducing 154 | their time-frame.* 155 | - Devise: use the [timeoutable 156 | module](http://www.rubydoc.info/github/plataformatec/devise/Devise/Models/Timeoutable). 157 | - [ ] Notify user via email on password change. *Does not prevent an attacker 158 | from changing the victim's password, but warns the victim so he can contact the 159 | system administrator to revoke the attacker's access.* 160 | - Devise: set `config.send_password_change_notification = true` in 161 | `config/initializers/devise.rb`. 162 | - [ ] Use generic error messages such as "Invalid email or password" instead of 163 | specifying which part (e-mail or password) is invalid. *Mitigates [user 164 | enumeration](https://www.owasp.org/index.php/Testing_for_user_enumeration_(OWASP-AT-002)) 165 | and brute-force attacks.* 166 | - Devise: setting `config.paranoid = true` in 167 | `config/initializers/devise.rb` will protect the `confirmable`, 168 | `recoverable` and `unlockable` modules against user enumeration. To protect 169 | the `registerable` module, add a captcha to the registration page (see 170 | [instructions in the Devise 171 | Wiki](https://github.com/plataformatec/devise/wiki/How-To:-Use-Recaptcha-with-Devise)). 172 | - [ ] Ensure all non-public controllers/actions require authentication. *Avoid 173 | unauthorized access due to developer forgetfulness.* 174 | - Devise: add `before_action :authenticate_user!` to 175 | `ApplicationController` and 176 | `skip_before_action :authenticate_user!` to publicly accessible 177 | controllers/actions. 178 | - [ ] Consider using two-factor authentication (2FA) as provided by 179 | [Authy](https://www.authy.com/). *Provides a highly effective extra layer of 180 | authentication security.* 181 | - Devise: see 182 | the [devise-two-factor](https://github.com/tinfoil/devise-two-factor) and 183 | [authy-devise](https://github.com/authy/authy-devise) gems. 184 | - [ ] Consider requiring authentication in `config/routes.rb`. Requiring 185 | authentication in both controllers and routes may not be DRY, but such 186 | redundancy provides additional security (see [Defense in 187 | depth](https://en.wikipedia.org/wiki/Defense_in_depth_(computing))). 188 | - Devise: Place non-public resources within a `authenticate :user do` block 189 | (see the [Devise 190 | Wiki](https://github.com/plataformatec/devise/wiki/How-To:-Define-resource-actions-that-require-authentication-using-routes.rb)). 191 | - [ ] Consider limiting the number of simultaneous sessions per account. *May 192 | reduce application exposure on account compromise (e.g. leaked passwords).* 193 | - Devise: use the 194 | [devise-security](https://github.com/devise-security/devise-security) 195 | gem. 196 | - [ ] Avoid implementing "security questions" such as "What is your mother's 197 | maiden name?" as their answers may be reused across multiple sites and easily 198 | found by means of [social 199 | engineering](https://en.wikipedia.org/wiki/Social_engineering_(security)). See 200 | [this article](https://www.wired.com/2016/09/time-kill-security-questions-answer-lies/). 201 | - [ ] If using role-based access control (RBAC), do not include the role 202 | attribute in the strong parameters of the controller(s) used for user 203 | registration and profile editing. *Prevent malicious users from assigning admin 204 | role to themselves.* 205 | - Devise: Do not pass the role parameter key to 206 | `devise_parameter_sanitizer.permit`. 207 | - [ ] Consider restricting administrator access by IP. If the client's IP is 208 | dynamic, restrict by IP block/ASN or by country via IP geolocation. 209 | 210 | #### Sessions & Cookies 211 | Broken Authentication and Session Management are #2 at the [OWASP Top 10](https://www.owasp.org/index.php/Top_10_2013-Top_10). 212 | - [ ] Don't store data such as money/point balances or user privileges in a 213 | cookie or a CookieStore Session. Store it in the database instead. *Mitigates 214 | replay attacks.* 215 | - [ ] Consider always using encrypted cookies. This is the default behavior in 216 | Rails 4+ when `secret_key_base` is set. *Strengthens cookie encryption and 217 | mitigates multiple attacks involving cookie tampering.* 218 | - [ ] Unless your JavaScript frontend needs to read cookies generated by the 219 | Rails server, set all cookies as `httponly`. Search the project for cookie 220 | accessors and add `httponly: true`. Example: `cookies[:login] = {value: 'user', 221 | httponly: true}`. *Restricts cookie access to the Rails server. Mitigates 222 | attackers from using the victim's browser JavaScript to steal cookies after a 223 | successful XSS attack.* 224 | 225 | Resources: 226 | - [Ruby on Rails Security Guide - Sessions](http://guides.rubyonrails.org/security.html#sessions) 227 | 228 | #### Cross-Site Scripting (XSS) 229 | XSS is #3 at the [OWASP Top 10](https://www.owasp.org/index.php/Top_10_2013-Top_10). 230 | 231 | ###### Handling User Input 232 | - [ ] Always validate user input that may eventually be displayed to other 233 | users. Attempting to blacklist characters, strings or sanitize input tends to be 234 | ineffective ([see examples of how to bypass such 235 | blacklists](https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet)). A 236 | whitelisting approach is usually safer. *Mitigates multiple XSS attacks.* 237 | - [ ] Consider using the 238 | [loofah-activerecord](https://github.com/flavorjones/loofah-activerecord) gem 239 | to scrub your model attribute values. *Mitigates multiple XSS attacks*. 240 | - [ ] If you must create links from user inputted URLs, be sure to validate 241 | them. In particular, it should be possible to limit URL schemes to http/https 242 | in nearly all cases. The URL passed to `link_to` (the second argument) will be 243 | HTML escaped. However, `link_to` allows any scheme for the URL. If using regex, 244 | ensure that the string **begins** with the expected protocol(s), as in 245 | `\Ahttps?`. *Mitigates XSS attacks such as entering 246 | `javascript:dangerous_stuff()//http://www.some-legit-url.com` as a website URL 247 | or a dangerous `data:` payload that is displayed to other users (e.g., in a 248 | user profile page).* 249 | - [ ] When using regex for input validation, use `\A` and `\z` to match string 250 | beginning and end. Do **not** use `^` and `$` as anchors. *Mitigates XSS 251 | attacks that involve slipping JS code after line breaks, such as 252 | `me@example.com\n`.* 253 | - [ ] Do not trust validations implemented at the client (frontend) as most 254 | implementations can be bypassed. Always (re)validate at the server. 255 | 256 | ###### Output Escaping & Sanitization 257 | - [ ] Escape all HTML output. Rails does that by default, but calling 258 | `html_safe` or `raw` at the view suppresses escaping. Look for calls to these 259 | methods in the entire project, check if you are generating HTML from 260 | user-inputted strings and if those strings are effectively validated. Note that 261 | there are [dozens of ways to evade 262 | validation](https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet). If 263 | possible, avoid calling `html_safe` and `raw` altogether. Most templating 264 | libraries also provide a way of skipping escaping. ERB uses the double `==`: 265 | `<%== params[:query] %>`. For custom scrubbing, see 266 | [ActionView::Helpers::SanitizeHelper](http://api.rubyonrails.org/classes/ActionView/Helpers/SanitizeHelper.html) 267 | *Mitigates XSS attacks.* 268 | - [ ] Always enclose attribute values with double quotes. Even without 269 | `html_safe`, it is possible to introduce cross-site scripting into templates 270 | with unquoted attributes. In the following code 271 | `

...

`, an attacker can insert a space into 272 | the style parameter and suddenly the payload is outside the attribute value and 273 | they can insert their own payload. And when a victim mouses over the paragraph, 274 | the XSS payload will fire. *Mitigates XSS attacks.* 275 | - [ ] Rendering JSON inside of HTML templates is tricky. You can't just HTML 276 | escape JSON, especially when inserting it into a script context, because 277 | double-quotes will be escaped and break the code. But it isn't safe to not 278 | escape it, because browsers will treat a `` tag as HTML no matter 279 | where it is. The Rails documentation recommends always using `json_escape` 280 | just in case `to_json` is overridden or the value is not valid JSON. 281 | *Mitigates XSS attacks.* 282 | - [ ] Be careful when using `render inline: ...`. The value passed in will be 283 | treated like an ERB template by default. Take a look at this code: 284 | `render inline: "Thanks #{@user.name}!"`. Assuming users can set their own 285 | name, an attacker might set their name to `<%= rm -rf / %>` which will execute 286 | `rm -rf /` on the server! This is called Server Side Template Injection and it 287 | allows arbitrary code execution (RCE) on the server. If you must use an inline 288 | template treat all input the same as you would in a regular ERB template: 289 | `render inline: "Thanks <%= @user.name %>"`. *Mitigates XSS attacks.* 290 | - [ ] Avoid sending user inputted strings in e-mails to other users. Attackers 291 | may enter a malicious URL in a free text field that is not intended to contain 292 | URLs and does not provide URL validation. Most e-mail clients display URLs as 293 | links. *Mitigates XSS, phishing, malware infection and other attacks.* 294 | - [ ] If an I18n key ends up with `_html`, it will automatically be marked as html safe while the key interpolations will be escaped! See [(example code)](#when-i18n-key-ends-up-with-_html). 295 | 296 | ###### XSS protection in HAML templates 297 | 298 | - [ ] Be careful when using `!=` in Haml and it should be made sure that no 299 | user data is rendered unescaped. The `!=` notation in Haml works the way 300 | `<%= raw(…) %>` works in ERB. See [(example code)](#haml-xss-protection). 301 | 302 | Resources: 303 | - [Ruby on Rails Security Guide - XSS](http://guides.rubyonrails.org/security.html#cross-site-scripting-xss) 304 | - [OWASP XSS Filter Evasion Cheat Sheet](https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet) 305 | - [OWASP Ruby on Rails Cheatsheet - Cross-site Scripting (XSS)](https://www.owasp.org/index.php/Ruby_on_Rails_Cheatsheet#Cross-site_Scripting_.28XSS.29) 306 | - [Plataformatec Blog - The new HTML sanitizer in Rails 4.2](http://blog.plataformatec.com.br/2014/07/the-new-html-sanitizer-in-rails-4-2) 307 | - [Brakeman Pro - Cross-Site Scripting in Rails](https://brakemanpro.com/2017/09/08/cross-site-scripting-in-rails) 308 | - [Preventing security issues in Rails](https://www.railscarma.com/blog/technical-articles/preventing-security-issues-rails/) 309 | - [Security tips for rails apps](https://drivy.engineering/security-tips-for-rails-apps/) 310 | 311 | ###### Content Security Policy (CSP) 312 | - [ ] Content Security Policy (CSP) is an added layer of security that helps to 313 | detect and mitigate various types of attacks on our web applications, including 314 | Cross Site Scripting (XSS) and data injection attacks. 315 | 316 | Resources: 317 | - [Rails 5.2 DSL for configuring Content Security Policy](https://blog.bigbinary.com/2018/10/23/rails-5-2-adds-dsl-for-configuring-content-security-policy-header.html) 318 | 319 | #### Insecure Direct Object Reference 320 | - [ ] An IDOR issue arises when the user is supposed to have access to url 321 | `"/get/post/6"`, for example, but not `"/get/post/9"` but the system does not 322 | properly check those permissions. And if we change "6" in the URL, what happens? 323 | We can see the data of all users. This may be due to the fact that the data was 324 | generated as follows: `@user = User.find_by(id: params[:user_id])` – which is 325 | basically getting the ID from the GET parameter in the URL. Instead a more 326 | secure way of doing this is setting the `@user` parameter based on the 327 | `"current_user"` session variable like this: `@user = current_user`. 328 | 329 | Resources: 330 | - [Rails Vulnerabilities and Where To Find Them – Part 1](https://www.vdalabs.com/2018/01/12/rails-vulnerabilities-find-part-1/) 331 | 332 | #### HTTP & TLS 333 | - [ ] Force HTTPS over TLS (formerly known as SSL). Set 334 | `config.force_ssl = true` in `config/environments/production.rb`. May also be 335 | done in a TLS termination point such as a load balancer, Nginx or Passenger 336 | Standalone. *Mitigates man-in-the-middle and other attacks.* 337 | - [ ] Use the [SSL Server Test tool from Qualys SSL 338 | Lab](https://www.ssllabs.com/ssltest/) to check the grade of your TLS 339 | certificate. Be sure to use the strongest (yet widely compatible) protocols 340 | and cipher suites, preferably with Ephemeral Diffie-Hellman support. The 341 | [Mozilla SSL Configuration Generator](https://mozilla.github.io/server-side-tls/ssl-config-generator/) 342 | can give you some suggestions. *Mitigates multiple SSL/TLS-related attacks 343 | such as BEAST and POODLE.* 344 | - [ ] Consider rate-limiting incoming HTTP requests, as implemented by the 345 | [rack-attack](https://github.com/kickstarter/rack-attack) and 346 | [rack-throttle](https://github.com/dryruby/rack-throttle) gems. See [sample 347 | code](#throttling-requests). *Mitigates web scraping, HTTP floods, and other 348 | attacks.* 349 | 350 | ###### Security-related headers 351 | - [ ] Consider using the [Secure Headers 352 | gem](https://github.com/twitter/secureheaders). *Mitigates several attacks.* 353 | - [ ] Consider obfuscating the web server banner string. In other words, hide 354 | your web server name and version. *Mitigates HTTP fingerprinting, making it 355 | harder for attackers to determine which exploits may work on your web server.* 356 | 357 | #### Memcached Security 358 | 359 | - [ ] Use a firewall. Memcached needs to be accessible from your other servers 360 | but there's no reason to expose it to the internet. In short, only your other 361 | production servers have access to your production memcached servers. This alone 362 | would prevent your server from being used in an attack. Memcached out of the box 363 | doesn't use authentication so anyone who can connect to your server will be able 364 | to read your data. 365 | - [ ] Listen on a private interface. If you're running one server for your Rails 366 | application and memcached, you should listen on `127.0.0.1`. For availability 367 | reasons, you shouldn't have 1 server in production anyway. For staging and test 368 | environments, follow this rule. For production setups where you have multiple 369 | Rails servers that need to connect to memcached, use the private IP of the 370 | server. This is something like `192.168.0.1`, `172.16.0.1`, or `10.0.0.1`. When 371 | you start memcached, use `--listen 127.0.0.1` or `--listen 192.168.0.1`. 372 | - [ ] Disable UDP. It is enabled by default. To disable UDP, use `-U 0` when 373 | starting memcached. 374 | 375 | Resources: 376 | - [Memcached Security aka Don't Attack GitHub](https://www.engineyard.com/blog/memcached-security-aka-dont-attack-github-) 377 | 378 | #### Authorization (Pundit) 379 | - [ ] Implement authorization at the back end. Hiding links/controls in the UI 380 | is not enough to protect resources against unauthorized access. *Mitigates 381 | forced browsing attacks.* 382 | - [ ] Ensure all controllers/actions which require authorization call the 383 | `authorize` or `policy_scope` method ([sample code](#pundit-ensure-all-actions-are-authorized)). 384 | *Mitigates forced browsing attacks due to developers forgetting to require 385 | authorization in some controller actions.* 386 | - [ ] When using DB records associated to users to populate select 387 | boxes, radio buttons or checkboxes, instead of querying by association 388 | (`user.posts`), consider using `policy_scope`. See [additional details and sample 389 | code](#pundit-ensure-all-actions-are-authorized). *Improves 390 | readability and maintainability of authorization policies.* 391 | 392 | Resources: 393 | - [Pundit: Ensuring policies and scopes are used](https://github.com/elabs/pundit#ensuring-policies-and-scopes-are-used) 394 | - [Pundit: Scopes](https://github.com/elabs/pundit#scopes) 395 | 396 | #### Files 397 | 398 | ###### File Uploads 399 | - [ ] Avoid using user controlled filenames. If possible, assign "random" 400 | names to uploaded files when storing them in the OS. If not possible, 401 | whitelist acceptable characters. It is safer to deny uploads with invalid 402 | characters in the filenames than to attempt to sanitize them. 403 | *Mitigates Directory Traversal Attacks such as attempting to overwrite 404 | system files by uploading files with names like `../../passwd`.* 405 | - [ ] Avoid using libraries such as ImageMagick to process images and videos 406 | on your server. If possible, use an image/video processing service such as 407 | [Transloadit](https://transloadit.com/), 408 | [Cloudinary](http://cloudinary.com/features#manipulation), or 409 | [imgix](https://www.imgix.com/solutions). *Mitigates multiple image/video 410 | processing related vulnerabilities such as [these](https://imagetragick.com).* 411 | - [ ] If using [paperclip](https://github.com/thoughtbot/paperclip) gem with 412 | [imagemagick](https://www.imagemagick.org) for file upload and processing, make 413 | sure: 414 | - Imagemagick [policies](https://www.imagemagick.org/source/policy.xml) are 415 | suited for your environment to avoid exploits like [pixel flood attack]( 416 | https://hackerone.com/reports/390). 417 | - Content spoofing is handled manually since it fails in scenarios like 418 | [#2426](https://github.com/thoughtbot/paperclip/issues/2426). 419 | - [ ] Process uploaded files asynchronously. If not possible, implement 420 | per-client rate limiting. *Mitigates DoS Attacks that involve overloading the 421 | server CPU by flooding it with uploads that require processing.* 422 | - [ ] Do not trust validations implemented at the client (frontend) as most 423 | implementations can be bypassed. Always (re)validate at the server. 424 | - [ ] Validate files before processing. *Mitigates DoS Attacks such 425 | as image bombs.* 426 | - [ ] Whitelist acceptable file extensions and acceptable Media Types (formerly 427 | known as MIME types). Validating file extensions without checking their media 428 | types is not enough as attackers may disguise malicious files by changing 429 | their extensions. *Mitigates the upload of dangerous file formats such as shell 430 | or Ruby scripts.* 431 | - [ ] Limit file size. *Mitigates against DoS attacks involving the 432 | upload of very large files.* 433 | - [ ] Consider uploading directly from the client (browser) to S3 or a similar 434 | cloud storage service. *Mitigates multiple security issues by keeping uploaded 435 | files on a separate server than your Rails application.* 436 | - [ ] If allowing uploads of malware-prone files (e.g., exe, msi, zip, rar, 437 | pdf), scan them for viruses/malware. If possible, use a third party service to 438 | scan them outside your server. *Mitigates server infection (mostly in Windows 439 | servers) and serving infected files to other users.* 440 | - [ ] If allowing upload of archives such as zip, rar, and gz, validate 441 | the target path, estimated unzip size and media types of compressed files 442 | **before** unzipping. *Mitigates DoS attacks such as zip bombs, zipping 443 | malicious files in an attempt to bypass validations, and overwriting of system 444 | files such as `/etc/passwd`.* 445 | 446 | ###### File Downloads 447 | - [ ] Do not allow downloading of user-submitted filenames and paths. If not 448 | possible, use a whitelist of permitted filenames and paths. *Mitigates the 449 | exploitation of directory traversal vulnerabilities to download sensitive 450 | files.* 451 | 452 | Resources: 453 | - [Ruby on Rails Security Guide - File Uploads and Downloads](http://guides.rubyonrails.org/security.html#file-uploads) 454 | 455 | #### Cross-Site Request Forgery (CSRF) 456 | - [ ] Enforce CSRF protection by setting `protect_from_forgery with: 457 | :exception` in all controllers used by web views or in 458 | `ApplicationController`. 459 | - [ ] Use HTTP verbs in a RESTful way. Do not use GET requests to alter the 460 | state of resources. *Mitigates CSRF attacks.* 461 | - [ ] Up to Rails 4, there was a single CSRF token for all forms, actions, and 462 | methods. Rails 5 implements per-form CSRF tokens, which are only valid for a 463 | single form and action/method. Enable it by setting 464 | `config.action_controller.per_form_csrf_tokens = true`. 465 | 466 | Resources: 467 | - [Ruby on Rails Security Guide - CSRF](http://guides.rubyonrails.org/security.html#cross-site-request-forgery-csrf) 468 | - [Big Binary Blog - Each form gets its own CSRF token in Rails 5](http://blog.bigbinary.com/2016/01/11/per-form-csrf-token-in-rails-5.html) 469 | 470 | #### Cross Origin Resource Sharing (CORS) 471 | - [ ] Occasionally the need to share some resources across many domains appears. 472 | For example, you want to upload a file using AJAX request and send it to the 473 | other app. The receiving side should specify a whitelist of domains that are 474 | allowed to make those requests. There are few HTTP headers that control that. 475 | 476 | You can use `rack-cors` gem and in `config/application.rb` specify your 477 | configuration ([code sample](#rack-cors-configuration)). 478 | 479 | Resources: 480 | - [Security issues solutions in RoR](https://syndicode.com/2017/10/23/security-issues-solutions-in-ror/) 481 | 482 | #### Sensitive Data Exposure 483 | - [ ] If possible, avoid storing sensitive data such as credit cards, tax IDs 484 | and third-party authentication credentials in your application. If not 485 | possible, ensure that all sensitive data is encrypted at rest (in the DB) and 486 | in transit (use HTTPS over TLS). *Mitigate theft/leakage of sensitive data.* 487 | - [ ] Do not log sensitive data such as passwords and credit card numbers. You 488 | may include parameters that hold sensitive data in `config.filter_parameters` at 489 | `initializers/filter_parameter_logging.rb`. For added security, consider 490 | converting `filter_parameters` into a whitelist. See [sample 491 | code](#convert-filter_parameters-into-a-whitelist). *Prevents plain-text storage 492 | of sensitive data in log files.* 493 | - [ ] HTML comments are viewable to clients and should not contain details that 494 | can be useful to attackers. Consider using server-side comments such as `<%# 495 | This comment syntax with ERB %>` instead of HTML comments. *Avoids exposure of 496 | implementation details.* 497 | - [ ] Avoid exposing numerical/sequential record IDs in URLs, form HTML source 498 | and APIs. Consider using slugs (A.K.A. friendly IDs, vanity URLs) to identify 499 | records instead of numerical IDs, as implemented by the [friendly_id 500 | gem](https://github.com/norman/friendly_id). Additional benefits include SEO and 501 | better-looking URLs. *Mitigates forced browsing attacks and exposure of metrics 502 | about your business, such as the number of registered users, number of 503 | products on stock, or number of receipts/purchases.* 504 | - [ ] If using slugs instead of numerical IDs for URLs, consider returning a 505 | `404 Not Found` status code instead of `403 Forbidden` for authorization errors. 506 | Prevents leakage of attribute values used to generate the slugs. For instance, 507 | visiting `www.myapp.com/users/john-doe` and getting a `403` return status 508 | indicates the application has a user named John Doe.* 509 | - [ ] Do not set `config.consider_all_requests_local = true` in the production 510 | environment. If you need to set `config.consider_all_requests_local = true` to 511 | use the [better_errors](https://github.com/charliesome/better_errors) gem, do it 512 | on `config/environments/development.rb`. *Prevents leakage of exceptions and 513 | other information that should only be accessible to developers.* 514 | - [ ] Don't install development/test-related gems such as 515 | [better_errors](https://github.com/charliesome/better_errors) and 516 | [web-console](https://github.com/rails/web-console) in the production 517 | environment. Place them within a `group :development, :test do` block 518 | in the `Gemfile`. *Prevents leakage of exceptions and even **REPL access** 519 | if using better_errors + web-console.* 520 | 521 | ###### Credentials 522 | - [ ] The encryption key, located on `config/master.key` is created when you run 523 | `rails new`. It's also added to `.gitignore` so it doesn't get committed to your 524 | repository. *Mitigates credential leaks/theft.* 525 | - [ ] Don't edit the `config/credentials.yml.enc` file directly. To add 526 | credentials, run `bin/rails credentials:edit`. Use a flat format which means you 527 | don't have to put development or production anymore. *Mitigates credential 528 | leaks/theft.* 529 | - [ ] If you want to generate a new secret key base run, `bin/rails secret` and 530 | add that to your credentials by running `bin/rails credentials:edit`. 531 | - [ ] Upload `master.key` securely. You can scp or sftp the file. Upload the key 532 | to a shared directory. Shared here means shared between releases, not a shared 533 | filesystem. On each deploy, you symlink `config/master.key` to 534 | `/path/to/shared/config/master.key`. 535 | - [ ] If you need to give a developer a copy of the key, never send it via email 536 | (unless you're using encrypted emails which most of us don't!) You can use a 537 | password manager because they use encryption. 538 | - [ ] Put the key on the `RAILS_MASTER_KEY` environment variable. In some cases 539 | where you can't upload a file, this is the only option. Even though this is 540 | convenient, make sure you know the risks of using environment variables. The 541 | risks can be mitigated, but if you can upload master.key then use that option. 542 | 543 | Resources: 544 | - [Rails Encrypted Credentials on Rails 5.2](https://www.engineyard.com/blog/rails-encrypted-credentials-on-rails-5.2) 545 | 546 | #### Routing, Template Selection, and Redirection 547 | - [ ] Don't perform URL redirection based on user inputted strings. In other 548 | words, don't pass user input to `redirect_to`. If you have no choice, create 549 | a whitelist of acceptable redirect URLs or limit to only redirecting to 550 | paths within your domain [(example code)](https://www.owasp.org/index.php/Ruby_on_Rails_Cheatsheet#Redirects_and_Forwards). 551 | *Mitigates redirection to phishing and malware sites. Prevent attackers from 552 | providing URLs such as 553 | `http://www.my-legit-rails-app.com/redirect?to=www.dangeroussite.com` to 554 | victims.* 555 | - [ ] Do not use a user inputted string to determine the name of the template or 556 | view to be rendered. *Prevents attackers from rendering arbitrary views such as 557 | admin-only pages.* 558 | - [ ] Avoid "catch-all" routes such as ` 559 | match ':controller(/:action(/:id(.:format)))'` and make non-action controller 560 | methods private. *Mitigates unintended access to controller methods.* 561 | 562 | Resources: 563 | - [OWASP Ruby on Rails Cheatsheet - Redirects and Forwards (URL validation)](https://www.owasp.org/index.php/Ruby_on_Rails_Cheatsheet#Redirects_and_Forwards) 564 | 565 | #### Third-party Software 566 | - [ ] Apply the latest security patches in the OS frequently. Pay special 567 | attention to internet-facing services such as application servers (Passenger, 568 | Puma, Unicorn), web servers (Nginx, Apache, Passenger Standalone) and SSH 569 | servers. 570 | - [ ] Update Ruby frequently. 571 | - [ ] Watch out for security vulnerabilities in your gems. Run 572 | [bundler-audit](https://github.com/rubysec/bundler-audit) frequently or use 573 | a service like [Snyk](https://snyk.io), [GuardRails](https://www.guardrails.io/) 574 | (both free for open-source development). 575 | 576 | #### Security Tools 577 | - [ ] Run [Brakeman](http://brakemanscanner.org/) before each deploy. 578 | If using an automated code review tool like 579 | [Code Climate](https://codeclimate.com/), enable the [Brakeman 580 | engine](https://docs.codeclimate.com/v1.0/docs/brakeman). 581 | - [ ] Adding a gem trust policy with `MediumSecurity` is a good way to stop 582 | malicious gems getting installed on the server. For example, 583 | `bundle --trust-policy MediumSecurity`. 584 | - [ ] You can use `rubocop` gem and enables security-related rules in the 585 | `.rubocop.yml` configuration file. 586 | - [ ] Consider using a continuous security service such as 587 | [Detectify](https://detectify.com/). 588 | - [ ] Consider using a Web Application Firewall (WAF) such as 589 | [NAXSI](https://github.com/nbs-system/naxsi) for Nginx, 590 | [ModSecurity](https://github.com/SpiderLabs/ModSecurity) for Apache and Nginx. 591 | *Mitigates XSS, SQL Injection, DoS, and many other attacks.* 592 | 593 | Resources: 594 | - [Keeping Rails Application Secured](https://fq.nz/blog/2019/04/14/keeping-rails-application-secured.html) 595 | - [How RuboCop can secure your Ruby and Rails Applications](https://www.guardrails.io/blog/2018/11/25/how-rubocop-can-secure-your-ruby-and-rails-applications) 596 | 597 | #### Testing 598 | - [ ] Include security tests in your test suite. Look at OWASP's 599 | [RailsGoat](https://github.com/OWASP/railsgoat) application for examples of 600 | [security-related Capybara 601 | specs](https://github.com/OWASP/railsgoat/tree/master/spec/vulnerabilities). 602 | *Raises additional security awareness and mitigates security-related 603 | regressions.* 604 | - [ ] Create security tests in pairs: one for the access denied scenario and 605 | another for the access granted scenario. 606 | - [ ] When using TDD, consider implementing authentication in the early stages 607 | of development, as it tends to break multiple preexisting tests. 608 | 609 | #### Others 610 | - [ ] Use strong parameters in the controllers. This is the default behavior as 611 | of Rails 4+. *Mitigates mass assignment attacks such as overwriting the `role` 612 | attribute of the `User` model for privilege escalation purposes.* 613 | - [ ] Implement Captcha or Negative Captcha on publicly exposed forms. 614 | [reCAPTCHA](https://developers.google.com/recaptcha/) is a great option, and 615 | there is [a gem](https://github.com/ambethia/recaptcha) that facilitates Rails 616 | integration. Other options are the 617 | [rucaptcha](https://github.com/huacnlee/rucaptcha) and 618 | [negative-captcha](https://github.com/subwindow/negative-captcha) gems. 619 | *Mitigates automated SPAM (spambots).* 620 | 621 | ## Details and Code Samples 622 | 623 | #### Command Injection example 624 | ``` 625 | # User input 626 | params[:shop][:items_ids] # Maybe you expect this to be an array inside a string. 627 | # But it can contain something very dangerous like: 628 | # "Kernel.exec('Whatever OS command you want')" 629 | 630 | # Vulnerable code 631 | evil_string = params[:shop][:items_ids] 632 | eval(evil_string) 633 | ``` 634 | 635 | If you see a call to eval you must be very sure that you are properly sanitizing 636 | it. Using regular expressions is a good way to accomplish that. 637 | ``` 638 | # Secure code 639 | evil_string = params[:shop][:items_ids] 640 | secure_string = /\[\d*,?\d*,?\d*\]/.match(evil_string).to_s 641 | 642 | eval(secure_string) 643 | ``` 644 | 645 | #### Password validation regex 646 | We may implement password strength validation in Devise by adding the 647 | following code to the `User` model. 648 | ``` 649 | validate :password_strength 650 | 651 | private 652 | 653 | def password_strength 654 | minimum_length = 8 655 | # Regex matches at least one lower case letter, one uppercase, and one digit 656 | complexity_regex = /\A(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])/ 657 | # When a user is updated but not its password, the password param is nil 658 | if password.present? && 659 | (password.length < minimum_length || !password.match(complexity_regex)) 660 | errors.add :password, 'must be 8 or more characters long, including 661 | at least one lowercase letter, one uppercase 662 | letter, and one digit.' 663 | end 664 | end 665 | ``` 666 | 667 | #### Pundit: ensure all actions are authorized 668 | 669 | Add the following to `app/controllers/application_controller.rb` 670 | ``` 671 | after_action :verify_authorized, except: :index, unless: :devise_controller? 672 | after_action :verify_policy_scoped, only: :index, unless: :devise_controller? 673 | ``` 674 | 675 | Add the following to controllers that do not require authorization. You may 676 | create a concern for DRY purposes. 677 | ``` 678 | after_action_skip :verify_authorized 679 | after_action_skip :verify_policy_scoped 680 | ``` 681 | 682 | #### Pundit: only display appropriate records in select boxes 683 | Think of a blog-like news site where users with `editor` role have access to 684 | specific news categories, and `admin` users have access to all categories. The 685 | `User` and the `Category` models have an HMT relationship. When creating a blog 686 | post, there is a select box for choosing a category. We want editors only to see 687 | their associated categories in the select box, but admins must see all 688 | categories. We could populate that select box with `user.categories`. However, 689 | we would have to associate all admin users with all categories (and update these 690 | associations every time a new category is created). A better approach is to use 691 | [Pundit Scopes](https://github.com/elabs/pundit#scopes) to determine which 692 | categories are visible to each user role and use the `policy_scope` method when 693 | populating the select box. 694 | 695 | ``` 696 | # app/views/posts/_form.html.erb 697 | f.collection_select :category_id, policy_scope(Category), :id, :name 698 | ``` 699 | 700 | #### Convert filter_parameters into a whitelist 701 | Developers may forget to add one or more parameters that contain sensitive data 702 | to `filter_parameters`. Whitelists are usually safer than blacklists as they do 703 | not generate security vulnerabilities in case of developer forgetfulness. 704 | The following code converts `filter_parameters` into a whitelist. 705 | 706 | ``` 707 | # config/initializers/filter_parameter_logging.rb 708 | if Rails.env.production? 709 | # Parameters whose values are allowed to appear in the production logs: 710 | WHITELISTED_KEYS = %w(foo bar baz) 711 | 712 | # (^|_)ids? matches the following parameter names: id, *_id, *_ids 713 | WHITELISTED_KEYS_MATCHER = /((^|_)ids?|#{WHITELISTED_KEYS.join('|')})/.freeze 714 | SANITIZED_VALUE = '[FILTERED]'.freeze 715 | 716 | Rails.application.config.filter_parameters << lambda do |key, value| 717 | unless key.match(WHITELISTED_KEYS_MATCHER) 718 | value.replace(SANITIZED_VALUE) 719 | end 720 | end 721 | else 722 | # Keep the default blacklist approach in the development environment 723 | Rails.application.config.filter_parameters += [:password] 724 | end 725 | ``` 726 | 727 | #### rack-cors configuration 728 | 729 | ``` 730 | module Sample 731 | class Application < Rails::Application 732 | config.middleware.use Rack::Cors do 733 | allow do 734 | origins 'someserver.example.com' 735 | resource %r{/users/\d+.json}, 736 | headers: ['Origin', 'Accept', 'Content-Type'], 737 | methods: [:post, :get] 738 | end 739 | end 740 | end 741 | end 742 | ``` 743 | 744 | #### Throttling Requests 745 | 746 | On some pages like the login page, you'll want to throttle your users to a few 747 | requests per minute. This prevents bots from trying thousands of passwords 748 | quickly. 749 | 750 | Rack Attack is a Rack middleware that provides throttling among other features. 751 | 752 | ``` 753 | Rack::Attack.throttle('logins/email', :limit => 6, :period => 60.seconds) do |req| 754 | req.params['email'] if req.path == '/login' && req.post? 755 | end 756 | ``` 757 | 758 | #### When I18n key ends up with \_html 759 | 760 | Instead of the following example: 761 | ``` 762 | # en.yml 763 | en: 764 | hello: "Welcome %{user_name}!" 765 | ``` 766 | ``` 767 | <%= t('hello', user_name: current_user.first_name).html_safe %> 768 | ``` 769 | Use the next one: 770 | ``` 771 | # en.yml 772 | en: 773 | hello_html: "Welcome %{user_name}!" 774 | ``` 775 | ``` 776 | <%= t('hello_html', user_name: current_user.first_name) %> 777 | ``` 778 | 779 | #### HAML: XSS protection 780 | By default, 781 | ```ruby 782 | ="emphasized" 783 | != "emphasized" 784 | ``` 785 | compiles to: 786 | ```ruby 787 | <em>emphasized</em> 788 | emphasized 789 | ``` 790 | 791 | ## Authors 792 | 793 | - **Bruno Facca** - [LinkedIn](https://www.linkedin.com/in/brunofacca/) - 794 | Email: bruno at facca dot info 795 | 796 | ## Contributing 797 | 798 | Contributions are welcome. If you would like to correct an error or add new 799 | items to the checklist, feel free to create an issue followed by a PR. See the 800 | [TODO](#TODO) section for contribution suggestions. 801 | 802 | If you are interested in contributing regularly, drop me a line at the above 803 | e-mail to become a collaborator. 804 | 805 | ## TODO 806 | * Add sample tests (RSpec and/or Minitest) to detect the presence of 807 | vulnerabilities. See OWASP's [RailsGoat security-related Capybara 808 | specs](https://github.com/OWASP/railsgoat/tree/master/spec/vulnerabilities) for 809 | inspiration. 810 | * Compare upload gems regarding their implementation of the [File 811 | Uploads](#file-uploads) items of this checklist (build a table). 812 | * Compare authentication gems regarding their implementation of the 813 | [Authentication](#authentication) items of this checklist (build a table). 814 | 815 | ## References and Further Reading 816 | - [Ruby on Rails Security Guide](http://guides.rubyonrails.org/security.html) 817 | - [The Rails 4 Way by Obie Fernandez](https://www.amazon.com/Rails-Way-Addison-Wesley-Professional-Ruby/dp/0321944275/), Chapter 15 818 | - [OWASP Top Ten](https://www.owasp.org/index.php/Top_10_2013-Top_10) 819 | - [SitePoint: Common Rails Security Pitfalls and Their Solutions](https://www.sitepoint.com/common-rails-security-pitfalls-and-their-solutions/) 820 | - [Rails Security Audit by Hardhat](https://github.com/hardhatdigital/rails-security-audit) 821 | - [Rails Security Checklist by Eliot Sykes](https://github.com/eliotsykes/rails-security-checklist) 822 | - [Ruby on Rails Security 17-Item Checklist](https://www.engineyard.com/blog/ruby-on-rails-security-checklist) 823 | - [Rails security best practices](https://github.com/ankane/secure_rails) 824 | - [Awesome Ruby Security resources](https://github.com/pxlpnk/awesome-ruby-security) 825 | - [Awesome Rails Security](https://github.com/edwardqiu/awesome-rails-security) 826 | - [Securing Sensitive Data in Rails](https://ankane.org/sensitive-data-rails) 827 | 828 | ## License 829 | 830 | Released under the [MIT License](https://opensource.org/licenses/MIT). 831 | --------------------------------------------------------------------------------