CrackedRuby CrackedRuby

Overview

SQL injection represents one of the most critical security vulnerabilities in web applications. This attack vector occurs when untrusted data enters a SQL query without proper sanitization or parameterization, allowing attackers to manipulate the query structure and execute arbitrary SQL commands. The vulnerability exists at the intersection of application code and database interaction, where string concatenation or interpolation creates opportunities for malicious input to alter query semantics.

The fundamental problem stems from mixing code and data within SQL statements. When applications construct queries by concatenating user input directly into SQL strings, the database cannot distinguish between legitimate query structure and attacker-controlled data. An attacker can inject SQL syntax that closes the intended query, appends new commands, or modifies query logic to access unauthorized data, bypass authentication, or delete records.

Consider a simple authentication query constructed through string concatenation:

# Vulnerable approach
username = params[:username]
query = "SELECT * FROM users WHERE username = '#{username}'"
results = db.execute(query)

An attacker submitting admin' OR '1'='1 as the username transforms the query into:

SELECT * FROM users WHERE username = 'admin' OR '1'='1'

The injected OR '1'='1' condition always evaluates to true, returning all user records and potentially granting unauthorized access. More sophisticated attacks can extract entire databases, modify data, or execute operating system commands depending on database permissions and configuration.

Ruby applications interact with databases through various abstraction layers including raw database drivers, ActiveRecord ORM, Sequel, and other libraries. Each layer provides mechanisms for preventing SQL injection, but developers must understand and apply these protections consistently. The severity of SQL injection vulnerabilities and their prevalence in OWASP Top 10 lists underscore the critical importance of defensive programming practices.

Key Principles

SQL injection prevention operates on the principle of separating query structure from data values. The database must receive explicit instructions about which parts of a SQL statement represent code and which represent data. This separation prevents user input from altering query semantics regardless of the characters or SQL syntax the input contains.

Parameterized queries, also called prepared statements, implement this separation by sending the query structure and data values to the database in distinct phases. The database first parses and compiles the query with placeholders for data values, establishing the query structure. Subsequently, the application sends the actual data values, which the database treats exclusively as data parameters rather than executable code. This two-phase approach ensures that user input cannot escape the data context to become part of the query structure.

# Parameterized query structure
query = "SELECT * FROM users WHERE username = ?"
statement = db.prepare(query)
results = statement.execute(username)

The placeholder ? indicates a parameter position. When the database receives this query, it parses the structure with the parameter location marked but not filled. The subsequent execute call provides the actual username value, which the database inserts at the parameter position without parsing it as SQL syntax.

Input validation represents a complementary defense layer but cannot substitute for parameterized queries. Validation examines user input against expected patterns and rejects values that contain suspicious characters or patterns. However, validation approaches face fundamental challenges including the difficulty of creating comprehensive blacklists, the potential for encoding bypasses, and the legitimate need for special characters in some contexts. A username might legitimately contain an apostrophe, making it impossible to blanket-reject such characters.

Object-Relational Mappers provide higher-level abstractions that automatically implement parameterization when used correctly. ORMs translate method calls into parameterized SQL queries, handling the low-level database interaction details. However, ORMs also provide escape hatches for raw SQL queries, and developers must understand when they transition from safe ORM methods to potentially vulnerable raw query construction.

The principle of least privilege applies at the database level by restricting application database accounts to only the permissions required for legitimate operations. An application that only reads and writes specific tables should not have permissions to drop tables or execute administrative commands. This defense-in-depth approach limits the damage from a successful SQL injection attack by constraining what commands the attacker can execute through the compromised application.

Defense in depth acknowledges that no single protection mechanism provides complete security. Applications should combine parameterized queries with input validation, database permission restrictions, web application firewalls, and security monitoring. Each layer provides additional protection if another layer fails or contains a flaw.

Ruby Implementation

Ruby's database ecosystem provides multiple approaches to SQL injection prevention across different abstraction levels. The sqlite3 gem demonstrates low-level parameterized query interfaces:

