Explain how order clause can be exploited in Rails - sql

I am having difficulty understanding how this section from this website on Rails SQL Injections works.
Taking advantage of SQL injection in ORDER BY clauses is tricky, but a CASE statement can be used to test other fields, switching the sort column for true or false. While it can take many queries, an attacker can determine the value of the field.
Can someone explain? The bit where they say "switching the sort column for true or false" is the one that is hard to understand because I don't get how that would enable an attacker to reveal the value of another field.

If you are trying to determine the value of a field you know is in the table, but not being returned in the select you could iterate over it in the order by, until you get the value:
ORDER BY CASE WHEN variableIdLikeToDiscover < 'N' then 1 else 0 end
Then see whether it is greater than or less than 'N'. If it's less than, next you could try:
ORDER BY CASE WHEN variableIdLikeToDiscover < 'F' then 1 else 0 end
And so on and so forth until you have (eventually) determined the value.

The example shows that the :order parameter will be placed at the end of the statement, so if you add a comparison that is always true at the end, it will update all the rows.
For example, if you make a non-malicious order, it will be like:
params[:order] = "name"
User.update_all("admin = 1", "name LIKE 'B%'" , { :order => params[:order] })
The generated SQL will be:
UPDATE "users" SET admin = 1 WHERE "users"."id" IN (SELECT "users"."id" FROM "users" WHERE (name LIKE 'B%') ORDER BY name))
So, the update will be made on the users that have name LIKE 'B%'.
But, when the param is set to:
params[:order] = "name) OR 1=1;"
The generated SQL will be:
UPDATE "users" SET admin = 1 WHERE "users"."id" IN (SELECT "users"."id" FROM "users" WHERE (name LIKE 'B%') ORDER BY name) OR 1=1;)
Basically, an OR comparison will be added to the original WHERE, and the comparison will be: Update the users that have name LIKE 'B%' or 1=1. This will cause all the users to be update to admin=1 (in the given example).
Then the attacker can log in with any user an have admin privileges.
Hope it helps...

Related

ActiveRecord 'where not' and 'where' in same query

