Ruby on Rails Web Application Vulnerabilities: How to Make Your App Secure

  • 141116 views
  • 25 min
  • Jul 13, 2019
Gleb B.

Gleb B.

Copywriter

Vlad V.

Vlad V.

Chief Executive Officer

Share

Security is a major concern for any company involved in the development of web applications. Every enterprise wants to create secure web applications and websites. Though no code can be 100% bug-free, development teams should do their best to make their applications as unassailable as possible. Detecting vulnerabilities should not be a mere afterthought, since breaches and hacks might result in considerable losses – both financial and to a company’s reputation. Code security must be taken into account right from the start of application development.

Open-source software development frameworks, such as Ruby on Rails, are considered highly secure, and this is often quite true. Rails (particularly its latest versions, starting from 4.0) offers a number of built-in tools for fending off the vast majority of threats. However, forewarned is forearmed: developers should fully understand what kinds of vulnerabilities a Rails application might have and the best ways to prevent them from being exploited.

We've carried out in-depth research of the most common web application security breaches to get you ready to build fantastic and hack-proof applications!

Authentication

In a nutshell, authentication is the way to determine a user's identity and, thus, to prevent unauthorized access to an application. There are several major points you should consider with regard to the security of your authentication system: where and how your app implements authentication, how login credentials are secured, and whether login credentials are sent through secure channels. If your application has some breaches in the authentication system, it might be prone to password cracking, identity spoofing, unauthorized access, or other attacks.

In general, most applications use a password mechanism of authentication, which means that users supply their user id's and passwords in an HTML form. But this is where problems might arise if your app uses a weak authentication mechanism. For example, an attacker might steal credentials, hijack accounts, take over cookies, or even hack your database where all your sensitive data is stored.

Rails doesn't come with a built-in authentication mechanism. However, developers can use a number of useful gems that provide all necessary authentication functions.

Instant and Effective Solutions

Devise

Devise is one of the most popular authentication solutions for Ruby on Rails. The gem comprises ten different modules that collectively deliver the full scope of authentication-related functions.

Authlogic

Authlogic is another popular, simple, and unobtrusive Rails authentication plugin.

Secure Authentication Tips

Use Strong Passwords

Strong passwords are a must for preventing unauthorized access to your applications. It may sound strange, but the majority of passwords used by millions of people are rather simple to guess. Short and easy-to-remember passwords are easy for attackers to guess, and hence make it easy to steal an account. This is why you should follow simple rules for creating strong passwords. A good password should contain at least seven different characters (uppercase letters, lowercase letters, numbers). If your app requires users to provide strong passwords, the chance of their accounts being hacked will be minimal.

Require Email Confirmation

It's important to confirm email addresses of users after they create an account with your application. Once again, the Devise gem will be helpful here: the Confirmable module automatically sends emails with confirmation instructions to users.

Implement a Secure Password Change and Recovery System

It's important to ask users to input their old passwords before they set new ones. You should be sure that the person requesting a password change is the actual account owner. After a password has been changed, a notification email should be sent to the account owner, informing them about the latest changes to their account.

The same procedure should be used for password recovery. To prevent hacking, a password should always be changed instead of recovered. A user should be sent a password recovery message by email, containing a link to follow to input a new password. Once again, the Devise gem will be useful here as it contains a Recoverable module for resetting passwords.

Require Passwords for Email Address Changes

Changing the email address associated with an account is one more way for an attacker to hijack an account. The best solution to this problem is to require the password for all change of email address requests.

Use SSL Encryption

All user credentials should be encrypted with SSL. Keep in mind that SSL secures the data being transferred, but your app might still be vulnerable if an attacker manages to transfer malicious code via SSL. Also, don't forget to use a POST-request since a GET-request places user parameters directly in the URL.

Implement Logging

You should log all authentication-related actions, such as logins, logouts, password changes, failed login attempts, and so on. Remember that all sensitive data that you log should be encrypted. The Devise gem has a special Trackable module that logs sign-ins, timestamps, and IP addresses.

