CVE-2021-33496: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: Dutchcoders transfer.sh before 1.2.4 allows XSS via an inline view.
CWE description: The product does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users.
CVSS v3 base score: 6.1
Published: 2021-05-24T05:15Z
Repository: https://github.com/dutchcoders/transfer.sh
Language: Go
Files changed: cmd/cmd.go, server/handlers.go
Commit date: 2021-05-21 15:49:48 +0200
Commit message: fixes-20210521 (#373)


Diff: diff --git a/cmd/cmd.go b/cmd/cmd.go
index 1987d84..e840e32 100644
--- a/cmd/cmd.go
+++ b/cmd/cmd.go
@@ -10,11 +10,11 @@ import (
 	"github.com/fatih/color"
 	"github.com/urfave/cli"
 	"google.golang.org/api/googleapi"
 )
 
-var Version = "1.2.2"
+var Version = "1.2.4"
 var helpTemplate = `NAME:
 {{.Name}} - {{.Usage}}
 
 DESCRIPTION:
 {{.Description}}
diff --git a/server/handlers.go b/server/handlers.go
index 4cf4238..dbecb1e 100644
--- a/server/handlers.go
+++ b/server/handlers.go
@@ -92,10 +92,31 @@ func initHTMLTemplates() *html_template.Template {
 
 func healthHandler(w http.ResponseWriter, r *http.Request) {
 	fmt.Fprintf(w, "Approaching Neutral Zone, all systems normal and functioning.")
 }
 
+func canContainsXSS(contentType string) bool {
+	switch {
+	case strings.Contains(contentType, "cache-manifest"):
+		fallthrough
+	case strings.Contains(contentType, "html"):
+		fallthrough
+	case strings.Contains(contentType, "rdf"):
+		fallthrough
+	case strings.Contains(contentType, "vtt"):
+		fallthrough
+	case strings.Contains(contentType, "xml"):
+		fallthrough
+	case strings.Contains(contentType, "xsl"):
+		return true
+	case strings.Contains(contentType, "x-mixed-replace"):
+		return true
+	}
+
+	return false
+}
+
 /* The preview handler will show a preview of the content for browsers (accept type text/html), and referer is not transfer.sh */
 func (s *Server) previewHandler(w http.ResponseWriter, r *http.Request) {
 	vars := mux.Vars(r)
 
 	token := vars["token"]
@@ -261,15 +282,11 @@ func (s *Server) postHandler(w http.ResponseWriter, r *http.Request) {
 	w.Header().Set("Content-Type", "text/plain")
 
 	for _, fheaders := range r.MultipartForm.File {
 		for _, fheader := range fheaders {
 			filename := sanitize(fheader.Filename)
-			contentType := fheader.Header.Get("Content-Type")
-
-			if contentType == "" {
-				contentType = mime.TypeByExtension(filepath.Ext(fheader.Filename))
-			}
+			contentType := mime.TypeByExtension(filepath.Ext(fheader.Filename))
 
 			var f io.Reader
 			var err error
 
 			if f, err = fheader.Open(); err != nil {
@@ -472,15 +489,11 @@ func (s *Server) putHandler(w http.ResponseWriter, r *http.Request) {
 		log.Print("Empty content-length")
 		http.Error(w, errors.New("Could not upload empty file").Error(), 400)
 		return
 	}
 
-	contentType := r.Header.Get("Content-Type")
-
-	if contentType == "" {
-		contentType = mime.TypeByExtension(filepath.Ext(vars["filename"]))
-	}
+	contentType := mime.TypeByExtension(filepath.Ext(vars["filename"]))
 
 	token := Encode(INIT_SEED, s.randomTokenLength)
 
 	metadata := MetadataForRequest(contentType, s.randomTokenLength, r)
 
@@ -685,11 +698,11 @@ func (s *Server) CheckDeletionToken(deletionToken, token, filename string) error
 
 	var metadata Metadata
 
 	r, _, err := s.storage.Get(token, fmt.Sprintf("%s.metadata", filename))
 	if s.storage.IsNotExist(err) {
-		return nil
+		return errors.New("Metadata doesn't exist")
 	} else if err != nil {
 		return err
 	}
 
 	defer r.Close()
@@ -1006,11 +1019,11 @@ func (s *Server) getHandler(w http.ResponseWriter, r *http.Request) {
 	w.Header().Set("Content-Disposition", fmt.Sprintf("%s; filename=\"%s\"", disposition, filename))
 	w.Header().Set("Connection", "keep-alive")
 	w.Header().Set("X-Remaining-Downloads", remainingDownloads)
 	w.Header().Set("X-Remaining-Days", remainingDays)
 
-	if disposition == "inline" && strings.Contains(contentType, "html") {
+	if disposition == "inline" && canContainsXSS(contentType) {
 		reader = ioutil.NopCloser(bluemonday.UGCPolicy().SanitizeReader(reader))
 	}
 
 	if w.Header().Get("Range") == "" {
 		if _, err = io.Copy(w, reader); err != nil {

Vulnerable code: var Version = "1.2.2"
			contentType := fheader.Header.Get("Content-Type")

			if contentType == "" {
				contentType = mime.TypeByExtension(filepath.Ext(fheader.Filename))
			}
	contentType := r.Header.Get("Content-Type")

	if contentType == "" {
		contentType = mime.TypeByExtension(filepath.Ext(vars["filename"]))
	}
		return nil
	if disposition == "inline" && strings.Contains(contentType, "html") {
Fixed code: var Version = "1.2.4"
func canContainsXSS(contentType string) bool {
	switch {
	case strings.Contains(contentType, "cache-manifest"):
		fallthrough
	case strings.Contains(contentType, "html"):
		fallthrough
	case strings.Contains(contentType, "rdf"):
		fallthrough
	case strings.Contains(contentType, "vtt"):
		fallthrough
	case strings.Contains(contentType, "xml"):
		fallthrough
	case strings.Contains(contentType, "xsl"):
		return true
	case strings.Contains(contentType, "x-mixed-replace"):
		return true
	}

	return false
}

			contentType := mime.TypeByExtension(filepath.Ext(fheader.Filename))
	contentType := mime.TypeByExtension(filepath.Ext(vars["filename"]))
		return errors.New("Metadata doesn't exist")
	if disposition == "inline" && canContainsXSS(contentType) {