Skip to main content
CVE Vulnerability Database
Vulnerability Database/CVE-2026-54491

CVE-2026-54491: Koel Music Streaming SSRF Vulnerability

CVE-2026-54491 is a server-side request forgery flaw in Koel music streaming that enables authenticated attackers to access internal services via podcast and radio APIs. This article covers technical details, affected versions, and patches.

Updated:

CVE-2026-54491 Overview

CVE-2026-54491 is a Server-Side Request Forgery (SSRF) vulnerability [CWE-918] in Koel, a free open-source music streaming platform. Versions prior to 9.7.1 perform point-in-time host validation using App\Helpers\Network::isPublicHost() or isSafeUrl() without pinning the validated IP address. Most outbound paths lack redirect-hop validation and do not revalidate each redirect target. An authenticated user can trigger requests via podcast and radio APIs that follow attacker-controlled redirects to internal addresses or fall to DNS rebinding. This lets adversaries reach internal services or cloud metadata endpoints and receive parsed or streamed response content.

Critical Impact

Authenticated attackers can coerce the Koel backend into contacting internal-only services and cloud metadata endpoints such as 169.254.169.254, exposing credentials and internal APIs.

Affected Products

  • Koel music streaming server versions prior to 9.7.1
  • Podcast subsystem: PhanAn\Poddle\Poddle::fromUrl(), PodcastService::getStreamableUrl(), PodcastService::isPodcastObsolete()
  • Validation rules: App\Rules\HasAudioContentType, App\Rules\SafeUrl

Discovery Timeline

  • 2026-08-19 - CVE-2026-54491 published to NVD
  • 2026-08-20 - Last updated in NVD database

Technical Details for CVE-2026-54491

Vulnerability Analysis

The vulnerability is a classic Time-of-Check to Time-of-Use (TOCTOU) SSRF flaw. Koel validates the safety of outbound URLs with Network::isPublicHost() or isSafeUrl(). These helpers perform a single DNS resolution and confirm the resolved address is public. The validated address is never pinned for the subsequent HTTP request. Between validation and connection, DNS answers can flip from a public IP to a private one (DNS rebinding). Additionally, most outbound paths follow HTTP redirects without revalidating each hop.

An authenticated attacker submits a URL that initially resolves to a public host. The Koel server passes the initial safety check. On the actual fetch, the attacker-controlled server returns a 302 Location: http://127.0.0.1/ redirect, or DNS resolution returns an internal IP such as 169.254.169.254. The Koel backend then connects to internal infrastructure and streams or parses the response.

Root Cause

The fetching helpers implemented single-shot host validation without IP pinning and without per-hop redirect validation. Downstream consumers, including Poddle::fromUrl(), PodcastService::getStreamableUrl(), PodcastService::isPodcastObsolete(), and the SafeUrl and HasAudioContentType rules, followed redirects using default Guzzle behavior that did not re-invoke the safety check.

Attack Vector

Exploitable endpoints include createPodcastChannel, createInternetRadioStation, refreshPodcasts, the apiResource podcasts handler, and radio/stations. Authentication is required, but any low-privileged user account is sufficient. The response content from internal services can be returned to the attacker through parsed podcast metadata or streamed radio output.

php
// Patch: app/Helpers/SafeHttp.php introduces per-hop redirect validation
// Source: https://github.com/koel/koel/commit/c264a3d52513a83b21e1cc3a20e895caea97fc4a
<?php

namespace App\Helpers;

use App\Exceptions\UnsafeUrlException;
use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Middleware;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\UriInterface;

/**
 * Defenses for SSRF on outbound HTTP. Every call site that follows redirects
 * needs per-hop validation: an attacker-controlled URL can pass the initial
 * SafeUrl check, then 302 to 127.0.0.1, 169.254.169.254, etc.
 */
class SafeHttp
{
    public function __construct(
        private readonly Network $network,
    ) {}
}

Source: GitHub Commit c264a3d

Detection Methods for CVE-2026-54491

Indicators of Compromise

  • Outbound HTTP connections from the Koel server process to private ranges (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 127.0.0.0/8) or to 169.254.169.254
  • Podcast or radio station records created with URLs whose DNS resolution history shows short TTLs and both public and private answers
  • HTTP 302 responses from external hosts redirecting to loopback or link-local addresses in Koel application logs

Detection Strategies

  • Enable verbose Guzzle logging on the Koel backend and alert on any request whose effective URL host resolves to a non-public address
  • Correlate createPodcastChannel, createInternetRadioStation, and refreshPodcasts API calls with outbound network telemetry to detect anomalous destinations
  • Inspect the radio_stations and podcast tables for entries pointing to suspicious or freshly registered domains

Monitoring Recommendations

  • Monitor egress from application servers to cloud metadata endpoints; alert on any traffic destined to 169.254.169.254 originating from web workers
  • Track authenticated API usage per user and flag accounts that create abnormal volumes of podcast or radio entries
  • Enable network policy logging in Kubernetes or host firewalls to record blocked private-range egress attempts

How to Mitigate CVE-2026-54491

Immediate Actions Required

  • Upgrade Koel to version 9.7.1 or later, which introduces App\Helpers\SafeHttp with per-hop redirect validation
  • Restrict egress from the Koel host so that outbound connections to RFC1918 ranges, loopback, and 169.254.169.254 are blocked at the network layer
  • Audit existing podcast channels and radio stations for URLs pointing to internal or suspicious hosts and remove them

Patch Information

The fix is available in Koel 9.7.1. See GitHub Release v9.7.1 and the GHSA-6qvr-wjmv-v8mm advisory. Two commits deliver the remediation: commit 5f6ce2c hardens DNS rebinding and IPv6 transition handling, and commit c264a3d validates every redirect hop on outbound HTTP. See also Pull Request #2546 and Pull Request #2549.

Workarounds

  • Place the Koel backend behind an egress proxy that enforces an allowlist of external podcast and radio host domains
  • Deny outbound traffic from Koel workers to 127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16, and IPv6 loopback and link-local ranges
  • Temporarily disable podcast and internet radio features for untrusted users until the patch is applied
bash
# Example iptables egress rules blocking SSRF targets from the Koel host
iptables -A OUTPUT -d 127.0.0.0/8 -j REJECT
iptables -A OUTPUT -d 10.0.0.0/8 -j REJECT
iptables -A OUTPUT -d 172.16.0.0/12 -j REJECT
iptables -A OUTPUT -d 192.168.0.0/16 -j REJECT
iptables -A OUTPUT -d 169.254.0.0/16 -j REJECT

Disclaimer: This content was generated using AI. While we strive for accuracy, please verify critical information with official sources.

Default Legacy - Prefooter | Experience the World’s Most Advanced Cybersecurity Platform

Experience the Most Advanced Cybersecurity Platform

See how the world’s most intelligent, autonomous cybersecurity platform can protect your organization today and into the future.