CVE-2026-54494 Overview
CVE-2026-54494 is a Server-Side Request Forgery (SSRF) vulnerability [CWE-918] in Koel, a free and open-source music streaming platform. The flaw affects versions prior to 9.7.1 and stems from incomplete host validation in App\Helpers\Network::isPublicHost(). The function relies on filter_var() with FILTER_FLAG_NO_PRIV_RANGE and FILTER_FLAG_NO_RES_RANGE, which fails to classify NAT64 (64:ff9b::/96) and 6to4 (2002::/16) wrappers of private, loopback, or link-local IPv4 addresses as non-public. Authenticated users can supply a crafted podcast RSS <enclosure url> to force Koel to fetch internal resources.
Critical Impact
On hosts with NAT64 or 6to4 routing, an authenticated attacker can request internal services or cloud instance metadata endpoints and receive the response body through Koel's podcast episode fetcher.
Affected Products
- Koel music streaming server versions prior to 9.7.1
- Deployments with NAT64 (64:ff9b::/96) routing enabled
- Deployments with 6to4 (2002::/16) routing enabled
Discovery Timeline
- 2026-08-19 - CVE-2026-54494 published to NVD
- 2026-08-19 - Last updated in NVD database
Technical Details for CVE-2026-54494
Vulnerability Analysis
The vulnerability resides in Koel's URL safety check used to validate remote resources before fetching them. The isPublicHost() helper relies exclusively on PHP's filter_var() with the FILTER_FLAG_NO_PRIV_RANGE and FILTER_FLAG_NO_RES_RANGE flags. These flags evaluate IPv4 and IPv6 addresses independently and do not recognize IPv6 transition mechanisms that embed IPv4 addresses.
When the check receives an IPv6 address in the NAT64 (64:ff9b::/96) or 6to4 (2002::/16) range, the underlying IPv4 target, such as 127.0.0.1, 169.254.169.254, or an RFC 1918 address, is hidden inside the IPv6 wrapper. The filter treats the address as a public IPv6 address and allows the request to proceed.
Exploitation flows through EpisodePlayable::createForEpisode() in app/Values/Podcast/EpisodePlayable.php. After isSafeUrl() accepts the crafted URL, Http::sink($file)->get($url) fetches the target and stores the response, which is then returned to the requesting user.
Root Cause
The root cause is incomplete IPv6 address classification. filter_var() does not resolve IPv6 transition prefixes to their embedded IPv4 targets, so private, loopback, and link-local ranges are reachable through NAT64 and 6to4 wrappers. The isPublicHost() implementation trusts this filter as the sole gate.
Attack Vector
An authenticated Koel user submits a podcast subscription whose RSS feed contains an <enclosure url> pointing to an IPv6 transition address. Example targets include [64:ff9b::a9fe:a9fe] to reach the AWS instance metadata service at 169.254.169.254, or [64:ff9b::7f00:1] to reach 127.0.0.1. Koel resolves the enclosure, passes the URL through isSafeUrl(), and fetches it with the HTTP client. The response body is then exposed to the attacker.
// Patch excerpt from app/Helpers/Network.php
// Source: https://github.com/koel/koel/commit/5f6ce2cefd08f437a269236b677ad971517ccbb6
-<?php
-
-namespace App\Helpers;
-
-use Illuminate\Support\Uri;
-use Throwable;
-
-class Network
-{
- private const array SAFE_URL_SCHEMES = ['http', 'https'];
-
- /**
- * Check if a URL is safe to reach: HTTP/HTTPS scheme + a public host.
- * Does NOT perform any network calls beyond DNS resolution.
- * For full validation including effective-URL-after-redirect, use the SafeUrl validation rule.
- */
- public function isSafeUrl(string $url): bool
- {
- try {
- $uri = Uri::of($url);
- } catch (Throwable) {
- return false;
- }
-
- if (!in_array($uri->scheme(), self::SAFE_URL_SCHEMES, true)) {
- return false;
- }
-
- $host = $uri->host();
The patch removes the flawed helper and consolidates URL safety checks through the hardened SafeUrl validation rule that resolves and rejects IPv6 transition addresses. See the GitHub Security Advisory GHSA-rjg7-r26h-cfp2 and the pull request implementing the fix for full context.
Detection Methods for CVE-2026-54494
Indicators of Compromise
- Outbound HTTP requests from the Koel application host to IPv6 addresses in the 64:ff9b::/96 or 2002::/16 ranges.
- Koel podcast subscription entries containing enclosure URLs referencing IPv6 literals in square brackets, such as [64:ff9b::a9fe:a9fe] or [2002:7f00:1::].
- Application logs showing successful fetches of unusually small response payloads that resemble cloud metadata (iam/security-credentials, latest/meta-data).
Detection Strategies
- Inspect the Koel database podcasts and episodes tables for enclosure URLs containing bracketed IPv6 addresses or hostnames resolving into NAT64/6to4 ranges.
- Enable outbound egress logging on the Koel host and alert on connections to 169.254.169.254, loopback ranges, or IPv6 transition prefixes.
- Review web server access logs for authenticated POST requests to podcast subscription endpoints followed by outbound metadata-service traffic.
Monitoring Recommendations
- Instrument the Koel application host with egress firewall rules that deny traffic to link-local, loopback, and cloud metadata addresses, then alert on denies.
- Monitor DNS resolutions performed by the PHP-FPM process for hostnames returning IPv6 addresses in the 64:ff9b::/96 prefix.
- Track user activity for accounts that create podcast subscriptions with atypical or private-network hostnames.
How to Mitigate CVE-2026-54494
Immediate Actions Required
- Upgrade Koel to version 9.7.1 or later, which removes the vulnerable isPublicHost() helper and enforces validation through the hardened SafeUrl rule.
- Audit existing podcast subscriptions and remove any entries with enclosure URLs pointing to IPv6 transition addresses or private hosts.
- Rotate any cloud instance credentials that may have been exposed through the metadata service on affected hosts.
Patch Information
The fix is available in Koel release v9.7.1. The remediation is committed in commit 5f6ce2c and merged via pull request #2549. The patch replaces host allow-listing based on filter_var() flags with a SafeUrl validator that explicitly rejects NAT64, 6to4, DNS rebinding, and other bypass techniques.
Workarounds
- Disable IPv6 on the Koel application host, or drop routing for the 64:ff9b::/96 and 2002::/16 prefixes at the network layer.
- Restrict outbound egress from the Koel host to only the specific external endpoints required for podcast delivery.
- Block access to the cloud metadata endpoint (169.254.169.254) at the host firewall or via IMDSv2 enforcement on AWS.
# Upgrade Koel and block IPv6 transition prefixes at the host
cd /var/www/koel
git fetch --tags
git checkout v9.7.1
composer install --no-dev --optimize-autoloader
php artisan koel:init
# Block NAT64 and 6to4 egress with ip6tables
ip6tables -A OUTPUT -d 64:ff9b::/96 -j REJECT
ip6tables -A OUTPUT -d 2002::/16 -j REJECT
# Enforce IMDSv2 on AWS to prevent metadata exfiltration
aws ec2 modify-instance-metadata-options \
--instance-id i-0123456789abcdef0 \
--http-tokens required \
--http-endpoint enabled
Disclaimer: This content was generated using AI. While we strive for accuracy, please verify critical information with official sources.

