OpenObserve: The SSRF Guard Checks Only the First URL, Not the Redirect
The guard checks the submitted address, after which the HTTP client follows up to ten redirects with no further check.
Advisory ID: TP-2026-088
Product: OpenObserve (platform for logs, metrics and traces, Rust)
Vulnerability type: Server-side request forgery (CWE-918)
CVE: requested (GitHub CNA), assignment pending
CVSS 3.1: 8.5 (High) · CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:L/A:N
Affected versions: < 0.91.0
Vendor advisory: GHSA-rhm9-hcf6-v2f5
Fixed in: 0.91.0
Reported: 27 May 2026
Summary
OpenObserve can send alerts to external destinations and offers a test button that fetches the configured address right away. Before the fetch, a guard rejects loopback, private networks, the link-local range and the well-known cloud metadata addresses. That guard sees only the submitted address. The HTTP client behind it then follows up to ten redirects, and the guard runs again on none of them. Anyone who supplies a public address that answers with a redirect to an internal one reads the internal service's response verbatim in the API reply, and an ordinary organization account is enough.
Root cause
The test handler calls SsrfGuard::validate_url_with_config(url) on the submitted address alone (src/handler/http/request/alerts/destinations.rs:172). Right below it the client is built with reqwest::Client::builder().timeout(...).build() and no redirect policy, so reqwest follows its default and goes along for up to ten hops (:182). The guard itself parses and checks only the string it was handed and resolves no name, so a public name pointing at a private address passes as well (src/common/utils/ssrf_guard.rs:48,68). The response body is returned to the caller as response_body through response.text(), turning the blind request into a reading one (:233). The alert sender shares the same root cause, though blind there.
Proof of Concept
# 1. a public redirector answers with:
# HTTP/1.1 302 Found
# Location: http://169.254.169.254/latest/meta-data/iam/security-credentials/
# 2. as an ordinary organization user:
POST /api/{org}/alerts/destinations/test
{"url": "https://redirector.example/to-metadata", "method": "get"}
# the guard sees only the public name, reqwest follows the redirect,
# and response_body carries the internal service's answer.
A public name whose A record points at a private address achieves the same, because the guard never resolves names in the first place.
Impact
- Read access to internal HTTP services from the server's network, with the answer reflected verbatim in the API reply.
- Retrieval of the metadata service on a cloud deployment, and with it the instance's role credentials.
- Mapping of the internal network through the differing responses, without the attacker having to reach anything there.
- An ordinary organization account suffices; no administrator role is needed.
References
Is Something Like This in Your Software?
Our team found this vulnerability in the course of its work. Have your applications tested by the same specialists, with a penetration test from turingpoint.
