Product SiteDocumentation Site

3.1.1. Command injection

One of the most widespread types of attack is command injection attack, where data from untrusted source are being used by application to construct a command. The command is executed in the context of application and when the untrusted data is not sanitized properly, attacker might use this weakness to execute arbitrary command, potentially with elevated privileges.

3.1.1.1. SQL injection

SQL injection is the most common type of command injection, where application constructs a SQL query from user supplied data. If not properly escaped, malicious attacker might be able to execute any SQL command on application's database, that can lead to information disclosure, unauthorized modification of data, execution of administrative operations or destruction of data.
Ruby on Rails provides a good protection against SQL injection attacks by escaping several special SQL characters by default. However, this is far from making Rails applications safe against SQL injection. Consider a query against database:
User.where("name = '#{params[:name]}'")
This would be translated to following SQL query:
SELECT "users".* FROM "users" WHERE (name = 'username')
Such statement is vulnerable to SQL injection, since part of the SQL statement is passed as string in argument and Rails does not perform any escaping. Malicious string can match apostrophe and bracket in the statement, the follow with semicolon as statement separator and arbitrary SQL query. At the end double hyphens are necessary to comment out superfluous apostrophe:
>> params[:name] = "'); <arbitrary statement> --"
Using Rails console we can see this how such input is translated to a SQL query:
>> params[:name] = "noname'); SELECT name, password_digest FROM users where userid = 'admin' --"
=> "noname'); SELECT name, password_digest FROM users where userid = 'admin' --"

>> User.where("name = '#{params[:name]}'")
  User Load (2.4ms)  SELECT "users".* FROM "users" WHERE (name = 'noname'); SELECT name, password_digest FROM users where userid = 'admin' --')
=> [#<User name: "Administrator", password_digest: "$2a$10$m7XI628GGkdTH1JmkdMfluJyA360V1.QBtSbFMrc5Jwm...">]
3.1.1.1.1. (Un)safe Active Record queries
Safer approach is to pass either array or hash as an argument and use Rails escaping mechanism to protect against SQL, as in
User.where("name = ?", params[:name])
or
User.where(name: params[:name])
Alternatively, ActiveRecord also provides ActiveRecord::sanitize method which can be used to sanitize a string explicitly.
However, other ActiveRecord methods may be vulnerable to surprising SQL injection attacks, too. Consider exists? - when given string as an argument, it tries to convert it to integer, returning 0 when the conversion is impossible:
>> User.exists?("1")
  User Exists (0.9ms)  SELECT 1 AS one FROM "users" WHERE "users"."id" = 1 LIMIT 1
=> true

>> User.exists?("abc")
  User Exists (0.8ms)  SELECT 1 AS one FROM "users" WHERE "users"."id" = 0 LIMIT 1
=> false
This might look like a safe behaviour and imply the following query is safe from SQL injection attack:
User.exists?(params[:id])
The exists? method also accepts array as an argument - in which case first element of array is used directly in SQL query without escaping:
>> params[:id] = ["id = '1'"]
=> ["id = '1'"]

>> User.exists?(params[:id])
  User Exists (0.8ms)  SELECT 1 AS one FROM "users" WHERE (id = '1') LIMIT 1
=> true
This makes SQL injection attack possible:
>> params[:id] = ["1=1);UPDATE users SET password_digest='my_digest' WHERE userid='admin' --"]
=> ["1=1);UPDATE users SET password_digest='my_digest' WHERE userid='admin' --"]

>> User.exists?(params[:id])
  User Exists (67.6ms)  SELECT 1 AS one FROM "users" WHERE (1=1);UPDATE users SET password_digest='my_digest' WHERE userid='admin' --) LIMIT 1
=> false

>> User.where(userid: 'admin').first.password_digest
  User Load (1.0ms)  SELECT "users".* FROM "users" WHERE "users"."userid" = 'admin' LIMIT 1
  User Inst (0.4ms - 1rows)
=> "my_digest"
The last obstacle is passing the user supplied parameter as an Array. Usually, all values of parameters are passed by Rack as strings, but it is also possible to explicitly specify that value of parameter is supposed to be Array in the HTTP request. If the parameter looks like
key[]=value
Rack assumes it should be an Array and performs conversion before the parameter is passed to Rails application. HTTP request that exploits exists? method called on params[:id] then looks like this:
GET /controller/action?id[]=1 = 1);UPDATE users SET password_digest='my_digest' WHERE userid='admin' --

3.1.1.2. OS command injection

Another common vulnerability is invoking underlying OS commands with user supplied input without proper sanitization. Ruby provides several commands that can be used and if user's input is used as parameter to a system command without sanitization, he might be able to misuse it to execute arbitrary command.
For example, when application contains call like
system "echo Hello #{params[:name]}!"
user can use semicolon to terminate echo command and invoke command of his choice:
>> params[:name] = 'Joe;rm -rf /'
=> "Joe;touch /tmp/abc"
>> system "echo Hello #{params[:name]}"
Hello Joe
=> true        # and rm gets executed
system command can be used to explicitly separate OS command to invoke from the arguments passed to it:
system(command, *parameters)

Important

Whenever system command is executed with arguments from untrusted source, extra care must be taken to prevent arbitrary code execution.

3.1.1.3. References