require 'sqlite3'

db = SQLite3::Database.new('application.db')

# Positional parameters with ?
username = params[:username]
rows = db.execute("SELECT * FROM users WHERE username = ?", username)

# Named parameters with :name syntax
email = params[:email]
rows = db.execute(
  "SELECT * FROM users WHERE username = :user AND email = :email",
  user: username,
  email: email
)

# Prepared statement for repeated execution
stmt = db.prepare("INSERT INTO logs (user_id, action) VALUES (?, ?)")
user_actions.each do |action|
  stmt.execute(current_user_id, action)
end
stmt.close

The pg gem for PostgreSQL uses numbered parameters with similar semantics:

require 'pg'

conn = PG.connect(dbname: 'production')

# Numbered parameters $1, $2, etc.
result = conn.exec_params(
  "SELECT * FROM orders WHERE user_id = $1 AND status = $2",
  [user_id, 'pending']
)

# Prepared statement with name
conn.prepare('find_user', "SELECT * FROM users WHERE id = $1")
result = conn.exec_prepared('find_user', [user_id])

ActiveRecord provides automatic parameterization through its query interface. Methods like where, find_by, and find generate parameterized queries:

# Safe ActiveRecord queries
User.where(username: params[:username])
User.where("age > ?", minimum_age)
User.where("created_at BETWEEN ? AND ?", start_date, end_date)
User.find_by(email: params[:email])

# Multiple conditions with hash syntax
Order.where(user_id: current_user.id, status: 'shipped')

# Combining conditions safely
User.where("age > ? AND city = ?", min_age, city)
  .where(active: true)

ActiveRecord's where method accepts hash arguments that automatically parameterize values. String conditions with ? placeholders accept additional arguments as parameter values. The framework converts these method calls into parameterized SQL queries without developer intervention.

The Sequel gem provides similar protections with a more flexible syntax:

require 'sequel'

DB = Sequel.connect('sqlite://application.db')

# Symbol-based conditions
users = DB[:users].where(username: username)

# Placeholder conditions
users = DB[:users].where("age > ?", min_age)

# String interpolation with literal method (use cautiously)
users = DB[:users].where("status = ?", status)

# Filtering with multiple conditions
orders = DB[:orders]
  .where(user_id: user_id)
  .where("total > ?", minimum_total)

Ruby's sanitization helpers provide additional protection layers when constructing dynamic queries. ActiveRecord includes sanitize_sql_array and quote methods:

# Sanitize array-style conditions
condition = ActiveRecord::Base.sanitize_sql_array(
  ["username = ? AND status = ?", username, status]
)
User.where(condition)

# Quote individual values for interpolation contexts
quoted_city = ActiveRecord::Base.connection.quote(city)
# Still prefer parameterization over quoted interpolation

Database connection adapters provide identifier quoting for dynamic table or column names, though dynamic identifiers create additional security considerations:

# Quote table or column identifiers
table_name = params[:table]
quoted_table = ActiveRecord::Base.connection.quote_table_name(table_name)

# Whitelist approach for dynamic identifiers is safer
allowed_tables = %w[users orders products]
if allowed_tables.include?(table_name)
  results = ActiveRecord::Base.connection.execute(
    "SELECT * FROM #{quoted_table} WHERE id = ?", id
  )
end

Raw SQL queries through ActiveRecord require explicit parameterization:

# Vulnerable raw SQL
User.find_by_sql("SELECT * FROM users WHERE city = '#{city}'")

# Safe raw SQL with parameters
User.find_by_sql(["SELECT * FROM users WHERE city = ?", city])

# Execute with parameters
sql = "UPDATE users SET last_login = ? WHERE id = ?"
ActiveRecord::Base.connection.execute(
  ActiveRecord::Base.sanitize_sql_array([sql, Time.now, user_id])
)

Security Implications

