Ruby Rails Unsafe Reflection Vulnerability (Brakeman Check)

Critical Risk Code Injection
RubyRailsReflectionBrakemanCode InjectionMethod InvocationDynamic Execution

What it is

Application uses Ruby reflection methods with user-controlled input to dynamically call methods or instantiate classes, enabling code execution and security bypass attacks.

class ApiController < ApplicationController
  def call_method
    # Vulnerable: User controls method name
    method_name = params[:method]
    result = self.send(method_name)
    
    render json: { result: result }
  end
  
  def create_object
    # Vulnerable: User controls class name
    class_name = params[:class_name]
    klass = Object.const_get(class_name)
    
    # Can instantiate any class
    object = klass.new
    render json: { created: object.class.name }
  end
  
  def dynamic_attribute
    # Vulnerable: User controls attribute access
    user = User.find(params[:user_id])
    attribute = params[:attribute]
    
    # Can access any method/attribute
    value = user.send(attribute)
    render json: { attribute => value }
  end
  
  def execute_code
    # Extremely vulnerable: Direct code execution
    code = params[:code]
    result = eval(code)
    
    render json: { result: result }
  end
end

class ReportsController < ApplicationController
  def generate
    # Vulnerable: Dynamic report type instantiation
    report_type = params[:type]
    report_class = "#{report_type.camelize}Report"
    
    # User can instantiate arbitrary classes
    klass = report_class.constantize
    report = klass.new
    
    render json: report.generate
  end
end
class ApiController < ApplicationController
  # Allowlist of safe methods
  ALLOWED_METHODS = %w[
    current_time
    server_status
    public_info
    health_check
  ].freeze
  
  # Allowlist of safe classes
  ALLOWED_CLASSES = {
    'user' => User,
    'post' => Post,
    'comment' => Comment
  }.freeze
  
  def call_method
    # Secure: Validate against allowlist
    method_name = params[:method]
    
    unless ALLOWED_METHODS.include?(method_name)
      return render json: { error: 'Method not allowed' }, status: 403
    end
    
    # Safe: Only call allowed methods
    case method_name
    when 'current_time'
      result = Time.current.iso8601
    when 'server_status'
      result = 'online'
    when 'public_info'
      result = { version: '1.0', environment: Rails.env }
    when 'health_check'
      result = { status: 'healthy', timestamp: Time.current }
    else
      return render json: { error: 'Unknown method' }, status: 400
    end
    
    render json: { result: result }
  end
  
  def create_object
    # Secure: Use allowlist for class names
    class_name = params[:class_name]
    
    unless ALLOWED_CLASSES.key?(class_name)
      return render json: { error: 'Class not allowed' }, status: 403
    end
    
    begin
      # Safe: Only instantiate allowed classes
      klass = ALLOWED_CLASSES[class_name]
      object = klass.new
      
      render json: { 
        created: object.class.name,
        id: object.id
      }
    rescue => e
      render json: { error: 'Object creation failed' }, status: 500
    end
  end
  
  def dynamic_attribute
    # Secure: Use allowlist for attributes
    user_id = params[:user_id]
    attribute = params[:attribute]
    
    # Validate user ID
    begin
      id = Integer(user_id)
      user = User.find(id)
    rescue ArgumentError
      return render json: { error: 'Invalid user ID' }, status: 400
    rescue ActiveRecord::RecordNotFound
      return render json: { error: 'User not found' }, status: 404
    end
    
    # Allowlist of safe attributes
    safe_attributes = %w[id name email created_at updated_at status]
    
    unless safe_attributes.include?(attribute)
      return render json: { error: 'Attribute not allowed' }, status: 403
    end
    
    # Safe: Only access allowed attributes
    value = case attribute
            when 'id'
              user.id
            when 'name'
              user.name
            when 'email'
              user.email
            when 'created_at'
              user.created_at.iso8601
            when 'updated_at'
              user.updated_at.iso8601
            when 'status'
              user.status
            else
              nil
            end
    
    render json: { attribute => value }
  end
  
  def execute_code
    # Secure: Don't allow arbitrary code execution
    render json: { error: 'Code execution not allowed' }, status: 403
  end
end

class ReportsController < ApplicationController
  # Secure report type mapping
  REPORT_TYPES = {
    'sales' => SalesReport,
    'users' => UserReport,
    'activity' => ActivityReport
  }.freeze
  
  def generate
    # Secure: Use allowlist for report types
    report_type = params[:type]
    
    unless REPORT_TYPES.key?(report_type)
      return render json: { error: 'Invalid report type' }, status: 400
    end
    
    begin
      # Safe: Only instantiate known report classes
      report_class = REPORT_TYPES[report_type]
      report = report_class.new(current_user)
      
      # Validate user permissions
      unless report.accessible_by?(current_user)
        return render json: { error: 'Access denied' }, status: 403
      end
      
      result = report.generate
      render json: result
      
    rescue => e
      Rails.logger.error "Report generation failed: #{e.message}"
      render json: { error: 'Report generation failed' }, status: 500
    end
  end
