SQL Injection in ActiveRecord - sql

I'm trying to build a vulnerable demo application. I'm using SQLite, and I have ruby code that looks like this:
#value = current_user.accounts.calculate(:sum, params[:column])
And the SQL generates the following by default:
User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT 1 [["id", 1]]
(0.1ms) SELECT SUM("accounts"."account_value") AS sum_id FROM "accounts" WHERE "accounts"."user_id" = ? [["user_id", 1]]
Next, I put ssn) FROM users WHERE name = 'Texas'; -- into the form and I get the following:
(0.3ms) SELECT SUM(ssn) FROM users WHERE name = 'Texas'; --)) AS sum_id FROM "accounts" WHERE "accounts"."user_id" = ? [["user_id", 1]]
SQLite3::RangeException: bind or column index out of range: SELECT SUM(ssn) FROM users WHERE name = 'Texas'; --)) AS sum_id FROM "accounts" WHERE "accounts"."user_id" = ?
Completed 500 Internal Server Error in 2ms
ActiveRecord::StatementInvalid (SQLite3::RangeException: bind or column index out of range: SELECT SUM(ssn) FROM users WHERE name = 'Texas'; --)) AS sum_id FROM "accounts" WHERE "accounts"."user_id" = ?):
app/controllers/instant_calculator_controller.rb:3:in `sum'
I think the issue is that the 'user_id' section tacked onto the end as a paramiterized query is messing this up. I tried doing something like ssn) FROM users WHERE name = 'Texas'OR user_id = ?; -- just to throw that part of the query away, but that didn't seem to help.
Does anyone have any thoughts on I could make this work? I can change the code as well as the query, but I'd prefer to change the query before changing to code to make it SQLiable.
EDIT:
A bit more info. If I take the SQL that is generated and just change the last user_id to '1' so it looks like SELECT SUM(ssn) FROM users WHERE name = 'Texas'; --) AS sum_id FROM 'accounts' WHERE 'accounts'.'user_id' = 1 it works perfectly. I don't understand why this matters as everything after -- should be ignored.

Related

Rails and SQL straight request

In rails we have Model.find(id_number) request. What I want to ask is there any possibility to create straight request to DB, for example: SELECT * FROM users WHERE id = 1 and replace rails basic Model.where(id: 1)?
EDIT
In my model.rb I have:
scope :search_import, -> {includes(:translations)}
default_scope -> {self.search_import}
And request (in rails console) looks like:
User Load (0.5ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1
User::Translation Load (0.6ms) SELECT `users_translations`.* FROM `users_translations` WHERE `users_translations`.`category_id` IN (1)
But I don't want to User::Translation was executed (with unscoped I have the same result)
Sure, something like
ActiveRecord::Base.connection.execute("SELECT * FROM users WHERE id = 1")

Correct way to find models by their association

Both of these produce the same result:
User.where(account: 1)
User.where(account_id: 1)
But the generated SQL is different:
/* User.where(account: 1) */
SELECT "users".* FROM "users" WHERE "users"."account_id" = 1
/* User.where(account_id: 1) */
SELECT "users".* FROM "users" WHERE "users"."account_id" = $1 [["account_id", 1]]
Also both of these generate the same SQL as the first version:
a = Account.find(1)
User.where(account: a)
User.where(account_id: a)
# SELECT "users".* FROM "users" WHERE "users"."account_id" = 1
So which is the correct way to find a model by its association? Is the second version safer than the first? I tried to search for what's happening at the SQL level in the second version but I coudn't find anything.
There is no significant difference in your case. But if the account association is polymorphic, e.g. when there is a Business account and a Personal account, then the where(account: a) will generate something like WHERE account_type = 'Business' AND account_id = '123', while where(account_id: a) will generate just the WHERE account_id = '123'.

Make a request with activerecord to get only the users from groups and not from others

I'm trying to get users from few groups (with given ids) and exclude the users from other groups.
I've tried something like :
User.joins(:groups).where(groups: {id: ["8939","8950"]}).where.not(groups: {id: 8942}).map(&:id)
User Load (0.9ms) SELECT "users".* FROM "users" INNER JOIN "groups_users" ON "groups_users"."user_id" = "users"."id" INNER JOIN "groups" ON "groups"."id" = "groups_users"."group_id" WHERE "groups"."id" IN (8939, 8950) AND "groups"."id" != $1 [["id", 8942]]
=> [119491, 119489, 119490, 119492, 119488, 119484, 119483, 119491, 119482]
But that's not correct
The users in group 8942.
Group.find(8942).users.pluck(:id)
Group Load (0.4ms) SELECT "groups".* FROM "groups" WHERE "groups"."id" = $1 LIMIT 1 [["id", 8942]]
(0.6ms) SELECT "users"."id" FROM "users" INNER JOIN "groups_users" ON "users"."id" = "groups_users"."user_id" WHERE "groups_users"."group_id" = $1 [["group_id", 8942]]
=> [119490, 119492, 119491, 119457, 119423]
The where.not doesn't work on user "groups"."id" != $1 [["id", 8942]]. Why ?
Correct way to do such things is to use SQL EXISTS condition. I wish there was a specific ActiveRecord helper method for that, but there isn't at the moment.
Well, using pure SQL is just fine:
User.where("EXISTS (SELECT 1 FROM groups_users WHERE groups_users.user_id = users.id AND groups_users.group_id IN (?))", [8939, 8950]).
where("NOT EXISTS (SELECT 1 FROM groups_users WHERE groups_users.user_id = users.id AND groups_users.group_id IN (?))", [8942])
What you were doing with your original query is asking for not joining groups with [8942] ids to your query, and only joining groups with ids [8939, 8950]. Well, you can see right now that this doesn't make any sense: that's like asking to select every user whose name is bob and NOT charlie. Second condition doesn't add anything to the first one.
Join query is multiplicating columns, so if your user is in every group, result set would be:
user_id | group_id
1 | 8939
1 | 8950
1 | 8942
Then you filter out the latter row: 1 | 8942. Still, user 1 is in the result set and is returned.
And to ask the database to return only records which doesn't connect with another relation you should explicitly use NOT EXISTS which exists explicitly for that purpose :)
There is now a Where Exists gem which you can use. (Full disclosure: I've created that gem recently.)
With it you can achieve your task as simple as:
User.where_exists(:groups, id: [1, 2]).where_not_exists(:groups, id: [3, 4])

How to fix a multiple assignment issue? on Rails 4

So, I have 3 tables Accounts, Users, and Subscriptions all of which have the deleted_at column. Now when I delete an account the request gets sent to all models at once causing a Multiple Assignments error as shown below:
Started DELETE "/admin/accounts/3" for 127.0.0.1 at 2014-10-27 19:38:48 +0200
Processing by SaasAdmin::AccountsController#destroy as HTML
Parameters: {"authenticity_token"=>"qo8NxYhezJUz2YylGxHlx2ou125UNoRuEcXbrnzyiN4=", "id"=>"3"}
AppSetting Load (0.3ms) SELECT "app_settings".* FROM "app_settings" ORDER BY "app_settings"."id" ASC LIMIT 1
Tag Load (0.2ms) SELECT "tags".* FROM "tags" WHERE "tags"."id" = $1 LIMIT 1 [["id", 22]]
SaasAdmin Load (0.2ms) SELECT "saas_admins".* FROM "saas_admins" WHERE "saas_admins"."id" = 4 ORDER BY "saas_admins"."id" ASC LIMIT 1
Account Load (0.1ms) SELECT "accounts".* FROM "accounts" WHERE (accounts.deleted_at IS NULL) AND "accounts"."id" = $1 LIMIT 1 [["id", "3"]]
(0.1ms) BEGIN
Subscription Load (0.3ms) SELECT "subscriptions".* FROM "subscriptions" WHERE (subscriptions.deleted_at IS NULL) AND "subscriptions"."subscriber_id" = $1 AND "subscriptions"."subscriber_type" = $2 LIMIT 1 [["subscriber_id", 3], ["subscriber_type", "Account"]]
SQL (0.3ms) UPDATE "subscriptions" SET deleted_at = '2014-10-27 17:38:48.909001', deleted_at = '2014-10-27 17:38:48.909025' WHERE (subscriptions.deleted_at IS NULL) AND "subscriptions"."id" = 3
PG::SyntaxError: ERROR: multiple assignments to same column "deleted_at"
: UPDATE "subscriptions" SET deleted_at = '2014-10-27 17:38:48.909001', deleted_at = '2014-10-27 17:38:48.909025' WHERE (subscriptions.deleted_at IS NULL) AND "subscriptions"."id" = 3
(0.1ms) ROLLBACK
Completed 500 Internal Server Error in 7ms
ActiveRecord::StatementInvalid (PG::SyntaxError: ERROR: multiple assignments to same column "deleted_at"
: UPDATE "subscriptions" SET deleted_at = '2014-10-27 17:38:48.909001', deleted_at = '2014-10-27 17:38:48.909025' WHERE (subscriptions.deleted_at IS NULL) AND "subscriptions"."id" = 3):
activerecord (4.0.4) lib/active_record/connection_adapters/postgresql_adapter.rb:791:in `async_exec'
Additionally, the sql is generated by act as paranoid gem, so we don't write the finder. We do account.destroy, the rest goes by dependecies. Here's the link to the gem if helpful: https://github.com/ActsAsParanoid/acts_as_paranoid
What can we do to fix this?
See the full logs below by visiting the Gist link: https://gist.github.com/rmagnum2002/2358536587bb34dbbc52

