From 925fb7e2c9fa4b4c14a012d72a26ca149d6d6f86 Mon Sep 17 00:00:00 2001
From: Jannis Mattheis <contact@jmattheis.de>
Date: Wed, 28 Dec 2022 20:13:35 +0100
Subject: [PATCH] Fix file upload XSS

The application image file upload allowed authenticated users to upload
malious .html files. Opening such a file like

https://push.gotify.net/image/ViaxrjzNowdgL-xnEfVV-Ggv5.html

would allow the attacker to execute client side scripts.

The application image upload will now only allow the upload of files
with the following extensions: .gif, .png, .jpg and .jpeg.
---
 api/application.go                 |   8 ++++++++
 api/application_test.go            |  16 ++++++++++++++++
 test/assets/image-header-with.html | Bin 0 -> 154 bytes
 3 files changed, 24 insertions(+)
 create mode 100644 test/assets/image-header-with.html

diff --git a/api/application.go b/api/application.go
index ff64075f0..736330ee5 100644
--- a/api/application.go
+++ b/api/application.go
@@ -329,6 +329,14 @@ func (a *ApplicationAPI) UploadApplicationImage(ctx *gin.Context) {
 
 			ext := filepath.Ext(file.Filename)
 
+			switch ext {
+			case ".gif", ".png", ".jpg", ".jpeg":
+				// ok
+			default:
+				ctx.AbortWithError(400, errors.New("invalid file extension"))
+				return
+			}
+
 			name := generateNonExistingImageName(a.ImageDir, func() string {
 				return generateImageName() + ext
 			})
diff --git a/api/application_test.go b/api/application_test.go
index c7fe43d52..22777eb08 100644
--- a/api/application_test.go
+++ b/api/application_test.go
@@ -398,6 +398,22 @@ func (s *ApplicationSuite) Test_UploadAppImage_WithTextFile_expectBadRequest() {
 	assert.Equal(s.T(), s.ctx.Errors[0].Err, errors.New("file must be an image"))
 }
 
+func (s *ApplicationSuite) Test_UploadAppImage_WithHtmlFileHavingImageHeader() {
+	s.db.User(5).App(1)
+
+	cType, buffer, err := upload(map[string]*os.File{"file": mustOpen("../test/assets/image-header-with.html")})
+	assert.Nil(s.T(), err)
+	s.ctx.Request = httptest.NewRequest("POST", "/irrelevant", &buffer)
+	s.ctx.Request.Header.Set("Content-Type", cType)
+	test.WithUser(s.ctx, 5)
+	s.ctx.Params = gin.Params{{Key: "id", Value: "1"}}
+
+	s.a.UploadApplicationImage(s.ctx)
+
+	assert.Equal(s.T(), 400, s.recorder.Code)
+	assert.Equal(s.T(), s.ctx.Errors[0].Err, errors.New("invalid file extension"))
+}
+
 func (s *ApplicationSuite) Test_UploadAppImage_expectNotFound() {
 	s.db.User(5)
 
diff --git a/test/assets/image-header-with.html b/test/assets/image-header-with.html
new file mode 100644
index 0000000000000000000000000000000000000000..4d05a95869f0f75b1ec9fe5384edff9529b8f626
GIT binary patch
literal 154
zcmeAS@N?(olHy`uVBq!ia0vp@K+MO%1|+}KPrC%97>k44ofy`glX(f`u%tWsIx;Y9
z?C1WI$O`03d%8G=Xapz!`1kmFebC1hY8rA^S&lYGU0(m6q50Br{{2Ubj{uc1c)I$z
ttaD0evMEk3$}A|cOUy|vD$!8#NX^N~*HH-1FUm<#RMNDz(TB=&0RRasFtz{y

literal 0
HcmV?d00001