mirror of
https://github.com/caddyserver/caddy.git
synced 2024-12-27 22:23:48 +03: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/http"
|
||||||
"net/textproto"
|
"net/textproto"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
|
@ -314,7 +315,15 @@ func (m MatchPath) Provision(_ caddy.Context) error {
|
||||||
|
|
||||||
// Match returns true if r matches m.
|
// Match returns true if r matches m.
|
||||||
func (m MatchPath) Match(r *http.Request) bool {
|
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
|
// see #2917; Windows ignores trailing dots and spaces
|
||||||
// when accessing files (sigh), potentially causing a
|
// 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
|
// being matched by *.php to be treated as PHP scripts
|
||||||
lowerPath = strings.TrimRight(lowerPath, ". ")
|
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)
|
repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
|
||||||
|
|
||||||
for _, matchPath := range m {
|
for _, matchPath := range m {
|
||||||
|
@ -396,7 +415,26 @@ func (MatchPathRE) CaddyModule() caddy.ModuleInfo {
|
||||||
// Match returns true if r matches m.
|
// Match returns true if r matches m.
|
||||||
func (m MatchPathRE) Match(r *http.Request) bool {
|
func (m MatchPathRE) Match(r *http.Request) bool {
|
||||||
repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
|
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.
|
// CaddyModule returns the Caddy module information.
|
||||||
|
|
|
@ -257,6 +257,21 @@ func TestPathMatcher(t *testing.T) {
|
||||||
input: "/foo/BAR.txt",
|
input: "/foo/BAR.txt",
|
||||||
expect: true,
|
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{"*"},
|
match: MatchPath{"*"},
|
||||||
input: "/",
|
input: "/",
|
||||||
|
@ -326,15 +341,30 @@ func TestPathREMatcher(t *testing.T) {
|
||||||
expect: true,
|
expect: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
match: MatchPathRE{MatchRegexp{Pattern: "/foo"}},
|
match: MatchPathRE{MatchRegexp{Pattern: "^/foo"}},
|
||||||
input: "/foo",
|
input: "/foo",
|
||||||
expect: true,
|
expect: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
match: MatchPathRE{MatchRegexp{Pattern: "/foo"}},
|
match: MatchPathRE{MatchRegexp{Pattern: "^/foo"}},
|
||||||
input: "/foo/",
|
input: "/foo/",
|
||||||
expect: true,
|
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"}},
|
match: MatchPathRE{MatchRegexp{Pattern: "/bar"}},
|
||||||
input: "/foo/",
|
input: "/foo/",
|
||||||
|
|
Loading…
Reference in a new issue