Use CAPTCHAs

CAPTCHAs can help protect your application against automatic spam bots. In other words, a CAPTCHA will determine whether a response is generated by a computer or a human. Most CAPTCHAs ask users to type letters they see in a distorted image (these are called positive CAPTCHAs), but there are also negative CAPTCHAs. Whereas a positive CAPTCHA aims to check whether the response comes from a human, a negative one tracks down robots. Though CAPTCHAs are rather annoying for the majority of users, consider adding them to your application. You can also consider the smart reCAPTHA service from Google that won't annoy your users, as it's actually invisible to human users, so they won't even notice the service. ReCAPTCHA requires only bots or suspicious users go through the tedious verification process. There is a reCAPTCHA plug-in available for Ruby on Rails.

Major Web Application Authentication Threats

There are many types of authentication attacks, so let's quickly go over the most common.

Brute-Forcing Accounts

An attacker (or a malicious program) can simply try guessing a user’s login credentials. This simple hacking technique is called a brute-force attack, and works like this: an attacker tries to supply an email address (which is usually the login) and password repeatedly in order to find the right combination. People's emails are rather easy to get, while passwords are most often simple to guess.

Very often, applications reveal some sensitive information even in the case of failed authentication. For example, if an email address is submitted that isn't found in your app's database, the app will likely say something like "The email address {...} does not exist." The attacker will then be able to change the combination and do some online research to find an email that does exist in the system. Once a valid email address has been found, an attacker will then be able to either attempt guessing the password or try recovering it via the forgot password page.

To prevent brute-forcing attacks, enable a CAPTCHA after several unsuccessful authentication attempts. For even better web application protection, consider blocking accounts with multiple failed login attempts. In this case, the affected user will receive an email message with detailed instructions on what to do next to unblock the account. Finally, instead of revealing information about a user’s credentials, simply display a generic error message.

Account Hijacking

Brute-forcing isn't the only way to hijack an account. An attacker may alternatively steal a session cookie of a victim and in this way attempt to modify account credentials. The best way to fend off this type of attack is to protect the forgot password page from cross-site request forgery (CSRF) and require the password prior to changing any sensitive information.

An attacker may take over a victim's account using some sophisticated hacking methods, such as XSS or CSRF. Protecting your application against these attacks is highly recommended, and we will tell more about these particular vulnerabilities a bit later.

Authorization and Access Control

Unlike authentication, which checks whether users are who they claim to be, authorization determines what actions an authenticated user has permission to perform within your application. Authorization vulnerabilities may lead to data tampering, disclosure of sensitive information, and abuse of privileges. Remember that you need to ensure the security of both frontend and backend data and systems in your app.

There are several important points to consider when designing your app with regard to authorization. First of all, you should use multiple gatekeepers, as a single one might fail. Secondly, your application should be properly and securely authorized in the database. Thirdly, access to system-level resources should be restricted.

Comprehensive Security Solutions

CanCanCan

The CanCanCan authorization library for Ruby 2.0+ and Rails 3.0+ is a helpful tool to manage user access and assign user privileges. This gem will help you choose what resources a user has access to.

Pundit

Pundit is another popular authorization gem used by many Ruby developers. This library uses simple Ruby objects to manage access rules. Pundit is usually chosen for building large applications as the Pundit code is skinny and quite easy to read.

Authorization-Related Tips for Developers

Design your app in compliance with the so-called "least privilege" model, which says that a user should not be able to edit, add, or delete any data in the critical database unless he or she is specifically assigned to do so.

Do not leave any loopholes in access rights. Your applications users must not be able to access unauthorized pages (by simply entering the URL, for instance).

Thoroughly test your application for authorization vulnerabilities. You must be sure that no unauthorized access is possible.

Session Management

How does an application know that a request comes from the same user who has just logged in? Thanks to the session. Rails automatically creates a new session (generating a random session id) when a new user accesses your application. This session id is sent from the server app to the client and vice versa in a cookie.