end

# Secure base report class
class BaseReport
  def initialize(user)
    @user = user
  end
  
  def accessible_by?(user)
    # Implement permission checking
    user.admin? || user == @user
  end
  
  def generate
    raise NotImplementedError, 'Subclasses must implement generate method'
  end
end

class SalesReport < BaseReport
  def accessible_by?(user)
    user.admin? || user.sales_manager?
  end
  
  def generate
    # Safe report generation logic
    {
      type: 'sales',
      period: Date.current.beginning_of_month..Date.current.end_of_month,
      total_sales: calculate_sales
    }
  end
  
  private
  
  def calculate_sales
    # Safe calculation logic
    100000 # Placeholder
  end
end

💡 Why This Fix Works

See fix suggestions for detailed explanation.

Why it happens

Rails code uses constantize with params: class_name = params[:model]; klass = class_name.constantize. params[:model] user-controlled. Arbitrary class instantiation. Attackers specify dangerous classes. constantize converts strings to constants. Access to any Ruby class including system commands.

Root causes

Using constantize with User Input

Rails code uses constantize with params: class_name = params[:model]; klass = class_name.constantize. params[:model] user-controlled. Arbitrary class instantiation. Attackers specify dangerous classes. constantize converts strings to constants. Access to any Ruby class including system commands.

Using send or public_send with User-Controlled Method Names

Dynamic method calls: object.send(params[:method], args). params[:method] from user. Any method callable. Private methods accessible with send. public_send slightly safer but still dangerous. Method names should never come from user input.

Using eval or instance_eval with User Data

Code evaluation: eval(params[:code]) or object.instance_eval(params[:expression]). eval executes arbitrary Ruby code. instance_eval evaluates in object context. Full code execution. User controls code string. Remote code execution vulnerability.

Dynamic Class Instantiation from Parameters

Class selection from params: model_class = params[:type].constantize; instance = model_class.new(attributes). User chooses class to instantiate. No allowlist validation. Attackers instantiate unintended classes. Mass assignment with arbitrary classes.

Using const_get Without Validation

Constant lookup: Object.const_get(params[:constant]). const_get accesses constants dynamically. User specifies constant name. No namespace restriction. Access to any constant in application. Similar to constantize but more flexible and dangerous.

Fixes

1

Never Use constantize with User Input, Use Allowlist

Validate against allowlist: ALLOWED_MODELS = ['User', 'Post', 'Comment']; raise unless ALLOWED_MODELS.include?(params[:model]); klass = params[:model].constantize. Explicit allowed classes. String validation before constantize. No arbitrary class access. Allowlist approach essential.

2

Use Case Statements or Hash Mapping Instead of send

Map actions to methods: ACTIONS = {'show' => :display_record, 'edit' => :edit_form}; method_name = ACTIONS[params[:action]]; object.send(method_name) if method_name. Fixed method set. No user control. Case statement alternative: case params[:action]; when 'show' then display_record; end.

3

Never Use eval or instance_eval with User Input

Avoid eval completely with untrusted data. No safe usage. Replace with safe alternatives: case statements, hash lookups, explicit method calls. If dynamic behavior required, use DSL or parser library. eval with user input is remote code execution.

4

Use Polymorphic Associations Instead of Dynamic Classes

Rails polymorphic: class Comment < ApplicationRecord; belongs_to :commentable, polymorphic: true; end. Database stores type. Application controls valid types. No user selection of classes. Polymorphic associations safe alternative to dynamic class instantiation.

5

Implement Strict Type Validation for Parameters

Strong parameters with type validation: def model_params; params.require(:model_type); TYPE_MAPPING = {'user': User, 'post': Post}; TYPE_MAPPING[params[:model_type].to_sym]; end. Symbol keys. Hardcoded mapping. Type safety. No string-to-class conversion from user input.

6

Run Brakeman to Detect Unsafe Reflection

Use Brakeman scanner: brakeman -A detects unsafe reflection. Review Dangerous Send and Remote Code Execution warnings. Fix flagged calls. Automated detection in CI/CD. Regular scans prevent reflection vulnerabilities. Brakeman specifically checks send, eval, constantize.

Detect This Vulnerability in Your Code

Sourcery automatically identifies ruby rails unsafe reflection vulnerability (brakeman check) and many other security issues in your codebase.