← Back to Feed CACHED · 2026-05-17 09:42:19 · cache_key CVE-2025-29912
CVE-2025-21617 · CWE-338 · Disclosed 2025-01-06

Guzzle OAuth Subscriber signs Guzzle requests using OAuth 1

ASSESSED — NOISGATE V0.5
Vendor
Reassessed
Verdict:
01 · The Real Story

This is a weak serial number on a sealed envelope, and it only matters if someone can already read the mail

CVE-2025-21617 is a low-entropy nonce bug in guzzlehttp/oauth-subscriber, the PHP middleware that signs Guzzle requests with OAuth 1.0. Affected versions are all releases before 0.8.1—practically the 0.1.0 through 0.8.0 line. Before the fix, the library generated oauth_nonce values predictably enough that a replay attack becomes more realistic if signed requests traverse the network without TLS protection.

In real enterprise conditions this is not a front-of-queue patch. The decisive friction is the attack precondition: the attacker typically needs on-path visibility or another way to capture OAuth 1.0 traffic *and* the deployment has to permit non-TLS transport or equivalent plaintext exposure on an internal segment. That sharply narrows the reachable population, so despite the security smell and the wide package install base, this lands as = ASSESSED AT LOW.

"Low-priority bug: it matters mainly when OAuth 1.0 requests travel without TLS and an attacker can replay them"
02 · The Attack Path

4 steps from start to impact.

STEP 01

Find a live OAuth 1.0 client on a vulnerable package

The attacker first identifies an application using guzzlehttp/oauth-subscriber prior to 0.8.1 and confirms it still signs requests with OAuth 1.0. Typical tooling here is basic package enumeration from leaked composer.lock, SBOMs, source disclosure, or CI artifact access rather than an internet-facing exploit scanner.
Conditions required:
  • Target uses guzzlehttp/oauth-subscriber <0.8.1
  • Application still relies on OAuth 1.0 signing
  • Attacker can identify the dependency or observe traffic shape
Where this breaks in practice:
  • Many modern PHP estates no longer use OAuth 1.0 at all
  • This is a library flaw, so there is no clean Shodan/Censys fingerprint
  • SCA usually catches the package once inventories exist
Detection/coverage: Good SCA/SBOM coverage from GitHub Dependabot, GitLab Dependency Scanning, and Composer ecosystem advisories; poor network scanner coverage because this is not a standalone service.
STEP 02

Get on-path access to signed traffic

To exploit the nonce weakness in practice, the attacker generally needs to capture or closely observe outbound signed requests, using tools like mitmproxy, tcpdump, or a compromised reverse proxy. The advisory explicitly ties impact to cases where TLS is not used, which means plaintext transport or equivalent internal visibility is the meaningful setup.
Conditions required:
  • Attacker has network position to observe client-to-server requests
  • TLS is absent, broken, bypassed internally, or traffic is otherwise exposed in plaintext
Where this breaks in practice:
  • HTTPS, mTLS, and API gateways kill this step in most sane deployments
  • If TLS is end-to-end, the nonce weakness is mostly academic
  • Requiring on-path visibility usually implies the attacker is already inside
Detection/coverage: Network IDS may see plaintext OAuth headers on HTTP, but most orgs will detect this deployment weakness through architecture review rather than exploit telemetry.
STEP 03

Replay or predict a usable OAuth request

Armed with the public fix diff and observed traffic, the attacker attempts replay with curl, Burp Repeater, or a custom script. The vulnerable code used a predictable nonce construction, later replaced with random_bytes(20), so the practical abuse case is making duplicate or guessable OAuth requests acceptable inside the server's nonce/timestamp handling window.
Conditions required:
  • Server accepts OAuth 1.0 requests from this client
  • Server-side replay protections are weak enough for the predictable nonce to matter
  • Attacker can resend within the accepted timestamp/nonce window
Where this breaks in practice:
  • Many OAuth 1.0 providers reject duplicate nonces aggressively
  • Timestamp windows are short
  • If the attacker can already replay raw plaintext traffic, the nonce bug only slightly improves their odds
Detection/coverage: Application logs may show repeated oauth_nonce, repeated oauth_timestamp, or duplicate business transactions. Most vuln scanners will not validate exploitability.
STEP 04

Gain limited business impact

Impact is usually bounded to whatever the signed API call could do: repeat a read, duplicate a state-changing request, or reissue a transaction the client was already authorized to make. There is no code execution path here; this is a constrained auth/message-integrity weakness whose blast radius is set by the specific API operation being replayed.
Conditions required:
  • Captured request performs a sensitive or non-idempotent action
  • Downstream application lacks its own anti-replay or duplicate-transaction logic
