// =========================================== // 多班级版班级管理系统 - Go 后端 // // 开发者: Canglan // 联系方式: admin@sea-studio.top // 版权归属: Sea Network Technology Studio // 许可证: Apache License 2.0 // // 版权所有 © Sea Network Technology Studio // =========================================== package middleware import ( "bytes" "encoding/json" "io" "net/url" "strings" "github.com/gin-gonic/gin" ) // Sanitize 输入清理中间件(路径遍历防护 + 长度限制) func Sanitize() gin.HandlerFunc { return func(c *gin.Context) { // 处理 POST、PUT、PATCH 请求体 if c.Request.Method == "POST" || c.Request.Method == "PUT" || c.Request.Method == "PATCH" { body, err := io.ReadAll(c.Request.Body) if err == nil && len(body) > 0 { var data interface{} if json.Unmarshal(body, &data) == nil { cleaned := sanitizeData(data) newBody, _ := json.Marshal(cleaned) c.Request.Body = io.NopCloser(bytes.NewBuffer(newBody)) c.Request.ContentLength = int64(len(newBody)) } else { // 非 JSON 请求体,恢复原始 body c.Request.Body = io.NopCloser(bytes.NewBuffer(body)) } } } // 清理查询参数(GET 等请求的 URL query string) if c.Request.URL.RawQuery != "" { params := c.Request.URL.Query() dirty := false for key, values := range params { for i, v := range values { cleaned := sanitizeString(v) if cleaned != v { values[i] = cleaned dirty = true } } params[key] = values } if dirty { c.Request.URL.RawQuery = params.Encode() } } c.Next() } } // sanitizeData 递归清理数据 func sanitizeData(data interface{}) interface{} { switch v := data.(type) { case map[string]interface{}: result := make(map[string]interface{}, len(v)) for key, val := range v { result[key] = sanitizeData(val) } return result case []interface{}: result := make([]interface{}, len(v)) for i, val := range v { result[i] = sanitizeData(val) } return result case string: return sanitizeString(v) default: return v } } // sanitizeString 清理字符串 func sanitizeString(value string) string { if value == "" { return "" } value = strings.TrimSpace(value) // 路径遍历防护(循环解码直到稳定,防止多层编码绕过) for { decoded, err := url.PathUnescape(value) if err != nil || decoded == value { break } value = decoded } // 大小写无关的路径遍历模式清理(循环移除直到无匹配) lower := strings.ToLower(value) for strings.Contains(lower, "../") || strings.Contains(lower, "..\\") { replaced := false for _, pattern := range []string{"../", "..\\"} { if idx := strings.Index(lower, pattern); idx >= 0 { value = value[:idx] + value[idx+len(pattern):] lower = lower[:idx] + lower[idx+len(pattern):] replaced = true break } } if !replaced { break } } // 限制长度(按 rune 截断,避免切断多字节 UTF-8 字符) runes := []rune(value) if len(runes) > 1000 { value = string(runes[:1000]) } // SQL 注入由 GORM 参数化查询防护,无需正则替换(避免破坏合法输入) return value }