-
Notifications
You must be signed in to change notification settings - Fork 776
Extras: Constantize
The constantize method is a Rails metaprogramming method that converts a string into a constant reference (typically a class or module). For example, "User".constantize returns the User class. This is commonly used to dynamically instantiate classes based on runtime conditions.
The Security Risk: When user-supplied input is passed to constantize, an attacker can instantiate any accessible class in the application, including dangerous system classes. This can lead to arbitrary code execution, information disclosure, or denial of service.
Within the file app/controllers/benefit_forms_controller.rb:
def download
begin
path = params[:name]
file = params[:type].constantize.new(path)
send_file file, disposition: "attachment"
rescue
redirect_to user_benefit_forms_path(user_id: current_user.id)
end
endThis code contains TWO separate vulnerabilities:
file = params[:type].constantize.new(path)The params[:type] value is passed directly to constantize without validation. This allows an attacker to:
- Instantiate any Ruby class accessible in the application
- Pass arbitrary arguments to the class constructor
- Potentially trigger dangerous behavior in class initialization
Expected behavior: The application expects params[:type] to be "File"
Actual behavior: An attacker can specify ANY class name
path = params[:name]The filename is taken directly from user input without validation, allowing path traversal attacks (e.g., ../../etc/passwd). While serious, this is a separate issue from the constantize vulnerability.
Instead of instantiating File, an attacker can instantiate Ruby's Pathname class which, when converted to a string by send_file, reveals file paths:
http://railsgoat.dev/download?name=/etc/passwd&type=Pathname
This bypasses the intended file download mechanism and can expose sensitive file paths.
An attacker can instantiate ERB with malicious template code:
http://railsgoat.dev/download?name=<%=`whoami`%>&type=ERB
When send_file attempts to process the ERB object, the template code may be evaluated, executing arbitrary commands.
An attacker can instantiate classes with expensive constructors or cause application errors:
http://railsgoat.dev/download?name=malicious_data&type=ActiveRecord::Base
This can crash the application or consume excessive resources.
The original documentation showed:
http://railsgoat.dev/download?name=|touch+testthis.txt&type=Logger
This example was problematic because:
- It focused on command injection through the
nameparameter (path traversal issue) - The
Loggerclass doesn't execute shell commands from its constructor - It didn't clearly demonstrate why
constantizeitself is dangerous - It mixed two separate vulnerabilities, making both harder to understand
def download
begin
path = params[:name]
file = params[:type].constantize.new(path)
send_file file, disposition: "attachment"
rescue
redirect_to user_benefit_forms_path(user_id: current_user.id)
end
enddef download
# Define allowed files with a whitelist approach
ALLOWED_FILES = {
"health" => "Health_n_Stuff.pdf",
"dental" => "Dental_n_Stuff.pdf"
}.freeze
# Define allowed types (though we only need File here)
ALLOWED_TYPES = ["File"].freeze
begin
# Validate the file identifier
unless ALLOWED_FILES.key?(params[:name])
flash[:error] = "Invalid file requested"
redirect_to user_benefit_forms_path(user_id: current_user.id) and return
end
# Validate the type parameter
unless ALLOWED_TYPES.include?(params[:type])
flash[:error] = "Invalid file type"
redirect_to user_benefit_forms_path(user_id: current_user.id) and return
end
# Use validated values
filename = ALLOWED_FILES[params[:name]]
path = Rails.root.join('public', 'docs', filename)
# No need for constantize here - just use File directly
send_file path, disposition: "attachment"
rescue => e
Rails.logger.error "File download error: #{e.message}"
flash[:error] = "File not found"
redirect_to user_benefit_forms_path(user_id: current_user.id)
end
end- Whitelist Approach: Only allow specific, predefined file identifiers
-
Remove Constantize: Eliminate dynamic class instantiation entirely - just use
Filedirectly viasend_file -
Path Construction: Build safe paths using
Rails.root.joinwith validated filename - Input Validation: Check all user inputs against allowed values before use
- Proper Error Handling: Log errors without exposing internal details to users
If you must use constantize (which should be rare):
-
Use a whitelist: Only allow specific, known-safe class names
ALLOWED_CLASSES = ["User", "Post", "Comment"].freeze klass = ALLOWED_CLASSES.include?(params[:type]) ? params[:type].constantize : nil
-
Namespace restrictions: Limit to specific namespaces
if params[:type].start_with?("Safe::") klass = params[:type].constantize end
-
Avoid if possible: Consider alternative designs that don't require dynamic class instantiation
When downloading benefit forms, users should only be able to access predefined, legitimate documents. Ask yourself:
- Why would users need to specify the class type?
- Why not use a simple ID-to-filename mapping?
- What classes could an attacker instantiate to cause harm?
Sections are divided by their OWASP Top Ten label (A1-A10) and marked as R4 and R5 for Rails 4 and 5.