Session management is an application-level responsibility, and you should provide the security of sessions in your application. There are four major attacks aimed at session ids: fixation, prediction, brute-forcing, and interception.

Application-Level Security Measures

Session management should never be neglected. There are several main precautions you should take to make your application secure in this respect:

  • Never pass authentication cookies via HTTP connections. Instead, force your app to use secure SSL connections.
  • Encrypt cookies even if an SSL connection is used. This way, an attacker will not be able to view or modify cookies even if they are intercepted, and the user will not be able to read and edit cookies in the browser.
  • Reduce the lifetime of sessions. The less time an attacker has, the more difficult capturing a session cookie is.

Session-Related Attacks

Brute-Force

As of today, brute-forcing a session is impossible in the latest Ruby on Rails versions. Every session id is a 32-byte long hash value that is impossible to guess. However, this kind of attack was possible in the past, when the range of values in a session id was limited.

Session Hijacking

A session hijacking attack is possible when an attacker steals a cookie that contains a session id. An attacker may try sniffing cookies on an insecure network. Once a cookie is stolen, an attacker can access an application on behalf of the victim. To prevent session hijacking, you should use SSL connections for all pages of the website.

Session Fixation

A session fixation attack involves fixing a victim's session id so it is known to the attacker. In short, an attacker creates a valid session in the targeted application and takes the session id from the response. Then, the attacker forces the victim's web browser to use this session id (with the help of XSS or by sending numerous HTTP requests in parallel):

There's a simple way to prevent a session fixation: a new session identifier must be issued after each successful sign-in. This is another place where the Devise gem will be extremely helpful, as it can automatically expire sessions upon sign-in and sign-out.

Replay Attacks for CookieStore Sessions

CookieStore has been the default session data storage since Rails 2. It saves a session data hash directly in a cookie, so a server can retrieve the session data hash without a need for a session id. This makes an application work really fast, but also makes it vulnerable to so-called replay attacks. A typical replay attack works as follows: a malicious user registers on some website that allows them to make purchases; this user has a certain credit (i.e. sum of money) the value of which is stored in the session. At this point, the attacker copies the cookie value, then buys something and receives a new, lower credit value (and consequently a new cookie). Finally, the attacker replaces the new cookie with the old one, restoring the initial credit. This allows a user to make a purchase without actually paying for it.

There's a simple way to prevent replay attacks, however: never store this kind of data in a session. Instead, store credits in the database.

Data and Input Validation

Application developers should be thoughtful about data and input validation to avoid really serious threats, such as SQL injection attacks, XSS, etc. Proper input validation will make your app well-protected against the most dangerous vulnerabilities.

Input validation attacks happen when an attacker succeeds at "injecting" malicious code into certain open parameters in your application. The injection may be into a URL, a header, a cookie, a script, or something else.

Securing Web Applications Against Data Input Threats

  • The best way to prevent data input vulnerabilities is to use Ruby input validation. Set up the server to validate all data against certain criteria. Keep in mind that input validation is the responsibility of a backend developer. You should never rely on client-side validation.
  • There are four major input validation methods you should use: constrain input (decide what is allowed), validate data (length, range, format, and type), reject known bad input, and sanitize input (make potentially malicious data safe).
  • Pay attention to canonicalization (converting data into its simplest form). For example, applications should not accept input file names from users.

What Attacks to Prevent

Cross-Site Scripting (XSS)

Cross-site scripting is one of the most dangerous vulnerabilities in applications, and developers should apply their best efforts to fend off these attacks. XSS happens when a hacker successfully injects malicious code that is saved by the app and later displayed to a victim. The capabilities of XSS are really impressive: such injections can steal cookies, hijack sessions, seize sensitive information, and more.

Here's an example of a typical XSS attack that can steal a cookie:

Surely, the URL in "img src" is non-existent, hence the browser will display nothing. However, the malicious user can now view web server log files to retrieve the victim's cookie.

By default, Rails 3.0 and later versions prevent cross-site scripting attacks, as the string data is escaped in views before being sent to the user's web browser.

