Rails 8.0 adds Brakeman
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 theuser_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
(orhtml_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 likename
,email
, andadmin
. 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
, ormodule_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 aneval
call. This means any malicious code injected by an attacker into thesearch_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 theparams[: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.