Rails 8.0 adds Brakeman

railsFebruary 13, 2024Dotby Alkesh Ghorpade

Rails 8 adds Brakeman by default to new apps. Brakeman acts as a security shield for your Rails projects, proactively identifying and preventing common vulnerabilities before they reach production.

What is Brakeman?

Brakeman is an open-source static analysis security tool for Ruby on Rails applications. Developed by Justin Collins, Brakeman scans your Rails codebase to detect potential security vulnerabilities, including common issues as follows:

  • SQL injection

    Here's an example of a potential SQL injection vulnerability in Rails that Brakeman might detect:

    user_id = params[:user_id]
    user = User.find_by(id: user_id)
    if user
      sql = "SELECT * FROM posts WHERE user_id = '#{user_id}'"
      posts = Post.connection.execute(sql)
      # Process posts...
    end

    This code retrieves a user ID from the params hash and attempts to find one with that ID. It then directly constructs a SQL string using string interpolation (#{user_id}) without proper sanitization. An attacker could inject malicious code into the user_id parameter, potentially manipulating the resulting SQL query and gaining unauthorized access to data or performing unintended actions.

    Brakeman would likely flag this code snippet and warn about the potential for SQL injection due to the unsanitized user input being directly used in the SQL query.

    You can fix the issue in several ways:

    • Using prepared statements:

      sql = "SELECT * FROM posts WHERE user_id = ?"
      posts = Post.connection.execute(sql, user_id)
    • Using ActiveRecord query methods:

      posts = user.posts
    • Sanitizing user input:

      user_id = sanitize_sql(params[:user_id])

      These solutions prevent malicious code from being injected into the SQL query, ensuring data integrity and application security.

  • Cross-Site Scripting (XSS)

    Brakeman identifies potential XSS vulnerabilities by examining user input within views, including parameters, cookies, and model attributes. It also scrutinizes dangerous methods like link_to to gauge the risk level accurately.

    @comment = Comment.new(content: params[:content])
    @comment.save
    
    # Displaying the comment on the view:
    <%= params[:content] %>

    This code creates a new comment object from user-provided content (params[:content]) and saves it to the database. Then, An attacker could submit a malicious script embedded in the content parameter, which would then be executed in the user's browser when the page renders, potentially stealing data, redirecting them to malicious sites, or performing other harmful actions.

    Brakeman would likely flag this code snippet and warn about the potential for XSS due to the unsanitized user input being directly rendered with html_safe.

    To fix this vulnerability, you should escape the user input before rendering it in the view. You can use the h (or html_escape) method to achieve this in Rails. Here's how you can fix the vulnerability:

    <%= h(params[:content]) %>

    Alternatively, you can also use Rails' sanitize helper method, which not only escapes HTML special characters but also allows you to specify which HTML tags and attributes are allowed.

  • Mass Assignment Vulnerabilities:

    Brakeman helps prevent common pitfalls like directly assigning user input to models, which can lead to unauthorized database changes.

    Imagine a User model with attributes like name, email, and admin. Let's say you update a user's details using this code:

    class UsersController < ApplicationController
      def update
        # Vulnerable code
        user = User.find(params[:id])
        user.update(params[:user])
      end  
    end

    Rails suggests using strong_parameters to whitelist only safe attributes for explicit updates.

    class UsersController < ApplicationController
      def update
        # Vulnerable code
        user = User.find(params[:id])
        user.update(user_params)
      end  
    end
    
    private
    
    def user_params
      params.require(:user).permit(:first_name, :last_name, :email)
    end
  • Dangerous Evaluation:

    Brakeman flags instances where you use methods like eval, instance_eval, class_eval, or module_eval with user input, which is considered a "Dangerous Evaluation" warning. These methods allow the execution of arbitrary code based on the provided input, opening a significant security hole.

    # using eval in view file
    <%= eval(params[:code]) %>
    
    # using user input in eval
    user_input = params[:search_query]
    search_results = eval(user_input)

    In this example, user input from params[:search_query] is directly used within an eval call. This means any malicious code injected by an attacker into the search_query would be executed, potentially leading to:

    • Remote Code Execution (RCE): The attacker could gain control of your server.

    • Data Theft: Sensitive information could be accessed or exfiltrated.

    • Denial-of-Service (DoS): An attacker could crash your application or overload resources.

  • Denial Of Service (DoS):

    Brakeman can identify several potential DoS (Denial of Service) vulnerabilities in Rails applications. Here are a few examples:

    • Regex DoS:

      user_input = params[:search_query]
      results = User.where(name: /#{user_input}/).all

      This code uses user input directly in a regular expression (/#{user_input}/). If an attacker provides a complex or crafted input, it could create a computationally expensive regular expression to match, potentially slowing down or crashing the server due to excessive resource usage.

    • String DoS (before Ruby 2.2):

      user_input = params[:username]
      symbol = user_input.to_sym

      Before Ruby 2.2, symbols weren't automatically garbage collected. Converting large amounts of user input to symbols could create memory leaks and lead to DoS attacks.

    • Unbounded loops with user input:

      user_input = params[:count].to_i
      
      user_input.times do
      # Perform some action
      end

      If an attacker provides a tremendous value for the count, the loop could run for an extended period, consuming excessive resources and potentially leading to DoS.

  • Session Manipulation:

    Brakeman flagged a vulnerability where the session data is manipulated directly by user input, which can lead to session fixation or hijacking attacks. For instance, consider the following vulnerable code snippet in a controller:

    class SessionsController < ApplicationController
      def create
        session[:user_id] = params[:user_id]
        redirect_to root_path
      end
    end

    In this snippet, the create action sets the :user_id session variable directly to the value provided in the params[:user_id]. This presents a security risk, allowing attackers to manipulate the session data and potentially impersonate other users.

    To fix this vulnerability, you should avoid directly setting sensitive session variables based on untrusted user input. Instead, use a more secure mechanism for managing user sessions, such as session cookies or encrypted tokens.

    Here's how you can fix the vulnerability by using session cookies:

    class SessionsController < ApplicationController
      def create
        user = User.find_by(id: params[:user_id])
        if user
          session[:user_id] = user.id
          redirect_to root_path
        else
          flash[:error] = "Invalid user ID"
          redirect_to login_path
        end
      end
    end

    In this updated code, we first retrieve the user record based on the params[:user_id]. If a user with the specified ID exists, we set the :user_id session variable to the user's ID. Otherwise, we display an error message and redirect the user to the login page.

Installing Brakeman

Brakeman is easy to install and use. You can add it to your Rails project as a gem:

gem "brakeman"

Execute the bundle install and run the command below.

brakeman

I created a sample demo repository and executed the above command. The brakeman command produced the below output.

== Brakeman Report ==

Application Path: /Users/alkesh/opensource/demo
Rails Version: 7.1.2
Brakeman Version: 6.1.2
Scan Date: 2024-02-11 19:40:17 +0530
Duration: 2.125906 seconds
Checks Run: BasicAuth, BasicAuthTimingAttack, CSRFTokenForgeryCVE, ContentTag, CookieSerialization, CreateWith, CrossSiteScripting, DefaultRoutes, Deserialize, DetailedExceptions, DigestDoS, DynamicFinders, EOLRails, EOLRuby, EscapeFunction, Evaluation, Execute, FileAccess, FileDisclosure, FilterSkipping, ForgerySetting, HeaderDoS, I18nXSS, JRubyXML, JSONEncoding, JSONEntityEscape, JSONParsing, LinkTo, LinkToHref, MailTo, MassAssignment, MimeTypeDoS, ModelAttrAccessible, ModelAttributes, ModelSerialize, NestedAttributes, NestedAttributesBypass, NumberToCurrency, PageCachingCVE, Pathname, PermitAttributes, QuoteTableName, Ransack, Redirect, RegexDoS, Render, RenderDoS, RenderInline, ResponseSplitting, RouteDoS, SQL, SQLCVEs, SSLVerify, SafeBufferManipulation, SanitizeConfigCve, SanitizeMethods, SelectTag, SelectVulnerability, Send, SendFile, SessionManipulation, SessionSettings, SimpleFormat, SingleQuotes, SkipBeforeFilter, SprocketsPathTraversal, StripTags, SymbolDoSCVE, TemplateInjection, TranslateBug, UnsafeReflection, UnsafeReflectionMethods, ValidationRegex, VerbConfusion, WeakRSAKey, WithoutProtection, XMLDoS, YAMLParsing

== Overview ==

Controllers: 1
Models: 4
Templates: 2
Errors: 0
Security Warnings: 6

== Warning Types ==

Command Injection: 6

== Warnings ==

Confidence: Medium
Category: Command Injection
Check: Execute
Message: Possible command injection
Code: system("sudo -k -p \"#{(("\n\n" + "        Your user account isn't allowed to install to the system RubyGems.\n        You can cancel this installation and run:\n\n            bundle config set --local path 'vendor/bundle'\n            bundle install\n\n        to install the gems into ./vendor/bundle/, or you can enter your password\n        and install the bundled gems to RubyGems using sudo.\n\n        Password:\n".gsub(/^ {6}/, "").strip) + " ")}\" true")
File: gems/bundler-2.3.26/lib/bundler.rb
Line: 556

Confidence: Medium
Category: Command Injection
Check: Execute
Message: Possible command injection
Code: `sudo -p "#{(("\n\n" + "        Your user account isn't allowed to install to the system RubyGems.\n        You can cancel this installation and run:\n\n            bundle config set --local path 'vendor/bundle'\n            bundle install\n\n        to install the gems into ./vendor/bundle/, or you can enter your password\n        and install the bundled gems to RubyGems using sudo.\n\n        Password:\n".gsub(/^ {6}/, "").strip) + " ")}" #{str}`
File: gems/bundler-2.3.26/lib/bundler.rb
Line: 562

Confidence: Medium
Category: Command Injection
Check: Execute
Message: Possible command injection
Code: Kernel.exec("man #{Hash[Dir.glob(File.join(File.expand_path("man", __dir__), "**", "*")).grep(/.*\.\d*\Z/).collect do  [File.basename(f, ".*"), f]  end][("gemfile" or "bundle")]}")
File: gems/bundler-2.3.26/lib/bundler/cli.rb
Line: 128

Confidence: Medium
Category: Command Injection
Check: Execute
Message: Possible command injection
Code: Kernel.exec(Bundler.which("bundler-#{("gemfile" or nil)}"), "--help")
File: gems/bundler-2.3.26/lib/bundler/cli.rb
Line: 133

Confidence: Medium
Category: Command Injection
Check: Execute
Message: Possible command injection
Code: Kernel.exec(Bundler.which("bundler-#{command}"), *ARGV[(1..-1)])
File: gems/bundler-2.3.26/lib/bundler/cli.rb

You can refer to the brakeman gem to know more about Brakeman and its usage.

Configure Brakeman

By default, the brakeman command evaluates all the current directory files. Brakeman command options can be stored and read from YAML files.

Use -C to export current options and streamline config file creation.

$ brakeman -C --skip-files specs/

---
:skip_files:
- specs/

Options passed when running the program (command line) take priority over options stored in a separate file (configuration file).

You can create a brakeman.yml file under the config directory. To specify the configuration, you must use the -c option when running the brakeman command.

Integrate into CI/CD Pipeline:

To ensure ongoing security compliance, consider integrating Brakeman into your continuous integration and continuous deployment (CI/CD) pipeline. By automating security scanning as part of your development workflow, you can proactively identify and address vulnerabilities with each code change.

Why Brakeman Matters:

  • Early Detection of Vulnerabilities:

    By integrating Brakeman into your development process, you can catch security vulnerabilities early in the development lifecycle, minimizing the risk of deploying insecure code into production.

  • Comprehensive Security Analysis:

    Brakeman thoroughly analyses your Rails codebase, scanning controllers, models, views, and routes to identify potential vulnerabilities across your application stack.

  • Customizable Configuration:

    Brakeman offers a range of configuration options, allowing you to tailor the scanning process to your specific application requirements. Whether you're working on a small-scale project or an extensive enterprise application, Brakeman can be customized to suit your needs.

  • Active Community Support:

    With an active community of developers and security professionals, Brakeman receives regular updates and improvements, ensuring it stays up-to-date with the latest security best practices and emerging threats.

Conclusion:

Rails 8 includes Brakeman by default, is a significant security enhancement. Developers wouldn't need to separately add and configure Brakeman in their projects. This integration could lead to better security practices by encouraging developers to regularly scan their applications for potential vulnerabilities during development.

To know more about this feature, please refer to this PR.

Closing Remark

Could your team use some help with topics like this and others covered by ShakaCode's blog and open source? We specialize in optimizing Rails applications, especially those with advanced JavaScript frontends, like React. We can also help you optimize your CI processes with lower costs and faster, more reliable tests. Scraping web data and lowering infrastructure costs are two other areas of specialization. Feel free to reach out to ShakaCode's CEO, Justin Gordon, at justin@shakacode.com or schedule an appointment to discuss how ShakaCode can help your project!
Are you looking for a software development partner who can
develop modern, high-performance web apps and sites?
See what we've doneArrow right