Command Injection Flaws

Your application may be vulnerable to command injection attacks if it needs to execute some commands in the underlying operating system. Ruby offers several methods to do execute such commands: exec(command), syscall(command), system(command), and command. These functions require your particular attention since users may be able to enter the whole command or at least part of it. Keep in mind that in most shells, it's possible to execute another command after the first one if they are chained with the help of a vertical bar (|) or a semicolon (;).

To fend off such attacks, you should use system(command, parameters), which provides a safe way to pass command line parameters.

Another potential danger is an ImageMagick command injection. When this software is used, Rails passes command line arguments to an executable. However, the arguments can be modified in order to force ImageMagick to overload the CPU. Eventually, this may lead to a server failure.

To protect your app from this kind of attack, validate user input against regex. You can use the Dragonfly gem to check user arguments.

SQL Injection

SQL injection vulnerabilities allow attackers to avoid authorization and read arbitrary data in a database. Generally, Rails apps are protected against SQL injections, as many operations are sanitized by default. However, some operations might not be sanitized:

Taking advantage of such a vulnerability, an attacker can create the following link:

By doing this, the hacker will be able to see all clients on the website. The final SQL code will be:

There's a simple way to prevent an SQL injection: you should sanitize the variable in order to get the following code:

The final SQL code will look like this:

This is all really time-consuming and tedious, but you can use code-scanning gems, such as Brakeman, to find these and similar problems in your code.

Mass Assignment

Mass assignment vulnerabilities in an application allow a hacker to update numerous fields in a database, even those that aren't supposed to be changed by a common user. For example, let's say you need to modify several attributes at once:

If the "users" table contains the "admin" column, attackers may edit the code in their browsers and, in the end, get admin rights.

To cure this vulnerability, strong parameters were added to the Rails core in version 4.0. Now, a developer can whitelist the attributes that users are allowed to modify.

If your application is based on Rails 3.x, you can make use of the Strong Parameters gem; a similar gem is available for Rails 2.x as well.

Cross-Site Request Forgery (CSRF)

A CSRF attack happens when a hacker forges a malicious link that executes a certain action on the target website on behalf of the victim (who must be logged in). The attack is carried out automatically, hence the victim doesn't even notice it is happening. However, to make CSRF possible, the hacker must place the forged link on some website the victim is likely to visit. As usual, attackers place such links on forums, discussion boards, blogs, and other informational resources.

For example, let's say the victim has signed in to a payment service while browsing a forum. The web browser downloads the page with the forged link. Then, the CSRF attack happens: the victim's money is transferred to the attacker's account.

There are a number of ways to protect your application against CSRF vulnerabilities. First of all, you should clearly understand when GET and POST requests must be used (see the checklist on the W3C website).

GET requests don't require CSRF protection as they don't leak any sensitive information. For non-GET requests, the latest versions of the Ruby on Rails framework have a built-in CSRF token. This token is essentially a random string in a session; when a request comes to your application, Rails compares its token to the token in the session.

To turn on CSRF protection, use the "protect_from_forgery" method in your controllers.

Starting with the Rails 4 framework, the "protect_from_forgery" method takes care of cookies in case of unauthorized requests. If a request is verified, then Rails 4 will execute it. However, if the verification fails, the "handle_unverified_request" method will step in and remove sensitive data (session data, flash information, etc.).

File Uploads

A lot of modern applications allow users to upload files, such as images or text documents. However, a hacker can include a malicious file name that will overwrite a file on server.

Also, an attacker may upload a malicious file that will be automatically executed on the server (.php and .cgi files, for example). This is possible if uploads are placed in the Rails /public directory.

There are several Ruby on Rails gems that will help you manage file uploads properly, including Paperclip, Carrierwave, and Dragonfly.

Redirects and Forwards

Your application may be vulnerable to unvalidated redirects and forwards if it accepts untrusted input. The danger is that such input might contain a malicious URL to which the app will redirect the request. Hackers may take advantage of such vulnerabilities to steal user credentials or to carry out phishing attacks.