I would like to build a 'waiting_on' list where the users in the list meet two conditions: they do not belong to the current auction (User.where.not(auction: #auction)) and they do belong to the current_game (User.where(game: current_game).
How can I populate the array #waiting_on with users who meet both these requirements in ActiveRecord?
Here's my pseudocode attempt:
#waiting_on = User.where not(auction: #auction) and game: current_game
Update:
At the moment I have got it working like this, but it is a little ugly:
users_in_auction = User.where(auction: #auction)
users_in_game = User.where(game: current_game)
#waiting_on = users_in_game - users_in_auction
I have been trying this: User.where(game: current_game).where.not(auction: #auction), however the fact that the user that is not part of the auction has a nil value for auction_id seems to be messing it up. The SQL query output seemed to be exactly what I needed: SELECT "users".* FROM "users" WHERE "users"."game_id" = 3 AND ("users"."auction_id" != 2)
You can do it in one query by adding a null check:
User.where.not(auction: auction).or(User.where(auction: nil)).where(game: game)
This produces this SQL, which I think is what you want if I'm reading the question right:
SELECT "users".* FROM "users"
WHERE (("users"."auction_id" != 7) OR "users"."auction_id" IS NULL)
AND "users"."game_id" = 5

What does it mean for Rails merge to win a where clause?

The rails guide says, "If we do want the last where clause to win then Relation#merge can be used."
In the below example, why use merge if you can just have User.where(User.inactive)?
User.active.merge(User.inactive)
# SELECT "users".* FROM "users" WHERE "users"."state" = 'inactive'
User.active.merge(User.inactive) - you've applied two opposite scopes. state = "active" and state = "inactive". Joined with AND they will make you query nothing (state = "active" AND state = "inactive"). I think they said that merge will beat a scope used before #merge.

Activerecord or SQL statement to find users where something very specific happens in the join table

I have a User that have_many MyVersions associated.
A MyVersion is created every time the column "profile_id" or "state" are changed in User. MyVersion has these columns:
user_id, object_changed (profile_id or state), before, after
I need to find Users that where active and had a specific profile at a specific time. Meaning, to find all Users when this happens in its associated my_versions:
my_versions was created_at before a date AND where :object_changed is 'state' And within that time range:
1.1 THEN (is not AND) find the last one and only select the user if the value for :after is 'active'
my_versions was created_at before a date AND where :object_changed is 'profile_id' And within that time range:
2.1 THEN find the last one and only select the user if the value for :after is '1'
Select only users that match both 1.1 and 2.1
EDIT 1: Apparently I'm getting closer but still not sure this is getting what I need:
active_user_ids = User.joins(:my_versions).merge(MyVersion.where(
"my_versions.created_at = (SELECT MAX(created_at) from my_versions WHERE
user_id = users.id AND created_at < '2016-01-01' AND object_changed = 'state')
AND my_versions.after = 'activo'")).pluck(:id)
Now I have all user IDS that were active at the time (do I?). Then I can do the same for the profile, but passing also the previous IDS to combine the results properly:
active_and_right_profile =
User.joins(:my_versions).merge(MyVersion.where(
"my_versions.created_at = (SELECT MAX(created_at) from my_versions WHERE
user_id = users.id AND created_at < '2016-01-01' AND object_changed = 'profile_id')
AND my_versions.after = 1")).where(id: active_user_ids)
It doesn't look pretty and I'm not sure I'm getting what I describe above in the specifications. First tests appears to be right but I have many doubts because I don't understand some parts of the query:
Apparently when I use "SELECT MAX ... where user_id = users.id" I'm requiring the top value for each user id. Is that right?
If that's true, I'm getting and array of results and I'm passing it to the first created_at =. This means that if I have other versions outside of the scope of this query but with the exact timestamp, they will be in the results. Is that correct? That's relevant to me because few of those versions.created_at are being updated manually.
How does it look? Is there a way to make it better with only one query? Is there a way to avoid the problem of searching exact created_at values that I mention above?
Thanks!!
Previous attempts:
I tried this:
Class User...
scope :active_at, -> (date) {
joins(:my_versions).merge(MyVersion.on_state.before_date(date)
.where("my_versions.created_at = (SELECT MAX(created_at) FROM my_versions WHERE user_id = users.id AND after = 'activo')"))
}
But this create the folliwing query:
SELECT `users`.* FROM `users` INNER JOIN `my_versions` ON `my_versions`.`user_id` = `users`.`id` WHERE `my_versions`.`object_changed` = 'state' AND (my_versions.created_at < '2016-01-31') AND (my_versions.created_at = (SELECT MAX(created_at) FROM my_versions WHERE user_id = users.id AND after = 'activo'))
This is not what I need.

Query: getting the last record for each member

Given a table ("Table") as follows (sorry about the CSV style since I don't know how to make it look like a table with the Stack Overflow editor):
id,member,data,start,end
1,001,abc,12/1/2012,12/31/2999
2,001,def,1/1/2009,11/30/2012
3,002,ghi,1/1/2009,12/31/2999
4,003,jkl,1/1/2012,10/31/2012
5,003,mno,8/1/2011,12/31/2011
If using Ruby Sequel, how should I write my query so I will get the following dataset in return.
id,member,data,start,end
1,001,abc,12/1/2012,12/31/2999
3,002,ghi,1/1/2009,12/31/2999
4,003,jkl,1/1/2012,10/31/2012
I get the most current (largest end date value) record for EACH (distinct) member from the original table.
I can get the answer if I convert the table to an Array, but I am looking for a solution in SQL or Ruby Sequel query, if possible. Thank you.
Extra credit: The title of this post is lame...but I can't come up with a good one. Please offer a better title if you have one. Thank you.
The Sequel version of this is a bit scary. The best I can figure out is to use a subselect and, because you need to join the table and the subselect on two columns, a "join block" as described in Querying in Sequel. Here's a modified version of Knut's program above:
require 'csv'
require 'sequel'
# Create Test data
DB = Sequel.sqlite()
DB.create_table(:mytable){
field :id
String :member
String :data
String :start # Treat as string to keep it simple
String :end # Ditto
}
CSV.parse(<<xx
1,"001","abc","2012-12-01","2999-12-31"
2,"001","def","2009-01-01","2012-11-30"
3,"002","ghi","2009-01-01","2999-12-31"
4,"003","jkl","2012-01-01","2012-10-31"
5,"003","mno","2011-08-01","2011-12-31"
xx
).each{|x|
DB[:mytable].insert(*x)
}
# That was all setup, here's the query
ds = DB[:mytable]
result = ds.join(ds.select_group(:member).select_append{max(:end).as(:end)}, :member=>:member) do |j, lj, js|
Sequel.expr(Sequel.qualify(j, :end) => Sequel.qualify(lj, :end))
end
puts result.all
This gives you:
{:id=>1, :member=>"001", :data=>"abc", :start=>"2012-12-01", :end=>"2999-12-31"}
{:id=>3, :member=>"002", :data=>"ghi", :start=>"2009-01-01", :end=>"2999-12-31"}
{:id=>4, :member=>"003", :data=>"jkl", :start=>"2012-01-01", :end=>"2012-10-31"}
In this case it's probably easier to replace the last four lines with straight SQL. Something like:
puts DB[
"SELECT a.* from mytable as a
join (SELECT member, max(end) AS end FROM mytable GROUP BY member) as b
on a.member = b.member and a.end=b.end"].all
Which gives you the same result.
What's the criteria for your result?
If it is the keys 1,3 and 4 you may use DB[:mytable].filter( :id => [1,3,4]) (complete example below)
For more information about filtering with sequel, please refer the sequel documentation, especially Dataset Filtering.
require 'csv'
require 'sequel'
#Create Test data
DB = Sequel.sqlite()
DB.create_table(:mytable){
field :id
field :member
field :data
field :start #should be date, not implemented in example
field :end #should be date, not implemented in example
}
CSV.parse(<<xx
id,member,data,start,end
1,001,abc,12/1/2012,12/31/2999
2,001,def,1/1/2009,11/30/2012
3,002,ghi,1/1/2009,12/31/2999
4,003,jkl,1/1/2012,10/31/2012
5,003,mno,8/1/2011,12/31/2011
xx
).each{|x|
DB[:mytable].insert(*x)
}
#Create Test data - end -
puts DB[:mytable].filter( :id => [1,3,4]).all
In my opinion, you're approaching the problem from the wrong side. ORMs (and Sequel as well) represent a nice, DSL-ish layer above the database, but, underneath, it's all SQL down there. So, I would try to formulate the question and the answer in a way to get SQL query which would return what you need, and then see how it would translate to Sequel's language.
You need to group by member and get the latest record for each member, right?
I'd go with the following idea (roughly):
SELECT t1.*
FROM table t1
LEFT JOIN table t2 ON t1.member = t2.member AND t2.end > t1.end
WHERE t2.id IS NULL
Now you should see how to perform left joins in Sequel, and you'll need to alias tables as well. Shouldn't be that hard.

Rails 3.2 and getting AR to properly order this query

I currently have a scope where I am attempting to find last record created in an association and select it if a particular boolean value is false
IE Foo has_many Bar's and Bar's has a boolean column named bazzed
scope :no_baz, joins(:bars).order("bars.id DESC").limit(1).where("bars.bazzed = 'f'")
The problem with this is that rails turns this query into something like this
SELECT "foos".* FROM "foos" INNER JOIN "bars" ON "bars"."foo_id" = "foos"."id" WHERE (bars.bazzed = 'f') ORDER BY bars.id DESC LIMIT 1
the problem lies that rails is calling the order and limit after the where clause, what i'm looking for is to do the order and limit first to try and find the last bar that has bazzed set to false.
Is there a native AR way to perform the query I am attempting to accomplish?
EDIT
I am trying to grab the foo's that have a bar where the last bar they have has bazzed set to false and only if the last bar that that foo has has a false bazzed.
Ok, I would suggest this for the query on the "foo" model:
Foo.bars.where("bars.bazzed = ?", 'f').all( :order => "created_at DESC").first
Note: 'f' can be replaced by false, depending on the value you use in your "bazzed" column, of course.
[Edit]
Ok, as I think I better understand the problem, here is a suggestion, but for a public method and not a scoped query.
def no_baz
all_no_baz_foos = Array.new
Foo.all.each do |foo|
last_bar = foo.bars.all.order("bars.id DESC").first
if last_bar.bazzed == 'f'
all_no_baz_foos << foo
end
end
return all_no_baz_foos
end
This method will return an Array with all the no_baz_foos record in it. As I did not test my code, you may have to change few things for it to work, but I think you get the idea.
For the "scope" method, I just can't find a way to chain correctly the queries to have the desired result. If anyone else knows how to achieve that using a scope, I'll be glad to hear the solution too.
Using a class method for now but the problem with that lies that it returns an array object and not an active record relation which is what i'm trying to return. Still attempting to get the query correctly done.