
Loading

Loading
We use strictly necessary cookies to run this site, and analytics cookies to understand how it's used. See our Privacy Policy for details.
A GitHub-reviewed security advisory, not necessarily under active exploitation the way our CISA KEV advisories are, but a real, disclosed vulnerability in an open-source dependency.
Affected packages
Vulnerable: >= 2.2.5, < 2.9.1
Patched in 2.9.1
Details
Addressable::URI.normalize_path resolves some paths containing .. segments to a different path than RFC 3986 does. When a path climbs above the root before any other .. is resolved, the segments that later .. segments should remove are instead kept: /../../public/x/../../secret normalizes to /public/x/secret instead of /secret. URI#normalize, URI#join, URI#== and URI#=== all use this normalization. An application that checks a normalized Addressable path against an allowlist, but sends the original URI to something that resolves it according to RFC 3986, can be made to allow a request for a path outside the allowlist.
normalize_path in 2.9.0 (lib/addressable/uri.rb) removes dot segments by repeating a set of rewrites until nothing changes. On each pass it finds the first /segment/../ pair with RULE_2B_2C, then replaces every textual copy of that pair in the path with /:
pair = normalized_path.match(RULE_2B_2C)
...
regexp = "/#{Regexp.escape(parent.to_s)}/\\.\\./|"
regexp += "(/#{Regexp.escape(current.to_s)}/\\.\\.$)"
...
mod ||= normalized_path.gsub!(Regexp.new(regexp), SLASH)
When the first pair found is /../../, where the "segment" is itself .., every /../../ in the path is replaced with /. That includes copies later in the path, where each .. should have removed the segment before it. For /../../public/x/../../secret:
.. segments, which can't go above the root, then removes x and public, giving /secret./../../ with / in one step, giving /public/x/secret.Paths where an ordinary /segment/../ pair comes first, such as /a/../../b/c/../../d, are resolved correctly.
This was introduced in 2.2.5. Versions 2.0.0 through 2.2.4 and earlier versions resolve these paths correctly. It is fixed in 2.9.1, which replaces the rewrite loop with a single pass that correctly follows RFC 3986 §5.2.4.
require "addressable/uri"
require "uri"
url = "http://example.com/../../public/x/../../secret"
# Addressable's normalized path, compared with RFC 3986 resolution by Ruby's URI library
puts Addressable::URI.parse(url).normalize.path
puts URI.join("http://example.com/", "../../public/x/../../secret").path
# join and == use the same normalization
puts Addressable::URI.join("http://example.com/", "../../public/x/../../secret").path
puts Addressable::URI.parse(url) == Addressable::URI.parse("http://example.com/public/x/secret")
# An allowlist check on the normalized path passes, but the request reaches /secret
def allowed?(url) = Addressable::URI.parse(url).normalize.path.start_with?("/public/")
puts allowed?(url)
Output with Addressable 2.9.0:
/public/x/secret
/secret
/public/x/secret
true
true
Output with Addressable 2.9.1:
/secret
/secret
/secret
false
false
This is an interpretation conflict (CWE-436) between Addressable and other RFC 3986 resolvers, which could lead to path traversal past an allowlist (CWE-22). An application is affected when it does both of these:
/public/ may be proxied" or "only these redirect targets are allowed";An attacker may be able to bypass a check by exploiting behavioral differences between URI parsers. Impact from this is application-dependent. Applications that use Addressable's normalized form for both the check and the request are unaffected, as are applications that reject paths containing .. segments.
As a work-around for unpatched versions, before making access decisions on a path, either reject any containing .. segments, including percent-encoded forms such as %2e%2e, or use the same normalized value for both the check and the request.
More rubygems advisories
Talk to us about this
Find out whether this dependency (or others like it) is actually reachable in your codebase.
Get help patching, or building dependency-scanning into your pipeline going forward.
A secure code review or supply chain review finds this before an advisory does.