models/repo_editor: sanitize user-defined file name to prevent RCE (#5558)

Reported by PentesterLab (https://pentesterlab.com).
This commit is contained in:
Unknwon 2018-12-18 01:31:04 -05:00
parent d74437af57
commit 86ada87529
No known key found for this signature in database
GPG Key ID: 25B575AE3213B2B3
4 changed files with 31 additions and 6 deletions

View File

@ -328,7 +328,7 @@ func (upload *Upload) LocalPath() string {
func NewUpload(name string, buf []byte, file multipart.File) (_ *Upload, err error) { func NewUpload(name string, buf []byte, file multipart.File) (_ *Upload, err error) {
upload := &Upload{ upload := &Upload{
UUID: gouuid.NewV4().String(), UUID: gouuid.NewV4().String(),
Name: name, Name: tool.SanitizePath(name),
} }
localPath := upload.LocalPath() localPath := upload.LocalPath()

View File

@ -4,9 +4,18 @@
package tool package tool
import (
"strings"
)
// IsSameSiteURLPath returns true if the URL path belongs to the same site, false otherwise. // IsSameSiteURLPath returns true if the URL path belongs to the same site, false otherwise.
// False: //url, http://url, /\url // False: //url, http://url, /\url
// True: /url // True: /url
func IsSameSiteURLPath(url string) bool { func IsSameSiteURLPath(url string) bool {
return len(url) >= 2 && url[0] == '/' && url[1] != '/' && url[1] != '\\' return len(url) >= 2 && url[0] == '/' && url[1] != '/' && url[1] != '\\'
} }
// SanitizePath sanitizes user-defined file paths to prevent remote code execution.
func SanitizePath(path string) string {
return strings.TrimLeft(path, "./")
}

View File

@ -30,3 +30,19 @@ func Test_IsSameSiteURLPath(t *testing.T) {
} }
}) })
} }
func Test_SanitizePath(t *testing.T) {
Convey("Sanitize malicious user-defined path", t, func() {
testCases := []struct {
path string
expect string
}{
{"../../../../../../../../../data/gogs/data/sessions/a/9/a9f0ab6c3ef63dd8", "data/gogs/data/sessions/a/9/a9f0ab6c3ef63dd8"},
{"data/sessions/a/9/a9f0ab6c3ef63dd8", "data/sessions/a/9/a9f0ab6c3ef63dd8"},
}
for _, tc := range testCases {
So(SanitizePath(tc.path), ShouldEqual, tc.expect)
}
})
}

View File

@ -518,7 +518,7 @@ func UploadFilePost(c *context.Context, f form.UploadRepoFile) {
func UploadFileToServer(c *context.Context) { func UploadFileToServer(c *context.Context) {
file, header, err := c.Req.FormFile("file") file, header, err := c.Req.FormFile("file")
if err != nil { if err != nil {
c.Error(500, fmt.Sprintf("FormFile: %v", err)) c.Error(http.StatusInternalServerError, fmt.Sprintf("FormFile: %v", err))
return return
} }
defer file.Close() defer file.Close()
@ -541,19 +541,19 @@ func UploadFileToServer(c *context.Context) {
} }
if !allowed { if !allowed {
c.Error(400, ErrFileTypeForbidden.Error()) c.Error(http.StatusBadRequest, ErrFileTypeForbidden.Error())
return return
} }
} }
upload, err := models.NewUpload(header.Filename, buf, file) upload, err := models.NewUpload(header.Filename, buf, file)
if err != nil { if err != nil {
c.Error(500, fmt.Sprintf("NewUpload: %v", err)) c.Error(http.StatusInternalServerError, fmt.Sprintf("NewUpload: %v", err))
return return
} }
log.Trace("New file uploaded: %s", upload.UUID) log.Trace("New file uploaded by user[%d]: %s", c.UserID(), upload.UUID)
c.JSON(200, map[string]string{ c.JSONSuccess(map[string]string{
"uuid": upload.UUID, "uuid": upload.UUID,
}) })
} }