mirror of
https://github.com/caddyserver/caddy.git
synced 2024-12-16 21:56:40 -05:00
caddyhttp: Sanitize the path before evaluating path matchers (#4407)
This commit is contained in:
parent
f376a38b25
commit
e7457b43e4
2 changed files with 72 additions and 4 deletions
|
@ -21,6 +21,7 @@ import (
|
|||
"net/http"
|
||||
"net/textproto"
|
||||
"net/url"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
|
@ -314,7 +315,15 @@ func (m MatchPath) Provision(_ caddy.Context) error {
|
|||
|
||||
// Match returns true if r matches m.
|
||||
func (m MatchPath) Match(r *http.Request) bool {
|
||||
lowerPath := strings.ToLower(r.URL.Path)
|
||||
// PathUnescape returns an error if the escapes aren't
|
||||
// well-formed, meaning the count % matches the RFC.
|
||||
// Return early if the escape is improper.
|
||||
unescapedPath, err := url.PathUnescape(r.URL.Path)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
lowerPath := strings.ToLower(unescapedPath)
|
||||
|
||||
// see #2917; Windows ignores trailing dots and spaces
|
||||
// when accessing files (sigh), potentially causing a
|
||||
|
@ -323,6 +332,16 @@ func (m MatchPath) Match(r *http.Request) bool {
|
|||
// being matched by *.php to be treated as PHP scripts
|
||||
lowerPath = strings.TrimRight(lowerPath, ". ")
|
||||
|
||||
// Clean the path, merges doubled slashes, etc.
|
||||
// This ensures maliciously crafted requests can't bypass
|
||||
// the path matcher. See #4407
|
||||
lowerPath = path.Clean(lowerPath)
|
||||
|
||||
// Cleaning may remove the trailing slash, but we want to keep it
|
||||
if lowerPath != "/" && strings.HasSuffix(r.URL.Path, "/") {
|
||||
lowerPath = lowerPath + "/"
|
||||
}
|
||||
|
||||
repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
|
||||
|
||||
for _, matchPath := range m {
|
||||
|
@ -396,7 +415,26 @@ func (MatchPathRE) CaddyModule() caddy.ModuleInfo {
|
|||
// Match returns true if r matches m.
|
||||
func (m MatchPathRE) Match(r *http.Request) bool {
|
||||
repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
|
||||
return m.MatchRegexp.Match(r.URL.Path, repl)
|
||||
|
||||
// PathUnescape returns an error if the escapes aren't
|
||||
// well-formed, meaning the count % matches the RFC.
|
||||
// Return early if the escape is improper.
|
||||
unescapedPath, err := url.PathUnescape(r.URL.Path)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Clean the path, merges doubled slashes, etc.
|
||||
// This ensures maliciously crafted requests can't bypass
|
||||
// the path matcher. See #4407
|
||||
cleanedPath := path.Clean(unescapedPath)
|
||||
|
||||
// Cleaning may remove the trailing slash, but we want to keep it
|
||||
if cleanedPath != "/" && strings.HasSuffix(r.URL.Path, "/") {
|
||||
cleanedPath = cleanedPath + "/"
|
||||
}
|
||||
|
||||
return m.MatchRegexp.Match(cleanedPath, repl)
|
||||
}
|
||||
|
||||
// CaddyModule returns the Caddy module information.
|
||||
|
|
|
@ -257,6 +257,21 @@ func TestPathMatcher(t *testing.T) {
|
|||
input: "/foo/BAR.txt",
|
||||
expect: true,
|
||||
},
|
||||
{
|
||||
match: MatchPath{"/foo*"},
|
||||
input: "//foo/bar",
|
||||
expect: true,
|
||||
},
|
||||
{
|
||||
match: MatchPath{"/foo*"},
|
||||
input: "//foo",
|
||||
expect: true,
|
||||
},
|
||||
{
|
||||
match: MatchPath{"/foo*"},
|
||||
input: "/%2F/foo",
|
||||
expect: true,
|
||||
},
|
||||
{
|
||||
match: MatchPath{"*"},
|
||||
input: "/",
|
||||
|
@ -326,15 +341,30 @@ func TestPathREMatcher(t *testing.T) {
|
|||
expect: true,
|
||||
},
|
||||
{
|
||||
match: MatchPathRE{MatchRegexp{Pattern: "/foo"}},
|
||||
match: MatchPathRE{MatchRegexp{Pattern: "^/foo"}},
|
||||
input: "/foo",
|
||||
expect: true,
|
||||
},
|
||||
{
|
||||
match: MatchPathRE{MatchRegexp{Pattern: "/foo"}},
|
||||
match: MatchPathRE{MatchRegexp{Pattern: "^/foo"}},
|
||||
input: "/foo/",
|
||||
expect: true,
|
||||
},
|
||||
{
|
||||
match: MatchPathRE{MatchRegexp{Pattern: "^/foo"}},
|
||||
input: "//foo",
|
||||
expect: true,
|
||||
},
|
||||
{
|
||||
match: MatchPathRE{MatchRegexp{Pattern: "^/foo"}},
|
||||
input: "//foo/",
|
||||
expect: true,
|
||||
},
|
||||
{
|
||||
match: MatchPathRE{MatchRegexp{Pattern: "^/foo"}},
|
||||
input: "/%2F/foo/",
|
||||
expect: true,
|
||||
},
|
||||
{
|
||||
match: MatchPathRE{MatchRegexp{Pattern: "/bar"}},
|
||||
input: "/foo/",
|
||||
|
|
Loading…
Reference in a new issue