/* @Author: 1usir @Description: @File: excelize @Version: 1.0.0 @Date: 2024/2/21 14:10 */ package exce import ( "bytes" "errors" "fmt" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs" "github.com/xuri/excelize/v2" "io" "strings" ) type Rule struct { Name string `json:"name"` // 序号 - 规则集名字 Type string `json:"type"` // 攻击类型 - 规则分组名称 Regular string `json:"regular"` // 正则 Regulars []string `json:"regulars"` // 正则集合 Level string `json:"level"` // 威胁等级 Position []string `json:"position"` // 参数位置 - 参数 Description string `json:"description"` // 描述 - 备注 CVE string `json:"cve"` // cve 编号 Inbound bool `json:"inbound"` // 入站规则 Outbound bool `json:"outbound"` // 出站规则 IsAnd bool `json:"is_and"` // 多条件 } func saveErr(sheet string, nf *excelize.File, cols []string, sheetIndexs map[string]int) { index, ok := sheetIndexs[sheet] if !ok { nf.NewSheet(sheet) // 设置单元格的值 nf.SetCellValue(sheet, "A1", "序号") nf.SetCellValue(sheet, "B1", "攻击类型") nf.SetCellValue(sheet, "C1", "关键词") nf.SetCellValue(sheet, "D1", "正则") nf.SetCellValue(sheet, "E1", "威胁等级") nf.SetCellValue(sheet, "F1", "攻击语句") nf.SetCellValue(sheet, "G1", "攻击语句解码后") nf.SetCellValue(sheet, "H1", "参数位置") nf.SetCellValue(sheet, "I1", "描述") nf.SetCellValue(sheet, "J1", "CVE编号") nf.SetCellValue(sheet, "K1", "备注") nf.SetCellValue(sheet, "L1", "错误原因") sheetIndexs[sheet] = 2 index = 2 } for i, col := range cols { nf.SetCellValue(sheet, fmt.Sprintf("%c%d", 'A'+i, index), col) } sheetIndexs[sheet]++ } func ParseRules(r io.Reader) (*bytes.Buffer, []*Rule, error) { nf := excelize.NewFile() nf.DeleteSheet("Sheet1") f, err := excelize.OpenReader(r) if err != nil { return nil, nil, err } res := make([]*Rule, 0) sheets := f.GetSheetList() sheetIndexs := map[string]int{} for _, sheet := range sheets { rows, err := f.GetRows(sheet) if err != nil || len(rows) <= 1 { return nil, nil, err } /* 1 2 3 4 5 6 7 8 9 10 11 12 序号|攻击类型|关键字|正则|威胁等级|攻击语句|攻击语句解码后|参数位置|描述|CVE编号|备注|错误原因 */ for _, row := range rows[1:] { cols := make([]string, 12) copy(cols, row) if len(cols) < 8 || cols[0] == "" || cols[1] == "" || cols[3] == "" || cols[7] == "" { continue } r := &Rule{ Name: strings.TrimSpace(cols[0]), Type: strings.TrimSpace(cols[1]), Regular: strings.TrimSpace(cols[3]), Level: strings.TrimSpace(cols[4]), Position: strings.Split(cols[7], "\n"), } if strings.Contains(r.Regular, "\n") { //fmt.Println(fmt.Sprintf("无效规则1:Sheet[%s|%s] %s", sheet, r.Name, r.Regular)) //return nil, errors.New(fmt.Sprintf("无效规则:Sheet[%s|%s] %s", sheet, r.Name, r.Regular)) // 创建错误新表格 cols[11] = "无效正则" saveErr(sheet, nf, cols, sheetIndexs) continue } if len(cols) > 8 { r.Description = cols[8] } if len(cols) > 9 { r.CVE = cols[9] } // 特殊处理 if r.Type == "xss注入" { r.Type = "XSS" } // 支持多条件 var regulars []string var positions []string if strings.Contains(r.Regular, "且") { regulars, positions, err = parseRegulars(r.Regular) if err != nil { //fmt.Println(fmt.Sprintf("多规则解析失败:Sheet[%s|%s] %s %s", sheet, r.Name, r.Regular, err)) cols[11] = "多规则解析失败" saveErr(sheet, nf, cols, sheetIndexs) continue } r.IsAnd = true } else { regulars = []string{r.Regular} } for _, regular := range regulars { // 校验正则参数是否合理 rule := &firewallconfigs.HTTPFirewallRule{ IsOn: true, Operator: "match", Value: regular, IsCaseInsensitive: true, } if err := rule.Init(); err != nil { //fmt.Println(fmt.Sprintf("无效正则规则:Sheet[%s|%s] %s", sheet, r.Name, r.Regular)) // 创建错误新表格 cols[11] = "正则解析失败" saveErr(sheet, nf, cols, sheetIndexs) continue } } if r.IsAnd { r.Regulars = regulars r.Position = r.setString(positions) } else { // position 格式化去重 r.Position = r.setString(r.Position) } res = append(res, r) } } //nf.SaveAs("/Users/1usir/works/waf/open-waf/waf/EdgeAdmin/internal/utils/exce/WAF ALL Error.xlsx") if len(sheetIndexs) > 0 { _ = nf.DeleteSheet("Sheet1") buff, err := nf.WriteToBuffer() return buff, res, err } else { return nil, res, nil } } func (this *Rule) formatPosition(p string) string { switch p { case "REQUEST_FILENAME", "REQUEST_FILENAM": this.Inbound = true return "${requestUpload.name}" case "ARGS_NAMES", "ARGS": this.Inbound = true return "${args}" case "REQUEST_BODY", "body": this.Inbound = true return "${requestBody}" case "REQUEST_HEADERS", "head", "header", "headers": this.Inbound = true return "${headers}" case "REQUEST_HEADERS_NAMES": this.Inbound = true return "${headerNames}" case "REQUEST_COOKIES_NAMES", "REQUEST_COOKIES", "cookie": this.Inbound = true return "${cookies}" case "url", "uri", "REQUEST_RAW_URI", "REQUEST_URI": this.Inbound = true return "${requestURI}" case "RESPONSE_BODY": this.Outbound = true return "${responseBody}" case "CONTENT_TYPE": return "${contentType}" case "referer": return "${referer}" case "host": return "${host}" default: if strings.HasPrefix(p, "${") && strings.HasSuffix(p, "}") { return p } //fmt.Println("=========>?", p) //panic(p) return "" } } // 元素去重 func (this *Rule) setString(slice []string) []string { // 解析位置 p := []string{} for _, v := range slice { if strings.Contains(v, "ARGS_NAMES_LIST") { p = append(p, "uri") } else if strings.Contains(v, "REQUEST_HEADER_FIELDS") { p = append(p, "header") } else if strings.Contains(v, "COOKIES_NAMES_LIST") { p = append(p, "REQUEST_COOKIES_NAMES") } else if strings.Contains(v, ",") { p = append(p, strings.Split(v, ",")...) } else if strings.Contains(v, ",") { p = append(p, strings.Split(v, ",")...) } else { p = append(p, v) } } slice = p res := make([]string, 0, len(slice)) m := map[string]int{} for _, v := range slice { v = this.formatPosition(v) _, ok := m[v] if ok { continue } if v == "${headers}" || v == "${headerNames}" { // headers 包含headersNames 如果存在headers时 headersName 可以忽略 _, ok1 := m["${headers}"] idx2, ok2 := m["${headerNames}"] if ok2 { res[idx2] = "${headers}" delete(m, "${headerNames}") m["{headers}"] = idx2 continue } if ok1 { continue } } m[v] = len(res) res = append(res, v) } return res } // 支持多条件 /* uri:\/wp-login\.php且body:.{256,} uri:\/goform\/_aslvl且body:SAPassword=W2402 */ func parseRegulars(conditions string) ([]string, []string, error) { getFieldFunc := func(s string) (func(string) (string, func(string) string), string) { s = strings.ToLower(s) switch s { case "uri", "body", "host", "header", "headers", "head", "cookie", "referer": return nil, s case "user-agent", "ua": return nil, "${userAgent}" case "authorization": return func(s string) (string, func(string) string) { return "${headers}", func(s string) string { return "authorization:" + s } }, "" default: return nil, "" } } cdts := strings.Split(conditions, "且") var regulars []string var positions []string for _, cdt := range cdts { i := strings.Index(cdt, ":") if i == -1 { // 错误 return nil, nil, errors.New("invalid " + cdt) } // 提取position nextFc, field := getFieldFunc(cdt[:i]) var position, regular string if nextFc == nil && field == "" { // 无法识别 return nil, nil, errors.New("invalid " + cdt) } if nextFc != nil { field, getRegularFc := nextFc(cdt[i+1:]) if field == "" || getRegularFc == nil { // 无效正则 return nil, nil, errors.New("invalid " + cdt) } position = field regular = getRegularFc(cdt[i+1:]) } else { position = field regular = cdt[i+1:] } regulars = append(regulars, regular) positions = append(positions, position) } return regulars, positions, nil }