Files
waf-platform/EdgeAdmin/internal/utils/exce/excelize.go

309 lines
8.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
@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("无效规则1Sheet[%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
}