|
| 1 | +# Application Security |
| 2 | + |
| 3 | +Application security is a top priority for technology application development, which is why the Rails framework's security helper methods and countermeasures help speed up secure application delivery. However, the framework isn't useful by itself; its helper methods and configurations only work if they are used properly. |
| 4 | + |
| 5 | +Each item below will be checked if it is already implemented by default, or unchecked if it is not implemented by default. This is meant to be a living document and should be updated as additional security tools and configurations are implemented, as well as when new vulnerabilities are discovered or introduced. |
| 6 | + |
| 7 | +This document uses the Rails Guide to [Securing Rails Applications](https://guides.rubyonrails.org/security.html) to audit this project's Rails security best practices. |
| 8 | + |
| 9 | +## Sessions |
| 10 | +We use Devise to manage sessions and cookies. Devise configuration is managed in the [`devise.rb`](app-rails/config/initializers/devise.rb) file. For more detailed information, see the [Devise documentation](https://rubydoc.info/github/heartcombo/devise). |
| 11 | +- [x] SSL (`config.force_ssl = true`) is enforced in production environments. |
| 12 | +- [x] Provide the user with a prominent logout button to make it easy to clear the session on public computers. |
| 13 | +- [x] Cookies stored client side do not contain sensitive information. |
| 14 | +- [x] Cookies time out in 15 minutes of inactivity |
| 15 | + - Note: That is set with `config.timeout_in` in the [Devise configuration file](app-rails/config/initializers/devise.rb). |
| 16 | +- [x] Cookies are encrypted client side. |
| 17 | + - Note: Devise uses BCrypt and the secret_key_base by default for secret hashing. |
| 18 | +- [ ] Expire sessions after a set amount of time, regardless of activity, |
| 19 | + - Note: Automated session expiration can be easily set by the auth service, such as in AWS Cognito. |
| 20 | +- [ ] Use a nonce generator to protect against cookie replay attacks. |
| 21 | + - Note: The commented out code for this is located in [`/app-rails/config/initializers/content_security_policy.rb`](/app-rails/config/initializers/content_security_policy.rb) Review the impact this may have if there are several application servers. |
| 22 | +- [x] Automatically expire sessions on sign in and sign out. |
| 23 | + - Note. This is set in the [Devise configuration file](app-rails/config/initializers/devise.rb) with `config.expire_all_remember_me_on_sign_out = true`. |
| 24 | + |
| 25 | +## Cross-Site Request Forgery (CSRF) |
| 26 | +- [x] GET, POST, DELETE, and rails’ resources are used appropriately in the `routes.rb` file. |
| 27 | +- [x] Pass a CSRF token to the client. |
| 28 | + - Note: This is accomplished with `<%= csrf_meta_tags %>` in [application.html.erb](app-rails/app/views/layouts/application.html.erb) |
| 29 | +- [ ] Set forgery protection in production |
| 30 | + |
| 31 | +## Redirection and Files |
| 32 | +There is currently no file upload or download functionality at this time, so please review these items when adding file management functionality. |
| 33 | +- [x] Do not use user inputs to generate routes (ie. creating a route with the username), which is vulnerable to XSS attacks. |
| 34 | + - [x] `link_to` methods do not interpolate to user inputs. |
| 35 | + - [x] `redirect_to` methods do not interpolate to user inputs. |
| 36 | +- [ ] Prevent files from being uploaded if the filename do not match a set of permitted characters. |
| 37 | + - Note: Filtering on filename on its own can still leave an application vulnerable to XSS attacks. |
| 38 | +- [ ] Do not allow file uploads to place files in the public directory as code in those files may be executed by the browser. |
| 39 | +- [ ] Prevent users from downloading files to which they shouldn't have access. |
| 40 | + - [ ] Prevent files from being downloaded if the filename do not match a set of permitted characters. |
| 41 | + - [ ] For website search, prevent including files in the search results if the file is not from an appropriate directory. |
| 42 | + |
| 43 | +## User Management |
| 44 | +- [x] Store only cryptographically hashed passwords, not plain-text passwords. |
| 45 | +- [x] Consider Rails' built-in `has_secure_password` method which supports secure password hashing, confirmation, and recovery mechanisms. |
| 46 | + - Note: When using Devise there's no need to use `has_secure_password`. |
| 47 | +- [x] Username error is generic and does not indicate whether it was an error with the username or password. |
| 48 | +- [x] Forgot password confirms the email was sent, and not whether the username exists. |
| 49 | +- [x] Use a secondary verification when users change their password |
| 50 | + - Note: Change password requires 6 digit code from email sent to user's email address. |
| 51 | +- [ ] Require user's password when changing email. |
| 52 | +- [ ] Include honeypot fields and logic on Non logged in forms to catch bots that spam all fields (good resource: https://nedbatchelder.com/text/stopbots.html). |
| 53 | +- [ ] Consider using Captcha on account creation, login, change password, and change email forms. |
| 54 | + - Note: Captchas are often not accessible to screen readers and their use should be part of a UX discussion. |
| 55 | +- [x] Filter log entries so they do not include passwords or secrets |
| 56 | + - Note: Log filtering is set in [filter_parameter_logging.rb](app-rails/config/initializers/filter_parameter_logging.rb): `:passw, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn`. |
| 57 | +- [x] Use the correct Ruby REGEX: `\A` and `\z` and not the more common: `/^` and `$/`. |
| 58 | +- [ ] Add `multiline: true` to regex `format:` in validations. |
| 59 | +- [x] When searching for data belonging to the user, search using Active Record from the user and not from the target data object. ie. Instead of doing: `@task = Task.find(params[:id])`, instead do: `@user.tasks.find(params[:id])`. |
| 60 | + - Note: This application is also using [pundit](https://github.com/varvet/pundit) to support resource authorization. |
| 61 | + |
| 62 | +## Injection |
| 63 | +- [ ] When defining security related `before_action` and `after_action` on controllers, use `except: […]` instead of `only:[…]` Ie. Instead of `after_action :verify_policy_scoped, only: :index` use `after_action :verify_policy_scoped, except:[ :rails_health_check, :users, :dev, ...]`. This ensures that when new views are added, they're behind the security actions by default. |
| 64 | +- [x] Don't interpolate params into SQL fragments. Ie. `.where(“name ='#{params[:name]}'”`. |
| 65 | +- [x] Ensures all cookies are `httponly`. |
| 66 | + - Note: While we’re not setting `secure: true` on the cookies themselves, the `config.force_ssl = true` option in production sets them as `httponly`. |
| 67 | +- [x] Sanitize content in the erb files that come from user inputs, using `<%=h <some user provided input> =>` to protect against defacement. |
| 68 | +- [x] Use a permitted list of tags in inputs that allow html or when allowing a text input that will be converted into html, using: |
| 69 | + - Note: The most common Rails tool for text to html conversion is RedCloth. |
| 70 | + ``` |
| 71 | + tags = %w(a acronym b strong i em li ul ol h1 h2 h3 h4 h5 h6 blockquote br cite sub sup ins p) |
| 72 | + s = sanitize(user_input, tags: tags, attributes: %w(href title)) |
| 73 | + ``` |
| 74 | +- [x] Rails `sanitize()` method is used on inputs that will be presented to the UI, including the Admin UI if there is one. |
| 75 | + - Note: While consensus seems mixed about the necessity to sanitize Rails input fields for defacement, sanitizing inputs is very useful to protect against encoding injection. |
| 76 | +- [ ] Inputs for custom colors or CSS filters are sanitized with Rail's `sanitize()` method, and the application builds the CSS in the web application first and ensures it is valid CSS before sanitizing. |
| 77 | + - Note: We don't include that functionality, but this is a common attack vector in applications that do. |
| 78 | +- [x] Controllers that output strings, rather than views, are escaped. |
| 79 | +- [x] All methods called by the application to execute commands on the underlying operating system include the `parameters` parameter, ie. `system(command, parameters)`. Applicable methods include: |
| 80 | + * `system()` |
| 81 | + * `exec()` |
| 82 | + * `spawn()` |
| 83 | + * `command` |
| 84 | +- [x] Don't use the `open()` method to access files, instead use `File.open()` or `IO.open()` that will not execute commands. |
| 85 | +- [ ] [`ActionDispatch::HostAuthorization`](https://guides.rubyonrails.org/configuring.html#actiondispatch-hostauthorization) is configured in production to prevent DNS rebinding attacks. |
| 86 | +
|
| 87 | +## Unsafe Query Generation |
| 88 | +- [x] Confirm `deep_munge` hasn't been disabled. |
| 89 | + - Note: `config.action_dispatch.perform_deep_munge` is `true` by default. |
| 90 | +
|
| 91 | +## HTTP Security Headers |
| 92 | +Default security headers can be overridden in [application.rb](app-rails/config/application.rb) with `config.action_dispatch.default_headers`. |
| 93 | +- [x] Lock down X-Frame-Options to be as restrictive as possible. |
| 94 | + - Note: By default this is set to allow iframes from the same origin. |
| 95 | + - Note: Set this to deny if not using any iframes to embed. |
| 96 | + - Note: If you need to permit an iframe from another origin, a controller can then use an `after_action :allow_some_service`. |
| 97 | +- [ ] To help protect against XSS and injection attacks, define a Content-Security-Policy in the provided [content security policy file](app-rails/config/initializers/content_security_policy.rb). |
| 98 | + - Note: Set the policy to be more restrictive than you need and you can override defaults when necessary in the controllers. |
| 99 | +- [ ] Log content security policy violations to continuously improve security by setting the report_uri config on the content security policy and configure a controller to log the reports. |
| 100 | + - Note: `report_uri` is being deprecated, and will eventually become `report_to`. |
| 101 | +- [x] Set `csp_meta_tag` in [application.rb](app-rails/config/application.rb). |
| 102 | +- [ ] Use the `csp_meta_tag` tag with nonce generation in the content security policy. |
| 103 | +- [ ] Configure which browser features are allowed in [permissions_policy.rb](app-rails/config/initializers/permissions_policy.rb). |
| 104 | + - Note: This policy can be more restrictive than necessary because features can be allowed on specific controllers. |
| 105 | +- [ ] If opening up endpoints as APIs, configure CORS, by installing and configuring the `rack-cors` gem. |
| 106 | +
|
| 107 | +## Intranet and Admin Security |
| 108 | +If adding an Admin view, consider adding the following: |
| 109 | +- [ ] Sanitize all user inputs as they may be viewed here even if they aren't visible anywhere else in the application. |
| 110 | +Some effective admin protection strategies include: |
| 111 | + - [ ] Limit admin role privileges using the principle of least privilege |
| 112 | + - [ ] Geofence admin login IP to the USA. |
| 113 | + - [ ] Consider putting the admin app at a subdomain so the cookie for application can't be used for the admin console and vice-versa. |
| 114 | +
|
| 115 | +## Environmental Security |
| 116 | +- [x] Secrets are not stored in the application repository. |
| 117 | +
|
| 118 | +## Dependency Management and CVEs' |
| 119 | +- [x] Use a service to be notified when libraries are outdated |
| 120 | + - Note: We're using dependabot to notify us if we have outdated gems. |
| 121 | +
|
| 122 | +## Additional Reading |
| 123 | +* [Securing Rails Applications](https://guides.rubyonrails.org/security.html) |
0 commit comments