mirror of
https://github.com/caddyserver/caddy.git
synced 2025-01-05 18:44:58 +03:00
staticfiles: Prevent path-based open redirects
Not a huge issue, but has security implications if OAuth tokens leaked
This commit is contained in:
parent
a50f3a4cfe
commit
592d199315
2 changed files with 35 additions and 5 deletions
|
@ -107,6 +107,10 @@ func (fs FileServer) serveFile(w http.ResponseWriter, r *http.Request) (int, err
|
||||||
if d.IsDir() {
|
if d.IsDir() {
|
||||||
// ensure there is a trailing slash
|
// ensure there is a trailing slash
|
||||||
if urlCopy.Path[len(urlCopy.Path)-1] != '/' {
|
if urlCopy.Path[len(urlCopy.Path)-1] != '/' {
|
||||||
|
for strings.HasPrefix(urlCopy.Path, "//") {
|
||||||
|
// prevent path-based open redirects
|
||||||
|
urlCopy.Path = strings.TrimPrefix(urlCopy.Path, "/")
|
||||||
|
}
|
||||||
urlCopy.Path += "/"
|
urlCopy.Path += "/"
|
||||||
http.Redirect(w, r, urlCopy.String(), http.StatusMovedPermanently)
|
http.Redirect(w, r, urlCopy.String(), http.StatusMovedPermanently)
|
||||||
return http.StatusMovedPermanently, nil
|
return http.StatusMovedPermanently, nil
|
||||||
|
@ -131,6 +135,10 @@ func (fs FileServer) serveFile(w http.ResponseWriter, r *http.Request) (int, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if redir {
|
if redir {
|
||||||
|
for strings.HasPrefix(urlCopy.Path, "//") {
|
||||||
|
// prevent path-based open redirects
|
||||||
|
urlCopy.Path = strings.TrimPrefix(urlCopy.Path, "/")
|
||||||
|
}
|
||||||
http.Redirect(w, r, urlCopy.String(), http.StatusMovedPermanently)
|
http.Redirect(w, r, urlCopy.String(), http.StatusMovedPermanently)
|
||||||
return http.StatusMovedPermanently, nil
|
return http.StatusMovedPermanently, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -77,9 +77,9 @@ func TestServeHTTP(t *testing.T) {
|
||||||
{
|
{
|
||||||
url: "https://foo/dirwithindex/",
|
url: "https://foo/dirwithindex/",
|
||||||
expectedStatus: http.StatusOK,
|
expectedStatus: http.StatusOK,
|
||||||
expectedBodyContent: testFiles[webrootDirwithindexIndeHTML],
|
expectedBodyContent: testFiles[webrootDirwithindexIndexHTML],
|
||||||
expectedEtag: `"2n9cw"`,
|
expectedEtag: `"2n9cw"`,
|
||||||
expectedContentLength: strconv.Itoa(len(testFiles[webrootDirwithindexIndeHTML])),
|
expectedContentLength: strconv.Itoa(len(testFiles[webrootDirwithindexIndexHTML])),
|
||||||
},
|
},
|
||||||
// Test 4 - access folder with index file without trailing slash
|
// Test 4 - access folder with index file without trailing slash
|
||||||
{
|
{
|
||||||
|
@ -235,16 +235,38 @@ func TestServeHTTP(t *testing.T) {
|
||||||
expectedBodyContent: movedPermanently,
|
expectedBodyContent: movedPermanently,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
// Test 27 - Check etag
|
||||||
url: "https://foo/notindex.html",
|
url: "https://foo/notindex.html",
|
||||||
expectedStatus: http.StatusOK,
|
expectedStatus: http.StatusOK,
|
||||||
expectedBodyContent: testFiles[webrootNotIndexHTML],
|
expectedBodyContent: testFiles[webrootNotIndexHTML],
|
||||||
expectedEtag: `"2n9cm"`,
|
expectedEtag: `"2n9cm"`,
|
||||||
expectedContentLength: strconv.Itoa(len(testFiles[webrootNotIndexHTML])),
|
expectedContentLength: strconv.Itoa(len(testFiles[webrootNotIndexHTML])),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
// Test 28 - Prevent path-based open redirects (directory)
|
||||||
|
url: "https://foo//example.com%2f..",
|
||||||
|
expectedStatus: http.StatusMovedPermanently,
|
||||||
|
expectedLocation: "https://foo/example.com/../",
|
||||||
|
expectedBodyContent: movedPermanently,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Test 29 - Prevent path-based open redirects (file)
|
||||||
|
url: "https://foo//example.com%2f../dirwithindex/index.html",
|
||||||
|
expectedStatus: http.StatusMovedPermanently,
|
||||||
|
expectedLocation: "https://foo/example.com/../dirwithindex/",
|
||||||
|
expectedBodyContent: movedPermanently,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Test 29 - Prevent path-based open redirects (extra leading slashes)
|
||||||
|
url: "https://foo///example.com%2f..",
|
||||||
|
expectedStatus: http.StatusMovedPermanently,
|
||||||
|
expectedLocation: "https://foo/example.com/../",
|
||||||
|
expectedBodyContent: movedPermanently,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, test := range tests {
|
for i, test := range tests {
|
||||||
// set up response writer and rewuest
|
// set up response writer and request
|
||||||
responseRecorder := httptest.NewRecorder()
|
responseRecorder := httptest.NewRecorder()
|
||||||
request, err := http.NewRequest("GET", test.url, nil)
|
request, err := http.NewRequest("GET", test.url, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -518,7 +540,7 @@ var (
|
||||||
webrootNotIndexHTML = filepath.Join(webrootName, "notindex.html")
|
webrootNotIndexHTML = filepath.Join(webrootName, "notindex.html")
|
||||||
webrootDirFile2HTML = filepath.Join(webrootName, "dir", "file2.html")
|
webrootDirFile2HTML = filepath.Join(webrootName, "dir", "file2.html")
|
||||||
webrootDirHiddenHTML = filepath.Join(webrootName, "dir", "hidden.html")
|
webrootDirHiddenHTML = filepath.Join(webrootName, "dir", "hidden.html")
|
||||||
webrootDirwithindexIndeHTML = filepath.Join(webrootName, "dirwithindex", "index.html")
|
webrootDirwithindexIndexHTML = filepath.Join(webrootName, "dirwithindex", "index.html")
|
||||||
webrootSubGzippedHTML = filepath.Join(webrootName, "sub", "gzipped.html")
|
webrootSubGzippedHTML = filepath.Join(webrootName, "sub", "gzipped.html")
|
||||||
webrootSubGzippedHTMLGz = filepath.Join(webrootName, "sub", "gzipped.html.gz")
|
webrootSubGzippedHTMLGz = filepath.Join(webrootName, "sub", "gzipped.html.gz")
|
||||||
webrootSubGzippedHTMLBr = filepath.Join(webrootName, "sub", "gzipped.html.br")
|
webrootSubGzippedHTMLBr = filepath.Join(webrootName, "sub", "gzipped.html.br")
|
||||||
|
@ -544,7 +566,7 @@ var testFiles = map[string]string{
|
||||||
webrootFile1HTML: "<h1>file1.html</h1>",
|
webrootFile1HTML: "<h1>file1.html</h1>",
|
||||||
webrootNotIndexHTML: "<h1>notindex.html</h1>",
|
webrootNotIndexHTML: "<h1>notindex.html</h1>",
|
||||||
webrootDirFile2HTML: "<h1>dir/file2.html</h1>",
|
webrootDirFile2HTML: "<h1>dir/file2.html</h1>",
|
||||||
webrootDirwithindexIndeHTML: "<h1>dirwithindex/index.html</h1>",
|
webrootDirwithindexIndexHTML: "<h1>dirwithindex/index.html</h1>",
|
||||||
webrootDirHiddenHTML: "<h1>dir/hidden.html</h1>",
|
webrootDirHiddenHTML: "<h1>dir/hidden.html</h1>",
|
||||||
webrootSubGzippedHTML: "<h1>gzipped.html</h1>",
|
webrootSubGzippedHTML: "<h1>gzipped.html</h1>",
|
||||||
webrootSubGzippedHTMLGz: "1.gzipped.html.gz",
|
webrootSubGzippedHTMLGz: "1.gzipped.html.gz",
|
||||||
|
|
Loading…
Reference in a new issue