# opentelemetry-collector-contrib's azureauthextension Authenticate method does not validate bearer tokens, allowing auth bypass via replay

azureauthextension's Authenticate compares client bearer tokens by string equality against a server-minted token scoped by the client's Host header, letting any holder of a valid Azure token for the SP authenticate to OTLP receivers.

- Payout: Research
- Severity: high
- Program: OpenTelemetry Collector Contrib
- Found by: caitlinhalla
- Disclosed: 2026-05-06
- CVE: CVE-2026-42602
- Write-up quality: 8/10
- Original: https://github.com/advisories/GHSA-pjv4-3c63-699f
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-pjv4-3c63-699f
- Tags: area:cloud domain:infra impact:integrity kind:research platform:ghsa sev:high tech:go technique:chained technique:source-review vuln:access-control vuln:auth-bypass

**Target:** azureauthextension Authenticate on OTLP HTTP/gRPC receivers
**Root cause:** The extension reuses its outbound credential for inbound auth: instead of verifying the presented JWT signature/iss/aud/exp against tenant JWKS, it mints its own token and compares strings. The scope for that minted token is derived from the client-supplied Host header, so the attacker chooses the expected audience.
**Steps:**
1. Configure collector with azureauthextension on a receiver's `auth:` block (v0.124.0–v0.150.0).
2. Obtain any valid Azure access token issued to the collector's service principal/managed identity for any scope (e.g. `https://vault.azure.net/.default`).
3. Send `POST /v1/traces` with `Host: vault.azure.net` and `Authorization: Bearer <token>`.
4. Authenticate calls `getTokenForHost(ctx, host)` which requests scope `https://<Host>/.default`, gets the same cached token, and string comparison passes.
**How found:** Code review of the PR that added the Server interface path (#39178): noticed the inbound path reused the outbound credential object and compared strings rather than validating JWTs, and that the scope came from the client-supplied Host header.
**Exploitation:** Attacker with a token for any scope the SP can mint sets the Host header to that resource's hostname and replays the token to the OTLP receiver; the extension mints/returns the identical cached token for that scope and accepts the request, allowing arbitrary trace/metric/log injection. Tokens remain replayable for the full token TTL (hours for managed identity).
**Escalation:** Scope confusion broadens the attack surface: a token leaked from an unrelated Azure integration (ARM, Graph, Key Vault, Storage) authenticates to the collector, not just tokens for the collector's intended audience.
**Insight:** When a component implements both an outbound credential interface and an inbound authenticator interface, check whether the inbound path actually verifies the presented credential or just re-mints its own and compares — and whether any part of the expected value (audience/scope) is derived from request headers.

## Proof of concept (copied exactly from the original report)

**Replay request with matching Host header**

```
POST /v1/traces HTTP/1.1
Host: management.azure.com
Authorization: Bearer eyJ...            # token minted for management.azure.com
Content-Type: application/json

{"resourceSpans":[...]}

`Authenticate` calls `getTokenForHost(ctx, "management.azure.com")`, receives the identical cached token, and the string comparison passes.
```

**Scope confusion request using Key Vault token**

```
POST /v1/traces HTTP/1.1
Host: vault.azure.net
Authorization: Bearer eyJ...            # token minted for vault.azure.net
Content-Type: application/json

{"resourceSpans":[...]}

`Authenticate` calls `getTokenForHost(ctx, "vault.azure.net")`. The collector's credential mints (or returns cached) a token for `https://vault.azure.net/.default` — the same token the attacker holds, because both come from the same SP issued for the same scope by the same IdP. Comparison passes. The collector accepts telemetry gated on "proof of identity to Key Vault."
```

