mirror of
				https://github.com/TeaOSLab/EdgeNode.git
				synced 2025-11-04 16:00:25 +08:00 
			
		
		
		
	WAF操作符增加包含任一单词、包含所有单词、不包含任一单词
This commit is contained in:
		@@ -4,7 +4,7 @@ package re
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
type RuneMap map[rune]*RuneTree
 | 
					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)
 | 
						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/EdgeCommon/pkg/serverconfigs/filterconfigs"
 | 
				
			||||||
	"github.com/TeaOSLab/EdgeNode/internal/re"
 | 
						"github.com/TeaOSLab/EdgeNode/internal/re"
 | 
				
			||||||
	"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
 | 
						"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/checkpoints"
 | 
				
			||||||
	"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
 | 
						"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
 | 
				
			||||||
	"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
 | 
						"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
 | 
				
			||||||
@@ -77,7 +78,7 @@ func (this *Rule) Init() error {
 | 
				
			|||||||
		this.floatValue = types.Float64(this.Value)
 | 
							this.floatValue = types.Float64(this.Value)
 | 
				
			||||||
	case RuleOperatorNeq:
 | 
						case RuleOperatorNeq:
 | 
				
			||||||
		this.floatValue = types.Float64(this.Value)
 | 
							this.floatValue = types.Float64(this.Value)
 | 
				
			||||||
	case RuleOperatorContainsAny, RuleOperatorContainsAll:
 | 
						case RuleOperatorContainsAny, RuleOperatorContainsAll, RuleOperatorContainsAnyWord, RuleOperatorContainsAllWords, RuleOperatorNotContainsAnyWord:
 | 
				
			||||||
		this.stringValues = []string{}
 | 
							this.stringValues = []string{}
 | 
				
			||||||
		if len(this.Value) > 0 {
 | 
							if len(this.Value) > 0 {
 | 
				
			||||||
			var lines = strings.Split(this.Value, "\n")
 | 
								var lines = strings.Split(this.Value, "\n")
 | 
				
			||||||
@@ -546,6 +547,12 @@ func (this *Rule) Test(value any) bool {
 | 
				
			|||||||
			return true
 | 
								return true
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		return false
 | 
							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:
 | 
						case RuleOperatorContainsBinary:
 | 
				
			||||||
		data, _ := base64.StdEncoding.DecodeString(this.stringifyValue(this.Value))
 | 
							data, _ := base64.StdEncoding.DecodeString(this.stringifyValue(this.Value))
 | 
				
			||||||
		if this.IsCaseInsensitive {
 | 
							if this.IsCaseInsensitive {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -22,6 +22,9 @@ const (
 | 
				
			|||||||
	RuleOperatorSuffix             RuleOperator = "suffix"
 | 
						RuleOperatorSuffix             RuleOperator = "suffix"
 | 
				
			||||||
	RuleOperatorContainsAny        RuleOperator = "contains any"
 | 
						RuleOperatorContainsAny        RuleOperator = "contains any"
 | 
				
			||||||
	RuleOperatorContainsAll        RuleOperator = "contains all"
 | 
						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"
 | 
						RuleOperatorInIPList           RuleOperator = "in ip list"
 | 
				
			||||||
	RuleOperatorHasKey             RuleOperator = "has key" // has key in slice or map
 | 
						RuleOperatorHasKey             RuleOperator = "has key" // has key in slice or map
 | 
				
			||||||
	RuleOperatorVersionGt          RuleOperator = "version gt"
 | 
						RuleOperatorVersionGt          RuleOperator = "version gt"
 | 
				
			||||||
@@ -32,6 +35,7 @@ const (
 | 
				
			|||||||
	RuleOperatorNotContainsBinary RuleOperator = "not contains binary" // not contains binary
 | 
						RuleOperatorNotContainsBinary RuleOperator = "not contains binary" // not contains binary
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// ip
 | 
						// ip
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	RuleOperatorEqIP       RuleOperator = "eq ip"
 | 
						RuleOperatorEqIP       RuleOperator = "eq ip"
 | 
				
			||||||
	RuleOperatorGtIP       RuleOperator = "gt ip"
 | 
						RuleOperatorGtIP       RuleOperator = "gt ip"
 | 
				
			||||||
	RuleOperatorGteIP      RuleOperator = "gte ip"
 | 
						RuleOperatorGteIP      RuleOperator = "gte ip"
 | 
				
			||||||
@@ -42,10 +46,6 @@ const (
 | 
				
			|||||||
	RuleOperatorIPMod10    RuleOperator = "ip mod 10"
 | 
						RuleOperatorIPMod10    RuleOperator = "ip mod 10"
 | 
				
			||||||
	RuleOperatorIPMod100   RuleOperator = "ip mod 100"
 | 
						RuleOperatorIPMod100   RuleOperator = "ip mod 100"
 | 
				
			||||||
	RuleOperatorIPMod      RuleOperator = "ip mod"
 | 
						RuleOperatorIPMod      RuleOperator = "ip mod"
 | 
				
			||||||
 | 
					 | 
				
			||||||
	RuleCaseInsensitiveNone = "none"
 | 
					 | 
				
			||||||
	RuleCaseInsensitiveYes  = "yes"
 | 
					 | 
				
			||||||
	RuleCaseInsensitiveNo   = "no"
 | 
					 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type RuleOperatorDefinition struct {
 | 
					type RuleOperatorDefinition struct {
 | 
				
			||||||
@@ -54,174 +54,3 @@ type RuleOperatorDefinition struct {
 | 
				
			|||||||
	Description     string
 | 
						Description     string
 | 
				
			||||||
	CaseInsensitive RuleCaseInsensitive // default caseInsensitive setting
 | 
						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) {
 | 
					func Test_Template(t *testing.T) {
 | 
				
			||||||
	a := assert.NewAssertion(t)
 | 
						var a = assert.NewAssertion(t)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	template := Template()
 | 
						var waf = Template()
 | 
				
			||||||
	err := template.Init()
 | 
					
 | 
				
			||||||
 | 
						for _, group := range waf.Inbound {
 | 
				
			||||||
 | 
							group.IsOn = true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for _, set := range group.RuleSets {
 | 
				
			||||||
 | 
								set.IsOn = true
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err := waf.Init()
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		t.Fatal(err)
 | 
							t.Fatal(err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	testTemplate1001(a, t, template)
 | 
						testTemplate1001(a, t, waf)
 | 
				
			||||||
	testTemplate1002(a, t, template)
 | 
						testTemplate1002(a, t, waf)
 | 
				
			||||||
	testTemplate1003(a, t, template)
 | 
						testTemplate1003(a, t, waf)
 | 
				
			||||||
	testTemplate2001(a, t, template)
 | 
						testTemplate2001(a, t, waf)
 | 
				
			||||||
	testTemplate3001(a, t, template)
 | 
						testTemplate3001(a, t, waf)
 | 
				
			||||||
	testTemplate4001(a, t, template)
 | 
						testTemplate4001(a, t, waf)
 | 
				
			||||||
	testTemplate5001(a, t, template)
 | 
						testTemplate5001(a, t, waf)
 | 
				
			||||||
	testTemplate6001(a, t, template)
 | 
						testTemplate6001(a, t, waf)
 | 
				
			||||||
	testTemplate7001(a, t, template)
 | 
						testTemplate7001(a, t, waf)
 | 
				
			||||||
	testTemplate20001(a, t, template)
 | 
						testTemplate20001(a, t, waf)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func Test_Template2(t *testing.T) {
 | 
					func Test_Template2(t *testing.T) {
 | 
				
			||||||
	reader := bytes.NewReader([]byte(strings.Repeat("HELLO", 1024)))
 | 
						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 {
 | 
						if err != nil {
 | 
				
			||||||
		t.Fatal(err)
 | 
							t.Fatal(err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -65,15 +74,25 @@ func Test_Template2(t *testing.T) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func BenchmarkTemplate(b *testing.B) {
 | 
					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()
 | 
						err := waf.Init()
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		b.Fatal(err)
 | 
							b.Fatal(err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						b.ResetTimer()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for i := 0; i < b.N; i++ {
 | 
						for i := 0; i < b.N; i++ {
 | 
				
			||||||
		reader := bytes.NewReader([]byte(strings.Repeat("Hello", 1024)))
 | 
							req, err := http.NewRequest(http.MethodGet, "https://example.com/index.php?id=123", nil)
 | 
				
			||||||
		req, err := http.NewRequest(http.MethodGet, "http://example.com/index.php?id=123", reader)
 | 
					 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			b.Fatal(err)
 | 
								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) {
 | 
					func testTemplate20001(a *assert.Assertion, t *testing.T, template *WAF) {
 | 
				
			||||||
	// enable bot rule set
 | 
						// enable bot rule set
 | 
				
			||||||
	for _, g := range template.Inbound {
 | 
						for _, g := range template.Inbound {
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user