Sql injection from brakeman for Order by field in rails - ruby-on-rails

Rails version: 5.1.7
Getting Brakeman vulnerability warning for order by field with where clause query in rails
Can, anyone help me to resolve this issue?
Thanks in advance
Query for your reference:
DropdownValue.where(:dropdown_id => PreferenceValue.find(params[:id]).preference.dropdown_id).order("field(id, #{PreferenceValue.find(params[:id]).dropdown_value_ids.join(",")})")

With ActiveRecord sanitize_sql_for_order, i fixed the brakeman sql injection warning
DropdownValue.where(dropdown_id: #preference_value.preference.dropdown_id).order(ActiveRecord::Base.send(:sanitize_sql_for_order, "field(id, #{#preference_value.dropdown_value_ids.join(',')})" ))

I was getting that when using .order("field(id, ...)
Problem
I was trying this inside a model:
scope :order_by_type, lambda {
order_rule = %w[B A C]
order("my_field, #{order_rule}")
}
Then I was getting:
ActiveSupport::DeprecationException: DEPRECATION WARNING: Dangerous query method (method whose arguments are used as raw SQL) called with non-attribute argument(s): "FIELD(my_field, 'B','A','C')"
Solution
I ended up using this neat gem https://github.com/panorama-ed/order_as_specified
It got as simple as
MyModel.order_as_specified(my_field: order_rule)

While not quite a SQL injection vulnerability this whole query is an absolute mess and something that probably should be solved via joining or indirect associations.
You can fix the breakman warning by using a bound parameter instead:
.order("field(id, ?)", PreferenceValue.find(params[:id]).dropdown_value_ids)

It's happening because of this line
"#{PreferenceValue.find(params[:id]).dropdown_value_ids.join(",")}"
do this,
dropdown_ids = PreferenceValue.find(params[:id]).dropdown_value_ids.join(",")
and then in main query
"field(id, #{PreferenceValue.connection.quote(dropdown_ids)})"
connection.quote whitelists the interpolated values.
Give it a try.

Regarding the first answer,
there is no prepared statement in .order() method and this is not a solution.
For my case this one was the working solution:
#skus = skus_scope.preload(...).order(ActiveRecord::Base.send(:sanitize_sql_for_order, "field(id, #{sku_ids.join(',')})"))
You could also go for something cleaner like:
#skus = skus_scope.preload(...).order(sanitize_sql_for_order(["field(id, ?)", sku_ids]))

Related

rails query interface joins argument error

I am trying to make use of Active Record Query Interface joins
https://guides.rubyonrails.org/v2.3.11/active_record_querying.html#using-arrayhash-of-named-associations
I have the associations for the two models working correctly, but when I try to run this query...
Package.all :joins => :drugs
I receive the:
ArgumentError: wrong number of arguments (given 1, expected 0)
does anyone know why this might be?
What version of Rails are you running? The documentation is pretty old (v2.3) and a lot has changed since then.
Try using Package.joins(:drugs).all instead.

How does one convert ARRAY_TO_STRING and ARRAY_AGG into arel?

I have an Arel statement that looks like the following:
vulns.project(vulns[:id],
vulns[:cve_id],
vulns[:severity],
vulns[:published_on],
vulns[:description],
"ARRAY_TO_STRING((ARRAY_AGG(#{releases[:version]} ORDER BY #{releases[:released_on]} DESC))[1:10], ', ')")
.join(releases_vulns)
.on(releases_vulns[:vulnerability_id].eq(vulns[:id]))
The toughest part that I am running into is the array_agg and array_to_string. I've checked arel documentation and I did not see a particular matching method to work out a query like this. Has anyone encountered this problem? I could use some help. Thank You.
The way to convert it is the following:
Arel::Nodes::NamedFunction.new 'array_agg', [expression goes here]
Arel::Nodes::NamedFunction.new 'array_to_string', [array_input, Arel::Nodes::Quoted.new(',')]

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

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

Adding additional conditions to find_by_id in Rails 3

I'm using Rails 3.2.11 on a Mac
I have this statement that works fine:
object= Object.find_by_id(params[:id])
I'm trying to add a condition to that statement so I did this:
object = Object.where("id = :id AND level <= :level",{:id => params[:id], :level => current_user.level})
Will there be any risk in this method? Any alternatives?
There's no risk presented by this statement, provided that ActiveRecord continues to uphold the contract of sanitizing input. An alternative would be a scope, but that's really just doing the same thing in a different syntax.
One thing you could do is set a default scope that defines the level restriction, then you could just do a standard find_by_id. But if that's undesirable, just use the syntax properly:
Object.where(id: params[:id], level: current_user.level)
There is no risk but the way it is defined is not easy to understand.
The simple statement will work:
object = Object.where("id = ? AND level <= ?",{params[:id], current_user.level})
as long as you let rails handle the sanitizing of your values, you'll have no issues. what i mean by this is run a sql command from rails using user input like
Object.where("id = #{params[:id]} AND level <= #{current_user.level}")
will be vulnerable to sql injection

Resources