Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Log for which keys values were set to nil in deep_munge #13813

Merged
merged 1 commit into from
Jan 29, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions actionpack/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@

*Maurizio De Santis*

* Log which keys were affected by deep munge.

Deep munge solves CVE-2013-0155 security vulnerability, but its
behaviour is definately confusing, so now at least information
about for which keys values were set to nil is visible in logs.

*Łukasz Sarnacki*

* Automatically convert dashes to underscores for shorthand routes, e.g:

get '/our-work/latest'
Expand Down
9 changes: 9 additions & 0 deletions actionpack/lib/action_controller/log_subscriber.rb
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,15 @@ def unpermitted_parameters(event)
debug("Unpermitted parameters: #{unpermitted_keys.join(", ")}")
end

def deep_munge(event)
message = "Value for params[:#{event.payload[:keys].join('][:')}] was set"\
"to nil, because it was one of [], [null] or [null, null, ...]."\
"Go to http://guides.rubyonrails.org/security.html#unsafe-query-generation"\
"for more information."\

debug(message)
end

%w(write_fragment read_fragment exist_fragment?
expire_fragment expire_page write_page).each do |method|
class_eval <<-METHOD, __FILE__, __LINE__ + 1
Expand Down
13 changes: 9 additions & 4 deletions actionpack/lib/action_dispatch/request/utils.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,23 @@ class Utils # :nodoc:

class << self
# Remove nils from the params hash
def deep_munge(hash)
def deep_munge(hash, keys = [])
return hash unless perform_deep_munge

hash.each do |k, v|
keys << k
case v
when Array
v.grep(Hash) { |x| deep_munge(x) }
v.grep(Hash) { |x| deep_munge(x, keys) }
v.compact!
hash[k] = nil if v.empty?
if v.empty?
hash[k] = nil
ActiveSupport::Notifications.instrument("deep_munge.action_controller", keys: keys)
end
when Hash
deep_munge(v)
deep_munge(v, keys)
end
keys.pop
end

hash
Expand Down
4 changes: 4 additions & 0 deletions guides/source/action_controller_overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,10 @@ NOTE: The actual URL in this example will be encoded as "/clients?ids%5b%5d=1&id

The value of `params[:ids]` will now be `["1", "2", "3"]`. Note that parameter values are always strings; Rails makes no attempt to guess or cast the type.

NOTE: Values such as `[]`, `[nil]` or `[nil, nil, ...]` in `params` are replaced
with `nil` for security reasons by default. See [Security Guide](security.html#unsafe-query-generation)
for more information.

To send a hash you include the key name inside the brackets:

```html
Expand Down
4 changes: 4 additions & 0 deletions guides/source/configuring.md
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,10 @@ value. Defaults to `'encrypted cookie'`.
* `config.action_dispatch.encrypted_signed_cookie_salt` sets the signed
encrypted cookies salt value. Defaults to `'signed encrypted cookie'`.

* `config.action_dispatch.perform_deep_munge` configures whether `deep_munge`
method should be performed on the parameters. See [Security Guide](security.html#unsafe-query-generation)
for more information. It defaults to true.

* `ActionDispatch::Callbacks.before` takes a block of code to run before the request.

* `ActionDispatch::Callbacks.to_prepare` takes a block to run after `ActionDispatch::Callbacks.before`, but before the request. Runs for every request in `development` mode, but only once for `production` or environments with `cache_classes` set to `true`.
Expand Down
43 changes: 43 additions & 0 deletions guides/source/security.md
Original file line number Diff line number Diff line change
Expand Up @@ -915,6 +915,49 @@ Content-Type: text/html

Under certain circumstances this would present the malicious HTML to the victim. However, this only seems to work with Keep-Alive connections (and many browsers are using one-time connections). But you can't rely on this. _In any case this is a serious bug, and you should update your Rails to version 2.0.5 or 2.1.2 to eliminate Header Injection (and thus response splitting) risks._

Unsafe Query Generation
-----------------------

Due to the way Active Record interprets parameters in combination with the way
that Rack parses query parameters it was possible to issue unexpected database
queries with `IS NULL` where clauses. As a response to that security issue
([CVE-2012-2660](https://groups.google.com/forum/#!searchin/rubyonrails-security/deep_munge/rubyonrails-security/8SA-M3as7A8/Mr9fi9X4kNgJ),
[CVE-2012-2694](https://groups.google.com/forum/#!searchin/rubyonrails-security/deep_munge/rubyonrails-security/jILZ34tAHF4/7x0hLH-o0-IJ)
and [CVE-2013-0155](https://groups.google.com/forum/#!searchin/rubyonrails-security/CVE-2012-2660/rubyonrails-security/c7jT-EeN9eI/L0u4e87zYGMJ))
`deep_munge` method was introduced as a solution to keep Rails secure by default.

Example of vulnerable code that could be used by attacker, if `deep_munge`
wasn't performed is:

```ruby
unless params[:token].nil?
user = User.find_by_token(params[:token])
user.reset_password!
end
```

When `params[:token]` is one of: `[]`, `[nil]`, `[nil, nil, ...]` or
`['foo', nil]` it will bypass the test for `nil`, but `IS NULL` or
`IN ('foo', NULL)` where clauses still will be added to the SQL query.

To keep rails secure by default, `deep_munge` replaces some of the values with
`nil`. Below table shows what the parameters look like based on `JSON` sent in
request:

| JSON | Parameters |
|-----------------------------------|--------------------------|
| `{ "person": null }` | `{ :person => nil }` |
| `{ "person": [] }` | `{ :person => nil }` |
| `{ "person": [null] }` | `{ :person => nil }` |
| `{ "person": [null, null, ...] }` | `{ :person => nil }` |
| `{ "person": ["foo", null] }` | `{ :person => ["foo"] }` |

It is possible to return to old behaviour and disable `deep_munge` configuring
your application if you are aware of the risk and know how to handle it:

```ruby
config.action_dispatch.perform_deep_munge = false
```

Default Headers
---------------
Expand Down