CVEReports
CVEReports

Automated vulnerability intelligence platform. Comprehensive reports for high-severity CVEs generated by AI.

Product

  • Home
  • Dashboard
  • Sitemap
  • RSS Feed

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CVEReports. All rights reserved.

Made with love by Amit Schendel & Alon Barad



CVE-2026-27587
7.7

Caddy Shack: Bypassing ACLs with a Caps Lock Key

Alon Barad
Alon Barad
Software Engineer

Feb 25, 2026·5 min read·8 visits

PoC Available

Executive Summary (TL;DR)

Caddy versions prior to 2.11.1 contain a critical logic flaw in how they handle path matching with percent-encoded sequences. By capitalizing parts of the URL, an attacker can bypass path-based restrictions, potentially accessing administrative panels or protected files that rely on Caddy for security.

A logic error in Caddy's HTTP path matching engine allows attackers to bypass access control lists (ACLs) by manipulating URL casing. When a configuration pattern involves percent-encoded characters, Caddy fails to normalize the incoming request path to lowercase before comparison. This oversight renders case-insensitive rules ineffective, effectively letting attackers walk past the bouncer simply by shouting their destination (e.g., requesting '/ADMIN' instead of '/admin').

The Hook: The Safe-by-Default Myth

Caddy is the darling of the modern web server ecosystem. It's written in Go, it handles TLS automatically, and it markets itself as 'secure by default.' We love it because it usually stops us from shooting ourselves in the foot. But even the sleekest, most memory-safe tools can trip over the oldest hurdle in computer science: string comparison.

In the world of reverse proxies, the path matcher is often the primary line of defense. You set up a rule: "Block everything starting with /admin." You assume the server is smart enough to know that /ADMIN, /Admin, and /aDmin are the same place. Usually, Caddy is that smart.

However, CVE-2026-27587 reveals a crack in that armor. It turns out that if you get fancy with your configuration—specifically by including percent-encoded characters (like %2f for a slash)—Caddy's brain short-circuits. It stops normalizing the input. It stops thinking. And suddenly, your carefully crafted access control list is about as effective as a 'Keep Out' sign written in invisible ink.

The Flaw: A Tale of Two Code Paths