Let's have a look at an example of vulnerable code:

If your app doesn't validate the input, an attacker can redirect the users of your application right into a trap:

In many applications, users are allowed to forward requests from one part of the site to another. In this case, the app must check whether the user is authorized to do so. If you neglect this vulnerability, an attacker may craft a URL that will pass the access control of your app, enabling the hacker to perform administrative functions.

There are several ways to make these attacks impossible in your app:

  • Avoid using redirects and forwards.
  • Create a list of trusted URLs to sanitize input.
  • Don't allow URLs as user input. Otherwise, make sure that each supplied value and authorized for each user.
  • Notify users that they are leaving your site and make them confirm the action for all redirects.

Security-Related Headers

When a web page is requested from a web server, the server sends a response that contains HTTP response headers. These headers include content metadata and so-called security headers that are responsible for telling a browser what to do when processing the contents of the website.

Ruby on Rails 4 provides every HTTP response from an application with default security headers, called "default_headers":

Security headers provide an important layer of security as, if properly implemented, they protect for a variety of attacks.

There are six major security headers:

X-Frame-Options:

Used to protect against clickjacking.

The default value in Rails is "SAMEORIGIN," which means framing on the same domain is allowed. There are two more options: "DENY" (no framing at all) and "ALLOWALL" (framing is allowed for the entire site).

Strict-Transport-Security:

This header forces web browsers to use HTTPS connections only, so an insecure HTTP connection won't be established.

Access-Control-Allow-Origin:

Controls which websites can ignore same origin policies.

X-Content-Security-Policy:

This header controls what kinds of content can be loaded from certain websites. The correct configuration of this header allows you to prevent many attacks, such as XSS.

X-Content-Type-Options:

The default value in Rails for this security header is "nosniff," and it fends off attacks based on MIME-type confusion.

X-XSS-Protection:

In Rails, this header is enabled by default (with a value of "1; mode=block"); it enables XSS Auditor, which prevents XSS attacks.

If you wish to switch this protection off, you can change the value to "0".

You can use the SecurityHeaders.io service to check how well your website is protected and what security headers it has.

Earlier versions of Rails didn't have this functionality by default, and thus for older versions the Secure Headers gem should be used. This gem automatically applies several security-related headers.

Business Logic as a Potential Web Security Vulnerability

Any application might contain business logic errors that potentially lead to vulnerabilities. Unfortunately, such errors can't be detected with the help of automated tools. Therefore, you should perform a thorough review of your entire codebase for any errors in business logic.

Error Handling

No matter how proficient a development team is, errors are inevitable. And errors aren't to be neglected. Errors can be caused by a program itself or by a user. But no matter who or what causes them, errors need to be handled appropriately, and there are several major precautions that we must take with regard to error handling:

  • All errors must be logged for further analysis and prevention. The log should contain such information as error codes, user ids, times, dates, and lines. Moreover, error logs need to be encrypted as they are part of the critical information about your application.
  • Be careful with error messages displayed to end-users. Such messages as "Field not found," for example, may be used by a hacker to unravel the structure of your database.
  • Use structured exception handling to minimize the chance that your app will be left in an inconsistent state.
  • Make sure that any action that triggers an error is blocked in order to prevent unauthorized access.

To keep you informed about all errors that have occurred in your application, you can use the Exception Notification gem, which offers a number of built-in notifiers. There are also several special error tracking services available such as Airbrake and Rollbar. These full-stack solutions provide lots of handy tools and features for tracking and analyzing errors.

Logging

As usual, stick to the following rule: the more actions you log, the better. Logging will help you detect unauthorized access attempt and analyze errors and vulnerabilities.

We recommend logging authentication and authorization attempts, administrative activities, and modifications of data. Logs must be encrypted in order to prevent unauthorized access, and backed up for further analysis.

