GitHub Security Advisory: GHSA-pjv4-3c63-699f
Package: github.com/open-telemetry/opentelemetry-collector-contrib/extension/azureauthextension (GO)
Severity: HIGH (CVSS 8.1)
CWE: CWE-208: Observable Timing Discrepancy, CWE-287: Improper Authentication, CWE-290: Authentication Bypass by Spoofing, CWE-294: Authentication Bypass by Capture-replay, CWE-347: Improper Verification of Cryptographic Signature
Vulnerable versions: >= 0.124.0, <= 0.150.0
Patched in: unpatched

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

Details:
### Summary

A server-side authentication bypass in `azureauthextension` allows any party who holds a single valid Azure access token for *any scope the collector's configured identity can mint for* to authenticate to any OpenTelemetry receiver that uses `auth: azure_auth`. The extension's `Authenticate` method does not validate incoming bearer tokens as JWTs. Instead, it calls its own configured credential to obtain an access token and compares the client's token to the result with string equality — and the scope for that server-side token request is taken from the client-supplied `Host` header. As a result, a token minted for any Azure resource the service principal has ever been issued a token for (ARM, Graph, Key Vault, Storage, etc.) will authenticate to the collector if the attacker picks a matching `Host`. Tokens are replayable for the full issued lifetime (commonly several hours for managed identity tokens).

Severity: High (CVSS 8.1). See "Threat model" below for the preconditions that inform that score.

### Root cause

The extension implements both `extensionauth.HTTPClient` (outbound: "attach my identity to requests I send") and `extensionauth.Server` (inbound: "validate a credential someone presented to me"). Those two interfaces look symmetric but are not: holding a credential to present says nothing about the ability to validate a credential someone else presents. The outbound path only requires `credential.GetToken()`; the inbound path requires JWT signature verification against the issuer's JWKS, issuer/audience/exp/nbf checks, and an algorithm allowlist — none of which the extension does.

PR #39178 ("Implement extensionauth.HTTPClient and extensionauth.Server interface functions") added the `Server` path in v0.124.0 by reusing the same credential object and comparing strings. That server-side path is present in every release through v0.150.0. The outbound `HTTPClient` path (used by Azure exporters) is unaffected.

### Details

Vulnerable code — `extension/azureauthextension/extension.go:208–235`:

```go
func (a *authenticator) Authenticate(ctx context.Context, headers map[string][]string) (context.Context, error) {
    auth, err := getHeaderValue("Authorization", headers)
    if err != nil { return ctx, err }
    host, err := getHeaderValue("Host", headers)
    if err != nil { return ctx, err }

    authFormat := strings.Split(auth, " ")
    if len(authFormat) != 2 { /* ... */ }
    if authFormat[0] != "Bearer" { /* ... */ }

    token, err := a.getTokenForHost(ctx, host)   // asks the collector's own identity
    if err != nil { return ctx, err }
    if authFormat[1] != token {                  // string comparison, not JWT validation
        return ctx, errors.New("unauthorized: invalid token")
    }
    return ctx, nil
}
```

And `getTokenForHost` at `extension.go:187–206`:

```go
options := policy.TokenRequestOptions{
    Scopes: []string{
        fmt.Sprintf("https://%s/.default", host),   // client-supplied Host chooses s