SQL Injection and ActiveRecord - ruby-on-rails

Is this safe from SQL injection:
Guest.where(:event_id => params[:id])
I am sending in params[:id] without doing any type of sanitization.
and in general, are all of those activerecord method safe? (like where, joins, etc..)
And if not, what is the best practise to be safe? Also, please is there any caveats/edge cases I should be aware of?
Thanks

All of ActiveRecord's query-building methods, like where, group, order, and so on, are safe against SQL injection AS LONG AS you do not pass them raw SQL strings. This is vulnerable to SQL injection:
Model.where("event_id = #{params[:id]}")
When you pass a string to a query-building method like that, the string will be inserted directly into the generated SQL query. This is useful sometimes, but it does raise the danger of an injection vulnerability. On the other hand, when you pass a hash of values, like this:
Model.where(event_id: params[:id])
...then AR automatically quotes the values for you, protecting you against SQL injection.

Yes, your code is safely being cleansed before it is run on the database. Rails protects you from sql injection by automatically sanitizing input.
THE EXCEPTION is string interpolation:
Guest.where("event_id = #{params[:id]}") # NEVER do this
Use one of these 2 options instead:
Guest.where(:event_id => params[:id]) # if you want pure ruby, use this
# OR
Guest.where("event_id = ?", params[:id]) # if you prefer raw SQL, use this
Check out the Rails Guide on security for more information related to sql injection as well as other common attacks.

If you really need to use raw sql, you could use quote to prevent SQL injection
Here is an example that has been copied from here
conn = ActiveRecord::Base.connection
name = conn.quote("John O'Neil")
title = conn.quote(nil)
query = "INSERT INTO users (name,title) VALUES (#{name}, #{title})"
conn.execute(query)

Related

parameter based joins not susceptible for sql injection

I want to do non-standard query with join based on the parameters.
For example I have 2 tables: a_examples and b_examples both with fields: field_x and field_y.
I want to join rows when both tables have the same values on field_x(or field_y).
Example query can looks like this:
AExample.joins('INNER JOIN b_examples ON b_examples.field_x = a_examples.field_x')
The problem occurs when I have field name based on parameter.
For example I have variable field_name and want to use it for query. I can do it like this:
AExample.joins("INNER JOIN b_examples ON b_examples.#{field_name} = a_examples.#{field_name}")
This query works, but is susceptible for sql injection.
For where clause we have special syntax with ? to avoid sql injection but there isnt any such thing for joins. How can I make this query safe?
Do not attempt this:
(explanation below)
You can use the ActiveRecord::Sanitization module and write something like the following, inside your ActiveRecord model:
AExample.joins("INNER JOIN b_examples ON b_examples.#{sanitize_sql(field_name)} = a_examples.#{sanitize_sql(field_name)}")
Or you can include the module somewhere else and use it there (e.g. your controller).
Do use this, instead: (included from another answer)
AExample.joins("INNER JOIN b_examples ON b_examples.#{ActiveRecord::Base.connection.quote_column_name(field_name)} = a_examples.#{ActiveRecord::Base.connection.quote_column_name(field_name)}")
It will raise an error if the column is not found, preventing malicious code from entering your query.
However I wouldn't do this in my app as it looks suspicious, other programers may not understand what is happening, it may be implemented wrong, solid testing should be included, it may have bugs and such. In your problem, you only need to construct two different queries, with that information I would write something like:
case dynamic_field
when 'field_x'
AExample.joins('INNER JOIN b_examples ON b_examples.field_x = a_examples.field_x')
when 'field_y'
AExample.joins('INNER JOIN b_examples ON b_examples.field_y = a_examples.field_y')
else
raise "Some suspicious parameter was sent!: #{dynamic_field}"
end
Or even write scopes on your model and avoid this code to be flying around.
With problems of this nature, as with encryption, try to find a workaround and avoid implementing your own solutions as much as possible.
EDIT:
The method sanitize_sql is intended to sanitize conditions for a WHERE clause (ActiveRecord::Sanitization):
Accepts an array or string of SQL conditions and sanitizes them into a valid SQL fragment for a WHERE clause.
It is not an option as you try to sanitize for an INNER JOIN, or an ON clause.
Note that the ActiveRecord::Sanitization module only has options for WHERE, SET, ORDER and LIKE clauses. I was unable to find a sanitization method for a column name, an INNER JOIN or an ON clause. Perhaps is a useful funcionality that should be added on Rails on further version.
Using sanitize_sql with a string passes it almost unfiltered, so if the field_name variable has some malicious code as:
"field_x = a_examples.field_x; DROP TABLE a_examples; --"
It will be included in your query, without any error being raised.
This solution is not safe, and is for reasons like these that we should avoid writing code of this nature. Perhaps you find something helpful with Arel or other gems, but I would strongly advice not to.
EDIT 2:
Added the working solution to escape a column name. It raises an error if malicious code is being entered, as the column with that name will not be found.
You can sanitize parameters using ActiveRecord::Base.connection.quote(string) or even .quote_column

Rails Brakeman SQL injection warning while accessing an oracle view/function

