GitHub Security Advisory: GHSA-fgv2-4q4g-wc35
Package: ca.uhn.hapi.fhir:org.hl7.fhir.utilities (MAVEN)
Severity: HIGH (CVSS 7.4)
CWE: CWE-346: Origin Validation Error
Vulnerable versions: < 6.9.4
Patched in: 6.9.4

Summary: HAPI FHIR Core has Authentication Credential Leakage via Improper URL Prefix Matching on HTTP Redirect

Details:
## Summary

`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.

## Details

The root cause is in `ManagedWebAccessUtils.getServer()` at `org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedWebAccessUtils.java:26`:

```java
public static ServerDetailsPOJO getServer(String url, Iterable<ServerDetailsPOJO> serverAuthDetails) {
    if (serverAuthDetails != null) {
      for (ServerDetailsPOJO serverDetails : serverAuthDetails) {
          if (url.startsWith(serverDetails.getUrl())) {  // <-- no host boundary check
            return serverDetails;
          }
      }
    }
    return null;
}
```

The configured production terminology server URL is defined without a trailing slash in `FhirSettingsPOJO.java:19`:

```java
protected static final String TX_SERVER_PROD = "http://tx.fhir.org";
```

This means:
- `"http://tx.fhir.org.attacker.com/capture".startsWith("http://tx.fhir.org")` → **true**
- `"http://tx.fhir.org:8080/evil".startsWith("http://tx.fhir.org")` → **true**

**Exploit chain via SimpleHTTPClient (redirect path):**

1. `SimpleHTTPClient.get()` (`SimpleHTTPClient.java:68-105`) makes a request to `http://tx.fhir.org/ValueSet/$expand`
2. On each redirect, the loop calls `getHttpGetConnection(url, accept)` (line 84) → `setHeaders(connection)` (line 117)
3. `setHeaders()` (line 122-133) calls `authProvider.canProvideHeaders(url)` and `authProvider.getHeaders(url)` on the **redirect target URL**
4. `ServerDetailsPOJOHTTPAuthProvider.getServerDetails()` (line 83-84) delegates to `ManagedWebAccessUtils.getServer(url.toString(), servers)`
5. The `startsWith()` check matches `http://tx.fhir.org.attacker.com` against `http://tx.fhir.org`
6. Credentials are dispatched to the attacker's server via `ServerDetailsPOJOHTTPAuthProvider.getHeaders()` (lines 38-58):
   - Bearer tokens: `Authorization: Bearer {token}`
   - Basic auth: `Authorization: Basic {base64(user:pass)}`
   - API keys: `Api-Key: {apikey}`
   - Custom headers from server config

Note: 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.

**Exploit chain via ManagedFhirWebAccessor (OkHttp path):**

`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 a