SQL Injection Vulnerability in Ruby on Rails; affects all versions
groups.google.com
groups.google.com
You do know that to be able to exploit it you have to know the application's secret key, so you can create your own malicious encrypted session cookie that includes hashes instead of strings for the auth token lookup?
You do know that if someone has your app's secret key they can just write whatever they want into the session cookie, instantly compromising a large number of apps anyway? That's the whole point of the secret key!
This is an obscure issue which can be used to get around one layer of defense in rails security. It could never get around all of them. It requires intimate in-depth knowledge of the app to even attempt the exploit. Sure, it's a bug, and it's not to be taken lightly, but the howls of derision here are totally out of proportion.
This is a much more subtle SQL injection.
I believe the takeaway is that too much magic is a bad thing when it obscures the underlying behavior.
Post.find_by_id( ) accepts an argument. Here are some normal assumptions:
1. It might only take a number
2. The method might coerce it to a string or integer for you
3. The method might not coerce it.
4. The method might throw an error if it isn't an int or the object isn't found.
5. The parameter is treated like a hash and used for lookups.
This last one seems a bit too much magic to me. I wouldn't even guess that last one as normal, expected behavior.
What people seem to be reacting to is the idea that User.find_by_id(params[:id]) is exploitable because you can coerce params[:id] into a hash instead of a string (by using a query string like ?id[select]=some_thing_here instead of ?id=27). True, but what most people here are overlooking is that User.find_by_id actually rejects these hashes because their keys are strings and not symbols. Try it out.
This vulnerability can be coupled with other vulnerabilities (like having someone's session secret, which is a much worse vulnerability IMO), but people are talking about it as if you can do something nasty with it by itself alone. That's why it's an overreaction.
The howls of derision from people who flat out say they don't even use the framework and clearly don't understand the vulnerability are particularly ridiculous.
From looking at it, it seems that ANY HTTP parameter (e.g. POST or GET - which are completely user-controlled) can be manipulated if you know how it's going to be used in the code, e.g. an obvious object ID.
EDIT: tenderlove sets it straight below
The linked vuln report gives the impression that regular parameter handling is vulnerable.
However, that bug/exploit is based on the rails vuln and was patched in authlogic exactly how the Rails report instructed people to work around it (casting the parameter to a string).
Reading the actual report linked in the OP you'll see that generic and boilerplate code (e.g. the extremely common pattern: "Post.find_by_id(params[:id])") is going to be vulnerable to this bug.
The key is: "if they have the rails secret_token"
The secret token is autogenerated when the application is initially bootstrapped. Here is more information about it from any config/initializers/secret_token.rb file:
# Your secret key for verifying the integrity of signed cookies.
# If you change this key, all old signed cookies will become invalid!
# Make sure the secret is at least 30 characters and all random,
# no regular words or you'll be exposed to dictionary attacks.
Change the cookie secret token at config/initializers/secret_token.rb
Create a config/initializers/secret_token.rb file:
That will rename your app in the following files: ... config/initializers/secret_token.rb
Change your Application’s Secret Token ...
Change the secret token at /config/initializers/secret_token.rb
Those are the first six items in order and the trend continues at least through the first page of results.
As it says in the Django settings:
"Make this unique, and don't share it with anybody."
Your web application's security depends on it!
Have you got another way of getting unescaped code into the application such that this issue might be exploited? If so, the core team will be very, very interested to hear from you.
However from the description of the vulnerability found at [1], it would appear as though the cookie value can be set to a Ruby string value that is then parsed by server to produce a Ruby Hash value (rather than the AuthLogic plugin's assumed string value).
Is the eval() of the cookie value done by Rails or is it done by AuthLogic? Is that a potential security vulnerability in itself?
[1]http://phenoelit.org/blog/archives/2012/12/21/let_me_github_...
edit: forgot the URL
edit2: nvm ... apparently it's using the Ruby Marshal API, not an eval()-type call.
params[:id] = {:select => "select * from users where admin = 1 limit 1; --"}
User.find_by_id(params[:id]) # => finds the first admin
# generated SQL:
# SELECT * from users where admin = 1 limit 1; -- select * FROM `users` WHERE (`users`.`id` IS NULL) LIMIT 1
Seems like a bigger bug than you seem to think it is. Merely accepting JSON and not calling .to_i is enough for me to select any user.-- edit: maybe nvm on that, it requires an actual symbol, not a string key with a hash with indifferent access, like you'd get from JSON. Unless someone knows a way to get a symbol into a JSON or form-encoded field? Though you're vulnerable to this specifically if you use '.symbolize_keys' anywhere before it's passed in.
-- edit2: and I see I'm late to the game anyway, thanks tenderlove :)
3.2.6 (June 2012) https://groups.google.com/forum/?fromgroups=#!topic/rubyonra...
3.2.4 (May 2012) https://groups.google.com/forum/?fromgroups=#!topic/rubyonra...
Ask Steve Jobs and Woz about BlueBoxes; you need look no further to see the inherent dangers on inband signaling.
Slightly off-topic, but still relevant IMHO.
What's the last blueBox-able switch?
Esquire published "Secrets of the Little Blue Box" in the October 1971 issue of Esquire, based on the phone system MF design from the 1950s/1960s. That means blue boxing started at least 15 years before 5ESS, as tptacek pointed out.
The 1987 Phreakers Manual (http://fringe.davesource.com/Fringe/Hacking/Phreaking/Phreak... ) says "Blue boxing becomes harder as all Bell switching and transmission facilities go under to CCIS. Then to further complicate things, digital microwave, fiber optic, and satellite transmission are all coming to be digital and do not recognize 2600hz for the hang up signal. I predict that around 1990, blue boxes will be obsolete from all major cities."
The 2600 FAQ, Section C-07, says (the earliest date I found for this was August 9, 1993): "Because of ESS Blue boxing is impossible". This is incorrect. ... While the advent of ESS (and other electronic switches) has made the blue boxers task a bit more difficult, ESS is not the reason most of you are unable to blue box. The main culprit is the "forward audio mute" feature of CCIS (out of band signalling). ... So for the clever amongst you, you must somehow get yourself to the 1000's of trunks out there that still utilize MF signalling but bypass/disable the CCIS audio mute problem.
I don't know the switch models enough to say if it was exactly 5ESS, but everything suggests that that is the case.
for instance, before this patch you could do User.find_by_username('whocares', :conditions=>"ARBITRARY SQL")
I think the defaults are all pretty secure.
I'd prefer having constant security patches. It shows people are still constantly testing it for vulnerabilities.
With Rails, you get the expensive abstraction and apparently none of the security.
There's a real critique of Rails to be leveled here (there is some fucked up stuff going on with Rails request processing), but yours isn't it.
Is this an argument for hand rolling your own code, or for using some other framework that is apparently immune (or, to be charitable, has a stronger security track record)?
Anyhow...
Parameterized queries do not "fix the SQL injection problem altogether". They solve the most common issue where someone is simply building up a full SQL statement and passing user inputs as part of the string (not as parameters). I call that "Class 1" SQL injection problem. You find this a lot in hand-rolled web apps (especially PHP apps since the legacy MySQL library hasn't been snipped out yet and most tutorials explicitly tell you to do this, even though PHP has long supported parametric SQL).
However, many DB access libraries and ORMs offer facilities that generally revolve around the desire to allow the client application to customize or optimize the generated SQL created by the library (or bypass the SQL generation but leverage the library managed connection state). The API typically just trusts that you know what you are doing, blindly accepts the SQL you give it and injects it into (or replaces) whatever it generated on its own. These are the source of what I call those Class 2 injection vulnerabilities. That is, SQL gets injected in what cannot otherwise be parameterized. These can be mitigated by running a sanity check on the full SQL before it goes to the server (for example, searching it for comment strings and raising and exception or returning an error if they are detected). They can also be detected by scanning your query logs for the same things. Also, this is typically caused by a bug in your code, not the library, since it was trusting you to give it clean SQL.
But briefly, if you think of a grammar such as HTML in terms of an abstraction, the very fact that you need to encode output when creating HTML indicates that the abstraction can be "punctured".
And many abstractions, such as stored procedures, don't help you out at all. There is a oft-repeated untruth that somehow stored procedures magically prevent SQLi.
Yet it didn't get addressed until a few months ago.
PS: It's still broken IMO. It needed a rethinking of strategy and purpose. Instead it got a quick hack. If you want to see mass-assignment done right, look to Play Framework's First Class Forms support.
So when someone offers to do something inherently dangerous on your behalf, you should be incredibly deliberate.
There's no shortage of frameworks, platforms and libraries that have been bit (repeatedly) by SQL injection.
Lets not pretend that Ruby/Rails doesn't make major architectural decisions in favour of ease of use for the user.
e.g. if HTTP params weren't automatically marshalled into non-string data structures this bug wouldn't exist.
This doesn't seem to be the case: fugu preparation is licensed and regulated, and deaths are very rare indeed. I can't find exact numbers offhand, but according to a paper cited on Japanese Wikipedia, there were 315 fugu poisoning incidents in Japan in the 10 years from 1996 to 2005. Of those, 31 were fatal, and of those, the majority were due to preparation by unqualified individuals (presumably those who had caught fugu themselves).
Given the quantity of fugu consumed nationwide, it seems reasonable to say that, in fact, most chefs never cut fugu wrong - or at least that most chefs never serve wrongly cut fugu.
https://ja.wikipedia.org/wiki/%E3%83%95%E3%82%B0#.E3.83.95.E...
In Django you would do:
Post.objects.get(pk=request.GET['id'])
There really is no way to do SQL injection this way.
This line in rails looks almost exactly like how you would do it in Django:
Post.find_by_id(params[:id])
Also this seems really serious. It's not like a edge case where you need to grab a post by id. This is probably a very common use case of that method find_by_id.
Post.find(params[:id])
That method is unaffected.The methods that are affected by this are the dynamic finder methods `find_by_*` such as:
Post.find_by_id(params[:id])
This would most commonly occur when looking up users by a token or some other piece of data other than the id. User.find_by_token(params[:token])
I'm not sure why they chose to use find_by_id in the example. This is a serious bug, but it's not as serious as one might be lead to believe if one thought it was the standard way to find objects in Rails.Ex: "Enter your student ID"
s = Student.find_by_id(params[:id])
if s
# do stuff
else
# do other stuff
end
vs begin
s = Student.find_by_id(params[:id])
# do stuff
rescue
# do other stuff
end # note the exclamation mark
User.find_by_id!(id) # => raises exception when nothing is foundWith 3395 stars it seems to be a quite popular.
Deleted comment
That is why Python has kwargs. Those two stars stand out like a sore thumb and when you are passing positional arguments in the form of a hash it is pretty apparent.
find_by_id, find_by_name, etc. aren't really methods, they trigger calls to method_missing which interprets the code to generate SQL. It's a "neat" feature, but one which I've only used once or twice in 6 years of Rails development.
What the hell is going on with ActiveRecord?
This means if there's only one parameter, and someone can sneak a Hash in there where you weren't expecting it (params parsing, request body parsing, etc) then they can end up passing dodgy 'keyword arguments' into your method call.
vanilla ruby:
def find_by_name(name, options = {})
...
end
rails: def find_by_name(*args)
opts = args.extract_options!
name = args.shift
...
end
(note, this is not how the dynamic finders actually work, i just wanted to illustrate the difference in the calling and definition.)So, ruby doesn't include support for varargs with last positional parameter for options, rails builds that in. The fact that it is variable arity is very important -- in fact, that is the root of the present issue. The patches now check the number of arguments.
Of course, you could use the driver incorrectly to risk SQL injection, but that is a very obvious mistake that no experienced developer would make.
In theory when you do that you have already given up on letting the framework handle it for you, and you must take care of not feeding raw user input as the select code, for example.
The issue here is that the option to do this is exposed in a functionality where people do not expect it (dynamic finders) and thus people may be passing risky input there.
In Python, you would do something like this:
execute('select name, age from employees where id=?', (params['id'],))
This passes the id as the second argument to the execute function. If you do this, on the other hand, you open yourself to SQL injection, because %s is replaced with params[id] and no escaping is done: execute('select name, age from employees where id=%s' % params['id'])The orm supports building the sql piecemeal, e.g
find(select: "name, foo(bar) as baz",
conditions: 'x=y',
limit: 3)
this is a small step above a raw execute, and obviously ugly and low level. Also a somewhat obsolete practice, since for a few years you could write it as a composition of calls select("name, foo(bar) as baz").
where('x=y').
limit(3)
Anyway the functionality is there to compose SQL via bits using an hash of parameters, moving on.Now remember that ruby <2.0 does not support keyword arguments, so the common practice is to use one normal argument with an hash value wich contains the keyword args.
Rails has this (antipattern imo) of accepting arguments in a dozen way for some methods e.g.
find(1)
find(:first)
find([1])
find(1,limit: 1)
let us not argue whether this is good, it's there.And AR has dynamically generated finders (which Django does not have AFAIR).
One would expect the dynamically generated finder to be doing
def find_by_foo arg
where(foo, arg).limit(1)
end
but in reality it does def find_by_foo *args
many_options = args.extract_options!
opts = combine_with_foo_handling(many_options)
find(opts)
end
and here you get the problem that you may be unknowingly passing an hash object wich builds sql piecemeal.Notice that, as others already pointed out, usually as a user you shouldn't be able to create custom objects of the kind that exploits this issue (an hash with symbols as keys) unless your have other vulnerabilities already.
Post.objects.filter(some_field_name=some_value)With no obvious value over the former that I may think of anyway, I think they are mostly there for historical reasons.
(in a manager)
def find_by_foo(self, arg):
return self.filter(foo=arg)[:1]What you wrote is exactly what I wrote that a finder method _could be but it's not and that is the issue_
This seems like bad engineering fundamentals in the design of ActiveRecord for it to be perpetually subject to this sort of thing.
Eh, sure but you need to explicitly pass the dict as a kwarg with a double-asterisk, or else it's just a normal positional parameter.
In Ruby prior to 2.0, there is no formal concept of kwargs, so there is no distinction between passing a Hash as the last positional parameter and passing kwargs. This is the root problem, and I look forward to it going away when everyone moves to 2.0.
For example, this:
my_method(1, 2, three: 3, four: 4)
Is the same as this: my_method(1, 2, { three: 3, four: 4 })
Which can be picked up by the method like this: def my_method(one, two, opts)
three = opts[:three]
four = opts[:four]
puts one, two, three, four
endThis generally happens in one of two says:
1) (most common) You have a SQL statement that takes a user-provided parameter and you compose your SQL statement as a string, including that parameter (eg., sql = "SELECT * FROM person where id = " + form.id, or similar). This is typically solved by using parametric, prepared statements. Basically, you prepare a SQL statement that contains "?" for the parameter values and then bind values to the statement.
2) (Common in ORM frameworks) A user provided string is used to compose some other (non-parameter) piece of the SQL statement, such as a column or table name. This is usually caused by laziness. Rather than combining the string provided by the user (form values, URL components, etc.) you should instead look up the string to use from some internal data source, such as a list of domain classes, etc., and use that instead. In that way, the data that is user provided is kept entirely separate from data that will be executed.
You'll hear a lot of people talk about "why isn't this being escaped". And, frankly, it's a good question. But the real question is "why are you trusting data that could come from anywhere on earth?". Don't take it for granted that only your users will be sending queries to your application.
The code you write for Django (Post.objects.get(pk=request.GET['id']) is only secure from 1 and 2 if the framework is written in an appropriate manner to avoid trusting user provided data.
The fact that this keeps happening on Rails is the #1 reason I haven't bothered to take the time to do anything real with it. I don't have the time to read the code for the framework and I don't trust that it's written with security in mind.
ps. This type of problem applies to any kind of "data that is executable", be it strings passed to an "eval" function or strings passed to a web browser. SQL is just a giant eval() function.
ActiveRecord does escape user input.
The exploit here is that under certain obscure circumstances it is possible trick ActiveRecord into thinking the user input is an options hash passed by the caller.
From my understanding this is non-trivial to exploit on most applications, and requires passing in a Hash with symbol keys.
This is still a vulnerability that needs to be (and has been) fixed, but it is nowhere near as stupidly obvious as you are claiming.
The fact of the matter is, whether its in some dark edge-case or not, user-provided data is being used to compose a SQL statement that is being passed to the server. Escaped or otherwise, that's a recipe for an injection attack.
How do you implement authentication if you can't check an email (user provided data) matches a password (user provided data, probably hashed but still)? How do you look up blog posts by a user-provided tag, without using that tag in query composition? How do you save any user provided information at all without somehow including that information in an SQL query?
You have to use escaped user provided data all the time in a real application. Any actual web developer would know that.
This has been available in numerous database APIs for like, ever.
For example [1], [2], [3]. Any actual web developer will have read something along the lines of [4].
A lot people seem confused by my original post, which was in response to a Django user's question about how this sort of thing happens. I provided a general response which seems to have offended some people.
Yes, Rails does appear to use parameterized statements. However, when it is building those parameterized statements it's still using user provided data to build the SQL. If that weren't the case, then this wouldn't be a bug at all, would it? Of course not, so obviously it is using user-provided data in some way, otherwise an HTTP cookie's value wouldn't be getting passed to the database, would it? The prepared statement string shouldn't be composed with anything user provided.
[1] http://php.net/manual/en/mysqli-stmt.bind-param.php
The bug however is that it's possible for user input, with a session hijacking, to provide that hash with symbolic key. There is no SQL injection, this is straight up arbitrary execution of SQL.
I get that this would create some performance overhead, so it would ideally be configurable.
This isn't actually an SQL injection flaw.
>Carefully crafted requests can use the scope to inject >arbitrary SQL.
It's also titled "SQL Injection Vulnerability". Are we all missing something?
I don't quite understand the angst about this defect being called a SQL injection vulnerability. The vector for the attack doesn't change the end result.
The cause might be that the API was broken, but it doesn't change the fact that a guy wrote SQL code that was injected into the middle of the rest of the SQL generated by the ORM.
What frameworks do you use? Have you performed your own audit?
I personally prefer not to use ORMs for this specific reason: they are typically way too complicated to be able to plow through the code in any reasonable way. It's also generally not that hard to design your application in such a way that using a minimalist "ORM-ish" layer of your own making isn't exactly a waste of time. I've also found that they rarely follow these best practices (it's maddening).
I have, however, had to make use of Hibernate, SQL Alchemy and Django's ORM on projects where I didn't make the calls. I'm pretty sure Hibernate uses parametric, prepared statements. I believe SQLAlchemy and Django ORM do not, but use their own escaping mechanism internally. In addition, I don't know about Hibernate and SQLA, but I'm pretty sure that Django's ORM API does make it possible to cause the framework to generate SQL using user-provided data for column/table names in a manner similar to ActiveRecord.
By way of contradicting myself, I do believe that ORMs are great for writing internal use-utilities that are one-offs or quick-and-dirty tools. In general, those cases preclude the use of autonomously provided user data for query building. For world-facing code, ORMs are risky unless you've got someone on the team who knows it and has the ability to ensure it doesn't suffer from these types of design flaws.
> For world-facing code, ORMs are risky unless you've got someone on the team who knows it and has the ability to ensure it doesn't suffer from these types of design flaws.
This I think is wrong, for the same reason you don't want to be putting together your crypto package. If anything, these kind of security vulnerabilities demonstrate just how hard it is to get all of the subtleties pinned down. Rails is used widely and has been inspected by far more domain experts than you'd ever have on your team and yet.
Sometimes ActiveRecord gets a little in the way - especially back when has_and_belongs_to_many associations were considered best practice - but for the most part I haven't been able to empathize with these kinds of claims. AR is really flexible and gives you a lot of functionality for free.
In general, I think the problem that ORMs face is that they try and match every single problem thrown at them. People criticize your ORM saying "it doesn't handle egde case XYZ in my legacy data model" or "it suffers from this performance problem when somebody puts a tire boot on the server". Rather than saying "don't use an ORM to solve your unpaid parking ticket problem", the ORM team will devise a way of providing multiple method signatures in a language that loosely supports the feature so that unpaid parking tickets will always be paid prior to the server getting a boot.
Eventually the support for all these edge cases adds up to a very complex piece of software that, to your point, rivals the complexity and fragility of crypto code.
To me... it's more about saying "I have a limited set of use cases here, I don't need a leatherman to cut this noose around my neck I just need a steak knife". ActiveRecord is an impressive freaking tool and I don't begrudge anyone for using. If you ship working code using it then it did it's job.
My personal taste is to stick with simpler tools that don't have so many edge cases so I can sleep easier at night.
Suffice it to say, where you draw the line on "too complex for my taste" and where I would draw that line is probably different and the result of both our personal experiences as well as the problems we are trying to solve.
After years of working in ORMs I've come around to your thinking for bigger projects. ORMs are great, but they are large, complex, and sometimes opaque project dependencies and therefor should be employed sparingly. Parameterized SQL isn't that tough to write (and Python makes it easy) and often faster. The biggest drawback: it requires a dev team comfortable with SQL or the NoSQL library bindings you're using.
I am using my first framework (yiiframework) at the suggestion of another dev. I still do things like
query(user_input){
switch(user_input){
case(x):
do query_x;
break;
case(y):
do query_y;
break;
...
}
}
The other dev thinks I'm nuts, but I avoid a lot of worry with this. I may lose some performance I suppose...You have no credibility to talk about database if you can't tell what kind of statements are being executed.
You have no credibility to talk about my credibility if you read too much into every single sentence I write without context.
True, but Rails is not doing that, was never doing that, and the patch has nothing to do with this. So you're talking about something unrelated to this security flaw.
This is still, mathematically-speaking, a bug. The function is supposed to find a post by ID. If its implementation causes side effects or returns unexpected results for a certain subset of possible input data, then it doesn't conform to spec.
This becomes a question of trust. Do you trust ActiveRecord/ORM of choice to be bug-free, or do you treat it as untrusted code and basically have to worry about the implementation of data persistence in your non-DB code even though that's what ORMs are supposed to abstract away? Why is that shit running around in your codebase anyway and not part of the ORM?
And, yes, I would fully expect to be able to trust my data-abstraction layer to be bug free. Since Rails seems to have this problem regularly, I can't trust it and therefore choose not to use it for those purposes.
So, I think we agree here.
--- Edit ---
To whit, if you look at the bug report it says that the problem is when an application is passing user-provided data into the framework. They say "don't do that" and then apparently provide a patch to somehow get around if you don't (I don't know enough about the internals of rails to understand the patch).
To my mind, the problem is that Rails should be treating any data passed to it as user-provided data, rather than trusting somebody who just took a 21-day "hacker college" class to do anything other than just pass along user-provided data. The framework should be implementing this kind of security in a consistent and reliable way, rather than trusting you. That's kinda what a framework is for.
In all my experience with programming, I had become accustomed to the mentality of "the compiler/interpreter expects identifiers to be exactly right; it doesn't figure out what you 'really' mean". So it was frustrating to see these auto-generated methods, as I couldn't see the rhyme or reason behind where these methods were coming from.
Fortunately, I found work at a Django shop, where the framework is so much easier to follow and more explicit about how it does things.
[1] devbootcamp.com, first cohort, Spring 2012, though it was actually more like 60 days with 40 days of instruction. I'm now employed as a developer and trusted with production code.
They never tell you not to pass in user provided data. I have no idea where you got that conclusion, but you're obviously off and running with it. Quit spreading misinformation.
The post includes a simple workaround for people who are not able to upgrade to a version that includes this security release. It's not a "don't do that" statement.
> To my mind, the problem is that Rails should be treating any data passed to it as user-provided data
It does. This is a bug. Please quit spreading misinformation.
---- Impacted code passes user provided data to a dynamic finder like this:
Post.find_by_id(params[:id]) ----
It later tells you to apply the "to_s" function to the "user provided data" in order to avoid the problem.
I'm unclear on how that is "misinformation".
The problem is an argument parsing bug that leads to user provided data being used as programmer provided data. Rails does not force SQL sanity off on the developer.
There needs to be some sort of expertise cutoff and I think it's reasonable to expect in a web framework that it's user's are informed enough to avoid these sort of mistakes.
Eventually the scissors become so safe that you can't cut anything with them.
Anecdotally, it seems to have recurring problems with SQL injection.
EDIT: Just to be clear, tenderlove (Ruby/Rails committer) confirms that you do not need to edit the session to exploit this (http://news.ycombinator.com/item?id=4999767). It's still unclear how it is possible otherwise though, I assume he's being purposefully vague.
The original post of the problem goes like this:
1. Gain an application's secret key, used to sign session cookies. 2. Inject a marshalled hash with _symbol_ keys into the session cookie, sign it with the secret key. 3. Now you can exploit the SQL vulnerability in the dynamic finders, assuming the session value is used directly as input.
+ def test_find_by_id_with_hash
+ assert_raises(ActiveRecord::StatementInvalid) do
+ Post.find_by_id(:limit => 1)
+ end
+ end
+
+ def test_find_by_title_and_id_with_hash
+ assert_raises(ActiveRecord::StatementInvalid) do
+ Post.find_by_title_and_id('foo', :limit => 1)
+ end
+ end
+
I can't understand how it happens with real params, though (they are converted to hash with indifferent access internally).Example (real rails app):
1.9.3p327 :017 > Forum::Thread.find_by_id_and_forum_id(1, {:limit => 10}.with_indifferent_access)
ArgumentError: Unknown key: limit
[backtrace skipped]
NOTE: I originally posted (and quickly deleted) wrong answer because I looked up another CVE. I apologize if it confused anyone. 1.9.3p327 :025 > {:a => "b"}.with_indifferent_access.assert_valid_keys([:a])
ArgumentError: Unknown key: aedit: Above sandstrom posts the link to https://github.com/binarylogic/authlogic/pull/341 so I guess maybe you can use the session to do this, though you would need access to the secret_token.
This is a really, really bad hole, and you should patch or upgrade ASAP.
It's not just a SQL Injection vulnerability. With that secret token, you can set any session value you like.
Also, even open source projects typically ensure or recommend that the secret token be regenerated when using in production environments.
https://groups.google.com/forum/?fromgroups=#!topic/rubyonra...
http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2012-5664
It references two articles that require session secrets.
(I wouldn't have said it was possible unless I had a curl line that did it, for what it's worth.)
EDIT Well... he might, but I've never seen him do it. He's a security professional, after all.
ActiveRecord (rails' default ORM) has a feature called "dynamic finders". When you call a method like `Forum.find_by_url('news.ycombinator.org')` it gets a first forum with such url. This is a sugar over `Forum.where(:url => 'news.ycombinator.org').first`.
Normally, you use it like that `Forum.find_by_url(params[:url])` where `params` is a hash of parameters (that is auto-generated from http get/post params). What happens if instead of "normal" value like "news.ycombinator.org" you pass a hash?
1.9.3p327 :018 > Forum.find_by_id({:select => 'id FROM forums --'})
Forum Load (0.5ms) SELECT id FROM forums -- FROM `forums` WHERE `forums`.`id` IS NULL LIMIT 1
Uh-oh.However, not everything is lost. Rails params are converted to a special hash class. It's called HashWithIndifferentAccess, because you can access its values by string keys and symbol keys likewise. So that
h = {:a => 1, "b" => 2}.with_indifferent_access
h["a"] # => 1
h[:a] # => 1
h["b"] # => 2
h[:b] # => 2
What happens if we pass user-generated params? It seems like not much: 1.9.3p327 :024 > Forum.find_by_id({:select => 'id FROM forums --'}.with_indifferent_access)
ArgumentError: Unknown key: select
So I guess they are erring on a safe side here.In Rails 4.0, most dynamic finders are deprecated and removed from the source into a separate gem.
This patch does not fix a wide vulnerability. It just fixes a corner case, a just-in-case-somebody-might-write-vulnerable-code fix.
It just so appears that Authlogic does this. They pass a cookie value into a dynamic finder, so you can tamper the cookie to inject SQL.
It would have been really useful if the upgrade notification had included this level of detail to start with so people could make a much more informed decision about when/if to upgrade, rather than having to dive through bug reports, commits etc just to work out if our apps are vulnerable.
If you're just learning and creating an app for your own edification, this is not really an issue that will affect you. That is, it doesn't affect how you construct the app, so if for some reason the gem update process doesn't work, you won't be hindered from using RoR.
It probably will most, if not all, of the time for you. But the complexity of such things understandably means there will be obscure vulnerabilities that are hard to track down.
Sanitising -- and even validating -- your params at the controller level is a nice way to stop some of these problems before they reach your models.
The article says a work-around is to use .to_s or .to_i on user input. My standard practice is to cast to int for all IDs I get externally (in PHP too). Some projects I work on go so far as to validate type and data ranges of every argument passed in. Those applications would therefore not be vulnerable even though Rails itself is.
A huge selling point of Rails, other web frameworks, and ORMs in general, is that you don't have to be doing these checks. You can push the query through without concern of SQL injection, and handle the failure case there. This is convenient because you often have to handle that failure case anyway.
Rails' own "Getting Started" guide uses this technique because it's assumed to be secure: http://guides.rubyonrails.org/getting_started.html#showing-a...
We know you can "break" the application by tampering with IDs, but you shouldn't be able to pose a security threat.
Validating type and data ranges is common with things like dropdowns, where you need to ensure an actual option was selected. This is a very common security flaw, and it's definitely good practice to be checking (especially when it leads to adding/updating database rows).
update your Gemfile and set the version you want. In my case:
gem 'rails', '3.2.10'
locally, run
'bundle update rails' which will update your Gemfile.lock
check-in and deploy your code. If you are using capistranso, the default 'deploy' task should handle everything for you. Otherwise, run 'bundle update rails' on your production server.
Regardless, I think I have some patching to do tonight.
id=1
User.where(:id => params[:id]).first
User.where(:id => 1).first
Then I could construct a hash in the param: id[$gt]=0
This would perform the following find: User.where(:id => params[:id]).first
User.where(:id => {"$gt" => 0}).first
Which will return the first user record (probably).You should be performing casts (usually to strings) before you pass your data to your ODM.
id[$gt]=0
User.where(:id => params[:id].to_s).first
User.where(:id => "{:$gt=>0}").first
This will correctly fail to a find a document.edit: friendly_id does not use the dynamic find_by_x methods. I guess it should be safe.
The very concept of an ORM is broken. I know most devs don't know SQL well enough to do more code directly from SQL and I know most devs don't know anything else than SQL... But it's a bit sad to see all these "frameworks" tailored to the masses.
dev: "I want objects. I don't really understand set theory but I still want SQL because it's all I know."
chef: OK, here's a framework doing nearly everything from you.
Seriously guys. Mixing orthogonal concepts and posting provocatively titled blog entries like "OO and SQL aren't orthogonal concepts, there's no mismatch impedance" ain't going to help.
The only thing this creates is a lot of needless complexity and, of course, there shall always be major SNAFU like this one.
Maybe, just maybe, that we could realize that if the reader is set to "from now on do not eval anymore" code shall not be eval'ed? And maybe, just maybe, that we could realize that if you're not using SQL you're not subject to these monthly SQL injections issues?
How far does the SNAFU need to go for you to consider that OO + SQL is actually a very, very poor mix?