GHSA-gx7w-56w6-g48x Caddy: Remote Admin Authorization Bypass on PKI Endpoints via Prefix-Based Path Matching
AI DisclosureI used an LLM to help review the source code, reason about attack surface, and help draft and refine this report.
  I manually validated the finding by reproducing it locally, confirming the vulnerable code path, and verifying the HTTP behavior with `curl -v`.SummaryCaddy's remote admin access control performs path authorization using prefix matching:[`admin.go`](/caddy/admin.go#L719): `strings.HasPrefix(r.URL.Path, allowedPath)`This allows a client certificate authorized only for `/pki/ca/prod` to access sibling PKI resources whose paths merely share the same prefix, such as `/pki/ca/prod-backup`.This is an authorization bug in Caddy's source code, not a misconfiguration issue. The configured policy is more restrictive than the behavior that Caddy actually enforces.Affected ComponentRemote admin access control for PKI admin endpoints.Relevant code:[`admin.go`](/caddy/admin.go#L687)[`admin.go`](/caddy/admin.go#L719)[`modules/caddypki/adminapi.go`](/caddy/modules/caddypki/adminapi.go#L68)[`modules/caddypki/adminapi.go`](/caddy/modules/caddypki/adminapi.go#L164)Root CauseIn `RemoteAdmin.enforceAccessControls()`, allowed paths are checked like this:This does not enforce a path-segment boundary.So if the allowed path is:/pki/ca/prodthen all of the following are treated as authorized:/pki/ca/prod-backup/pki/ca/prod1/pki/ca/prodanythingFor PKI admin endpoints, the CA ID is taken directly from the request path:modules/caddypki/adminapi.go:164So /pki/ca/prod-backup is interpreted as CA ID prod-backup, even though only /pki/ca/prod was intended to be allowed.Security ImpactA remote admin client certificate restricted to one PKI CA path can access other CA resources with the same prefix.This breaks least-privilege remote admin policies and results in authenticated authorization bypass.Minimal ConfigurationFile: repro.jsonReproduction Steps From Scratch1. Generate a client certificate2. Convert the client certificate to base64 DERCLIENT_CERT_B64="$(openssl x509 -in client.crt -outform der | base64 | tr -d '\n')"3. Put that value into repro.jsonReplace:<CLIENT_CERT_BASE64_DER>with the value of CLIENT_CERT_B64.4. Run Caddygo run ./cmd/caddy run --config ./repro.json5. Confirm access to the intended allowed pathExpected result:HTTP/1.1 200 OK6. Request a different CA whose path shares the same prefixExpected secure behavior:HTTP/1.1 403 ForbiddenActual behavior:HTTP/1.1 200 OKPrecise HTTP Requests and OutputAllowed pathResponse excerpt:Unauthorized sibling path that is incorrectly allowedResponse excerpt:The body returned CA information for prod-backup, despite the configured permission only allowing /pki/ca/prod.Full Log Outputsever :curl :Why This Is Not Just MisconfigurationThe configuration explicitly attempts to restrict access to:/pki/ca/prodThe unsafe behavior is caused by Caddy's implementation using prefix matching instead of segment-aware matching. The product does not enforce the configured policy as written.Suggested FixPath authorization should allow:exact match, orsubpath match only when the next character is /For example:This preserves intended access to subresources like:/pki/ca/prod/certificateswhile correctly denying sibling resources like:/pki/ca/prod-backupWorking PatchWhy the Patch WorksThe patch changes authorization from naive prefix matching to segment-aware matching.This allows:/pki/ca/prod/pki/ca/prod/certificatesbut denies:/pki/ca/prod-backup/pki/ca/prod1which is consistent with the configured path policy.Suggested Regression TestsAt minimum:Allow /pki/ca/prod, request /pki/ca/prod, expect allowed.Allow /pki/ca/prod, request /pki/ca/prod/certificates, expect allowed.Allow /pki/ca/prod, request /pki/ca/prod-backup, expect denied.Allow /pki/ca/prod, request /pki/ca/prod1, expect denied.
CVSS v3.1 score: 4.3