OWASPCheat Sheet

CS-Ruby_on_Rails Ruby on Rails Cheat Sheet

Official page ↗
Project
OWASP Cheat Sheet Series

Introduction

This *Cheatsheet* intends to provide quick basic Ruby on Rails security tips for developers. It complements, augments or emphasizes points brought up in the Rails security guide from rails core. The Rails framework abstracts developers from quite a bit of tedious work and provides the means to accomplish complex tasks quickly and with ease. New developers, those unfamiliar with the inner-workings of Rails, likely need a basic set of guidelines to secure fundamental aspects of their application. The intended purpose of this doc is to be that guide.

Items

Command Injection Ruby offers a function called "eval" which will dynamically build new Ruby code based on Strings. It also has a number of ways to call system commands. ``` ruby eval("ruby code here") system("os command here") `ls -al /` # (backticks contain os command) exec("os command here") spawn("os command here") open("| os command here") Process.exec("os command here") Process.spawn("os command here") IO.binread("| os command here") IO.binwrite("| os command here", "foo") IO.foreach("| os command here") {} IO.popen("os command here") IO.read("| os command here") IO.readlines("| os command here") IO.write("| os command here", "foo") ``` While the power of these commands is quite useful, extreme care should be taken when using them in a Rails based application. Usually, its just a bad idea. If need be, an allow-list of possible values should be used and any input should be validated as thoroughly as possible. The guides from Rails and OWASP contain further information on command injection. SQL Injection Ruby on Rails is often used with an ORM called ActiveRecord, though it is flexible and can be used with other data sources. Typically very simple Rails applications use methods on the Rails models to query data. Many use cases protect for SQL Injection out of the box. However, it is possible to write code that allows for SQL Injection. ``` ruby name = params[:name] @projects = Project.where("name like '" + name + "'"); ``` The statement is injectable because the name parameter is not escaped. Here is the idiom for building this kind of statement: ``` ruby @projects = Project.where("name like ?", "%#{ActiveRecord::Base.sanitize_sql_like(params[:name])}%") ``` Use caution not to build SQL statements based on user controlled input. A list of more realistic and detailed examples is here: rails-sqli.org. OWASP has extensive information about SQL Injection. Cross-site Scripting (XSS) By default, protection against XSS comes as the default behavior. When string data is shown in views, it is escaped prior to being sent back to the browser. This goes a long way, but there are common cases where developers bypass this protection - for example to enable rich text editing. In the event that you want to pass variables to the front end with tags intact, it is tempting to do the following in your .erb file (ruby markup). ``` ruby # Wrong! Do not do this! # Wrong! Do not do this! # Wrong! Do not do this! ``` Unfortunately, any field that uses `raw`, `html_safe` or similar like this will be a potential XSS target. Note that there are also widespread misunderstandings about `html_safe()`. This writeup describes the underlying SafeBuffer mechanism in detail. Other tags that change the way strings are prepared for output can introduce similar issues. The method `html_safe` of String is somewhat confusingly named. It means that we know for sure the content of the string is safe to include in HTML without escaping. This method itself is un-safe! If you must accept HTML content from users, consider a markup language for rich text in an application (Examples include: Markdown and textile) and disallow HTML tags. This helps ensures that the input accepted doesn't include HTML content that could be malicious. If you cannot restrict your users from entering HTML, consider implementing content security policy to disallow the execution of any JavaScript. And finally, consider using the `#sanitize` method that lets you list allowed tags. Be careful, this method has been shown to be flawed numerous times and will never be a complete solution. User-controlled link destinations require URL validation in addition to HTML escaping: ``` ruby ``` If `@user.website` contains a `javascript:` URL, clicking the generated link can execute script: ``` html Personal Website ``` `link_to` does not enforce a URL-scheme allowlist. Validate personal website URLs to allow only `https` or `http` before rendering them, and retain HTML attribute escaping. See the XSS prevention rules for URL validation and encoding requirements. Do not use `html_safe` to bypass escaping. A restrictive Content Security Policy can additionally block `javascript:` navigation, but it does not replace URL validation. Brakeman scanner helps in finding XSS problems in Rails apps. OWASP provides more general information about XSS in a top level page: Cross-site Scripting (XSS). Sessions By default, Ruby on Rails uses a Cookie based session store. What that means is that unless you change something, the session will not expire on the server. That means that some default applications may be vulnerable to replay attacks. It also means that sensitive information should never be put in the session. The best practice is to use a database based session, which thankfully is very easy with Rails: ``` ruby Project::Application.config.session_store :active_record_store ``` There is an Session Management Cheat Sheet. Authentication As with all sensitive data, start securing your authentication with enabling TLS in your configuration: ``` ruby # config/environments/production.rb # Force all access to the app over SSL, use Strict-Transport-Security, # and use secure cookies config.force_ssl = true ``` Uncomment the line 3 as above in your configuration. Rails 8.0 introduced a built-in authentication generator for basic authentication and password reset functionality. It is a starting point that must be adapted to the application, including implementing its sign-up flow. Libraries such as Devise remain another option. The following examples use the Devise gem. Install it using: ```bash gem 'devise' ``` Then install it to the user model: ```bash rails generate devise:install ``` Next, specify which resources (routes) require authenticated access in routes: ``` ruby Rails.application.routes.draw do authenticate :user do resources :something do # these resource require authentication ... end end devise_for :users # sign-up/-in/out routes root to: 'static#home' # no authentication required end ``` To enforce password complexity, it is possible to use zxcvbn gem. Configure your user model with it: ``` ruby class User "https://www.example_commerce_site.com/checkout", 'our_app_2' => "https://www.example_user_site.com/change_settings" } ``` Will give this URL: `http://www.example.com/redirect?url=our_app_1` Redirection handling code: ``` ruby def redirect url = ACCEPTABLE_URLS["#{params[:url]}"] redirect_to url if url end ``` There is a more general OWASP resource about unvalidated redirects and forwards. Dynamic Render Paths In Rails, controller actions and views can dynamically determine which view or partial to render by calling the `render` method. If user input is used in or for the template name, an attacker could cause the application to render an arbitrary view, such as an administrative page. Care should be taken when using user input to determine which view to render. If possible, avoid any user input in the name or path to the view. Cross Origin Resource Sharing Occasionally, a need arises to share resources with another domain. For example, a file-upload function that sends data via an AJAX request to another domain. In these cases, the same-origin rules followed by web browsers must be sent. Modern browsers, in compliance with HTML5 standards, will allow this to occur but in order to do this; a couple precautions must be taken. When using a nonstandard HTTP construct, such as an atypical Content-Type header, for example, the following applies: The receiving site should list only those domains allowed to make such requests as well as set the `Access-Control-Allow-Origin` header in both the response to the `OPTIONS` request and `POST` request. This is because the OPTIONS request is sent first, in order to determine if the remote or receiving site allows the requesting domain. Next, a second request, a `POST` request, is sent. Once again, the header must be set in order for the transaction to be shown as successful. When standard HTTP constructs are used: *The request is sent and the browser, upon receiving a response, inspects the response headers in order to determine if the response can and should be processed.* Allowlist in Rails: Gemfile: ```bash gem 'rack-cors', :require => 'rack/cors' ``` config/application.rb: ```ruby module Sample class Application ['Origin', 'Accept', 'Content-Type'], :methods => [:post, :get] end end end end ``` Security-related headers To set a header value, simply access the response.headers object as a hash inside your controller (often in a before/after_filter). ```ruby response.headers['X-header-name'] = 'value' ``` Rails provides the `default_headers` functionality that will automatically apply the values supplied. This works for most headers in almost all cases. ```ruby ActionDispatch::Response.default_headers = { 'X-Frame-Options' => 'SAMEORIGIN', 'X-Content-Type-Options' => 'nosniff', 'X-XSS-Protection' => '0' } ``` Strict transport security is a special case, it is set in an environment file (e.g. `production.rb`) ```ruby config.force_ssl = true ``` For those not on the edge, there is a library (secure_headers) for the same behavior with content security policy abstraction provided. It will automatically apply logic based on the user agent to produce a concise set of headers. Business Logic Bugs Any application in any technology can contain business logic errors that result in security bugs. Business logic bugs are difficult to impossible to detect using automated tools. The best ways to prevent business logic security bugs are to do code review, pair program and write unit tests. Attack Surface Generally speaking, Rails avoids open redirect and path traversal types of vulnerabilities because of its /config/routes.rb file which dictates what URLs should be accessible and handled by which controllers. The routes file is a great place to look when thinking about the scope of the attack surface. An example might be as follows: ```ruby # this is an example of what NOT to do match ':controller(/:action(/:id(.:format)))' ``` In this case, this route allows any public method on any controller to be called as an action. As a developer, you want to make sure that users can only reach the controller methods intended and in the way intended. Sensitive Files Many Ruby on Rails apps are open source and hosted on publicly available source code repositories. Whether that is the case or the code is committed to a corporate source control system, there are certain files that should be either excluded or carefully managed. ```text /config/database.yml                 -  May contain production credentials. /config/initializers/secret_token.rb -  Contains a secret used to hash session cookie. /db/seeds.rb                         -  May contain seed data including bootstrap admin user. /db/development.sqlite3              -  May contain real data. ``` Encryption Rails uses OS encryption. Generally speaking, it is always a bad idea to write your own encryption. Devise by default uses bcrypt for password hashing, which is an appropriate solution. Typically, the following config causes the 10 stretches for production: `/config/initializers/devise.rb` ```ruby config.stretches = Rails.env.test? ? 1 : 10 ```

