Browse Source

报告编辑 过滤xss攻击

xyxie 11 months ago
parent
commit
43f092880c
6 changed files with 240 additions and 11 deletions
  1. 6 3
      controllers/english_report/report.go
  2. 10 5
      controllers/report.go
  3. 3 0
      go.mod
  4. 6 0
      go.sum
  5. 2 1
      services/report.go
  6. 213 2
      utils/common.go

+ 6 - 3
controllers/english_report/report.go

@@ -65,12 +65,13 @@ func (this *EnglishReportController) Add() {
 
 	var contentSub string
 	if req.Content != "" {
-		e := utils.ContentXssFilter(req.Content)
+		e := utils.ContentXssCheck(req.Content)
 		if e != nil {
 			br.Msg = "存在非法标签"
 			br.ErrMsg = "存在非法标签, Err: " + e.Error()
 			return
 		}
+		req.Content = utils.ContentXssFilter(req.Content)
 		content, e := services.FilterReportContentBr(req.Content)
 		if e != nil {
 			br.Msg = "内容去除前后空格失败"
@@ -188,12 +189,13 @@ func (this *EnglishReportController) Edit() {
 	}
 	var contentSub string
 	if req.Content != "" {
-		e := utils.ContentXssFilter(req.Content)
+		e := utils.ContentXssCheck(req.Content)
 		if e != nil {
 			br.Msg = "存在非法标签"
 			br.ErrMsg = "存在非法标签, Err: " + e.Error()
 			return
 		}
+		req.Content = utils.ContentXssFilter(req.Content)
 		content, e := services.FilterReportContentBr(req.Content)
 		if e != nil {
 			br.Msg = "内容去除前后空格失败"
@@ -1025,12 +1027,13 @@ func (this *EnglishReportController) SaveReportContent() {
 			content = this.GetString("Content")
 		}
 		if content != "" {
-			e := utils.ContentXssFilter(req.Content)
+			e := utils.ContentXssCheck(req.Content)
 			if e != nil {
 				br.Msg = "存在非法标签"
 				br.ErrMsg = "存在非法标签, Err: " + e.Error()
 				return
 			}
+			req.Content = utils.ContentXssFilter(req.Content)
 			contentClean, e := services.FilterReportContentBr(req.Content)
 			if e != nil {
 				br.Msg = "内容去除前后空格失败"

+ 10 - 5
controllers/report.go

@@ -525,12 +525,13 @@ func (this *ReportController) Add() {
 	}
 	var contentSub string
 	if req.Content != "" {
-		e := utils.ContentXssFilter(req.Content)
+		e := utils.ContentXssCheck(req.Content)
 		if e != nil {
 			br.Msg = "存在非法标签"
 			br.ErrMsg = "存在非法标签, Err: " + e.Error()
 			return
 		}
+		req.Content = utils.ContentXssFilter(req.Content)
 		content, e := services.FilterReportContentBr(req.Content)
 		if e != nil {
 			br.Msg = "内容去除前后空格失败"
@@ -663,12 +664,13 @@ func (this *ReportController) Edit() {
 	}
 	var contentSub string
 	if req.Content != "" {
-		e := utils.ContentXssFilter(req.Content)
+		e := utils.ContentXssCheck(req.Content)
 		if e != nil {
 			br.Msg = "存在非法标签"
 			br.ErrMsg = "存在非法标签, Err: " + e.Error()
 			return
 		}
+		req.Content = utils.ContentXssFilter(req.Content)
 		content, e := services.FilterReportContentBr(req.Content)
 		if e != nil {
 			br.Msg = "内容去除前后空格失败"
@@ -1215,12 +1217,13 @@ func (this *ReportController) SaveReportContent() {
 			content = this.GetString("Content")
 		}
 		if content != "" {
-			e := utils.ContentXssFilter(req.Content)
+			e := utils.ContentXssCheck(req.Content)
 			if e != nil {
 				br.Msg = "存在非法标签"
 				br.ErrMsg = "存在非法标签, Err: " + e.Error()
 				return
 			}
+			req.Content = utils.ContentXssFilter(req.Content)
 			contentClean, e := services.FilterReportContentBr(req.Content)
 			if e != nil {
 				br.Msg = "内容去除前后空格失败"
@@ -2447,12 +2450,13 @@ func (this *ReportController) EditDayWeekChapter() {
 	// 更新章节及指标
 	contentSub := ""
 	if req.Content != "" {
-		e := utils.ContentXssFilter(req.Content)
+		e := utils.ContentXssCheck(req.Content)
 		if e != nil {
 			br.Msg = "存在非法标签"
 			br.ErrMsg = "存在非法标签, Err: " + e.Error()
 			return
 		}
+		req.Content = utils.ContentXssFilter(req.Content)
 		contentClean, e := services.FilterReportContentBr(req.Content)
 		if e != nil {
 			br.Msg = "内容去除前后空格失败"
@@ -2924,12 +2928,13 @@ func (this *ReportController) PublishDayWeekReportChapter() {
 	// 更新章节信息
 	contentSub := ""
 	if req.Content != "" {
-		e := utils.ContentXssFilter(req.Content)
+		e := utils.ContentXssCheck(req.Content)
 		if e != nil {
 			br.Msg = "存在非法标签"
 			br.ErrMsg = "存在非法标签, Err: " + e.Error()
 			return
 		}
+		req.Content = utils.ContentXssFilter(req.Content)
 		contentClean, e := services.FilterReportContentBr(req.Content)
 		if e != nil {
 			br.Msg = "内容去除前后空格失败"

+ 3 - 0
go.mod

@@ -26,6 +26,7 @@ require (
 	github.com/gorilla/websocket v1.5.1
 	github.com/h2non/filetype v1.1.3
 	github.com/kgiannakakis/mp3duration v0.0.0-20191013070830-d834f8d5ed53
+	github.com/microcosm-cc/bluemonday v1.0.26
 	github.com/minio/minio-go/v7 v7.0.69
 	github.com/mojocn/base64Captcha v1.3.6
 	github.com/nosixtools/solarlunar v0.0.0-20211112060703-1b6dea7b4a19
@@ -56,6 +57,7 @@ require (
 	github.com/aliyun/credentials-go v1.3.1 // indirect
 	github.com/andybalholm/cascadia v1.3.2 // indirect
 	github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20211218165449-dd623ecc2f02 // indirect
+	github.com/aymerick/douceur v0.2.0 // indirect
 	github.com/beorn7/perks v1.0.1 // indirect
 	github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d // indirect
 	github.com/cespare/xxhash/v2 v2.2.0 // indirect
@@ -72,6 +74,7 @@ require (
 	github.com/gonum/lapack v0.0.0-20181123203213-e4cdc5a0bff9 // indirect
 	github.com/gonum/matrix v0.0.0-20181209220409-c518dec07be9 // indirect
 	github.com/google/uuid v1.6.0 // indirect
+	github.com/gorilla/css v1.0.0 // indirect
 	github.com/hashicorp/golang-lru v0.5.4 // indirect
 	github.com/jmespath/go-jmespath v0.4.0 // indirect
 	github.com/josharian/intern v1.0.0 // indirect

+ 6 - 0
go.sum

@@ -89,6 +89,8 @@ github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de/go.mod h1:DCaWoU
 github.com/astaxie/beego v1.12.3/go.mod h1:p3qIm0Ryx7zeBHLljmd7omloyca1s4yu1a8kM1FkpIA=
 github.com/aws/aws-sdk-go v1.51.2 h1:Ruwgz5aqIXin5Yfcgc+PCzoqW5tEGb9aDL/JWDsre7k=
 github.com/aws/aws-sdk-go v1.51.2/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk=
+github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
+github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
 github.com/beego/bee/v2 v2.1.0 h1:4WngbAnkvVOyKy74WXcRH3clon76wkjhuzrV2mx2fQU=
 github.com/beego/bee/v2 v2.1.0/go.mod h1:wDhKy5TNxv46LHKsK2gyxo38ObCOm9PbCN89lWHK3EU=
 github.com/beego/beego/v2 v2.1.0 h1:Lk0FtQGvDQCx5V5yEu4XwDsIgt+QOlNjt5emUa3/ZmA=
@@ -231,6 +233,8 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+
 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
 github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
 github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
+github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
+github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
 github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
 github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
 github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
@@ -292,6 +296,8 @@ github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW
 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
 github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
 github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
+github.com/microcosm-cc/bluemonday v1.0.26 h1:xbqSvqzQMeEHCqMi64VAs4d8uy6Mequs3rQ0k/Khz58=
+github.com/microcosm-cc/bluemonday v1.0.26/go.mod h1:JyzOCs9gkyQyjs+6h10UEVSe02CGwkhd72Xdqh78TWs=
 github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
 github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
 github.com/minio/minio-go/v7 v7.0.69 h1:l8AnsQFyY1xiwa/DaQskY4NXSLA2yrGsW5iD9nRPVS0=

+ 2 - 1
services/report.go

@@ -937,12 +937,13 @@ func PcCreateAndUploadSunCode(scene, page string) (imgUrl string, err error) {
 func CreateNewReport(req models.AddReq, adminInfo *system.Admin) (newReportId int64, reportCode, errMsg string, err error) {
 	contentSub := ""
 	if req.Content != "" {
-		e := utils.ContentXssFilter(req.Content)
+		e := utils.ContentXssCheck(req.Content)
 		if e != nil {
 			errMsg = "存在非法标签"
 			err = errors.New("存在非法标签, Err: " + e.Error())
 			return
 		}
+		req.Content = utils.ContentXssFilter(req.Content)
 		contentClean, e := FilterReportContentBr(req.Content)
 		if e != nil {
 			errMsg = "内容去除前后空格失败"

+ 213 - 2
utils/common.go

@@ -13,6 +13,7 @@ import (
 	"errors"
 	"fmt"
 	"github.com/PuerkitoBio/goquery"
+	"github.com/microcosm-cc/bluemonday"
 	"github.com/shopspring/decimal"
 	xhtml "golang.org/x/net/html"
 	"html"
@@ -2306,8 +2307,8 @@ func isValidSrc(src string) bool {
 	return validSchemes.MatchString(src)
 }
 
-// ContentXssFilter 过滤文本中的JS代码
-func ContentXssFilter(content string) (err error) {
+// ContentXssCheck 校验文本中的JS代码
+func ContentXssCheck(content string) (err error) {
 	// 解析HTML内容
 	node, err := xhtml.Parse(strings.NewReader(content))
 	if err != nil {
@@ -2358,3 +2359,213 @@ func ContentXssFilter(content string) (err error) {
 	}
 	return
 }
+
+func ContentXssFilter(content string) (cleanContent string) {
+	p := customXssPolicy()
+
+	// The policy can then be used to sanitize lots of input and it is safe to use the policy in multiple goroutines
+	cleanContent = p.Sanitize(
+		content,
+	)
+	return
+}
+
+func customXssPolicy() (p *bluemonday.Policy) {
+	p = bluemonday.NewPolicy()
+	p.AllowStandardAttributes()
+
+	//////////////////////////////
+	// Global URL format policy //
+	//////////////////////////////
+
+	p.AllowStandardURLs()
+
+	////////////////////////////////
+	// Declarations and structure //
+	////////////////////////////////
+
+	// "xml" "xslt" "DOCTYPE" "html" "head" are not permitted as we are
+	// expecting user generated content to be a fragment of HTML and not a full
+	// document.
+
+	//////////////////////////
+	// Sectioning root tags //
+	//////////////////////////
+
+	// "article" and "aside" are permitted and takes no attributes
+	p.AllowElements("article", "aside")
+
+	// "body" is not permitted as we are expecting user generated content to be a fragment
+	// of HTML and not a full document.
+
+	// "details" is permitted, including the "open" attribute which can either
+	// be blank or the value "open".
+	p.AllowAttrs(
+		"open",
+	).Matching(regexp.MustCompile(`(?i)^(|open)$`)).OnElements("details")
+
+	// "fieldset" is not permitted as we are not allowing forms to be created.
+
+	// "figure" is permitted and takes no attributes
+	p.AllowElements("figure")
+
+	// "nav" is not permitted as it is assumed that the site (and not the user)
+	// has defined navigation elements
+
+	// "section" is permitted and takes no attributes
+	p.AllowElements("section")
+
+	// "summary" is permitted and takes no attributes
+	p.AllowElements("summary")
+
+	//////////////////////////
+	// Headings and footers //
+	//////////////////////////
+
+	// "footer" is not permitted as we expect user content to be a fragment and
+	// not structural to this extent
+
+	// "h1" through "h6" are permitted and take no attributes
+	p.AllowElements("h1", "h2", "h3", "h4", "h5", "h6")
+
+	// "header" is not permitted as we expect user content to be a fragment and
+	// not structural to this extent
+
+	// "hgroup" is permitted and takes no attributes
+	p.AllowElements("hgroup")
+
+	/////////////////////////////////////
+	// Content grouping and separating //
+	/////////////////////////////////////
+
+	// "blockquote" is permitted, including the "cite" attribute which must be
+	// a standard URL.
+	p.AllowAttrs("cite").OnElements("blockquote")
+
+	// "br" "div" "hr" "p" "span" "wbr" are permitted and take no attributes
+	p.AllowElements("br", "div", "hr", "p", "span", "wbr")
+
+	///////////
+	// Links //
+	///////////
+
+	// "a" is permitted
+	p.AllowAttrs("href").OnElements("a")
+
+	// "area" is permitted along with the attributes that map image maps work
+	p.AllowAttrs("name").Matching(
+		regexp.MustCompile(`^([\p{L}\p{N}_-]+)$`),
+	).OnElements("map")
+	p.AllowAttrs("alt").Matching(bluemonday.Paragraph).OnElements("area")
+	p.AllowAttrs("coords").Matching(
+		regexp.MustCompile(`^([0-9]+,)+[0-9]+$`),
+	).OnElements("area")
+	p.AllowAttrs("href").OnElements("area")
+	p.AllowAttrs("rel").Matching(bluemonday.SpaceSeparatedTokens).OnElements("area")
+	p.AllowAttrs("shape").Matching(
+		regexp.MustCompile(`(?i)^(default|circle|rect|poly)$`),
+	).OnElements("area")
+	p.AllowAttrs("usemap").Matching(
+		regexp.MustCompile(`(?i)^#[\p{L}\p{N}_-]+$`),
+	).OnElements("img")
+
+	// "link" is not permitted
+
+	/////////////////////
+	// Phrase elements //
+	/////////////////////
+
+	// The following are all inline phrasing elements
+	p.AllowElements("abbr", "acronym", "cite", "code", "dfn", "em",
+		"figcaption", "mark", "s", "samp", "strong", "sub", "sup", "var")
+
+	// "q" is permitted and "cite" is a URL and handled by URL policies
+	p.AllowAttrs("cite").OnElements("q")
+
+	// "time" is permitted
+	p.AllowAttrs("datetime").Matching(bluemonday.ISO8601).OnElements("time")
+
+	////////////////////
+	// Style elements //
+	////////////////////
+
+	// block and inline elements that impart no semantic meaning but style the
+	// document
+	p.AllowElements("b", "i", "pre", "small", "strike", "tt", "u")
+
+	// "style" is not permitted as we are not yet sanitising CSS and it is an
+	// XSS attack vector
+
+	//////////////////////
+	// HTML5 Formatting //
+	//////////////////////
+
+	// "bdi" "bdo" are permitted
+	p.AllowAttrs("dir").Matching(bluemonday.Direction).OnElements("bdi", "bdo")
+
+	// "rp" "rt" "ruby" are permitted
+	p.AllowElements("rp", "rt", "ruby")
+
+	///////////////////////////
+	// HTML5 Change tracking //
+	///////////////////////////
+
+	// "del" "ins" are permitted
+	p.AllowAttrs("cite").Matching(bluemonday.Paragraph).OnElements("del", "ins")
+	p.AllowAttrs("datetime").Matching(bluemonday.ISO8601).OnElements("del", "ins")
+
+	///////////
+	// Lists //
+	///////////
+
+	p.AllowLists()
+
+	////////////
+	// Tables //
+	////////////
+
+	p.AllowTables()
+
+	///////////
+	// Forms //
+	///////////
+
+	// By and large, forms are not permitted. However there are some form
+	// elements that can be used to present data, and we do permit those
+	//
+	// "button" "fieldset" "input" "keygen" "label" "output" "select" "datalist"
+	// "textarea" "optgroup" "option" are all not permitted
+
+	// "meter" is permitted
+	p.AllowAttrs(
+		"value",
+		"min",
+		"max",
+		"low",
+		"high",
+		"optimum",
+	).Matching(bluemonday.Number).OnElements("meter")
+
+	// "progress" is permitted
+	p.AllowAttrs("value", "max").Matching(bluemonday.Number).OnElements("progress")
+
+	//////////////////////
+	// Embedded content //
+	//////////////////////
+
+	// Vast majority not permitted
+	// "audio" "canvas" "embed" "iframe" "object" "param" "source" "svg" "track"
+	// "video" are all not permitted
+
+	p.AllowImages()
+
+	// iframe
+	p.AllowElements("iframe")
+	p.AllowAttrs("width").Matching(bluemonday.Number).OnElements("iframe")
+	p.AllowAttrs("height").Matching(bluemonday.Number).OnElements("iframe")
+	p.AllowAttrs("src").OnElements("iframe")
+	p.AllowAttrs("frameborder").Matching(bluemonday.Number).OnElements("iframe")
+	p.AllowAttrs("allow").Matching(regexp.MustCompile(`[a-z; -]*`)).OnElements("iframe")
+	p.AllowAttrs("allowfullscreen").OnElements("iframe")
+	return
+}