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.