To understand this bug, you have to understand how Caddy optimizes matching. String comparison is expensive at scale, so Caddy tries to normalize everything upfront. When you provision the server, Caddy takes your configuration patterns (like /secret/*) and lowercases them. This way, when a request comes in, Caddy just lowercases the request and does a fast check.

But here's the catch: generic lowercasing destroys percent-encoding information. If you configured a rule for /api%2fv1 (implying a literal encoded slash), a standard lowercase operation might mangle the semantic meaning Caddy is trying to preserve.

So, the developers added a fork in the road. If the configuration pattern contains a % character, Caddy switches to a specialized function: matchPatternWithEscapeSequence. This function is supposed to handle the nuance of encoded characters.

The problem? In this specialized 'smart' branch, they forgot the basics. While the configuration pattern was already lowercased during the provisioning phase, the incoming request path was left raw. Caddy ends up comparing a lowercased rule against a raw, potentially mixed-case request. It's an apples-to-oranges comparison where the attacker controls the oranges.

The Code: The Smoking Gun

Let's look at the diff. This is from modules/caddyhttp/matchers.go. It is almost painfully simple, which is characteristic of the most dangerous logic bugs.

The vulnerable function, matchPatternWithEscapeSequence, takes the escapedPath (from the request) and the matchPath (from the config). Remember, matchPath is already lowercase here.

Before the fix:

func (MatchPath) matchPatternWithEscapeSequence(escapedPath, matchPath string) bool {
    // ... logic to handle wildcards ...
    return strings.HasPrefix(escapedPath, matchPath)
}

See the issue? escapedPath comes straight from the wire. If I send /ADMIN%2f, escapedPath is /ADMIN%2f. But matchPath is /admin%2f. "/ADMIN%2f" does not start with "/admin%2f" in a case-sensitive byte comparison.

The Fix (Commit a108119):

func (MatchPath) matchPatternWithEscapeSequence(escapedPath, matchPath string) bool {
+   escapedPath = strings.ToLower(escapedPath)
    // We would just compare the pattern against r.URL.Path,
    // but the pattern contains %, indicating that we should
    // compare at least some part of the path in raw/escaped
    // form. Matches are case-insensitive.
    // ...
}

It is a one-line fix. By forcefully lowercasing the request path at the start of the function, parity is restored. The attacker's /ADMIN%2f becomes /admin%2f, the match succeeds, and the ACL (hopefully) blocks the request.

The Exploit: Shouting to Bypass Security

Exploiting this requires two things: a Caddy server running a version older than 2.11.1, and a specific configuration anti-pattern.

First, we need a target configuration that looks something like this. Note the %2f in the path—this is the trigger that forces Caddy into the vulnerable code path:

{
  "match": [{"path": ["/private%2fdata*"]}],
  "handle": [{"handler": "authentication"}]
}

In this scenario, the admin wants to protect anything under /private/data. Because they used %2f, Caddy treats this as a 'complex' match.

The Attack Chain:

  1. Recon: We send a normal request to GET /private%2fdata/passwords.txt. Caddy sees the match, triggers the authentication handler, and we get a 401 Unauthorized or a login redirect.
  2. The Bypass: We simply toggle the case. We send GET /PRIVATE%2fdata/passwords.txt.
  3. The Logic Failure:
    • Caddy sees the % in the config pattern.
    • It calls matchPatternWithEscapeSequence.
    • It compares config /private%2fdata* vs request /PRIVATE%2fdata....
    • The match fails.
  4. The Payoff: Because the match failed, the authentication handler is skipped. The request falls through to the next handler—usually a file server or a reverse proxy to a backend app.

If the backend app (e.g., a Python or Node.js service) handles routing case-insensitively (which most do) or normalizes the path itself, it will happily serve the content. We have successfully bypassed the auth layer.

The Impact: When "No Match" Means "Yes, Please"

The severity of this vulnerability depends entirely on how Caddy is being used. If Caddy is just a dumb pipe passing everything to a backend that does its own strict authentication, this is a non-issue.

However, the modern trend is 'Sidecar Security.' DevOps teams love to offload authentication (OIDC, Basic Auth, mTLS) to the edge proxy (Caddy). They assume that if Caddy says 'you shall not pass,' then nobody passes.

This vulnerability creates a normalization inconsistency. The security layer (Caddy) thinks the path is one thing (/PRIVATE... -> No Match -> Allowed), while the application layer thinks it is another (/PRIVATE... -> Normalize -> /private... -> Serve Resource). This mismatch allows unauthenticated users to access administrative panels, metrics endpoints, or sensitive file directories simply by holding down the Shift key.

Official Patches

CaddyGitHub Commit a108119

Fix Analysis (1)

Technical Appendix

CVSS Score
7.7/ 10
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:H/VA:N/SC:N/SI:N/SA:N/E:P

Affected Systems

Caddy Web Server < 2.11.1

Affected Versions Detail

Product
Affected Versions
Fixed Version
Caddy
Caddy
< 2.11.12.11.1
AttributeDetail
CWE IDCWE-178
CVSS Score7.7 (High)
Attack VectorNetwork
Vulnerability TypeImproper Handling of Case Sensitivity
Exploit StatusPoC Available (Trivial)
Patch Date2026-02-20

MITRE ATT&CK Mapping

T1557Adversary-in-the-Middle
Credential Access
T1071.001Web Protocols
Command and Control
CWE-178
Improper Handling of Case Sensitivity

Known Exploits & Detection

ManualManual casing manipulation using curl or browser

Vulnerability Timeline

Fix committed by Matt Holt
2026-02-20
CVE Published & GHSA Advisory Released
2026-02-24
Caddy v2.11.1 Released
2026-02-24

References & Sources

  • [1]GHSA-g7pc-pc7g-h8jh
  • [2]NVD Entry

Attack Flow Diagram

Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.