Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

the change to SanitizedPathJoin in v2.8.x can cause routing to break in certain configurations #6352

Closed
elee1766 opened this issue May 30, 2024 · 5 comments · Fixed by #6360
Labels
bug 🐞 Something isn't working
Milestone

Comments

@elee1766
Copy link
Contributor

elee1766 commented May 30, 2024

in previous versions, this would work to route an SPA. i got this through google result showing matt's post here:

https://caddy.community/t/how-to-serve-a-single-page-app-spa-with-caddy-2/12770

:3838 {
        root * dir
	try_files {path} /
	file_server
}

however, in v2.8.0, this breaks. and must be replaced with

:3838 {
        root * dir
	try_files {path} /index.html
	file_server
}

after a git bisect, i find the commit where the behavior changed to be

6d97d8d87beb788d19a4084d07ec9157e5705b13 is the first bad commit
commit 6d97d8d87beb788d19a4084d07ec9157e5705b13
Author: Matt Holt <mholt@users.noreply.github.com>
Date:   Tue Apr 23 20:05:57 2024 -0400

    caddyhttp: Address some Go 1.20 features (#6252)

    Co-authored-by: Francis Lavoie <lavofr@gmail.com>

 cmd/x509rootsfallback.go                     | 33 ++++++++++++++++++++++++++++
 go.mod                                       |  1 +
 go.sum                                       |  2 ++
 modules/caddyhttp/caddyhttp.go               | 13 +++++++++--
 modules/caddyhttp/caddyhttp_test.go          | 26 ++++++++++++++++------
 modules/caddyhttp/requestbody/caddyfile.go   | 26 +++++++++++++++++++++-
 modules/caddyhttp/requestbody/requestbody.go | 30 +++++++++++++++++++++++++
 modules/caddyhttp/responsewriter_test.go     | 10 ++++-----
 8 files changed, 125 insertions(+), 16 deletions(-)
 create mode 100644 cmd/x509rootsfallback.go

doing some more digging, but i thought i would raise this asap since perhaps other people were surprised by this change.

@elee1766
Copy link
Contributor Author

elee1766 commented May 30, 2024

my guess is it has something to do with the changes to SanitizedPathJoin

// with the local file system. If root is empty, the current
// directory is assumed. If the cleaned request path is deemed
// not local according to lexical processing (i.e. ignoring links),
// it will be rejected as unsafe and only the root will be returned.

after adding some debug logging, it seems that a different 'r.URL.Path' is being used in staticfiles.go:270

@elee1766
Copy link
Contributor Author

it seems the origin of the different r.URL.Path is in fileserver/matcher.go:365.

pre this commit, if i query for :3838/dir/notexist, i would get dir/notexist and dir/

after this change, i get dir/notexist and dir

this causes the different r.URL.Path down the line, as the fullpath in the matchcandidate is now different.

@elee1766 elee1766 changed the title v2.8.0 has a breaking change in, at the very least, how try_files works the change to SanitizedPathJoin in v2.8.0 causes routing to break in certain configurations May 30, 2024
@elee1766
Copy link
Contributor Author

@mholt we ran into this during smoke tests before production.

is this change intended? we are changing all our configs to be explicitly /index.html, but for some reason i feel like this is a bug. i feel like putting / as a file to try should make it try all the index files in that dir.

@elee1766 elee1766 changed the title the change to SanitizedPathJoin in v2.8.0 causes routing to break in certain configurations the change to SanitizedPathJoin in v2.8.0 can cause routing to break in certain configurations May 30, 2024
@elee1766 elee1766 changed the title the change to SanitizedPathJoin in v2.8.0 can cause routing to break in certain configurations the change to SanitizedPathJoin in v2.8.x can cause routing to break in certain configurations May 30, 2024
@willnorris
Copy link
Contributor

willnorris commented Jun 1, 2024

I think @elee1766 is definitely on the right track here. The lack of trailing slash causes this strictFileExists check to fail since requests must have a trailing slash if and only if they map to a directory.

The change that fixes this for me is to update SanitizePathJoin to consider a path unsafe only if the cleaned path is non-empty. So update this block to be:

	if relPath != "" && !filepath.IsLocal(relPath) {
		// path is unsafe (see https://github.com/golang/go/issues/56336#issuecomment-1416214885)
		return root
	}

I'm still walking through the code to verify that this makes sense and is the best place to fix it. I'm not 100% convinced yet, but am getting closer.

willnorris added a commit to willnorris/caddy that referenced this issue Jun 1, 2024
SanitizePathJoin protects against directory traversal attacks by
checking for requests whose URL path look like they are trying to
request something other than a local file, and returns the root
directory in those cases.

The method is also careful to ensure that requests which contain a
trailing slash include a trailing slash in the returned value.  However,
for requests that contain only a slash (requests for the root path), the
IsLocal check returns early before the matching trailing slash is
re-added.

This change updates SanitizePathJoin to only perform the
filepath.IsLocal check if the cleaned request URL path is non-empty.

Fixes caddyserver#6352
willnorris added a commit to willnorris/caddy that referenced this issue Jun 1, 2024
SanitizePathJoin protects against directory traversal attacks by
checking for requests whose URL path look like they are trying to
request something other than a local file, and returns the root
directory in those cases.

The method is also careful to ensure that requests which contain a
trailing slash include a trailing slash in the returned value.  However,
for requests that contain only a slash (requests for the root path), the
IsLocal check returns early before the matching trailing slash is
re-added.

This change updates SanitizePathJoin to only perform the
filepath.IsLocal check if the cleaned request URL path is non-empty.

Fixes caddyserver#6352
willnorris added a commit to willnorris/caddy that referenced this issue Jun 1, 2024
SanitizePathJoin protects against directory traversal attacks by
checking for requests whose URL path look like they are trying to
request something other than a local file, and returns the root
directory in those cases.

The method is also careful to ensure that requests which contain a
trailing slash include a trailing slash in the returned value.  However,
for requests that contain only a slash (requests for the root path), the
IsLocal check returns early before the matching trailing slash is
re-added.

This change updates SanitizePathJoin to only perform the
filepath.IsLocal check if the cleaned request URL path is non-empty.

Fixes caddyserver#6352
willnorris added a commit to willnorris/caddy that referenced this issue Jun 1, 2024
SanitizePathJoin protects against directory traversal attacks by
checking for requests whose URL path look like they are trying to
request something other than a local file, and returns the root
directory in those cases.

The method is also careful to ensure that requests which contain a
trailing slash include a trailing slash in the returned value.  However,
for requests that contain only a slash (requests for the root path), the
IsLocal check returns early before the matching trailing slash is
re-added.

This change updates SanitizePathJoin to only perform the
filepath.IsLocal check if the cleaned request URL path is non-empty.

---

This change also updates the existing SanitizePathJoin tests to use
filepath.FromSlash rather than filepath.Join. This makes the expected
value a little easier to read, but also has the advantage of not being
processed by filepath.Clean like filepath.Join is. This means that the
exact expect value will be compared, not the result of first cleaning
it.

Fixes caddyserver#6352
@mholt
Copy link
Member

mholt commented Jun 2, 2024

Will's patch is elegant and simple. I'll tag a release momentarily.

@mholt mholt added the bug 🐞 Something isn't working label Jun 2, 2024
@mholt mholt added this to the v2.8.2 milestone Jun 2, 2024
mholt pushed a commit that referenced this issue Jun 2, 2024
SanitizePathJoin protects against directory traversal attacks by
checking for requests whose URL path look like they are trying to
request something other than a local file, and returns the root
directory in those cases.

The method is also careful to ensure that requests which contain a
trailing slash include a trailing slash in the returned value.  However,
for requests that contain only a slash (requests for the root path), the
IsLocal check returns early before the matching trailing slash is
re-added.

This change updates SanitizePathJoin to only perform the
filepath.IsLocal check if the cleaned request URL path is non-empty.

---

This change also updates the existing SanitizePathJoin tests to use
filepath.FromSlash rather than filepath.Join. This makes the expected
value a little easier to read, but also has the advantage of not being
processed by filepath.Clean like filepath.Join is. This means that the
exact expect value will be compared, not the result of first cleaning
it.

Fixes #6352
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug 🐞 Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants