Skip to content

Commit 14daeb9

Browse files
authored
Security: c.Attachment and c.Inline should escape name in Content-Disposition header to avoid 'Reflect File Download' vulnerability. (#2541)
This is same as Go std does it https://github.com/golang/go/blob/9d836d41d0d9df3acabf7f9607d3b09188a9bfc6/src/mime/multipart/writer.go#L132
1 parent 50ebcd8 commit 14daeb9

File tree

2 files changed

+65
-21
lines changed

2 files changed

+65
-21
lines changed

context.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -584,8 +584,10 @@ func (c *context) Inline(file, name string) error {
584584
return c.contentDisposition(file, name, "inline")
585585
}
586586

587+
var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"")
588+
587589
func (c *context) contentDisposition(file, name, dispositionType string) error {
588-
c.response.Header().Set(HeaderContentDisposition, fmt.Sprintf("%s; filename=%q", dispositionType, name))
590+
c.response.Header().Set(HeaderContentDisposition, fmt.Sprintf(`%s; filename="%s"`, dispositionType, quoteEscaper.Replace(name)))
589591
return c.File(file)
590592
}
591593

context_test.go

+62-20
Original file line numberDiff line numberDiff line change
@@ -414,30 +414,72 @@ func TestContextStream(t *testing.T) {
414414
}
415415

416416
func TestContextAttachment(t *testing.T) {
417-
e := New()
418-
rec := httptest.NewRecorder()
419-
req := httptest.NewRequest(http.MethodGet, "/?pretty", nil)
420-
c := e.NewContext(req, rec).(*context)
421-
422-
err := c.Attachment("_fixture/images/walle.png", "walle.png")
423-
if assert.NoError(t, err) {
424-
assert.Equal(t, http.StatusOK, rec.Code)
425-
assert.Equal(t, "attachment; filename=\"walle.png\"", rec.Header().Get(HeaderContentDisposition))
426-
assert.Equal(t, 219885, rec.Body.Len())
417+
var testCases = []struct {
418+
name string
419+
whenName string
420+
expectHeader string
421+
}{
422+
{
423+
name: "ok",
424+
whenName: "walle.png",
425+
expectHeader: `attachment; filename="walle.png"`,
426+
},
427+
{
428+
name: "ok, escape quotes in malicious filename",
429+
whenName: `malicious.sh"; \"; dummy=.txt`,
430+
expectHeader: `attachment; filename="malicious.sh\"; \\\"; dummy=.txt"`,
431+
},
432+
}
433+
for _, tc := range testCases {
434+
t.Run(tc.name, func(t *testing.T) {
435+
e := New()
436+
rec := httptest.NewRecorder()
437+
req := httptest.NewRequest(http.MethodGet, "/", nil)
438+
c := e.NewContext(req, rec).(*context)
439+
440+
err := c.Attachment("_fixture/images/walle.png", tc.whenName)
441+
if assert.NoError(t, err) {
442+
assert.Equal(t, tc.expectHeader, rec.Header().Get(HeaderContentDisposition))
443+
444+
assert.Equal(t, http.StatusOK, rec.Code)
445+
assert.Equal(t, 219885, rec.Body.Len())
446+
}
447+
})
427448
}
428449
}
429450

430451
func TestContextInline(t *testing.T) {
431-
e := New()
432-
rec := httptest.NewRecorder()
433-
req := httptest.NewRequest(http.MethodGet, "/?pretty", nil)
434-
c := e.NewContext(req, rec).(*context)
435-
436-
err := c.Inline("_fixture/images/walle.png", "walle.png")
437-
if assert.NoError(t, err) {
438-
assert.Equal(t, http.StatusOK, rec.Code)
439-
assert.Equal(t, "inline; filename=\"walle.png\"", rec.Header().Get(HeaderContentDisposition))
440-
assert.Equal(t, 219885, rec.Body.Len())
452+
var testCases = []struct {
453+
name string
454+
whenName string
455+
expectHeader string
456+
}{
457+
{
458+
name: "ok",
459+
whenName: "walle.png",
460+
expectHeader: `inline; filename="walle.png"`,
461+
},
462+
{
463+
name: "ok, escape quotes in malicious filename",
464+
whenName: `malicious.sh"; \"; dummy=.txt`,
465+
expectHeader: `inline; filename="malicious.sh\"; \\\"; dummy=.txt"`,
466+
},
467+
}
468+
for _, tc := range testCases {
469+
t.Run(tc.name, func(t *testing.T) {
470+
e := New()
471+
rec := httptest.NewRecorder()
472+
req := httptest.NewRequest(http.MethodGet, "/", nil)
473+
c := e.NewContext(req, rec).(*context)
474+
475+
err := c.Inline("_fixture/images/walle.png", tc.whenName)
476+
if assert.NoError(t, err) {
477+
assert.Equal(t, tc.expectHeader, rec.Header().Get(HeaderContentDisposition))
478+
479+
assert.Equal(t, http.StatusOK, rec.Code)
480+
assert.Equal(t, 219885, rec.Body.Len())
481+
}
482+
})
441483
}
442484
}
443485

0 commit comments

Comments
 (0)