CVE-2026-54494
PHP vulnerability analysis and mitigation

Overview

CVE-2026-54494 is a full-read Server-Side Request Forgery (SSRF) vulnerability in Koel, an open-source personal music streaming server. The flaw exists in App\Helpers\Network::isPublicHost(), which uses PHP's filter_var() with FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE to classify IP addresses — a check that fails to recognize IPv6 transition address forms (NAT64 64:ff9b::/96 per RFC 6052 and 6to4 2002::/16 per RFC 3056) that embed private or loopback IPv4 addresses. All versions of phanan/koel up to and including 9.7.0 are affected; version 9.7.1 contains the fix. The vulnerability was reported by researcher tonghuaroot, published as GHSA-rjg7-r26h-cfp2 on June 4, 2026, and added to the GitHub Advisory Database on July 15, 2026. It carries a CVSS v4.0 base score of 5.3 (Medium) (GitHub Advisory, Koel Advisory).

Technical details

The root cause (CWE-918) is that PHP's filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) correctly rejects RFC 1918 addresses, loopback, link-local, and IPv4-mapped IPv6 (::ffff:a.b.c.d), but treats NAT64 (64:ff9b::/96) and 6to4 (2002::/16) transition wrappers as globally routable public addresses. This means addresses like 64:ff9b::7f00:1 (encoding 127.0.0.1), 64:ff9b::a9fe:a9fe (encoding the AWS/GCP IMDS endpoint 169.254.169.254), or 2002:a00:1:: (encoding 10.0.0.1) all pass the guard and return true. The vulnerable guard is the sole SSRF defense in front of App\Values\Podcast\EpisodePlayable::createForEpisode(), which fetches a podcast episode via Http::sink($file)->get($url) and streams the full response body back to the requesting user. Because the <enclosure url> in a podcast RSS feed is fully attacker-controlled, and any authenticated Koel user can subscribe to an arbitrary feed, an attacker can publish a feed with an enclosure whose hostname resolves via AAAA record to a NAT64/6to4 wrapper of an internal IP; on hosts with NAT64 or 6to4/dual-stack routing (standard on IPv6-only AWS/GCP subnets), the kernel routes the connection to the embedded private IPv4 (GitHub Advisory, Koel Advisory).

Impact

Successful exploitation enables a full-read SSRF attack, where the Koel server fetches an internal HTTP endpoint and returns the complete response body to the attacker. The most critical impact on cloud-hosted deployments is theft of cloud instance metadata service (IMDS) credentials — for example, querying http://[64:ff9b::a9fe:a9fe]/latest/meta-data/iam/security-credentials/ on AWS to obtain IAM role tokens, enabling lateral movement and privilege escalation within the cloud environment. Internal-only HTTP services reachable from the Koel host (admin panels, databases with HTTP interfaces, localhost daemons) are also exposed. The redirect callback in EpisodePlayable reuses the same flawed isSafeUrl() guard, so a redirect chain to a NAT64/6to4 host is equally bypassed (GitHub Advisory, Koel Advisory).

Exploitability

A working proof-of-concept is included in the public advisory, demonstrating that isPublicHost() returns true for NAT64 and 6to4 wrappers of private IPs and that Http::sink()->get() successfully retrieves internal content. Exploitation requires an authenticated Koel account (low privilege) and a Koel server running on a host with NAT64 or 6to4/dual-stack routing — the default configuration on IPv6-only AWS and GCP subnets. No evidence of in-the-wild exploitation or threat actor attribution has been reported. The CVE status is listed as Reserved, and no CISA KEV catalog entry or EPSS score is currently available (GitHub Advisory, Koel Advisory).