Where this breaks in practice:
  • Many APIs add app-layer idempotency controls or transaction deduplication
  • Read-only calls reduce impact to limited confidentiality exposure
  • Blast radius is usually one client integration, not whole-host compromise
Detection/coverage: Look for duplicate API operations from the same consumer key with abnormal timing or repeated request signatures; business fraud monitoring is often better than endpoint telemetry here.
03 · Intelligence Metadata

The supporting signals.

In-the-wild statusNo KEV listing was provided in the prompt, and I found no credible public reporting of active exploitation for this CVE.
Proof-of-concept availabilityNo standalone weaponized PoC stood out in public search results. The public fix commit is enough for a competent attacker to understand the bug.
EPSSPrompt-supplied EPSS is 0.00409 (0.409%), which is low signal for near-term exploitation pressure.
KEV statusNot KEV-listed per the prompt; no urgency boost from CISA exploitation evidence.
CVSS contextNo vendor baseline was supplied for comparison, so noisgate treats this as a first-principles assessment. The public CVSS context visible in NVD/OSV is CVSS:4.0/AV:N/AC:H/AT:N/PR:N/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N, which already hints at high attack complexity and limited direct impact.
Affected versionsAffected package is guzzlehttp/oauth-subscriber before 0.8.1; OSV enumerates the vulnerable line as 0.1.0 through 0.8.0.
Fixed versionsUpgrade to 0.8.1 or later. I found no distro backport guidance tied to this package; treat this as a normal Composer dependency update.
Exposure realityThis is a library, not a remotely fingerprintable appliance. Shodan/Censys/GreyNoise-style internet counts are not meaningful here; runtime exposure depends on whether an app still uses OAuth 1.0 and sends those requests without end-to-end TLS.
Ecosystem reachPackagist shows roughly 14.9M installs and 164 dependents, so the package is common enough that inventory hits will exist, even if exploitability is narrow.
Disclosure and creditPublished 2025-01-06 in GitHub advisory GHSA-237r-r8m4-4q88; credited finder is psyker156.
04 · The Call

noisgate verdict.

Final Verdict
= UNCHANGED to LOW (2.8/10)

The single biggest suppressor is the attacker-position requirement: this bug matters mainly when OAuth 1.0 traffic is exposed without TLS, which usually means an on-path or already-inside adversary. That turns a theoretically network-reachable flaw into a niche, post-compromise-style abuse case with limited blast radius.

HIGH Affected version range and fix version
MEDIUM Exploitability in real enterprise deployments
MEDIUM Lack of public exploitation evidence

Why this verdict

  • Requires plaintext or equivalent visibility: the advisory itself says replay risk materializes when TLS is not used, so secure transport removes most real-world exposure.
  • Attacker position is unfavorable: practical abuse usually needs on-path access, implying internal foothold, proxy compromise, or another prior-stage win before this CVE matters.
  • Blast radius is narrow: impact is limited to replaying the specific OAuth-signed action available to that client integration, not host takeover or broad tenant escape.

Why not higher?

This is not an unauthenticated internet-to-RCE story. To matter operationally, the deployment usually has to be using legacy OAuth 1.0, running a vulnerable dependency, and exposing signed traffic without proper TLS or equivalent confidentiality. Those are compounding friction points, not edge-case details.

Why not lower?

It is still a real security defect in a broadly used package, and some enterprises absolutely have internal HTTP hops, legacy API gateways, or test/prod exceptions that break the 'TLS everywhere' assumption. If the signed request performs a non-idempotent action, replay can become a legitimate business-risk issue rather than a paper cut.

05 · Compensating Control

What to do — in priority order.

  1. Enforce TLS end to end — Make sure OAuth 1.0 requests are protected from client through every proxy hop to the API origin, not just at the internet edge. For a LOW verdict there is no formal noisgate mitigation SLA; treat this as backlog hygiene, but fix any plaintext exception immediately if you discover one because that is the real enabler.
  2. Block cleartext egress for OAuth clients — Use proxy policy, service mesh policy, or firewall rules to prevent app tiers from sending OAuth 1.0 requests over plain HTTP. Again, LOW means no formal mitigation SLA, so fold this into routine network-hardening work unless you find an active non-TLS path.
  3. Add replay rejection server-side — Where you own the receiving API, verify nonce uniqueness and timestamp windows tightly, and add idempotency controls for state-changing operations. Treat this as backlog hardening for low-severity exposure, especially on legacy integrations you cannot retire quickly.
  4. Inventory and retire OAuth 1.0 usage — The best long-term control is reducing the population that still depends on OAuth 1.0 signing at all. Because the verdict is LOW, schedule this with normal architecture debt reduction rather than emergency response.