Rails sql issue with a simple where statement

I am trying to get a very simple statement working.
Node.where("nodeid = ?", nstart).select('id')
Results in
Parameters: {"utf8"=>"✓", "authenticity_token"=>"WpSpzLzaFUx8QgFbwEfygQUkkqvbUgZl8Hh0UxJvT8E=", "edge"=>{"kind"=>"IsA", "start_id"=>"blabla", "end_id"=>"bliblib", "property1"=>"bloblbo"}, "commit"=>"Create Edge"}
(0.0ms) begin transaction
Node Load (0.1ms) SELECT "nodes".* FROM "nodes" WHERE "nodes"."id" = ? LIMIT 1 [["id", 0]]
CACHE (0.0ms) SELECT "nodes".* FROM "nodes" WHERE "nodes"."id" = ? LIMIT 1 [["id", 0]]
(0.0ms) rollback transaction
It should be just a ´select nodes.id from nodes where nodeid = blabla´ The limit doesn't matter.
However if I add .first.
Node.where("nodeid = ?", nstart).select('id').first
I get
Parameters: {"utf8"=>"✓", "authenticity_token"=>"WpSpzLzaFUx8QgFbwEfygQUkkqvbUgZl8Hh0UxJvT8E=", "edge"=>{"kind"=>"IsA", "start_id"=>"blabla", "end_id"=>"bliblib", "property1"=>"bloblbo"}, "commit"=>"Create Edge"}
Node Load (0.1ms) SELECT "nodes"."id" FROM "nodes" WHERE (nodeid = 'blabla') ORDER BY "nodes"."id" ASC LIMIT 1
Node Load (0.1ms) SELECT "nodes"."id" FROM "nodes" WHERE (nodeid = 'bliblib') ORDER BY "nodes"."id" ASC LIMIT 1
(0.0ms) begin transaction
Node Load (0.1ms) SELECT "nodes".* FROM "nodes" WHERE "nodes"."id" = ? LIMIT 1 [["id", 0]]
CACHE (0.0ms) SELECT "nodes".* FROM "nodes" WHERE "nodes"."id" = ? LIMIT 1 [["id", 0]]
(0.1ms) rollback transaction
The first select now is what it should be but the follow up is again like before and seems to determine the eventual return value (because it doesn't work either). I just want the id when I only know the nodeid which basically is the name of the node.
What is going on in Rails here?
If you just want the id value you could do this
Node.where("nodeid = ?", nstart).limit(1).pluck(:id).first
This would return 1 integer with the value
EDIT:
ok scratch that, i guess you don't really need to use limit so a simple first would just do
Node.where("nodeid = ?", nstart).first[:id]
or
Node.where("nodeid = ?", nstart).first.id
You're making a query:
Node.where("nodeid = ?", nstart).select('id')
will return an array of objects (or empty array if there's no suitable records) *not array actually. Activerecord::Relation
If you use 'first' like Mohammad AbuShady told:
Node.where("nodeid = ?", nstart).select('id').first
you get an object (or nil if there's no record)
If you want an integer id value, you can get it from the object
Node.where("nodeid = ?", nstart).first.id
select('id') tells active record not to retrieve all the fields in the table, just 'id'