I have rails code that is consuming an oracle view/function.
This is my code:
def run_query
connection.exec_query(
"SELECT * FROM TABLE(FN_REQ(#{demo_type_param},#{demo_tid_param}}))")
end
When run Brakeman analyzer it warns of possible "sql injection attack"
I need to understand if this is a valid warning, if so, how do I remediate it?
Since this is a function & not an actual table, I am not sure what's the right way.
If it was a normal model, i would have just followed this pattern:
Model.where("mycolumn1= ? AND mycolumn2= ?", demo_type_param, demo_tid_param).first
Yes, it is real. Almost every time, you build any SQL query from simply concatenating variables, you are vulnerable to SQL injection. Generally, an SQL injection happens each time when data inserted into the query can look like valid SQL and can result in additional queries executed.
The only solution is to manually enforce appropriate escaping or to use prepared statements, with the latter being the preferred solution.
With ActiveRecord / Rails, you can use exec_query with binds directly
sql = 'SELECT * FROM TABLE(FN_REQ(?,?))'
connection.exec_query(sql, 'my query', [demo_type_param, demo_tid_param])
Here, Rails will prepare the statement on the database and add the parameters to it on execution, ensuring that everything is correctly escaped and save from SQL injection.

Is this code snippet from Rails vulnerable to sqli ? if so what is the payload

Am used to working with PHP and Prepared statement, now when i was looking at the following piece of code from rails ( since i a new to rails and Not sure about the syntax and stuff ) , i was wondering if the code is prone to SQLI injection
Code snippet (controller ) , param q is the value from a search box :
def index
query = %w(% %).join params[:q].to_s.gsub('%', '\\%').gsub('_', '\\_')
#posts = Post.where("name LIKE ? OR body LIKE ?", query, query).order(params[:order])
end
Thanks
What you have is intended to be safe. If it is not, then it's a bug in Rails.
.where accepts conditions in several formats. One is a raw string. If you build that string yourself, all bets are off and you are vulnerable.
As some recent documentation says:
Note that building your own string from user input may expose your
application to injection attacks if not done properly. As an
alternative, it is recommended to use one of the following methods.
In other words, ALL of the "following" (every other supported way) ways of doing things, are OK.
So if you are doing .where with anything other than string parameter, you should be fine.
As long as you don't interpolate within your where clause it should be safe. There are some good examples of SQL injection code here

Could this ActiveRecord/SQL statement in my code base be susceptible to SQL injection?

I was reading up on SQL injection attacks from the Rails Guides and was wondering if the below line could be susceptible to a SQL Injection attack:
accounts.where("lower(name) LIKE '%#{params[:query].downcase}%'")
If I read and understand the guide correctly, could a user do something like
`'\''; DROP TABLE users;`
Would that drop my table?
------EDIT------
Same for these examples:
Account.where(id: account_id).first
current_user.actions.where(id: params[:clear_action_reminder]).first
edits.where.not(account_id: non_human_ids)
Rating.where(project_id: #project.id, account_id: current_user.id).first_or_initialize
These are of course application specific queries but because there is no ? in the query could these all potentially be disastrous?
Account.where("lower(name) LIKE '%#{params[:query].downcase}%'")
Yes, the above query is vulnerable.
Building your own string from user input may expose your application to injection attacks.
Instead of passing a string to the conditions option, you can pass an array to sanitize tainted strings like this:
query = params[:query].downcase
Account.where('lower(name) LIKE ?', "%#{query}%")
This will make your code safe & readable in complex queries. If #where is called with multiple arguments, these are treated as if they were passed as the elements of a single array. You can call with hash as well. Both array and hash are safe.
Your following queries are safe:
Account.where(id: account_id).first
current_user.actions.where(id: params[:clear_action_reminder]).first
Edit.where.not(account_id: non_human_ids)
Rating.where(project_id: #project.id, account_id: current_user.id).first_or_initialize

What is the best possible way to avoid the sql injection?

I am using ruby 1.8.7 and rails 2.3.2
The following code is prone to sql injection
params[:id] = "1) OR 1=1--"
User.delete_all("id = #{params[:id]}")
My question is by doing the following will be the best solution to avoid sql injection or not. If not then what is the best way to do so?
User.delete_all("id = #{params[:id].to_i}")
What about:
User.where(id: params[:id]).delete_all
Ok sorry for Rails 2.x its:
User.delete_all(["id = ?", params[:id]])
Check doc
Btw, be sure you want to use delete_all instead of destroy_all, the former doesn't trigger callbacks.
You can use this also
User.delete(params[:id])
The other answers answer this well for Rails and it'll work fine if you follow their suggestions. In a more generic setting when you have to handle this yourself you can typically use a regular expression to extract a value that's in an expected format. This is really simple with an integer id. Think of it like this:
if params[:id] =~ /(\d+)/
safe_id = $1.to_i
# do something with safe_id now
end
That gets a little more complicated when you're handling strings and arbitrary data. If you have to handle such data then you can use the quoting methods available for the database adapters. In Rails this is ultimately rolled into a consistent interface:
safe_string = ActiveRecord::Base.connection.quote(unsafe_string)
For most database systems this will handle single quotes and backslashes in a special manner.
If you're outside of Rails you will have to use the quoting methods specific to your database adapter, but usage is quite similar.
The takeaway:
If your data has a particular format, enforce the format with a regular expression
Otherwise, use your database adapter's quoting function to make the data "safe" for use in a query
Rails will handle most of this for you if you properly use the various methods and "conditions"
Use the rails methods to pass your where options. You can always hardcode them, as in the example that you give, but the usual way would be something like:
User.where(:id => params[:id]).delete_all
User.where("id = ?", params[:id]).delete_all
User.where("id = :id", :id => params[:id]).delete_all
They are well tested and in case a new vulnerability is detected, an update will fix the problem and your code will not need to be changed.
By the way, if you just want to delete 1 record based on its id, what I would do is:
User.find(params[:id]).destroy

Resources