The Rails Semantic Logger gem is a helpful logging system that you can add to your Rails code. Moreover, consider using one of the many log management services. For example, Papertrail can aggregate all logs (app logs, text log files, and syslog) in one place for easy access and analysis. New Relic offers a Ruby application performance solution for monitoring everything in your app, from the user experience to server information.

Don't Keep Sensitive Data in Logs!

Logs may contain sensitive information such as user credentials and credit card numbers. Attackers may acquire logs if they hack the server, and therefore you must tell Rails not to put such data in log files.

This line of code, for example, will exclude passwords from log files:

Web Application and Server Configuration

There are some common and well-known web server vulnerabilities. Therefore, servers require patching and configuration before you install web services on them. You should always review your default server settings, deleting or disabling those services you don't need.

Sensitive Files and Private Tokens

Analyze how your application deals with sensitive data in store, in application memory, and when it's transferred over the network. There are simple precautions you can take in this respect:

  • Avoid storing sensitive data if it's not necessary.
  • Don't put sensitive data in your server code.
  • Either encrypt sensitive data or send it via an encrypted channel to avoid eavesdropping.

Your application is likely to use several private tokens to interact with third-party APIs or for OAuth 2 authentication. These tokens must never be exposed to the public. The best way to secure them is to extract them into environmental variables. Ruby on Rails makes this easy with several gems, such as dotenv-rails.

Rails Updates and Updating Dependencies

Since Ruby on Rails is an open source framework, it's constantly being developed and enhanced. Yet, most gems aren't signed by their authors and, thus, building a project using only trusted libraries is almost impossible, which is why you need to audit and update your gems as well as Rails itself.

Ruby on Rails Web App Security Gems

Fortunately for app developers, Ruby on Rails has a number of helpful security gems that will scan your code and detect common web application threats.

Bundler-audit

This Rails security library checks your app for vulnerable gems in Gemfile.lock.

Brakeman

Brakeman is one of the most popular vulnerability assessment gems, and one that many experienced Ruby on Rails developers use. In short, it's a static code analysis tool that checks the source code. This security gem has seen significant progress over the past few years, and it's truly efficient and widely used by Ruby developers.

Dawnscanner

An efficient code scanning gem that scans plain scripts.

Rack-attack

This gem is a rack middleware tool that protects your application against bad clients.

Hakiri_toolbelt

A command line interface that allows developers to search for vulnerabilities in gems, databases, servers, and other technologies.

Reek

A helpful code smell detector that analyzes Ruby code and pinpoints smells.

Rubocop

Rubocop checks whether your code adhered to the Ruby Style Guide. If your code contains outdated syntax or variable naming, this gem will help you find it and fix it.

Ruby on Rails Application Security Assessment Services

Though there are dozens of Ruby gems you can use, there still remains the question of whether you can trust them. The gems you use in your Ruby on Rails project may have some dangerous vulnerabilities of their own. Thankfully, there's a way out: developers can use one of several Ruby on Rails security services that track down most types of web application threats.

Codeclimate

Code Climate provides an in-depth Ruby code review from the command line to the cloud.

Gemnasium

This code security service not only checks apps for vulnerabilities but can even automatically update dependencies.

Hakiri

Hakiri is a powerful code security service that uses best practices to track web application vulnerabilities.

Pullreview

This code security service not only finds web application security threats but also explains why they are dangerous and how they can be fixed.

The Challenge of Providing Security for Web Applications

As you can see, making a Ruby on Rails application secure isn't an easy task. You should carefully plan each step when developing an app and always keep security in mind. A single vulnerability in your code might result in big problems for your users, which is why we decided to write this article to share our knowledge of web application security methods and techniques that we use at RubyGarage while working on our projects.

CONTENTS

Authors:

Gleb B.

Gleb B.

Copywriter

Vlad V.

Vlad V.

Chief Executive Officer

Rate this article!

Nay
So-so
Not bad
Good
Wow
10 rating, average 4.7 out of 5

Share article with

Comments (0)

There are no comments yet

Leave a comment

Subscribe via email and know it all first!