Ruby on Rails (often called Rails) is a web application framework written in Ruby that emphasizes convention over configuration and the principle of "don't repeat yourself" (DRY). It provides developers with a structured and efficient way to build database-backed web applications through pre-built patterns for rapid development.
A malicious attacker can use a carefully crafted request to inject arbitrary SQL due to a vulnerability in the PostgreSQL adapter related to the range datatype.
Details
Module Info
- Product: Ruby on Rails Framework
- Affected packages: activerecord
- GitHub repository:
https://github.com/rails/rails/tree/main/activerecord - Published package: The individual ActiveRecord library or the entire Rails Framework (which includes ActiveStorage).
- Package manager: gem
Vulnerability Info
The Rails' SQL quoting mechanism fails to properly sanitize user input when data types unique to PostgreSQL (bitstring and range) are used in queries. Since these data types are unique to PostgreSQL, this vulnerability is relevant only to users of the PostgreSQL database.
Vulnerable code will take the form of:
Model.where(range: params[:from]..params[:to])
Addressing the Issue
There are no known workarounds for this issue. Addressing the issue involves disallowing user-controlled values to be used in queries with the affected data types.
Steps to Reproduce
1. Install a version of Rails that contains this vulnerability, such as 4.0.6:
gem install rails -v 4.0.6
rails _4.0.6_ new cve_test_app
cd cve_test_app
bundle install
Configure the application to use PostgreSQL by updating the Gemfile:
gem 'pg', '~> 0.17.1'
2. Set up the database configuration in config/database.yml to connect to your PostgreSQL database.
3. Generate a model (e.g., Product) with a range attribute:
rails generate model Product price_range:daterange
rake db:create db:migrate
4. Implement a vulnerable query by adding a method to the ProductsController. It queries products within a certain price range.
class ProductsController < ApplicationController
def index
if params[:min_price] && params[:max_price]
range = (params[:min_price].to_f..params[:max_price].to_f)
@products = Product.where(price_range: range)
else
@products = Product.all
end
end
end
This setup directly interpolates user input into a range query, making it susceptible to SQL injection.
5. Start the Rails server:
rails server
6. Access the produces index with malicious parameters:
http://localhost:3000/products?min_price=0&max_price=') OR 1=1;--
This input attempts to terminate the current query and append a tautology (OR 1=1), which would return all records, demonstrating a successful SQL injection.
7. Observe the effect of the query. If the application returns all products regardless of the intended price range, it indicates that the SQL injection was successful.
Credits
- Sean Griffin
Mitigation
Users of affected versions of Ruby on Rails should follow one of the following mitigations:
- Upgrade to a corrected version.
- Leverage a commercial support partner like HeroDevs for post-EOL security support.