Do not pass params directly into url_for or URL helpers

Updated . Posted . Visible to the public. Repeats.

Rails' url_for is useful for generating routes from a Hash Show archive.org snapshot , but can lead to an open redirect vulnerability Show archive.org snapshot .

Your application's generated route methods with a _url suffix Show archive.org snapshot are also affected because they use url_for unter the hood Show archive.org snapshot .

The problem

Imagine your application contains code that checks if the current request's path is what it would generate internally.
If different, it would redirect users to the generated/expected path.

expected_path = url_for(params.to_unsafe_h) # ❌ this is not safe!

if expected_path != request.original_fullpath
  redirect_to expected_path
end

While this works in terms of fixing the expected path, it introduces an Open Redirect vulnerability.

It's as simple as passing a host=evil.tld URL parameter. Rails would see url_for(..., host: "evil.tld") and happily generate a URL to that foreign host.
This makes phishing much easier, because an attacker can use links with your application's hostname, cause a redirect to evil.tld and present a sign-in form which looks like yours. Users will enter their login credentials and submit them to evil.tld.

What can I do?

  • Use redirect_to(url, allow_other_host: false) to avoid redirecting to foreign hosts.
    Careful: this only works for redirect_to. For example, link_to(url) will still generate a link to a foreign host and has no :allow_other_host option.
  • If you want to redirect and keep all parameters, we strongly recommend redirecting from your routes.
  • When you need to use url_for, use request.path_parameters and request.query_parameters as described below.
    If possible, specify allowed parameters explicitly, e.g. url_for(params.permit(:foo, :bar)).

Rails 7.1+

url_for(path_params: request.path_parameters, params: request.query_parameters)

Rails versions before 7.1

url_for(**request.path_parameters, params: request.query_parameters)

Edge case for this approach: When a route contains path parameters like :host (e.g. /foo/:host), those will be part of request.path_parameters and url_for(**request.path_parameters) would then again generate a URL to a foreign host.
If your application defines such routes, you must mitigate the issue explicitly, or upgrade to Rails 7.1 and use its new :path_params option.

We suggest you also add the following snippet which will raise a reminder message once you've upgraded to a newer Rails version.

if Gem::Version.new(Rails.version) >= Gem::Version.new("7.1")
  raise "Replace **request.path_parameters with path_params: request.path_parameters"
end

Side note: Rails 7 mitigates Open Redirects by default

In Rails 7 applications, redirect_to defaults to allow_other_host: false.
If you want to redirect to foreign hosts, you must specify allow_other_host: true instead.

As mentioned above, this helps only with redirect_to, not if you place such URLs in links or similar.

Arne Hartherz
Last edit
Michael Leimstädtner
License
Source code in this card is licensed under the MIT License.
Posted by Arne Hartherz to makandra dev (2023-09-13 13:27)