Skip to main content

External control of file name or path

Need

Enforce secure file handling and prevent unauthorized access to sensitive files

Context

  • Usage of Ruby for building dynamic and object-oriented applications
  • Usage of Rails framework for building web applications

Description

Non compliant code

def upload
uploaded_io = params[:file]
File.open(Rails.root.join('public', 'uploads', params[:filename]), 'wb') do |file|
file.write(uploaded_io.read)
end
end

This code represents a file upload functionality in a Ruby on Rails application. The upload method takes a file from the request parameters (params[:file]) and writes it to a specified location on the server.

The vulnerability lies in the usage of params[:filename] to determine the name and path of the saved file. This parameter is controlled by the user, which means an attacker can manipulate it to overwrite any file on the server that the application has write access to.

For example, an attacker could set params[:filename] to '../../../config/database.yml', which would cause the application to overwrite its own database configuration file with the contents of the uploaded file.

This vulnerability is a type of Path Traversal vulnerability and it's also known as "Unrestricted File Upload". It can lead to various types of attacks like code execution, denial of service, sensitive information disclosure etc. depending on what the overwritten file does and the contents of the uploaded file.

Steps

  • Validate the file name and path before saving it.
  • Use a whitelist approach to only allow specific file extensions or formats.
  • Sanitize the file name to remove any potentially malicious characters or sequences.
  • Generate a unique file name to prevent overwriting existing files.
  • Store the uploaded files in a secure directory outside of the public folder.
  • Implement access controls to restrict unauthorized access to the uploaded files.
  • Consider using a file storage service or cloud storage provider for added security and scalability.

Compliant code

def upload
uploaded_io = params[:file]
filename = sanitize_filename(params[:filename])
raise "Invalid file format." unless valid_file_format?(filename)

unique_filename = generate_unique_filename(filename)
secure_directory = Rails.root.join('secure_uploads')

File.open(File.join(secure_directory, unique_filename), 'wb') do |file|
file.write(uploaded_io.read)
end
end

private

def sanitize_filename(filename)
# Remove any directory paths and replace invalid characters with underscore
filename.gsub(/[^0-9A-Za-z.\\-]/, '_').split('/').last
end

def valid_file_format?(filename)
# Add or modify the file extensions in the array as per your requirements
allowed_formats = ['.jpg', '.png', '.pdf']
allowed_formats.include? File.extname(filename)
end

def generate_unique_filename(filename)
# Generate a unique filename using the current timestamp
"#{Time.now.to_i}_#{filename}"
end

The updated code includes several security measures to prevent external control of file name or path.

  1. Sanitization of filename: The sanitize_filename method removes any directory paths and replaces invalid characters with underscore. This prevents directory traversal attacks.

  2. Validation of file format: The valid_file_format? method checks if the file extension is in the list of allowed formats. This is a whitelist approach that only allows specific file extensions.

  3. Generation of unique filename: The generate_unique_filename method generates a unique filename using the current timestamp. This prevents overwriting of existing files.

  4. Secure directory for uploads: The uploaded files are saved in a 'secure_uploads' directory which is outside of the public folder. This prevents unauthorized access to the uploaded files.

Remember to implement access controls at the application level to further restrict unauthorized access to the uploaded files. Also, consider using a file storage service or cloud storage provider for added security and scalability.

References