mirror of
				https://github.com/TeaOSLab/EdgeNode.git
				synced 2025-11-04 07:40:56 +08:00 
			
		
		
		
	WAF操作符增加包含任一单词、包含所有单词、不包含任一单词
This commit is contained in:
		@@ -4,7 +4,7 @@ package re
 | 
			
		||||
 | 
			
		||||
type RuneMap map[rune]*RuneTree
 | 
			
		||||
 | 
			
		||||
func (this *RuneMap) Lookup(s string, caseInsensitive bool) bool {
 | 
			
		||||
func (this RuneMap) Lookup(s string, caseInsensitive bool) bool {
 | 
			
		||||
	return this.lookup([]rune(s), caseInsensitive, 0)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										110
									
								
								internal/utils/runes/runes.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										110
									
								
								internal/utils/runes/runes.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,110 @@
 | 
			
		||||
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
 | 
			
		||||
 | 
			
		||||
package runes
 | 
			
		||||
 | 
			
		||||
// ContainsAnyWord 检查字符串是否包含任一单词
 | 
			
		||||
func ContainsAnyWord(s string, words []string, isCaseInsensitive bool) bool {
 | 
			
		||||
	var allRunes = []rune(s)
 | 
			
		||||
	if len(allRunes) == 0 || len(words) == 0 {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, word := range words {
 | 
			
		||||
		if ContainsWordRunes(allRunes, []rune(word), isCaseInsensitive) {
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ContainsAllWords 检查字符串是否包含所有单词
 | 
			
		||||
func ContainsAllWords(s string, words []string, isCaseInsensitive bool) bool {
 | 
			
		||||
	var allRunes = []rune(s)
 | 
			
		||||
	if len(allRunes) == 0 || len(words) == 0 {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, word := range words {
 | 
			
		||||
		if !ContainsWordRunes(allRunes, []rune(word), isCaseInsensitive) {
 | 
			
		||||
			return false
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ContainsWordRunes 检查字符列表是否包含某个单词子字符列表
 | 
			
		||||
func ContainsWordRunes(allRunes []rune, subRunes []rune, isCaseInsensitive bool) bool {
 | 
			
		||||
	var l = len(subRunes)
 | 
			
		||||
	if l == 0 {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var al = len(allRunes)
 | 
			
		||||
 | 
			
		||||
	for index, r := range allRunes {
 | 
			
		||||
		if EqualRune(r, subRunes[0], isCaseInsensitive) && (index == 0 || !isChar(allRunes[index-1]) /**boundary check **/) {
 | 
			
		||||
			var found = true
 | 
			
		||||
			if l > 1 {
 | 
			
		||||
				for i := 1; i < l; i++ {
 | 
			
		||||
					var subIndex = index + i
 | 
			
		||||
					if subIndex > al-1 || !EqualRune(allRunes[subIndex], subRunes[i], isCaseInsensitive) {
 | 
			
		||||
						found = false
 | 
			
		||||
						break
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// check after charset
 | 
			
		||||
			if found && (al <= index+l || !isChar(allRunes[index+l]) /**boundary check **/) {
 | 
			
		||||
				return true
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ContainsSubRunes 检查字符列表是否包含某个子子字符列表
 | 
			
		||||
// 与 ContainsWordRunes 不同,这里不需要检查边界符号
 | 
			
		||||
func ContainsSubRunes(allRunes []rune, subRunes []rune, isCaseInsensitive bool) bool {
 | 
			
		||||
	var l = len(subRunes)
 | 
			
		||||
	if l == 0 {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var al = len(allRunes)
 | 
			
		||||
 | 
			
		||||
	for index, r := range allRunes {
 | 
			
		||||
		if EqualRune(r, subRunes[0], isCaseInsensitive) {
 | 
			
		||||
			var found = true
 | 
			
		||||
			if l > 1 {
 | 
			
		||||
				for i := 1; i < l; i++ {
 | 
			
		||||
					var subIndex = index + i
 | 
			
		||||
					if subIndex > al-1 || !EqualRune(allRunes[subIndex], subRunes[i], isCaseInsensitive) {
 | 
			
		||||
						found = false
 | 
			
		||||
						break
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// check after charset
 | 
			
		||||
			if found {
 | 
			
		||||
				return true
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// EqualRune 判断两个rune是否相同
 | 
			
		||||
func EqualRune(r1 rune, r2 rune, isCaseInsensitive bool) bool {
 | 
			
		||||
	const d = 'a' - 'A'
 | 
			
		||||
	return r1 == r2 ||
 | 
			
		||||
		(isCaseInsensitive && r1 >= 'a' && r1 <= 'z' && r1-r2 == d) ||
 | 
			
		||||
		(isCaseInsensitive && r1 >= 'A' && r1 <= 'Z' && r1-r2 == -d)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func isChar(r rune) bool {
 | 
			
		||||
	return r >= 'a' && r <= 'z' || r >= 'A' && r <= 'Z' || r >= '0' && r <= '9'
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										97
									
								
								internal/utils/runes/runes_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								internal/utils/runes/runes_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,97 @@
 | 
			
		||||
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
 | 
			
		||||
 | 
			
		||||
package runes_test
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/TeaOSLab/EdgeNode/internal/utils/runes"
 | 
			
		||||
	"github.com/iwind/TeaGo/assert"
 | 
			
		||||
	"runtime"
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestContainsAllWords(t *testing.T) {
 | 
			
		||||
	var a = assert.NewAssertion(t)
 | 
			
		||||
	a.IsTrue(runes.ContainsAllWords("How are you?", []string{"are", "you"}, false))
 | 
			
		||||
	a.IsFalse(runes.ContainsAllWords("How are you?", []string{"how", "are", "you"}, false))
 | 
			
		||||
	a.IsTrue(runes.ContainsAllWords("How are you?", []string{"how", "are", "you"}, true))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestContainsAnyWord(t *testing.T) {
 | 
			
		||||
	var a = assert.NewAssertion(t)
 | 
			
		||||
	a.IsTrue(runes.ContainsAnyWord("How are you?", []string{"are", "you"}, false))
 | 
			
		||||
	a.IsTrue(runes.ContainsAnyWord("How are you?", []string{"are", "you", "ok"}, false))
 | 
			
		||||
	a.IsFalse(runes.ContainsAnyWord("How are you?", []string{"how", "ok"}, false))
 | 
			
		||||
	a.IsTrue(runes.ContainsAnyWord("How are you?", []string{"how"}, true))
 | 
			
		||||
	a.IsTrue(runes.ContainsAnyWord("How are you?", []string{"how", "ok"}, true))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestContainsWordRunes(t *testing.T) {
 | 
			
		||||
	var a = assert.NewAssertion(t)
 | 
			
		||||
	a.IsFalse(runes.ContainsWordRunes([]rune(""), []rune("How"), true))
 | 
			
		||||
	a.IsFalse(runes.ContainsWordRunes([]rune("How are you?"), []rune(""), true))
 | 
			
		||||
	a.IsTrue(runes.ContainsWordRunes([]rune("How are you?"), []rune("How"), true))
 | 
			
		||||
	a.IsFalse(runes.ContainsWordRunes([]rune("How are you?"), []rune("how"), false))
 | 
			
		||||
	a.IsTrue(runes.ContainsWordRunes([]rune("How are you?"), []rune("you"), false))
 | 
			
		||||
	a.IsTrue(runes.ContainsWordRunes([]rune("How are you?"), []rune("are"), false))
 | 
			
		||||
	a.IsFalse(runes.ContainsWordRunes([]rune("How are you?"), []rune("re"), false))
 | 
			
		||||
	a.IsTrue(runes.ContainsWordRunes([]rune("How are you w?"), []rune("w"), false))
 | 
			
		||||
	a.IsTrue(runes.ContainsWordRunes([]rune("w How are you?"), []rune("w"), false))
 | 
			
		||||
	a.IsTrue(runes.ContainsWordRunes([]rune("How are w you?"), []rune("w"), false))
 | 
			
		||||
	a.IsTrue(runes.ContainsWordRunes([]rune("How are how you?"), []rune("how"), false))
 | 
			
		||||
	a.IsTrue(runes.ContainsWordRunes([]rune("How are you?"), []rune("how"), true))
 | 
			
		||||
	a.IsTrue(runes.ContainsWordRunes([]rune("How are you?"), []rune("ARE"), true))
 | 
			
		||||
	a.IsTrue(runes.ContainsWordRunes([]rune("How are you"), []rune("you"), false))
 | 
			
		||||
	a.IsTrue(runes.ContainsWordRunes([]rune("How are you"), []rune("YOU"), true))
 | 
			
		||||
	a.IsTrue(runes.ContainsWordRunes([]rune("How are you?"), []rune("YOU"), true))
 | 
			
		||||
	a.IsFalse(runes.ContainsWordRunes([]rune("How are you1?"), []rune("YOU"), true))
 | 
			
		||||
	a.IsFalse(runes.ContainsWordRunes([]rune("How are you1?"), []rune("YOU YOU YOU YOU YOU YOU YOU"), true))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestContainsSubRunes(t *testing.T) {
 | 
			
		||||
	var a = assert.NewAssertion(t)
 | 
			
		||||
	a.IsFalse(runes.ContainsSubRunes([]rune(""), []rune("How"), true))
 | 
			
		||||
	a.IsFalse(runes.ContainsSubRunes([]rune("How are you?"), []rune(""), true))
 | 
			
		||||
	a.IsTrue(runes.ContainsSubRunes([]rune("How are you1?"), []rune("YOU"), true))
 | 
			
		||||
	a.IsTrue(runes.ContainsSubRunes([]rune("How are you1?"), []rune("ow"), false))
 | 
			
		||||
	a.IsTrue(runes.ContainsSubRunes([]rune("How are you1?"), []rune("H"), false))
 | 
			
		||||
	a.IsTrue(runes.ContainsSubRunes([]rune("How are you1?"), []rune("How"), false))
 | 
			
		||||
	a.IsTrue(runes.ContainsSubRunes([]rune("How are you doing"), []rune("oi"), false))
 | 
			
		||||
	a.IsTrue(runes.ContainsSubRunes([]rune("How are you doing"), []rune("g"), false))
 | 
			
		||||
	a.IsTrue(runes.ContainsSubRunes([]rune("How are you doing"), []rune("ing"), false))
 | 
			
		||||
	a.IsFalse(runes.ContainsSubRunes([]rune("How are you doing"), []rune("int"), false))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestEqualRune(t *testing.T) {
 | 
			
		||||
	var a = assert.NewAssertion(t)
 | 
			
		||||
	a.IsTrue(runes.EqualRune('a', 'a', false))
 | 
			
		||||
	a.IsTrue(runes.EqualRune('a', 'a', true))
 | 
			
		||||
	a.IsFalse(runes.EqualRune('a', 'A', false))
 | 
			
		||||
	a.IsTrue(runes.EqualRune('a', 'A', true))
 | 
			
		||||
	a.IsFalse(runes.EqualRune('c', 'C', false))
 | 
			
		||||
	a.IsTrue(runes.EqualRune('c', 'C', true))
 | 
			
		||||
	a.IsTrue(runes.EqualRune('C', 'C', true))
 | 
			
		||||
	a.IsTrue(runes.EqualRune('C', 'c', true))
 | 
			
		||||
	a.IsTrue(runes.EqualRune('Z', 'z', true))
 | 
			
		||||
	a.IsTrue(runes.EqualRune('z', 'Z', true))
 | 
			
		||||
	a.IsFalse(runes.EqualRune('z', 'z'+('a'-'A'), true))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func BenchmarkContainsWordRunes(b *testing.B) {
 | 
			
		||||
	runtime.GOMAXPROCS(4)
 | 
			
		||||
 | 
			
		||||
	b.RunParallel(func(pb *testing.PB) {
 | 
			
		||||
		for pb.Next() {
 | 
			
		||||
			_ = runes.ContainsWordRunes([]rune("How are you"), []rune("YOU"), true)
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func BenchmarkContainsSubRunes(b *testing.B) {
 | 
			
		||||
	runtime.GOMAXPROCS(4)
 | 
			
		||||
 | 
			
		||||
	b.RunParallel(func(pb *testing.PB) {
 | 
			
		||||
		for pb.Next() {
 | 
			
		||||
			_ = runes.ContainsSubRunes([]rune("How are you"), []rune("YOU"), true)
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
@@ -9,6 +9,7 @@ import (
 | 
			
		||||
	"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/filterconfigs"
 | 
			
		||||
	"github.com/TeaOSLab/EdgeNode/internal/re"
 | 
			
		||||
	"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
 | 
			
		||||
	"github.com/TeaOSLab/EdgeNode/internal/utils/runes"
 | 
			
		||||
	"github.com/TeaOSLab/EdgeNode/internal/waf/checkpoints"
 | 
			
		||||
	"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
 | 
			
		||||
	"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
 | 
			
		||||
@@ -77,7 +78,7 @@ func (this *Rule) Init() error {
 | 
			
		||||
		this.floatValue = types.Float64(this.Value)
 | 
			
		||||
	case RuleOperatorNeq:
 | 
			
		||||
		this.floatValue = types.Float64(this.Value)
 | 
			
		||||
	case RuleOperatorContainsAny, RuleOperatorContainsAll:
 | 
			
		||||
	case RuleOperatorContainsAny, RuleOperatorContainsAll, RuleOperatorContainsAnyWord, RuleOperatorContainsAllWords, RuleOperatorNotContainsAnyWord:
 | 
			
		||||
		this.stringValues = []string{}
 | 
			
		||||
		if len(this.Value) > 0 {
 | 
			
		||||
			var lines = strings.Split(this.Value, "\n")
 | 
			
		||||
@@ -546,6 +547,12 @@ func (this *Rule) Test(value any) bool {
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
		return false
 | 
			
		||||
	case RuleOperatorContainsAnyWord:
 | 
			
		||||
		return runes.ContainsAnyWord(this.stringifyValue(value), this.stringValues, this.IsCaseInsensitive)
 | 
			
		||||
	case RuleOperatorContainsAllWords:
 | 
			
		||||
		return runes.ContainsAllWords(this.stringifyValue(value), this.stringValues, this.IsCaseInsensitive)
 | 
			
		||||
	case RuleOperatorNotContainsAnyWord:
 | 
			
		||||
		return !runes.ContainsAnyWord(this.stringifyValue(value), this.stringValues, this.IsCaseInsensitive)
 | 
			
		||||
	case RuleOperatorContainsBinary:
 | 
			
		||||
		data, _ := base64.StdEncoding.DecodeString(this.stringifyValue(this.Value))
 | 
			
		||||
		if this.IsCaseInsensitive {
 | 
			
		||||
 
 | 
			
		||||
@@ -22,6 +22,9 @@ const (
 | 
			
		||||
	RuleOperatorSuffix             RuleOperator = "suffix"
 | 
			
		||||
	RuleOperatorContainsAny        RuleOperator = "contains any"
 | 
			
		||||
	RuleOperatorContainsAll        RuleOperator = "contains all"
 | 
			
		||||
	RuleOperatorContainsAnyWord    RuleOperator = "contains any word"
 | 
			
		||||
	RuleOperatorContainsAllWords   RuleOperator = "contains all word"
 | 
			
		||||
	RuleOperatorNotContainsAnyWord RuleOperator = "not contains any word"
 | 
			
		||||
	RuleOperatorInIPList           RuleOperator = "in ip list"
 | 
			
		||||
	RuleOperatorHasKey             RuleOperator = "has key" // has key in slice or map
 | 
			
		||||
	RuleOperatorVersionGt          RuleOperator = "version gt"
 | 
			
		||||
@@ -32,6 +35,7 @@ const (
 | 
			
		||||
	RuleOperatorNotContainsBinary RuleOperator = "not contains binary" // not contains binary
 | 
			
		||||
 | 
			
		||||
	// ip
 | 
			
		||||
 | 
			
		||||
	RuleOperatorEqIP       RuleOperator = "eq ip"
 | 
			
		||||
	RuleOperatorGtIP       RuleOperator = "gt ip"
 | 
			
		||||
	RuleOperatorGteIP      RuleOperator = "gte ip"
 | 
			
		||||
@@ -42,10 +46,6 @@ const (
 | 
			
		||||
	RuleOperatorIPMod10    RuleOperator = "ip mod 10"
 | 
			
		||||
	RuleOperatorIPMod100   RuleOperator = "ip mod 100"
 | 
			
		||||
	RuleOperatorIPMod      RuleOperator = "ip mod"
 | 
			
		||||
 | 
			
		||||
	RuleCaseInsensitiveNone = "none"
 | 
			
		||||
	RuleCaseInsensitiveYes  = "yes"
 | 
			
		||||
	RuleCaseInsensitiveNo   = "no"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type RuleOperatorDefinition struct {
 | 
			
		||||
@@ -54,174 +54,3 @@ type RuleOperatorDefinition struct {
 | 
			
		||||
	Description     string
 | 
			
		||||
	CaseInsensitive RuleCaseInsensitive // default caseInsensitive setting
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var AllRuleOperators = []*RuleOperatorDefinition{
 | 
			
		||||
	{
 | 
			
		||||
		Name:            "数值大于",
 | 
			
		||||
		Code:            RuleOperatorGt,
 | 
			
		||||
		Description:     "使用数值对比大于",
 | 
			
		||||
		CaseInsensitive: RuleCaseInsensitiveNone,
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		Name:            "数值大于等于",
 | 
			
		||||
		Code:            RuleOperatorGte,
 | 
			
		||||
		Description:     "使用数值对比大于等于",
 | 
			
		||||
		CaseInsensitive: RuleCaseInsensitiveNone,
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		Name:            "数值小于",
 | 
			
		||||
		Code:            RuleOperatorLt,
 | 
			
		||||
		Description:     "使用数值对比小于",
 | 
			
		||||
		CaseInsensitive: RuleCaseInsensitiveNone,
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		Name:            "数值小于等于",
 | 
			
		||||
		Code:            RuleOperatorLte,
 | 
			
		||||
		Description:     "使用数值对比小于等于",
 | 
			
		||||
		CaseInsensitive: RuleCaseInsensitiveNone,
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		Name:            "数值等于",
 | 
			
		||||
		Code:            RuleOperatorEq,
 | 
			
		||||
		Description:     "使用数值对比等于",
 | 
			
		||||
		CaseInsensitive: RuleCaseInsensitiveNone,
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		Name:            "数值不等于",
 | 
			
		||||
		Code:            RuleOperatorNeq,
 | 
			
		||||
		Description:     "使用数值对比不等于",
 | 
			
		||||
		CaseInsensitive: RuleCaseInsensitiveNone,
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		Name:            "字符串等于",
 | 
			
		||||
		Code:            RuleOperatorEqString,
 | 
			
		||||
		Description:     "使用字符串对比等于",
 | 
			
		||||
		CaseInsensitive: RuleCaseInsensitiveNo,
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		Name:            "字符串不等于",
 | 
			
		||||
		Code:            RuleOperatorNeqString,
 | 
			
		||||
		Description:     "使用字符串对比不等于",
 | 
			
		||||
		CaseInsensitive: RuleCaseInsensitiveNo,
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		Name:            "正则匹配",
 | 
			
		||||
		Code:            RuleOperatorMatch,
 | 
			
		||||
		Description:     "使用正则表达式匹配,在头部使用(?i)表示不区分大小写,<a href=\"http://teaos.cn/doc/regexp/Regexp.md\" target=\"_blank\">正则表达式语法 »</a>",
 | 
			
		||||
		CaseInsensitive: RuleCaseInsensitiveYes,
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		Name:            "正则不匹配",
 | 
			
		||||
		Code:            RuleOperatorNotMatch,
 | 
			
		||||
		Description:     "使用正则表达式不匹配,在头部使用(?i)表示不区分大小写,<a href=\"http://teaos.cn/doc/regexp/Regexp.md\" target=\"_blank\">正则表达式语法 »</a>",
 | 
			
		||||
		CaseInsensitive: RuleCaseInsensitiveYes,
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		Name:            "包含字符串",
 | 
			
		||||
		Code:            RuleOperatorContains,
 | 
			
		||||
		Description:     "包含某个字符串",
 | 
			
		||||
		CaseInsensitive: RuleCaseInsensitiveNo,
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		Name:            "不包含字符串",
 | 
			
		||||
		Code:            RuleOperatorNotContains,
 | 
			
		||||
		Description:     "不包含某个字符串",
 | 
			
		||||
		CaseInsensitive: RuleCaseInsensitiveNo,
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		Name:            "包含前缀",
 | 
			
		||||
		Code:            RuleOperatorPrefix,
 | 
			
		||||
		Description:     "包含某个前缀",
 | 
			
		||||
		CaseInsensitive: RuleCaseInsensitiveNo,
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		Name:            "包含后缀",
 | 
			
		||||
		Code:            RuleOperatorSuffix,
 | 
			
		||||
		Description:     "包含某个后缀",
 | 
			
		||||
		CaseInsensitive: RuleCaseInsensitiveNo,
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		Name:            "包含索引",
 | 
			
		||||
		Code:            RuleOperatorHasKey,
 | 
			
		||||
		Description:     "对于一组数据拥有某个键值或者索引",
 | 
			
		||||
		CaseInsensitive: RuleCaseInsensitiveNo,
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		Name:            "版本号大于",
 | 
			
		||||
		Code:            RuleOperatorVersionGt,
 | 
			
		||||
		Description:     "对比版本号大于",
 | 
			
		||||
		CaseInsensitive: RuleCaseInsensitiveNo,
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		Name:            "版本号小于",
 | 
			
		||||
		Code:            RuleOperatorVersionLt,
 | 
			
		||||
		Description:     "对比版本号小于",
 | 
			
		||||
		CaseInsensitive: RuleCaseInsensitiveNo,
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		Name:            "版本号范围",
 | 
			
		||||
		Code:            RuleOperatorVersionRange,
 | 
			
		||||
		Description:     "判断版本号在某个范围内,格式为version1,version2",
 | 
			
		||||
		CaseInsensitive: RuleCaseInsensitiveNo,
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		Name:            "IP等于",
 | 
			
		||||
		Code:            RuleOperatorEqIP,
 | 
			
		||||
		Description:     "将参数转换为IP进行对比",
 | 
			
		||||
		CaseInsensitive: RuleCaseInsensitiveNo,
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		Name:            "IP大于",
 | 
			
		||||
		Code:            RuleOperatorGtIP,
 | 
			
		||||
		Description:     "将参数转换为IP进行对比",
 | 
			
		||||
		CaseInsensitive: RuleCaseInsensitiveNo,
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		Name:            "IP大于等于",
 | 
			
		||||
		Code:            RuleOperatorGteIP,
 | 
			
		||||
		Description:     "将参数转换为IP进行对比",
 | 
			
		||||
		CaseInsensitive: RuleCaseInsensitiveNo,
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		Name:            "IP小于",
 | 
			
		||||
		Code:            RuleOperatorLtIP,
 | 
			
		||||
		Description:     "将参数转换为IP进行对比",
 | 
			
		||||
		CaseInsensitive: RuleCaseInsensitiveNo,
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		Name:            "IP小于等于",
 | 
			
		||||
		Code:            RuleOperatorLteIP,
 | 
			
		||||
		Description:     "将参数转换为IP进行对比",
 | 
			
		||||
		CaseInsensitive: RuleCaseInsensitiveNo,
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		Name:            "IP范围",
 | 
			
		||||
		Code:            RuleOperatorIPRange,
 | 
			
		||||
		Description:     "IP在某个范围之内,范围格式可以是英文逗号分隔的ip1,ip2,或者CIDR格式的ip/bits",
 | 
			
		||||
		CaseInsensitive: RuleCaseInsensitiveNo,
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		Name:            "不在IP范围",
 | 
			
		||||
		Code:            RuleOperatorNotIPRange,
 | 
			
		||||
		Description:     "IP不在某个范围之内,范围格式可以是英文逗号分隔的ip1,ip2,或者CIDR格式的ip/bits",
 | 
			
		||||
		CaseInsensitive: RuleCaseInsensitiveNo,
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		Name:            "IP取模10",
 | 
			
		||||
		Code:            RuleOperatorIPMod10,
 | 
			
		||||
		Description:     "对IP参数值取模,除数为10,对比值为余数",
 | 
			
		||||
		CaseInsensitive: RuleCaseInsensitiveNo,
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		Name:            "IP取模100",
 | 
			
		||||
		Code:            RuleOperatorIPMod100,
 | 
			
		||||
		Description:     "对IP参数值取模,除数为100,对比值为余数",
 | 
			
		||||
		CaseInsensitive: RuleCaseInsensitiveNo,
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		Name:            "IP取模",
 | 
			
		||||
		Code:            RuleOperatorIPMod,
 | 
			
		||||
		Description:     "对IP参数值取模,对比值格式为:除数,余数,比如10,1",
 | 
			
		||||
		CaseInsensitive: RuleCaseInsensitiveNo,
 | 
			
		||||
	},
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -16,29 +16,38 @@ import (
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func Test_Template(t *testing.T) {
 | 
			
		||||
	a := assert.NewAssertion(t)
 | 
			
		||||
	var a = assert.NewAssertion(t)
 | 
			
		||||
 | 
			
		||||
	template := Template()
 | 
			
		||||
	err := template.Init()
 | 
			
		||||
	var waf = Template()
 | 
			
		||||
 | 
			
		||||
	for _, group := range waf.Inbound {
 | 
			
		||||
		group.IsOn = true
 | 
			
		||||
 | 
			
		||||
		for _, set := range group.RuleSets {
 | 
			
		||||
			set.IsOn = true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err := waf.Init()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	testTemplate1001(a, t, template)
 | 
			
		||||
	testTemplate1002(a, t, template)
 | 
			
		||||
	testTemplate1003(a, t, template)
 | 
			
		||||
	testTemplate2001(a, t, template)
 | 
			
		||||
	testTemplate3001(a, t, template)
 | 
			
		||||
	testTemplate4001(a, t, template)
 | 
			
		||||
	testTemplate5001(a, t, template)
 | 
			
		||||
	testTemplate6001(a, t, template)
 | 
			
		||||
	testTemplate7001(a, t, template)
 | 
			
		||||
	testTemplate20001(a, t, template)
 | 
			
		||||
	testTemplate1001(a, t, waf)
 | 
			
		||||
	testTemplate1002(a, t, waf)
 | 
			
		||||
	testTemplate1003(a, t, waf)
 | 
			
		||||
	testTemplate2001(a, t, waf)
 | 
			
		||||
	testTemplate3001(a, t, waf)
 | 
			
		||||
	testTemplate4001(a, t, waf)
 | 
			
		||||
	testTemplate5001(a, t, waf)
 | 
			
		||||
	testTemplate6001(a, t, waf)
 | 
			
		||||
	testTemplate7001(a, t, waf)
 | 
			
		||||
	testTemplate20001(a, t, waf)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Test_Template2(t *testing.T) {
 | 
			
		||||
	reader := bytes.NewReader([]byte(strings.Repeat("HELLO", 1024)))
 | 
			
		||||
	req, err := http.NewRequest(http.MethodGet, "https://example.com/index.php?id=123", reader)
 | 
			
		||||
	req, err := http.NewRequest(http.MethodPost, "https://example.com/index.php?id=123", reader)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
@@ -65,15 +74,25 @@ func Test_Template2(t *testing.T) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func BenchmarkTemplate(b *testing.B) {
 | 
			
		||||
	waf := Template()
 | 
			
		||||
	var waf = Template()
 | 
			
		||||
 | 
			
		||||
	for _, group := range waf.Inbound {
 | 
			
		||||
		group.IsOn = true
 | 
			
		||||
 | 
			
		||||
		for _, set := range group.RuleSets {
 | 
			
		||||
			set.IsOn = true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err := waf.Init()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		b.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	b.ResetTimer()
 | 
			
		||||
 | 
			
		||||
	for i := 0; i < b.N; i++ {
 | 
			
		||||
		reader := bytes.NewReader([]byte(strings.Repeat("Hello", 1024)))
 | 
			
		||||
		req, err := http.NewRequest(http.MethodGet, "http://example.com/index.php?id=123", reader)
 | 
			
		||||
		req, err := http.NewRequest(http.MethodGet, "https://example.com/index.php?id=123", nil)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			b.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
@@ -312,6 +331,68 @@ func testTemplate7001(a *assert.Assertion, t *testing.T, template *WAF) {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestTemplateSQLInjection(t *testing.T) {
 | 
			
		||||
	var template = Template()
 | 
			
		||||
	errs := template.Init()
 | 
			
		||||
	if len(errs) > 0 {
 | 
			
		||||
		t.Fatal(errs)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	var group = template.FindRuleGroupWithCode("sqlInjection")
 | 
			
		||||
	if group == nil {
 | 
			
		||||
		t.Fatal("group not found")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	//
 | 
			
		||||
	//for _, set := range group.RuleSets {
 | 
			
		||||
	//	for _, rule := range set.Rules {
 | 
			
		||||
	//		t.Logf("%#v", rule.singleCheckpoint)
 | 
			
		||||
	//	}
 | 
			
		||||
	//}
 | 
			
		||||
 | 
			
		||||
	req, err := http.NewRequest(http.MethodPost, "https://example.com/?id=1234", nil)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	_, _, result, err := group.MatchRequest(requests.NewTestRequest(req))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if result != nil {
 | 
			
		||||
		t.Log(result)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func BenchmarkTemplateSQLInjection(b *testing.B) {
 | 
			
		||||
	var template = Template()
 | 
			
		||||
	errs := template.Init()
 | 
			
		||||
	if len(errs) > 0 {
 | 
			
		||||
		b.Fatal(errs)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	var group = template.FindRuleGroupWithCode("sqlInjection")
 | 
			
		||||
	if group == nil {
 | 
			
		||||
		b.Fatal("group not found")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	b.ResetTimer()
 | 
			
		||||
 | 
			
		||||
	b.RunParallel(func(pb *testing.PB) {
 | 
			
		||||
		for pb.Next() {
 | 
			
		||||
			req, err := http.NewRequest(http.MethodPost, "https://example.com/?id=1234", nil)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				b.Fatal(err)
 | 
			
		||||
			}
 | 
			
		||||
			_, _, result, err := group.MatchRequest(requests.NewTestRequest(req))
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				b.Fatal(err)
 | 
			
		||||
			}
 | 
			
		||||
			_ = result
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func testTemplate20001(a *assert.Assertion, t *testing.T, template *WAF) {
 | 
			
		||||
	// enable bot rule set
 | 
			
		||||
	for _, g := range template.Inbound {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user