Read more

Do not pass params directly into url_for or URL helpers

Arne Hartherz
September 13, 2023Software engineer at makandra GmbH

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 .

Illustration UI/UX Design

UI/UX Design by makandra brand

We make sure that your target audience has the best possible experience with your digital product. You get:

  • Design tailored to your audience
  • Proven processes customized to your needs
  • An expert team of experienced designers
Read more 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.

Posted by Arne Hartherz to makandra dev (2023-09-13 15:27)