SQL injection vulnerabilities enable attackers to bypass authentication, access unauthorized data, modify database records, delete information, and potentially execute operating system commands. The severity depends on database permissions, configuration, and the specific database system in use.

Authentication bypass represents the most common exploitation scenario. Attackers inject logic that makes authentication queries always return true or return administrative accounts:

# Vulnerable authentication
username = params[:username]
password = params[:password]
query = "SELECT * FROM users WHERE username = '#{username}' 
         AND password = '#{password}'"
user = db.execute(query).first

# Attack input: username = admin' --
# Resulting query:
# SELECT * FROM users WHERE username = 'admin' --' AND password = ''
# The -- comments out the password check

Data exfiltration attacks extract sensitive information from databases. Attackers use UNION queries to append results from other tables:

# Vulnerable search query
search = params[:query]
query = "SELECT title, content FROM articles WHERE title LIKE '%#{search}%'"

# Attack input: query = %' UNION SELECT username, password FROM users --
# Resulting query returns article data mixed with user credentials

Database modification attacks insert, update, or delete records. Stacked queries in databases that support multiple statements enable arbitrary command execution:

# Vulnerable update query
comment = params[:comment]
query = "UPDATE posts SET comment = '#{comment}' WHERE id = #{post_id}"

# Attack input: comment = '; DROP TABLE users; --
# If stacked queries are enabled, executes DROP TABLE command

Some database systems support extended stored procedures or features that enable operating system command execution. SQL Server's xp_cmdshell and similar features in other databases can execute shell commands when enabled and accessible to the application database account:

-- Potential OS command execution through SQL injection
'; EXEC xp_cmdshell 'net user attacker password /ADD'; --

Second-order SQL injection occurs when the application stores attacker input safely but later retrieves and uses it in an unsafe query context. The initial storage uses parameterization, but subsequent retrieval and use in string concatenation creates the vulnerability:

# Initial safe storage
User.create(username: params[:username])

# Later vulnerable usage
user = User.find(id)
query = "SELECT * FROM logs WHERE username = '#{user.username}'"
# If username contains injection payload, vulnerability triggers here

Blind SQL injection techniques extract information when the application does not display query results directly. Boolean-based blind injection infers information by observing application behavior differences based on true/false conditions:

# Application shows different behavior based on query success
search = params[:id]
user = User.find_by_sql("SELECT * FROM users WHERE id = #{search}").first

# Attack tests conditions character by character:
# id = 1 AND SUBSTRING(password, 1, 1) = 'a'
# Observe response time or error messages to infer password characters

Time-based blind injection uses database functions that introduce delays to extract information bit by bit:

-- Attack payload introducing time delay on true condition
1 AND IF(SUBSTRING(password,1,1)='a', SLEEP(5), 0)
-- Response delay indicates the condition evaluated true

Error-based SQL injection extracts information through detailed database error messages. When applications display full error messages including query details, attackers craft inputs that generate errors revealing database structure:

# Application displays database errors
begin
  User.find_by_sql("SELECT * FROM users WHERE id = #{params[:id]}")
rescue => e
  render plain: "Error: #{e.message}"
end

# Attack input generates error revealing table structure
# id = 1 AND (SELECT * FROM users) 
# Error message may reveal column names and data types

NoSQL injection affects document databases and key-value stores when query construction concatenates user input. MongoDB queries constructed from user input can suffer similar injection vulnerabilities:

