-
Notifications
You must be signed in to change notification settings - Fork 776
Extras: Metaprogramming
Metaprogramming is a powerful feature in Ruby that allows code to dynamically manipulate itself at runtime. While this enables elegant and flexible solutions, it also introduces significant security risks when user input is involved.
Metaprogramming allows programs to:
- Dynamically define or modify classes and methods
- Invoke methods by name using strings or symbols
- Access and modify object internals
- Generate code at runtime
Ruby and Rails provide several metaprogramming methods that become dangerous when combined with user input:
Converts strings to constant references (classes/modules), allowing attackers to instantiate arbitrary classes.
Example:
# Dangerous
params[:type].constantize.new # User can instantiate ANY classRead Full Constantize Documentation →
Dynamically invokes methods on objects, allowing attackers to call any method including private ones.
Example:
# Dangerous
user.send(params[:method]) # User can call ANY methodRead Full Send Method Documentation →
Additional metaprogramming methods to be cautious with:
-
eval: Executes arbitrary Ruby codeeval(params[:code]) # NEVER do this - direct code execution
-
instance_eval/class_eval: Evaluates code in object/class context@user.instance_eval(params[:code]) # Dangerous
-
define_method: Dynamically creates methodsdefine_method(params[:name]) { } # Can create unwanted methods
-
const_get: Similar to constantizeObject.const_get(params[:class]) # Can access any constant
-
method: Returns a method object that can be called@user.method(params[:method_name]).call # Similar to send
-
instance_variable_get/set: Direct instance variable access@user.instance_variable_set(params[:var], params[:value]) # Bypass validations
When user input controls metaprogramming operations:
- Bypassed Access Controls: Private/protected methods become accessible
- Unexpected Behavior: Code paths you never intended get executed
- Data Exposure: Sensitive attributes and methods become accessible
- Code Execution: In severe cases, arbitrary code can be executed
- Business Logic Bypass: Validations and security checks can be circumvented
Most metaprogramming vulnerabilities follow this pattern:
# 1. Application expects specific, safe input
expected_input = "safe_method"
# 2. User provides malicious input instead
actual_input = params[:input] # "dangerous_method" or "admin="
# 3. Metaprogramming executes user-controlled code
object.send(actual_input) # Oops - executed dangerous_method!The best defense is not using metaprogramming with user input at all:
# Instead of:
object.send(params[:method])
# Use explicit code:
case params[:method]
when 'name' then object.name
when 'email' then object.email
else raise "Invalid method"
endIf you must use metaprogramming, use strict whitelists:
ALLOWED_METHODS = %w[name email created_at].freeze
if ALLOWED_METHODS.include?(params[:method])
object.send(params[:method])
else
raise "Unauthorized method"
endValidate all user input before using in metaprogramming:
# Check format
unless params[:class].match?(/\A[A-Z][a-zA-Z0-9]*\z/)
raise "Invalid class name"
end
# Check whitelist
ALLOWED_CLASSES = %w[User Post Comment].freeze
unless ALLOWED_CLASSES.include?(params[:class])
raise "Unauthorized class"
end
# Now safe(r) to use
klass = params[:class].constantizeRails provides built-in protections - use them:
# Strong Parameters
def user_params
params.require(:user).permit(:name, :email)
end
# Instead of:
user.send("#{params[:attr]}=", params[:value])
# Use:
user.update(user_params)Design your code to minimize metaprogramming needs:
- Use explicit method calls when possible
- Create specific API endpoints instead of generic ones
- Limit dynamic behavior to safe, well-tested contexts
- Default to static code, use metaprogramming sparingly
RailsGoat demonstrates metaprogramming vulnerabilities in:
-
Benefit Forms Controller - Unchecked
constantizeusage- See:
app/controllers/benefit_forms_controller.rb - Documentation
- See:
-
Other Controllers - Various metaprogramming anti-patterns throughout the application
When testing applications, look for:
-
User input in metaprogramming methods
- Search for:
constantize,send,eval,instance_eval - Check if parameters flow into these methods
- Search for:
-
Dynamic method/class resolution
- Look for patterns like:
params[:type].constantize - Check for:
object.send(params[:method])
- Look for patterns like:
-
Missing input validation
- Are there whitelists for user-controlled values?
- Can users provide arbitrary strings to metaprogramming methods?
-
Privilege escalation opportunities
- Can users call admin-only methods?
- Can users modify role/permission attributes?
- Metaprogramming is powerful but dangerous with user input
- Always whitelist user input before metaprogramming operations
- Prefer explicit code over dynamic code when possible
- Defense in depth: Validate input, check permissions, log access
- Test thoroughly - try to break your own whitelists
- Constantize Vulnerability Details
- Send Method Vulnerability Details
- OWASP Code Injection
- Ruby Security Guide
To understand these vulnerabilities hands-on:
- Set up RailsGoat locally
- Navigate to the Benefit Forms section
- Try attacking the constantize vulnerability
- Review the vulnerable code
- Implement the suggested fixes
- Verify the fixes prevent the attacks
Remember: RailsGoat is intentionally vulnerable for educational purposes. Never deploy it to production!
Sections are divided by their OWASP Top Ten label (A1-A10) and marked as R4 and R5 for Rails 4 and 5.