Exploitation steps

  1. Obtain authenticated access: Register or log in to the target Koel instance with any valid user account (the podcast subscription feature is available to all authenticated users).
  2. Set up a malicious DNS record: Configure a domain (e.g., int.attacker.example) with an AAAA record pointing to a NAT64 or 6to4 wrapper of the target internal IP — for example, 64:ff9b::a9fe:a9fe to target the AWS/GCP IMDS endpoint (169.254.169.254), or 2002:a00:1:: to target 10.0.0.1.
  3. Host a malicious podcast RSS feed: Create a valid RSS feed containing a podcast episode whose <enclosure url> points to the attacker-controlled domain (e.g., http://int.attacker.example/latest/meta-data/iam/security-credentials/).
  4. Subscribe to the feed: Use Koel's podcast subscription feature to subscribe to the attacker-hosted RSS feed URL.
  5. Trigger episode playback/streaming: Initiate playback or streaming of the malicious episode. This causes EpisodePlayable::createForEpisode() to call isSafeUrl() on the enclosure URL.
  6. Guard bypass: isPublicHost() resolves the AAAA record to the NAT64/6to4 address, passes it through filter_var(FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE), which returns true (incorrectly classifying it as public).
  7. SSRF execution and data exfiltration: Http::sink($file)->get($url) connects to the internal endpoint via the kernel's NAT64/6to4 routing, reads the full response body (e.g., IAM credentials), and streams it back to the attacker as the episode content (GitHub Advisory, Koel Advisory).

Indicators of compromise

  • Network: Outbound HTTP connections from the Koel server to IPv6 addresses in the 64:ff9b::/96 (NAT64) or 2002::/16 (6to4) ranges, particularly to 64:ff9b::a9fe:a9fe (IMDS) or RFC 1918-embedding 6to4 addresses; unexpected connections to 169.254.169.254 or internal subnet IPs originating from the Koel process.
  • Logs: Koel application logs showing podcast episode fetch requests to hostnames resolving to NAT64/6to4 addresses; Laravel/Guzzle HTTP client logs recording GET requests to [64:ff9b::...]:port or [2002:...]:port endpoints; access logs on internal services showing requests with a User-Agent consistent with Guzzle/Laravel HTTP client.
  • File System: Temporary episode files in the Koel storage directory containing unexpected content (e.g., JSON-formatted cloud metadata or IAM credential responses) rather than audio data.
  • Behavior: Authenticated users subscribing to external podcast feeds with unusual or newly registered domains; episode streaming requests that return non-audio content types (e.g., application/json, text/plain) from the Koel server.

Mitigation and workarounds

Upgrade to Koel version 9.7.1, which was released on June 4, 2026, and contains the fix in commit 5f6ce2c. The fix replaces the hand-rolled filter_var predicate with the mlocati/ip-lib library's RangeType enum, which correctly classifies NAT64 (64:ff9b::/96) as T_RESERVED and 6to4 (2002::/16) by the embedded IPv4 type, and adds an extractEmbeddedIpv4() helper to unwrap transition addresses before the private-range check. Additionally, the fix introduces IP pinning via CURLOPT_RESOLVE (DNS pinning) to prevent DNS rebinding attacks on redirect hops. No configuration-based workaround is available for unpatched versions; the only remediation is upgrading to v9.7.1 or later (Koel Release, Fix Commit).

Community reactions

The vulnerability was reported by researcher tonghuaroot and acknowledged by Koel maintainer phanan, who merged the fix PR (#2549) on June 4, 2026. The advisory notes that this SSRF class was not covered by the preceding v9.7.0 security scope, which the maintainer acknowledged as an oversight. No significant broader media coverage or notable community commentary beyond the GitHub advisory and PR discussion has been identified (Koel PR, Koel Advisory).

Additional resources


SourceThis report was generated using AI

Related PHP vulnerabilities:

CVE ID

Severity

Score

Technologies

Component name

CISA KEV exploit

Has fix

Published date

CVE-2026-54493HIGH7.7
  • PHP logoPHP
  • phanan/koel
NoYesAug 19, 2026
CVE-2026-54491HIGH7.1
  • PHP logoPHP
  • phanan/koel
NoYesAug 19, 2026
CVE-2026-61807MEDIUM6.3
  • PHP logoPHP
  • snipe/snipe-it
NoYesAug 19, 2026
CVE-2026-54494MEDIUM5.3
  • PHP logoPHP
  • phanan/koel
NoYesAug 19, 2026
CVE-2026-54492MEDIUM4.3
  • PHP logoPHP
  • phanan/koel
NoYesAug 19, 2026

Free Vulnerability Assessment

Benchmark your Cloud Security Posture

Evaluate your cloud security practices across 9 security domains to benchmark your risk level and identify gaps in your defenses.

Request assessment

Get a personalized demo

Ready to see Wiz in action?

"Best User Experience I have ever seen, provides full visibility to cloud workloads."
David EstlickCISO
"Wiz provides a single pane of glass to see what is going on in our cloud environments."
Adam FletcherChief Security Officer
"We know that if Wiz identifies something as critical, it actually is."
Greg PoniatowskiHead of Threat and Vulnerability Management