What doesn't work
  • A WAF does not solve predictable client nonce generation; the flaw is in how the request is signed before it ever hits the application perimeter.
  • EDR on the client host will not reliably catch protocol-level replay if the attacker is just resending captured HTTP requests from elsewhere.
  • Rotating OAuth secrets alone does not fix the weak nonce behavior; it changes credentials, not the replay surface created by predictable or observable requests.
06 · Verification

Crowdsourced verification payload.

Run this on the application host, CI workspace, or any auditor workstation that can read the target project's composer.lock. Invoke it as python3 check_cve_2025_21617.py /path/to/app or point directly at the lockfile like python3 check_cve_2025_21617.py /var/www/html/composer.lock. It needs only read access; no admin privileges are required.

noisgate-verify.py
PYTHONREAD-ONLYSAFE
#!/usr/bin/env python3
# Check for CVE-2025-21617 in guzzlehttp/oauth-subscriber
# Usage: python3 check_cve_2025_21617.py /path/to/app-or-composer.lock
# Exit codes: 0=PATCHED, 1=VULNERABLE, 2=UNKNOWN

import json
import os
import re
import sys

PACKAGE = 'guzzlehttp/oauth-subscriber'
FIXED = (0, 8, 1)


def normalize_version(v):
    if v is None:
        return None
    v = v.strip()
    if v.startswith('v'):
        v = v[1:]
    v = v.split('@', 1)[0]
    if any(tag in v.lower() for tag in ['dev', 'alpha', 'beta', 'rc', 'patch', 'pl']):
        # Composer branches / pre-release naming are ambiguous for this simple offline check
        return None
    m = re.match(r'^(\d+)\.(\d+)\.(\d+)(?:[.+-].*)?$', v)
    if not m:
        return None
    return tuple(int(x) for x in m.groups())


def compare_version(a, b):
    return (a > b) - (a < b)


def load_lockfile(path):
    with open(path, 'r', encoding='utf-8') as f:
        return json.load(f)


def find_package(data, name):
    for section in ('packages', 'packages-dev'):
        for pkg in data.get(section, []) or []:
            if pkg.get('name') == name:
                return pkg
    return None


def resolve_target(arg):
    if os.path.isdir(arg):
        candidate = os.path.join(arg, 'composer.lock')
        if os.path.isfile(candidate):
            return candidate
        return None
    if os.path.isfile(arg) and os.path.basename(arg) == 'composer.lock':
        return arg
    return None


def main():
    if len(sys.argv) != 2:
        print('UNKNOWN - usage: python3 check_cve_2025_21617.py /path/to/app-or-composer.lock')
        sys.exit(2)

    target = resolve_target(sys.argv[1])
    if not target:
        print('UNKNOWN - composer.lock not found')
        sys.exit(2)

    try:
        data = load_lockfile(target)
    except Exception as e:
        print(f'UNKNOWN - failed to read lockfile: {e}')
        sys.exit(2)

    pkg = find_package(data, PACKAGE)
    if not pkg:
        print(f'UNKNOWN - package {PACKAGE} not present in lockfile')
        sys.exit(2)

    raw_version = pkg.get('version')
    parsed = normalize_version(raw_version)
    if parsed is None:
        print(f'UNKNOWN - package present but version is non-semver or ambiguous: {raw_version}')
        sys.exit(2)

    if compare_version(parsed, FIXED) < 0:
        print(f'VULNERABLE - {PACKAGE} {raw_version} < 0.8.1')
        sys.exit(1)

    print(f'PATCHED - {PACKAGE} {raw_version} >= 0.8.1')
    sys.exit(0)


if __name__ == '__main__':
    main()
07 · Bottom Line

If you remember one thing.

TL;DR
Monday morning: do not burn an emergency window on this unless you discover a real non-TLS OAuth 1.0 path. For a LOW verdict there is no noisgate mitigation SLA and no noisgate remediation SLA; treat it as backlog hygiene, validate whether any vulnerable app is sending OAuth 1.0 traffic over plain HTTP or exposed internal hops, and then roll guzzlehttp/oauth-subscriber to 0.8.1+ in the next normal dependency-maintenance cycle while documenting the rationale for anything left temporarily unpatched.

Sources

  1. GitHub Security Advisory GHSA-237r-r8m4-4q88
  2. Fix commit 92b619b
  3. Release 0.8.1
  4. NVD entry for CVE-2025-21617
  5. OSV entry for CVE-2025-21617
  6. Packagist package metadata
  7. GitLab Advisory Database entry
Peer Review

What defenders are saying.

Submit a review attribution: handle + country only
0 flags selected · stored anonymously
Validation Results

Crowdsourced verification outputs.

Results submitted by users who ran the verification payload against their environment.