Updating Rails and Having a Process for Updating Dependencies

In early 2013, a number of critical vulnerabilities were identified in the Rails Framework. Organizations that had fallen behind current versions had more trouble updating and harder decisions along the way, including patching the source code for the framework itself. An additional concern with Ruby applications in general is that most libraries (gems) are not signed by their authors. It is literally impossible to build a Rails based project with libraries that come from trusted sources. One good practice might be to audit the gems you are using. In general, it is important to have a process for updating dependencies. An example process might define three mechanisms for triggering an update of response: • Every month/quarter dependencies in general are updated. • Every week important security vulnerabilities are taken into account and potentially trigger an update. • In EXCEPTIONAL conditions, emergency updates may need to be applied.

Tools

Use brakeman, an open source code analysis tool for Rails applications, to identify many potential issues. It will not necessarily produce comprehensive security findings, but it can find easily exposed issues. A great way to see potential issues in Rails is to review the brakeman documentation of warning types. A newer alternative is bearer, an open source code security and privacy analysis tool for both Ruby and JavaScript/TypeScript code, in order to identify a broad range of OWASP Top 10 potential issues. It provides many configuration options and can easily integrate into your CI/CD pipeline. There are emerging tools that can be used to track security issues in dependency sets, like automated scanning from GitHub and GitLab. Another area of tooling is the security testing tool Gauntlt which is built on cucumber and uses gherkin syntax to define attack files. Launched in May 2013 and very similar to brakeman scanner, the dawnscanner rubygem is a static analyzer for security issues that work with Rails, Sinatra and Padrino web applications. Version 1.6.6 has more than 235 ruby specific CVE security checks.

References