Home Restrict open redirection in Rails

# Restrict open redirection in Rails

Max
1#
Max Published in 2018-01-11 03:52:11Z
 I have a Rails application that needs to allow a separate application to direct a signed-in user to our application along with a signed JWT, create a session in our application for the user, and then redirect the user to one of various pages within the application. Open redirects are bad, so I'm trying to figure out if there are accepted best practices (or even just good suggestions) to deal with restricting the redirect URL in Rails. One wrinkle is that the approach should ideally work for an application deployed within a subdirectory. That is, redirects to a location on the same server but outside of the application's root directory should be blocked. This isn't essential, though. Things I've looked into: Testing the user-supplied URL to see if it matches one of the application's routes. Unfortunately, I haven't been able to find a well-supported method to do this. ActionDispatch::Routing::RouteSet has a recognize_path method, but it isn't part of the public API and isn't even used by the Rails routing mechanism. ActionDispatch::Journey::Router has a recognize method that likewise isn't directly called by the Rails routing mechanism (though it has very similar logic) and also isn't part of the public API. And it has a find_routes method that is used by the routing mechanism but is a private method. Checking that the user-supplied URL starts with the domain and path of the application. This isn't too bad but gets tricky with subdirectory deployment and the possibility of directory traversal (/../). URI(root_url).merge(params[:url]) seems to resolve the directory traversal and can just be checked to see if it starts with root_url, so this could work. But I noticed that /%2E%2E/ will be ignored by merge yet still treated as /../ by Apache and Chrome (at the very least). I'm not sure if there are other, even less obvious edge cases. If there's a better approach to this kind of situation than having a redirect URL as a user-provided parameter, I'm happy to hear that too. While it isn't essential that every page in the Rails application is accessible through this method, I also don't want to put in a lot of effort every time another page needs to be allowed.
Max
2#
 After a bit more research, I decided to go with the URI#merge option, with an extra substitution to deal with the percent-encoding issue. Something along the lines of: normalized_url = params[:url].gsub(/%2E/i, '.') merged_url = URI(root_url).merge(normalized_url).to_s merged_url.start_with?(root_url)  According to section 2.3 of RFC 3986, the period is an "unreserved character," and "URIs that differ in the replacement of an unreserved character with its corresponding percent-encoded US-ASCII octet are equivalent." So I felt reasonably certain about replacing %2E and %2e with periods. Section 6 of the same RFC mentions normalizing generic URIs by: normalizing case: "the hexadecimal digits within a percent-encoding triplet" should be treated as case-insensitive decoding percent-encoded versions of unreserved characters resolving . and .. as necessary That covers the case for a generic URI, and RFC 7230 doesn't seem to add anything relevant to this situation specifically for HTTP and HTTPS URIs. So I felt like I was covering the most likely issues, though I can't be sure there isn't an edge case I've missed for some clients or servers.