# Vulnerable MongoDB query construction
username = params[:username]
password = params[:password]
user = User.where("this.username == '#{username}' && 
                   this.password == '#{password}'").first

# Attack input: username = ' || '1'=='1
# Bypasses authentication through JavaScript injection

Practical Examples

Web application login forms represent a common SQL injection target. A vulnerable implementation constructs authentication queries through string interpolation:

class SessionsController < ApplicationController
  def create
    # VULNERABLE IMPLEMENTATION
    username = params[:username]
    password = params[:password]
    
    query = "SELECT * FROM users WHERE username = '#{username}' 
             AND password_hash = '#{Digest::SHA256.hexdigest(password)}'"
    
    user = ActiveRecord::Base.connection.execute(query).first
    
    if user
      session[:user_id] = user['id']
      redirect_to dashboard_path
    else
      flash[:error] = "Invalid credentials"
      render :new
    end
  end
end

The secure implementation uses ActiveRecord's parameterized query methods:

class SessionsController < ApplicationController
  def create
    # SECURE IMPLEMENTATION
    username = params[:username]
    password = params[:password]
    
    user = User.find_by(username: username)
    
    if user&.authenticate(password)
      session[:user_id] = user.id
      redirect_to dashboard_path
    else
      flash[:error] = "Invalid credentials"
      render :new
    end
  end
end

Search functionality across multiple fields requires combining conditions safely:

class ProductsController < ApplicationController
  def search
    # VULNERABLE SEARCH
    query = params[:q]
    category = params[:category]
    
    sql = "SELECT * FROM products WHERE name LIKE '%#{query}%'"
    sql += " AND category = '#{category}'" if category.present?
    
    @products = Product.find_by_sql(sql)
  end
end

The secure approach builds queries through ActiveRecord's query interface:

class ProductsController < ApplicationController
  def search
    # SECURE SEARCH
    @products = Product.all
    
    if params[:q].present?
      @products = @products.where("name LIKE ?", "%#{params[:q]}%")
    end
    
    if params[:category].present?
      @products = @products.where(category: params[:category])
    end
    
    if params[:min_price].present?
      @products = @products.where("price >= ?", params[:min_price])
    end
    
    @products = @products.order(created_at: :desc)
  end
end

Dynamic sorting and filtering based on user parameters requires careful handling. Column names cannot be parameterized as values, requiring whitelist validation:

class UsersController < ApplicationController
  ALLOWED_SORT_COLUMNS = %w[name email created_at last_login].freeze
  ALLOWED_SORT_DIRECTIONS = %w[asc desc].freeze
  
  def index
    @users = User.all
    
    # Validate sort parameters against whitelist
    sort_column = params[:sort].presence_in(ALLOWED_SORT_COLUMNS) || 'created_at'
    sort_direction = params[:direction].presence_in(ALLOWED_SORT_DIRECTIONS) || 'desc'
    
    # Safe to interpolate after whitelist validation
    @users = @users.order("#{sort_column} #{sort_direction}")
    
    # Filter conditions still use parameterization
    if params[:status].present?
      @users = @users.where(status: params[:status])
    end
  end
end

Report generation with date ranges and aggregations demonstrates complex query construction:

class ReportsController < ApplicationController
  def sales_summary
    start_date = Date.parse(params[:start_date])
    end_date = Date.parse(params[:end_date])
    region = params[:region]
    
    # Secure aggregation query
    @summary = Order
      .select('DATE(created_at) as date, SUM(total) as daily_total, COUNT(*) as order_count')
      .where('created_at BETWEEN ? AND ?', start_date, end_date)
      .where(region: region)
      .group('DATE(created_at)')
      .order('date DESC')
  end
end

Batch operations processing multiple records require parameterization for each record:

class BulkUpdateService
  def update_prices(price_updates)
    # price_updates is array of {id: 1, price: 29.99}
    
    ActiveRecord::Base.transaction do
      price_updates.each do |update|
        # Each update uses parameterized query
        Product.where(id: update[:id])
          .update_all(price: update[:price], updated_at: Time.current)
      end
    end
  end
end

Common Pitfalls

String interpolation in SQL queries represents the most common vulnerability. Developers often use Ruby's string interpolation without recognizing the security implications:

# VULNERABLE: String interpolation
def find_user(email)
  User.find_by_sql("SELECT * FROM users WHERE email = '#{email}'")
end

# SECURE: Parameterized query
def find_user(email)
  User.find_by_sql(["SELECT * FROM users WHERE email = ?", email])
end

The vulnerability persists even when the interpolated value comes from trusted sources if those sources originally came from user input:

# VULNERABLE: Trusting stored values without context
def recent_activity(user)
  # user.username came from user input originally
  query = "SELECT * FROM logs WHERE username = '#{user.username}'"
  ActiveRecord::Base.connection.execute(query)
end

Dynamic column or table names cannot use parameter placeholders, leading developers to interpolate identifiers incorrectly:

# VULNERABLE: Direct identifier interpolation
def sort_users(column)
  User.order("#{column} DESC")
end

# SECURE: Whitelist validation
ALLOWED_SORT_COLUMNS = %w[name email created_at].freeze

def sort_users(column)
  column = ALLOWED_SORT_COLUMNS.include?(column) ? column : 'created_at'
  User.order("#{column} DESC")
end

Partial parameterization where some parts of a query use parameters while other parts use interpolation creates vulnerabilities:

# VULNERABLE: Mixing parameterization and interpolation
def search_products(category, name)
  Product.where("category = '#{category}' AND name LIKE ?", "%#{name}%")
end

# SECURE: Full parameterization
def search_products(category, name)
  Product.where("category = ? AND name LIKE ?", category, "%#{name}%")
end

Like clause pattern construction requires attention to both SQL injection and LIKE metacharacter escaping:

# VULNERABLE: Unescaped LIKE pattern
def search(query)
  Product.where("name LIKE ?", "%#{query}%")
end
# Input containing % or _ has unintended behavior

# SECURE: Escaped LIKE pattern
def search(query)
  sanitized = query.gsub(/[%_]/) { |m| "\\#{m}" }
  Product.where("name LIKE ?", "%#{sanitized}%")
end

ORM finder methods that accept SQL fragments can introduce vulnerabilities:

# VULNERABLE: find_by with interpolated SQL
def find_admin(username)
  User.find_by("username = '#{username}' AND role = 'admin'")
end

# SECURE: Hash conditions or parameterized SQL
def find_admin(username)
  User.find_by(username: username, role: 'admin')
end

Raw SQL in migrations, seeds, or rake tasks receives less scrutiny than controller code but requires the same protections:

# VULNERABLE: Migration with interpolated values
class AddDefaultUsers < ActiveRecord::Migration[7.0]
  def up
    username = ENV['ADMIN_USERNAME']
    execute("INSERT INTO users (username, role) VALUES ('#{username}', 'admin')")
  end
end

# SECURE: Parameterized execution
class AddDefaultUsers < ActiveRecord::Migration[7.0]
  def up
    username = ENV['ADMIN_USERNAME']
    execute(sanitize_sql_array(["INSERT INTO users (username, role) VALUES (?, 'admin')", username]))
  end
end

Query fragments in scopes or class methods can hide vulnerabilities:

# VULNERABLE: Scope with interpolated value
class User < ApplicationRecord
  def self.in_city(city)
    where("city = '#{city}'")
  end
end

# SECURE: Parameterized scope
class User < ApplicationRecord
  def self.in_city(city)
    where(city: city)
  end
end

JSON or XML data parsed from user input and used in queries requires validation:

# VULNERABLE: Using parsed JSON directly
def bulk_update
  updates = JSON.parse(params[:data])
  updates.each do |update|
    sql = "UPDATE products SET price = #{update['price']} WHERE id = #{update['id']}"
    ActiveRecord::Base.connection.execute(sql)
  end
end

# SECURE: Parameterized updates with validation
def bulk_update
  updates = JSON.parse(params[:data])
  updates.each do |update|
    Product.where(id: update['id']).update_all(price: update['price'])
  end
end

Testing Approaches

SQL injection testing verifies that application code properly parameterizes queries and rejects malicious input. Unit tests exercise data access methods with injection payloads:

require 'test_helper'

class UserTest < ActiveSupport::TestCase
  test "find_by_username protects against SQL injection" do
    # Create test user
    user = User.create(username: 'testuser', email: 'test@example.com')
    
    # Attempt SQL injection through username
    injection_payload = "testuser' OR '1'='1"
    result = User.find_by(username: injection_payload)
    
    # Should return nil, not the user (payload should be treated as literal string)
    assert_nil result
    
    # Valid username should still work
    valid_result = User.find_by(username: 'testuser')
    assert_equal user, valid_result
  end
  
  test "search method parameterizes query correctly" do
    user = User.create(username: 'admin', email: 'admin@example.com')
    
    # Injection payload attempting to expose all users
    payload = "%' OR '1'='1"
    results = User.search_by_username(payload)
    
    # Should return empty result, not all users
    assert_empty results
  end
end

Integration tests validate end-to-end protection through controller and request specs:

require 'rails_helper'

RSpec.describe 'Authentication', type: :request do
  describe 'POST /login' do
    let!(:user) { User.create(username: 'validuser', password: 'secret123') }
    
    it 'protects against SQL injection in login' do
      # SQL injection payload attempting authentication bypass
      post '/login', params: {
        username: "admin' OR '1'='1' --",
        password: 'anything'
      }
      
      expect(response).to have_http_status(:unauthorized)
      expect(session[:user_id]).to be_nil
    end
    
    it 'allows legitimate authentication' do
      post '/login', params: {
        username: 'validuser',
        password: 'secret123'
      }
      
      expect(response).to have_http_status(:redirect)
      expect(session[:user_id]).to eq(user.id)
    end
  end
end

Security-focused tests verify that injection payloads fail to extract unauthorized data:

require 'rails_helper'

RSpec.describe ProductsController, type: :controller do
  describe 'GET #search' do
    let!(:product) { Product.create(name: 'Widget', price: 29.99) }
    let!(:user) { User.create(username: 'admin', password_hash: 'secret') }
    
    it 'prevents UNION-based data extraction' do
      # Attempt UNION injection to extract user table data
      get :search, params: {
        q: "' UNION SELECT username, password_hash FROM users --"
      }
      
      # Verify user data not in response
      expect(response.body).not_to include('admin')
      expect(response.body).not_to include('password_hash')
      expect(assigns(:products)).not_to include(user)
    end
    
    it 'handles special characters safely' do
      get :search, params: { q: "'; DROP TABLE products; --" }
      
      # Verify table still exists and query executed safely
      expect { Product.count }.not_to raise_error
      expect(Product.count).to eq(1)
    end
  end
end

Property-based testing generates diverse injection payloads to test defensive measures:

require 'rails_helper'
require 'rantly/rspec_extensions'

RSpec.describe User, type: :model do
  describe '.find_by_email' do
    it 'safely handles arbitrary input strings' do
      property_of {
        # Generate random strings including SQL metacharacters
        Rantly { sized(20) { string(:alnum) + ['\'', '"', ';', '--', '/*'].sample } }
      }.check(100) { |random_input|
        expect { User.find_by(email: random_input) }.not_to raise_error
      }
    end
  end
end

Query logging analysis during tests detects parameter usage:

require 'rails_helper'

RSpec.describe 'Query Parameterization', type: :request do
  it 'uses parameterized queries for user search' do
    queries = []
    
    subscriber = ActiveSupport::Notifications.subscribe('sql.active_record') do |_, _, _, _, payload|
      queries << payload[:sql] if payload[:name] != 'SCHEMA'
    end
    
    get '/users/search', params: { name: "O'Brien" }
    
    ActiveSupport::Notifications.unsubscribe(subscriber)
    
    # Verify queries contain placeholders, not interpolated values
    search_query = queries.find { |q| q.include?('WHERE') }
    expect(search_query).to match(/\?|\$\d+/)  # Contains parameter placeholder
    expect(search_query).not_to include("O'Brien")  # Doesn't contain literal value
  end
end

Database audit logs can be checked in test environments to verify no unauthorized queries execute:

require 'rails_helper'

RSpec.describe 'Security Audit', type: :request do
  before { DatabaseAuditor.start_recording }
  after { DatabaseAuditor.stop_recording }
  
  it 'prevents DROP TABLE commands through injection' do
    post '/products', params: {
      name: "Product'; DROP TABLE products; --"
    }
    
    audit_log = DatabaseAuditor.recorded_queries
    drop_commands = audit_log.select { |q| q.upcase.include?('DROP TABLE') }
    
    expect(drop_commands).to be_empty
  end
end

Reference

Parameter Placeholder Syntax

Library Positional Named Example
sqlite3 ? :name execute("SELECT * FROM users WHERE id = ?", id)
pg $1, $2 Not supported exec_params("SELECT * FROM users WHERE id = $1", [id])
mysql2 ? Not supported prepare("SELECT * FROM users WHERE id = ?")
ActiveRecord ? Not applicable where("age > ?", min_age)
Sequel ? Not applicable where("status = ?", status)

ActiveRecord Safe Query Methods

Method Usage Safe
find User.find(id) Yes - parameterizes automatically
find_by User.find_by(username: name) Yes - hash conditions parameterized
where User.where(email: email) Yes - hash conditions parameterized
where User.where("age > ?", age) Yes - uses parameter placeholders
where User.where("name = 'value'") Depends - safe only if no user input
find_by_sql User.find_by_sql(["SELECT...", param]) Yes - array format parameterizes
find_by_sql User.find_by_sql("SELECT... #{var}") No - interpolation vulnerable
order User.order(:created_at) Yes - symbol ordering is safe
order User.order("created_at DESC") Depends - safe only if no user input
select User.select(:name, :email) Yes - symbol selection is safe
joins User.joins(:posts) Yes - association joins are safe

Input Validation Patterns

Validation Type Purpose Example
Whitelist Allow only known-safe values ALLOWED_COLUMNS.include?(param)
Type casting Ensure correct data type Integer(params[:id])
Format validation Match expected pattern email.match?(/\A[\w+-.]+@[a-z\d-]+(.[a-z]+)*.[a-z]+\z/i)
Length limits Prevent oversized input username.length <= 50
Character set Restrict to safe characters username.match?(/\A[a-zA-Z0-9_]+\z/)

Common Injection Payloads for Testing

Payload Type Example Purpose
Authentication bypass admin' OR '1'='1 Tests boolean injection
Comment injection admin' -- Tests comment bypass
UNION injection ' UNION SELECT username, password FROM users -- Tests data extraction
Stacked queries '; DROP TABLE users; -- Tests command execution
Time-based blind ' AND SLEEP(5) -- Tests blind injection
Boolean blind ' AND 1=1 -- Tests boolean inference

Security Configuration Checklist

Configuration Setting Purpose
Database user privileges Minimal required permissions Limits injection damage
Error messages Generic messages in production Prevents information disclosure
Query logging Enabled for security monitoring Detects injection attempts
Parameter limits Max length and count restrictions Reduces attack surface
WAF rules SQL injection pattern detection Blocks obvious attacks
Prepared statement caching Enabled Improves performance and security

Framework-Specific Methods

Framework Safe Method Unsafe Pattern
ActiveRecord where(column: value) where("column = '#{value}'")
Sequel dataset.where(column: value) dataset.where("column = '#{value}'")
DataMapper Model.all(column: value) Model.all(conditions: ["column = '#{value}'"])
ROM relation.where(column: value) relation.where("column = '#{value}'")

Sanitization Methods

Method Library Usage
sanitize_sql_array ActiveRecord sanitize_sql_array(["SELECT * FROM users WHERE id = ?", id])
quote Connection adapter connection.quote(value)
quote_table_name Connection adapter connection.quote_table_name(table)
quote_column_name Connection adapter connection.quote_column_name(column)
escape_string mysql2 client.escape(string)