Skip to content

Commit 88d975a

Browse files
committed
Fix directory traversal vulnerability under Windows in Static middleware when default Echo filesytem is used (effectively middleware.StaticConfig{Filesystem: nil})
1 parent 68aaf3a commit 88d975a

13 files changed

Lines changed: 135 additions & 236 deletions

File tree

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# Changelog
22

3+
## v5.0.3 - 2026-02-06
4+
5+
**Security**
6+
7+
* Fix directory traversal vulnerability under Windows in Static middleware when default Echo filesytem is used (effectively `middleware.StaticConfig{Filesystem: nil}`).
8+
9+
310
## v5.0.2 - 2026-02-02
411

512
**Security**

_fixture/dist/public/test.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
test.txt contents

context.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"net"
1616
"net/http"
1717
"net/url"
18+
"path"
1819
"path/filepath"
1920
"strings"
2021
"sync"
@@ -579,6 +580,7 @@ func (c *Context) FileFS(file string, filesystem fs.FS) error {
579580
}
580581

581582
func fsFile(c *Context, file string, filesystem fs.FS) error {
583+
file = path.Clean(file) // `os.Open` and `os.DirFs.Open()` behave differently, later does not like ``, `.`, `..` at all, but we allowed those now need to clean
582584
f, err := filesystem.Open(file)
583585
if err != nil {
584586
return ErrNotFound

echo.go

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -785,14 +785,11 @@ func newDefaultFS() *defaultFS {
785785
dir, _ := os.Getwd()
786786
return &defaultFS{
787787
prefix: dir,
788-
fs: nil,
788+
fs: os.DirFS(dir),
789789
}
790790
}
791791

792792
func (fs defaultFS) Open(name string) (fs.File, error) {
793-
if fs.fs == nil {
794-
return os.Open(name) // #nosec G304
795-
}
796793
return fs.fs.Open(name)
797794
}
798795

group_test.go

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -467,13 +467,14 @@ func TestGroup_Static(t *testing.T) {
467467

468468
func TestGroup_StaticMultiTest(t *testing.T) {
469469
var testCases = []struct {
470-
name string
471-
givenPrefix string
472-
givenRoot string
473-
whenURL string
474-
expectHeaderLocation string
475-
expectBodyStartsWith string
476-
expectStatus int
470+
name string
471+
givenPrefix string
472+
givenRoot string
473+
whenURL string
474+
expectHeaderLocation string
475+
expectBodyStartsWith string
476+
expectBodyNotContains string
477+
expectStatus int
477478
}{
478479
{
479480
name: "ok",
@@ -582,6 +583,22 @@ func TestGroup_StaticMultiTest(t *testing.T) {
582583
expectStatus: http.StatusOK,
583584
expectBodyStartsWith: "<!doctype html>",
584585
},
586+
{
587+
name: "nok, URL encoded path traversal (single encoding, slash - unix separator)",
588+
givenRoot: "_fixture/dist/public",
589+
whenURL: "/%2e%2e%2fprivate.txt",
590+
expectStatus: http.StatusNotFound,
591+
expectBodyStartsWith: "{\"message\":\"Not Found\"}\n",
592+
expectBodyNotContains: `private file`,
593+
},
594+
{
595+
name: "nok, URL encoded path traversal (single encoding, backslash - windows separator)",
596+
givenRoot: "_fixture/dist/public",
597+
whenURL: "/%2e%2e%5cprivate.txt",
598+
expectStatus: http.StatusNotFound,
599+
expectBodyStartsWith: "{\"message\":\"Not Found\"}\n",
600+
expectBodyNotContains: `private file`,
601+
},
585602
{
586603
name: "do not allow directory traversal (backslash - windows separator)",
587604
givenPrefix: "/",
@@ -618,6 +635,9 @@ func TestGroup_StaticMultiTest(t *testing.T) {
618635
} else {
619636
assert.Equal(t, "", body)
620637
}
638+
if tc.expectBodyNotContains != "" {
639+
assert.NotContains(t, body, tc.expectBodyNotContains)
640+
}
621641

622642
if tc.expectHeaderLocation != "" {
623643
assert.Equal(t, tc.expectHeaderLocation, rec.Result().Header["Location"][0])

0 commit comments

Comments
 (0)