Jul 28, 2026·7 min read·6 visits
The oauth2 Ruby gem leaks active Authorization bearer tokens to untrusted external hosts when following protocol-relative redirects (starting with //) and enables server-side request forgery.
An issue in the ruby-oauth2 gem allows unauthenticated attackers to steal OAuth access tokens or perform Server-Side Request Forgery (SSRF) via a protocol-relative URI in HTTP Location redirect headers.
The oauth2 Ruby gem is an open-source library that implements a client wrapper for the OAuth 2.0 authorization framework. It is widely integrated into Ruby on Rails and other Rack-based web applications to handle identity verification, Single Sign-On (SSO), and API resource consumption. The library interacts with Identity Providers (IdPs) and authorization servers, managing access tokens on behalf of users.
Historically, standard compliance updates introduced support for relative URI redirects within the gem's network request cycle. However, this implementation introduced a critical security flaw: during the handling of standard 3xx redirect status codes (301, 302, 303, and 307), the client fails to securely handle protocol-relative URIs received in the HTTP Location response header.
When a protocol-relative URI (one starting with double slashes, such as //attacker.example) is processed, it completely overrides the original target host and port while keeping the original request options intact. Consequently, the client recursively transmits the active bearer credential to the newly parsed host. This flaw corresponds to CWE-200 (Exposure of Sensitive Information to an Unauthorized Actor) and CWE-601 (URL Redirection to Untrusted Site), yielding a high-severity risk.
The root cause of this vulnerability lies in the unsafe interaction between HTTP redirection logic and Ruby's standard library URI merging utility (URI#merge). In the vulnerable versions of the library, the redirection handler dynamically extracts the Location header from a 3xx HTTP response. It then resolves the redirection destination by calling response.response.env.url.merge(location), where env.url represents the absolute URI of the original request.
According to the URI standard specified in RFC 3986, a string starting with two forward slashes (//) represents a network-path reference. This format inherits the active URI scheme but indicates a complete replacement of the authority component (the domain and port). When URI#merge combines the base absolute URL https://trusted-idp.example/oauth/token with the protocol-relative input //attacker-controlled.example/steal, the resolved URL evaluates directly to https://attacker-controlled.example/steal.
Furthermore, the client library recursively calls OAuth2::Client#request using the newly computed redirect target URL. Crucially, the local variable req_opts—which contains the HTTP request configurations and headers—is passed directly into the recursive call without validation or modifications. Since the original request parameters contained an Authorization header containing the user's active bearer access token, the client sends this high-privilege token to the attacker-controlled server.
To understand the flaw at the source-code level, consider the original request loop within lib/oauth2/client.rb in version 2.0.21:
def request(verb, url, req_opts = {}, &block)
response = execute_request(verb, url, req_opts, &block)
status = response.status
case status
when 301, 302, 303, 307
req_opts[:redirect_count] ||= 0
req_opts[:redirect_count] += 1
return response if req_opts[:redirect_count] > options[:max_redirects]
if status == 303
verb = :get
req_opts.delete(:body)
end
location = response.headers["location"]
if location
# Vulnerability: protocol-relative URI replaces authority component
full_location = response.response.env.url.merge(location)
# Vulnerability: req_opts passed unmodified, including Authorization headers
request(verb, full_location, req_opts)
end
end
endTo remediate this, the patched version 2.0.22 intercepts protocol-relative URIs and sanitizes the outgoing headers before initiating recursion. The fix modifies the redirect flow to call resolve_redirect_location and sanitize_redirect_options:
# The patched request execution logic
if location
current_location = response.response.env.url
# Step 1: Prevent authority hijacking by prefixing relative URLs
full_location = resolve_redirect_location(current_location, location)
# Step 2: Strip sensitive headers during cross-origin shifts
request(verb, full_location, sanitize_redirect_options(req_opts, current_location, full_location))
endAdditionally, the patch implements three helper methods to enforce boundary limits. The resolve_redirect_location helper checks if the target location string starts with //. If so, it prepends ./ to neutralize the authority replacement, converting the string into a relative-path component on the same origin. The sanitize_redirect_options helper checks if a cross-origin redirect occurred using cross_origin_redirect? and purges any keys matching Authorization (case-insensitively) from the request options hash.
An attacker can exploit this vulnerability through multiple vectors, provided they can influence the destination of an HTTP request executed by the victim application's OAuth client. In a standard enterprise scenario, an attacker identifies an Open Redirect vulnerability on the target Identity Provider (IdP) server. During authentication, the OAuth client calls a token exchange or user-profile endpoint, and the compromised or open-redirecting IdP responds with an HTTP 302 containing Location: //attacker.example/log.
Alternatively, in multi-tenant SaaS environments, tenant administrators can configure custom OIDC metadata endpoints. An attacker can set up a rogue IdP and configure the application to use it as the trusted OAuth authority. When the application's backend queries the malicious metadata or token endpoint, the rogue server returns a protocol-relative redirect targeting the administrative API or internal network addresses.
Below is a sequence diagram illustrating the transaction and subsequent credential disclosure:
Because the browser is not involved in back-channel API calls, no user interaction is required to trigger the final request. Once the server-side application resolves the redirect, the bearer token is sent directly to the adversary-controlled web server. The attacker-controlled server parses the incoming Authorization header and logs the stolen token for persistent API access.
The impact of CVE-2026-54603 is significant, representing a high-probability vector for complete credential compromise. The exposure of active access tokens allows attackers to bypass multi-factor authentication (MFA) and access control lists, enabling unauthorized data access or session hijacking on downstream systems. This behavior results in a CVSS v3.1 score of 8.6, emphasizing the critical nature of the flaw.
In addition to credential theft, this vulnerability represents a Server-Side Request Forgery (SSRF) hazard. If an attacker controls the endpoint or redirects the outbound connection, they can force the application server to make arbitrary requests to local network interfaces, metadata endpoints, or database APIs. Because the target URI undergoes standard resolution, local system paths (e.g., //127.0.0.1 or //169.254.169.254) can be accessed blindly through the redirection loop.
The scope of the vulnerability is further broadened by its presence in all versions of the oauth2 library ranging from 0.4.0 to 2.0.21, representing years of production releases. Due to the gem's widespread use as a foundational dependency in OmniAuth strategies and enterprise single-sign-on systems, the potential attack surface spans thousands of internet-exposed applications.
The recommended remediation path is to upgrade the oauth2 dependency to version 2.0.22 or later. This release enforces validation rules that block relative-authority hijacking and strip authentication headers during cross-origin transitions. Teams should inspect their dependency trees via Gemfile.lock to ensure all transitive dependencies (e.g., specific OmniAuth provider plugins) inherit the patched version.
When direct upgrades are blocked by legacy environment dependencies, a monkey-patch must be loaded during the application bootstrap process. This patch overrides the request loop in OAuth2::Client to manually intercept // relative redirects and purge the Authorization header when a cross-origin boundary is traversed. Applying structural monkey-patches provides immediate defense-in-depth without modifying base infrastructure.
Additionally, network-level and application-level controls should be configured to restrict outbound HTTP requests originating from backend application servers. Restricting server egress connections to designated Identity Providers using firewall egress policies prevents connections to unknown domains. Developers should also verify that Identity Provider endpoint configurations are read-only and cannot be manipulated by untrusted administrative accounts.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
oauth2 ruby-oauth | >= 0.4.0, <= 2.0.21 | 2.0.22 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-200, CWE-601 |
| Attack Vector | Network (AV:N) |
| CVSS v3.1 Score | 8.6 (High) |
| EPSS Score | Not Available |
| Impact | High (Credential Disclosure / SSRF) |
| Exploit Status | Proof-of-Concept |
| KEV Status | Not Listed |
The product exposes sensitive information to an actor who is not authorized to have access to that information.
A protection mechanism failure in the netfoil DNS proxy prior to version 0.4.0 causes blocked domains to resolve to null IP addresses (0.0.0.0 or ::) with a NOERROR status instead of NXDOMAIN. On Linux systems, connections to these addresses are routed to the loopback interface (localhost), allowing local processes to intercept sensitive HTTP traffic, including authorization headers and session cookies, intended for the blocked domains.
A directory traversal vulnerability exists in the `activerecord-tenanted` Ruby gem's local storage path resolution logic. Prior to version 0.7.0, the `path_for` method failed to sanitize input keys, allowing remote attackers to traverse directories and access arbitrary files on the host filesystem.
An uncontrolled resource consumption vulnerability in the OpenTelemetry Javaagent RMI context propagation mechanism allows remote unauthenticated attackers to cause a Denial of Service (DoS) via heap memory exhaustion.
A sensitive data exposure vulnerability (CWE-532) exists in OpenTelemetry Java Instrumentation prior to version 2.28.0. The JDBC auto-instrumentation component's SQL statement sanitizer contains lexer flaws that prevent the correct redaction of administrative passwords under specific conditions, such as when double-quotes represent identifiers or during multi-statement executions separated by semicolons.
CVE-2026-54705 is a critical DOM-based Cross-Site Scripting (XSS) vulnerability affecting the MathLive library prior to version 0.110.0. The flaw stems from a lack of proper character escaping in the rendering path for LaTeX text-mode commands such as \text{} and \mbox{}, enabling unauthenticated attackers to execute arbitrary JavaScript in the victim's browser.
A critical authorization bypass vulnerability was identified in the ZITADEL identity management platform, tracked as CVE-2026-54693 (GHSA-jq8w-8q2f-ffm9). Due to incorrect privilege validation in self-service profile modification endpoints, standard authenticated users could obtain plaintext verification codes during email or phone number updates. This allows attackers to claim and verify arbitrary email addresses or phone numbers without controlling the underlying contact methods.