CSV injection - Ruby

CSV injection - Ruby

Need

Prevention of CSV injection attacks

Context

  • Usage of Ruby for building dynamic and object-oriented applications
  • Usage of CSV for reading and writing CSV files
  • Usage of the User dependency for managing user-related functionality

Description

Non compliant code

        def export_to_csv
  @users = User.all
  csv = CSV.generate do |csv|
    csv << ["Name", "Email"]
    @users.each do |user|
      csv << [user.name, user.email]
    end
  end
  send_data csv, type: 'text/csv; charset=utf-8; header=present', disposition: "attachment; filename=users.csv"
end
        
        

This code is a method in a Ruby on Rails controller that exports all user data to a CSV file. The method first fetches all users from the database. It then generates a CSV file with the names and email addresses of the users. Finally, it sends the generated CSV file as a download to the client.

The vulnerability in this code is that it directly inserts user data into the CSV file without sanitizing or escaping it. This means that if a user has a name or email address that contains a CSV injection payload (such as =SUM(A1:A3)), this payload will be inserted into the CSV file. When this file is opened in a program like Excel, the payload will be interpreted as a formula and executed. This could lead to various forms of attacks, such as data exfiltration or remote code execution.

Steps

  • Validate and sanitize user input before including it in the CSV file.
  • Escape special characters that have a special meaning in CSV files, such as equals (=) sign.
  • Consider using a library or framework that provides built-in protection against CSV injection, such as the 'csv' library in Ruby.
  • Avoid using user input directly in formulas or calculations within the CSV file.
  • Educate users about the risks of CSV injection and encourage them to use caution when entering data.

Compliant code

        def export_to_csv
  @users = User.all
  csv = CSV.generate do |csv|
    csv << ["Name", "Email"]
    @users.each do |user|
      csv << [sanitize(user.name), sanitize(user.email)]
    end
  end
  send_data csv, type: 'text/csv; charset=utf-8; header=present', disposition: "attachment; filename=users.csv"
end

def sanitize(data)
  data.to_s.gsub(/[=+@-]/, '^')
end
        
        

The original code was vulnerable to CSV Injection, as it was directly using user input in the CSV file without any sanitization or validation. This could allow an attacker to inject formulas into fields that are later exported as part of CSV files and can be interpreted by Excel.

The fixed code includes a sanitize method that is used to sanitize the user input before including it in the CSV file. This method replaces special characters that have a special meaning in CSV files, such as equals (=) sign, with a caret (^) symbol. This effectively neutralizes any potential CSV injection attack.

The sanitize method is called for each user's name and email before they are added to the CSV file. This ensures that all user input is sanitized, regardless of where it comes from.

In addition to the code fix, it's also recommended to educate users about the risks of CSV injection and encourage them to use caution when entering data. This can help to further reduce the risk of CSV injection attacks.

References