In Rails 3 I could use sanitize_sql_array to sanitize raw SQL for those occassional moments where a raw SQL query is needed. But this appears to have been removed in Rails 4, or not so much removed, but moved to ActiveRecord::Sanitization. However, I can not figure out how to call sanitize_sql_array now, so what's the best way to sanitize raw SQL in Rails 4?
I want to clarify that I am talking about a full raw SQL query here, not using Rail's models. I'm aware that this is not best practice, this is just what I have to do for this specific query since it can't be represented by Rails's nice ActiveRecord interface (Trust me, I've tried).
Here is a sample call, which is obviously simpler than what my query actually looks like:
query = "SELECT * FROM users
LEFT OUTER JOIN posts ON users.id=posts.user_id
AND posts.topic_id = '#{topic.id}'"
# ^- Obviously bad and very vulnerable, this is what we're trying to fix
ActiveRecord::Base.connection.select_all(query)
If you really need to write raw SQL you can use quote to sanitize it:
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)
From the Active Record docs, the best way to sanitize a SQL query is to avoid to build our own conditions as pure strings, in other words, inserts the parameters directly into the query, like this:
User.find_by("user_name = '#{user_name}' AND password = '#{password}'")
and instead use array or hash conditions.
Array conditions:
Client.where("orders_count = ? AND locked = ?", params[:orders], false)
Hash conditions:
Client.where(is_active: true)
A clarifying example:
class User < ActiveRecord::Base
# UNSAFE - susceptible to SQL-injection attacks
def self.authenticate_unsafely(user_name, password)
where("user_name = '#{user_name}' AND password = '#{password}'").first
end
# SAFE
def self.authenticate_safely(user_name, password)
where("user_name = ? AND password = ?", user_name, password).first
end
# SAFE
def self.authenticate_safely_simply(user_name, password)
where(user_name: user_name, password: password).first
end
end
Here are some references:
http://api.rubyonrails.org/classes/ActiveRecord/Base.html#method-c-sanitize_sql_array
http://guides.rubyonrails.org/active_record_querying.html
The quote method and other ActiveRecord::Base sanitization methods have been deprecated, and were never part of the public API.
https://github.com/rails/rails/issues/28947
The official sanitization methods are
http://api.rubyonrails.org/classes/ActiveRecord/Sanitization/ClassMethods.html
Related
I'm just wondering if there's a way to access the raw SQL that's executed for an update_all ActiveRecord request. As an example, take the simple example below:
Something.update_all( ["to_update = ?"], ["id = ?" my_id] )
In the rails console I can see the raw SQL statement so I'm guessing it's available for me to access in some way?
PS - I'm specifically interested in update_all and can't change it to anything else.
Thanks!
If you look at the way update_all is implemented you can't call to_sql on it like you can on relations since it executes directly and returns an integer (the number of rows executed).
There is no way to tap into the flow or get the desired result except by duplicating the entire method and changing the last line:
module ActiveRecord
# = Active Record \Relation
class Relation
def update_all_to_sql(updates)
raise ArgumentError, "Empty list of attributes to change" if updates.blank?
if eager_loading?
relation = apply_join_dependency
return relation.update_all(updates)
end
stmt = Arel::UpdateManager.new
stmt.set Arel.sql(#klass.sanitize_sql_for_assignment(updates))
stmt.table(table)
if has_join_values? || offset_value
#klass.connection.join_to_update(stmt, arel, arel_attribute(primary_key))
else
stmt.key = arel_attribute(primary_key)
stmt.take(arel.limit)
stmt.order(*arel.orders)
stmt.wheres = arel.constraints
end
#- #klass.connection.update stmt, "#{#klass} Update All"
stmt.to_sql
end
end
end
The reason you see the log statements is that they are logged by the connection when it executes the statements. While you can override the logging its not really possible to do it for calls from a single AR method.
If you have set RAILS_LOG_LEVEL=debug Rails shows you which SQL statement it executed.
# Start Rails console in debug mode
$ RAILS_LOG_LEVEL=debug rails c
# Run your query
[1] pry(main)> Something.update_all( ["to_update = ?"], ["id = ?" my_id] )
SQL (619.8ms) UPDATE "somethings" WHERE id = 123 SET to_update = my_id;
# ^it prints out the query it executed
I have a rails API that handles requests from my front end. These requests include query parameters in the url for refining and sorting results from the database. An example URL query looks like this:
http://localhost:8000/clients?_sort=name&_order=DESC&_start=0&_end=10
My index method in my controller grabs these params and uses them for filtering and sorting:
def index
#all_clients = Client.all
response.headers['X-Total-Count'] = #all_clients.count
if (Client.column_names.include?(params[:_sort]))
if (params[:_order] == 'ASC')
#clients = Client.filtered(params[:_start].to_i, params[:_end].to_i).order("#{params[:_sort]} asc")
else
#clients = Client.filtered(params[:_start].to_i, params[:_end].to_i).order("#{params[:_sort]} desc")
end
end
json_response(#clients || #all_clients)
end
the filtered method is a scope which looks like this:
scope :filtered, -> (_start, _end) { limit(_end-_start).offset(_start) }
My question is this: by using Client.column_names.include? to check if params[:_sort] is a valid attribute to sort by, am I effectively whitelisting against SQL Injection? If not, how could I alter this code to protect against SQL Injection?
The important thing to consider here is not "the whitelisting of params" (since you're already cherry-picking which parameters to use anyway, rather than blindly using the whole params hash for something), but rather how you are constructing the SQL.
There are two potential injection areas in the code:
limit(_end-_start)
Is this vulnerable? No. If _end or _start are anything besides integers, then the code will just fail with an error message - such as:
NoMethodError: undefined method `-' for "DROP_TABLE":String
or
ArgumentError: invalid value for Integer(): 3.14159
order("#{params[:_sort]} desc")
Is this vulnerable? Yes. (But not easily.) This page gives a concrete example:
params[:_sort] = "(CASE SUBSTR(password, 1, 1) WHEN 's' THEN 0 else 1 END)"
You should never use direct string interpolation in SQL, unless you are absolutely 100% sure that the string is "safe". In this case, you could just write it as:
order(params[:_sort] => :asc)
I have an object with a bunch of attributes that represent searchable model attributes, and I would like to dynamically create an sql query using only the attributes that are set. I created the method below, but I believe it is susceptible to sql injection attacks. I did some research and read over the rails active record query interface guide, but it seems like the where condition always needs a statically defined string as the first parameter. I also tried to find a way to sanitize the sql string produced by my method, but it doesn't seem like there is a good way to do that either.
How can I do this better? Should I use a where condition or just somehow sanitize this sql string? Thanks.
def query_string
to_return = ""
self.instance_values.symbolize_keys.each do |attr_name, attr_value|
if defined?(attr_value) and !attr_value.blank?
to_return << "#{attr_name} LIKE '%#{attr_value}%' and "
end
end
to_return.chomp(" and ")
end
Your approach is a little off as you're trying to solve the wrong problem. You're trying to build a string to hand to ActiveRecord so that it can build a query when you should simply be trying to build a query.
When you say something like:
Model.where('a and b')
that's the same as saying:
Model.where('a').where('b')
and you can say:
Model.where('c like ?', pattern)
instead of:
Model.where("c like '#{pattern}'")
Combining those two ideas with your self.instance_values you could get something like:
def query
self.instance_values.select { |_, v| v.present? }.inject(YourModel) do |q, (name, value)|
q.where("#{name} like ?", "%#{value}%")
end
end
or even:
def query
empties = ->(_, v) { v.blank? }
add_to_query = ->(q, (n, v)) { q.where("#{n} like ?", "%#{v}%") }
instance_values.reject(&empties)
.inject(YourModel, &add_to_query)
end
Those assume that you've properly whitelisted all your instance variables. If you haven't then you should.
Is there a way to convert a Rails model into an insert query?
For instance, if I have a model like:
m = Model.new
m.url = "url"
m.header = "header"
How can I get the corresponding SQL query ActiveRecord would generate if I did m.save?
I want to get: "INSERT INTO models(url, header) VALUES('url', 'header')" if possible.
Note: I don't want to actually save the model and get the query back (from log file, etc). I want to get the query IF I chose to save it.
On Rails 4.1, I found the below code snippet working:
record = Post.new(:title => 'Yay', :body => 'This is some insert SQL')
record.class.arel_table.create_insert
.tap { |im| im.insert(record.send(
:arel_attributes_with_values_for_create,
record.attribute_names)) }
.to_sql
Thanks to https://coderwall.com/p/obrxhq/how-to-generate-activerecord-insert-sql
Tested in Rails 3.2.13: I think I got it right this time, it definitely does not persist to the db this time. It also won't fire validations or callbacks so anything they change won't be in the results unless you've called them some other way.
Save this in lib as insert_sqlable.rb and you can then
#in your models or you can send it to ActiveRecord::Base
include InsertSqlable
Then it is model.insert_sql to see it.
#lib/insert_sqlable
module InsertSqlable
def insert_sql
values = arel_attributes_values
primary_key_value = nil
if self.class.primary_key && Hash === values
primary_key_value = values[values.keys.find { |k|
k.name == self.class.primary_key
}]
if !primary_key_value && connection.prefetch_primary_key?(self.class.table_name)
primary_key_value = connection.next_sequence_value(self.class.sequence_name)
values[self.class.arel_table[self.class.primary_key]] = primary_key_value
end
end
im = self.class.arel_table.create_insert
im.into self.class.arel_table
conn = self.class.connection
substitutes = values.sort_by { |arel_attr,_| arel_attr.name }
binds = substitutes.map do |arel_attr, value|
[self.class.columns_hash[arel_attr.name], value]
end
substitutes.each_with_index do |tuple, i|
tuple[1] = conn.substitute_at(binds[i][0], i)
end
if values.empty? # empty insert
im.values = Arel.sql(self.class.connectionconnection.empty_insert_statement_value)
else
im.insert substitutes
end
conn.to_sql(im,binds)
end
end
It turns out the code is in ActiveRecord::Relation and not ActiveRecord::Persistence. The only significant change is the last line which generates the sql instead of performing it.
If you dont want to save the model you call m.destroy when you are done with the object.
You can log the sql query by debugging it like this
Rails.logger.debug "INSERT INTO models(url, header) VALUES(#{m.url}, #{m.header}).inspect
After search a lot over the Internet and forums, I think I found a better solution for your problem: just requires two line of code.
I found a good gem that do exactly what you want, but this gem only works for Rails 3.2 and older. I talked with author and he doesn't want support this gem anymore. So I discovered by myself how to support Rails 4.0 and now I'm maintaining this gem.
Download the "models-to-sql-rails" gem here, supporting Rails 4.0 and older.
With this gem, you can easily do the following. (the examples inside values are just a joke, you will get the correct values when using it in your object).
For objects:
object.to_sql_insert
# INSERT INTO modelName (field1, field2) VALUES ('Wow, amaze gem', 'much doge')
For array of objets:
array_of_objects.to_sql_insert
# INSERT INTO modelName (field1, field2) VALUES ('Awesome doge', "im fucking cop")
# INSERT INTO modelName (field1, field2) VALUES ('much profit', 'much doge')
# (...)
Just see the Github of this project and you'll find how to install and use this wonderful gem.
Given a query like:
current_user.conversations.where("params[:projectid] = ?", projectid).limit(10).find(:all)
params[:projectid] is being sent from jQuery ajax. Sometimes that is an integer and the above works fine. But if the use selects "All Projects, that's a value of '' which rails turns into 0. which yields an invalid query
How with rails do you say search params[:projectid] = ? if defined?
Thanks
I think you may have mistyped the query a bit. "params[:projectid] = ?" shouldn't be a valid query condition under any circumstances.
In any case, you could do some sort of conditional statement:
if params[:project_id].blank?
#conversations = current_user.conversations.limit(10)
else
#conversations = current_user.conversations.where("project_id = ?", params[:project_id]).limit(10)
end
Although, I'd probably prefer something like this:
#conversations = current_user.conversations.limit(10)
#converstaions.where("project_id = ?", params[:project_id]) unless params[:project_id].blank?
Sidenotes:
You don't have to use .find(:all). Rails will automatically execute the query when the resultset is required (such as when you do #conversations.each).
Wherever possible, try to adhere to Rails' snakecasing naming scheme (eg. project_id as opposed to projectid). You'll save yourself and collaborators a lot of headaches in the long run.
Thanks but if the where query has lets say 3 params, project_id, project_status, ... for example, then the unless idea won't work. I'm shocked that Rails doesn't have a better way to handle conditional query params
EDIT: If you have multiple params that could be a part of the query, consider the fact that where takes a hash as its argument. With that, you can easily build a parameter hash dynamically, and pass it to where. Something like this, maybe:
conditions = [:project_id, :project_status, :something_else].inject({}) do |hsh, field|
hsh[field] = params[field] unless params[field].blank?
hsh
end
#conversations = current_user.conversations.where(conditions).limit(10)
In the above case, you'd loop over all fields in the array, and add each one of them to the resulting hash unless it's blank. Then, you pass the hash to the where function, and everything's fine and dandy.
I didn't understand why you put:
where("params[:projectid] = ?", projectid)
if you receive params[:project] from the ajax request, the query string shouldn't be:
where("projectid = ?", params[:projectid])
intead?
And if you are receiving an empty string ('') as the parameter you can always test for:
unless params[:projectid].blank?
I don't think i undestood your question, but i hope this helps.