309 lines
8.4 KiB
Go
309 lines
8.4 KiB
Go
/*
|
||
@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
|
||
}
|