CVE-2023-0934: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: Cross-site Scripting (XSS) - Stored in GitHub repository answerdev/answer prior to 1.0.5.
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: 5.4
Published: 2023-02-21T15:15Z
Repository: https://github.com/answerdev/answer
Language: Go
Files changed: internal/base/validator/validator.go, internal/controller_admin/siteinfo_controller.go, internal/schema/siteinfo_schema.go
Commit date: 2023-02-15 15:51:12 +0800
Commit message: add input sanitizer

Diff: diff --git a/internal/base/validator/validator.go b/internal/base/validator/validator.go
index 206e5808..b0f7fbf7 100644
--- a/internal/base/validator/validator.go
+++ b/internal/base/validator/validator.go
@@ -31,10 +31,11 @@ import (
 	"github.com/go-playground/validator/v10/translations/pt"
 	"github.com/go-playground/validator/v10/translations/ru"
 	"github.com/go-playground/validator/v10/translations/vi"
 	"github.com/go-playground/validator/v10/translations/zh"
 	"github.com/go-playground/validator/v10/translations/zh_tw"
+	"github.com/microcosm-cc/bluemonday"
 	myErrors "github.com/segmentfault/pacman/errors"
 	"github.com/segmentfault/pacman/i18n"
 	"github.com/segmentfault/pacman/log"
 )
 
@@ -114,14 +115,31 @@ func NotBlank(fl validator.FieldLevel) (res bool) {
 	default:
 		return field.IsValid() && field.Interface() != reflect.Zero(field.Type()).Interface()
 	}
 }
 
+func Sanitizer(fl validator.FieldLevel) (res bool) {
+	field := fl.Field()
+	switch field.Kind() {
+	case reflect.String:
+		filter := bluemonday.UGCPolicy()
+		field.SetString(filter.Sanitize(field.String()))
+		return true
+	case reflect.Chan, reflect.Map, reflect.Slice, reflect.Array:
+		return field.Len() > 0
+	case reflect.Ptr, reflect.Interface, reflect.Func:
+		return !field.IsNil()
+	default:
+		return field.IsValid() && field.Interface() != reflect.Zero(field.Type()).Interface()
+	}
+}
+
 func createDefaultValidator(la i18n.Language) *validator.Validate {
 	validate := validator.New()
 	// _ = validate.RegisterValidation("notblank", validators.NotBlank)
 	_ = validate.RegisterValidation("notblank", NotBlank)
+	_ = validate.RegisterValidation("sanitizer", Sanitizer)
 	validate.RegisterTagNameFunc(func(fld reflect.StructField) (res string) {
 		defer func() {
 			if len(res) > 0 {
 				res = translator.Tr(la, res)
 			}
diff --git a/internal/controller_admin/siteinfo_controller.go b/internal/controller_admin/siteinfo_controller.go
index 98cb1fec..d799fe5b 100644
--- a/internal/controller_admin/siteinfo_controller.go
+++ b/internal/controller_admin/siteinfo_controller.go
@@ -203,11 +203,11 @@ func (sc *SiteInfoController) UpdateGeneral(ctx *gin.Context) {
 	req := schema.SiteGeneralReq{}
 	if handler.BindAndCheck(ctx, &req) {
 		return
 	}
 	err := sc.siteInfoService.SaveSiteGeneral(ctx, req)
-	handler.HandleResponse(ctx, err, nil)
+	handler.HandleResponse(ctx, err, req)
 }
 
 // UpdateInterface update site interface
 // @Summary update site info interface
 // @Description update site info interface
diff --git a/internal/schema/siteinfo_schema.go b/internal/schema/siteinfo_schema.go
index 64c08848..37291d78 100644
--- a/internal/schema/siteinfo_schema.go
+++ b/internal/schema/siteinfo_schema.go
@@ -16,15 +16,15 @@ import (
 const PermaLinkQuestionIDAndTitle = 1
 const PermaLinkQuestionID = 2
 
 // SiteGeneralReq site general request
 type SiteGeneralReq struct {
-	Name             string `validate:"required,gt=1,lte=128" form:"name" json:"name"`
-	ShortDescription string `validate:"omitempty,gt=3,lte=255" form:"short_description" json:"short_description"`
-	Description      string `validate:"omitempty,gt=3,lte=2000" form:"description" json:"description"`
-	SiteUrl          string `validate:"required,gt=1,lte=512,url" form:"site_url" json:"site_url"`
-	ContactEmail     string `validate:"required,gt=1,lte=512,email" form:"contact_email" json:"contact_email"`
+	Name             string `validate:"required,sanitizer,gt=1,lte=128" form:"name" json:"name"`
+	ShortDescription string `validate:"omitempty,sanitizer,gt=3,lte=255" form:"short_description" json:"short_description"`
+	Description      string `validate:"omitempty,sanitizer,gt=3,lte=2000" form:"description" json:"description"`
+	SiteUrl          string `validate:"required,sanitizer,gt=1,lte=512,url" form:"site_url" json:"site_url"`
+	ContactEmail     string `validate:"required,sanitizer,gt=1,lte=512,email" form:"contact_email" json:"contact_email"`
 }
 
 type SiteSeoReq struct {
 	PermaLink int    `validate:"required,lte=3,gte=0" form:"permalink" json:"permalink"`
 	Robots    string `validate:"required" form:"robots" json:"robots"`

Vulnerable code: 	handler.HandleResponse(ctx, err, nil)
	Name             string `validate:"required,gt=1,lte=128" form:"name" json:"name"`
	ShortDescription string `validate:"omitempty,gt=3,lte=255" form:"short_description" json:"short_description"`
	Description      string `validate:"omitempty,gt=3,lte=2000" form:"description" json:"description"`
	SiteUrl          string `validate:"required,gt=1,lte=512,url" form:"site_url" json:"site_url"`
	ContactEmail     string `validate:"required,gt=1,lte=512,email" form:"contact_email" json:"contact_email"`
Fixed code: 	"github.com/microcosm-cc/bluemonday"
func Sanitizer(fl validator.FieldLevel) (res bool) {
	field := fl.Field()
	switch field.Kind() {
	case reflect.String:
		filter := bluemonday.UGCPolicy()
		field.SetString(filter.Sanitize(field.String()))
		return true
	case reflect.Chan, reflect.Map, reflect.Slice, reflect.Array:
		return field.Len() > 0
	case reflect.Ptr, reflect.Interface, reflect.Func:
		return !field.IsNil()
	default:
		return field.IsValid() && field.Interface() != reflect.Zero(field.Type()).Interface()
	}
}

	_ = validate.RegisterValidation("sanitizer", Sanitizer)
	handler.HandleResponse(ctx, err, req)
	Name             string `validate:"required,sanitizer,gt=1,lte=128" form:"name" json:"name"`
	ShortDescription string `validate:"omitempty,sanitizer,gt=3,lte=255" form:"short_description" json:"short_description"`
	Description      string `validate:"omitempty,sanitizer,gt=3,lte=2000" form:"description" json:"description"`
	SiteUrl          string `validate:"required,sanitizer,gt=1,lte=512,url" form:"site_url" json:"site_url"`
	ContactEmail     string `validate:"required,sanitizer,gt=1,lte=512,email" form:"contact_email" json:"contact_email"`