diff --git a/internal/waf/injectionutils/libinjection_xss.c b/internal/waf/injectionutils/libinjection_xss.c new file mode 100644 index 0000000..2189045 --- /dev/null +++ b/internal/waf/injectionutils/libinjection_xss.c @@ -0,0 +1,4 @@ +#define LIBINJECTION_VERSION "3.9.1" + +#include "libinjection/src/libinjection_xss.c" +#include "libinjection/src/libinjection_html5.c" \ No newline at end of file diff --git a/internal/waf/injectionutils/utils.go b/internal/waf/injectionutils/utils_sqli.go similarity index 89% rename from internal/waf/injectionutils/utils.go rename to internal/waf/injectionutils/utils_sqli.go index 7b1f3e2..49e87d0 100644 --- a/internal/waf/injectionutils/utils.go +++ b/internal/waf/injectionutils/utils_sqli.go @@ -6,7 +6,6 @@ package injectionutils #cgo CFLAGS: -I./libinjection/src #include -#include #include */ import "C" @@ -27,7 +26,7 @@ func DetectSQLInjection(input string) bool { } // 兼容 /PATH?URI - if input[0] == '/' || strings.HasPrefix(input, "http://") || strings.HasPrefix(input, "https://") { + if (input[0] == '/' || strings.HasPrefix(input, "http://") || strings.HasPrefix(input, "https://")) && len(input) < 4096 { var argsIndex = strings.Index(input, "?") if argsIndex > 0 { var args = input[argsIndex+1:] diff --git a/internal/waf/injectionutils/utils_test.go b/internal/waf/injectionutils/utils_sqli_test.go similarity index 100% rename from internal/waf/injectionutils/utils_test.go rename to internal/waf/injectionutils/utils_sqli_test.go diff --git a/internal/waf/injectionutils/utils_xss.go b/internal/waf/injectionutils/utils_xss.go new file mode 100644 index 0000000..a4778c1 --- /dev/null +++ b/internal/waf/injectionutils/utils_xss.go @@ -0,0 +1,54 @@ +// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn . + +package injectionutils + +/* +#cgo CFLAGS: -I./libinjection/src + +#include +#include +*/ +import "C" +import ( + "net/url" + "strings" + "unsafe" +) + +// DetectXSS detect XSS in string +func DetectXSS(input string) bool { + if len(input) == 0 { + return false + } + + if detectXSSOne(input) { + return true + } + + // 兼容 /PATH?URI + if (input[0] == '/' || strings.HasPrefix(input, "http://") || strings.HasPrefix(input, "https://")) && len(input) < 4096 { + var argsIndex = strings.Index(input, "?") + if argsIndex > 0 { + var args = input[argsIndex+1:] + unescapeArgs, err := url.QueryUnescape(args) + if err == nil && args != unescapeArgs { + return detectXSSOne(args) || detectXSSOne(unescapeArgs) + } else { + return detectXSSOne(args) + } + } + } + + return false +} + +func detectXSSOne(input string) bool { + if len(input) == 0 { + return false + } + + var cInput = C.CString(input) + defer C.free(unsafe.Pointer(cInput)) + + return C.libinjection_xss(cInput, C.size_t(len(input))) == 1 +} diff --git a/internal/waf/injectionutils/utils_xss_test.go b/internal/waf/injectionutils/utils_xss_test.go new file mode 100644 index 0000000..c8b6642 --- /dev/null +++ b/internal/waf/injectionutils/utils_xss_test.go @@ -0,0 +1,49 @@ +// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn . + +package injectionutils_test + +import ( + "github.com/TeaOSLab/EdgeNode/internal/waf/injectionutils" + "github.com/iwind/TeaGo/assert" + "runtime" + "testing" +) + +func TestDetectXSS(t *testing.T) { + var a = assert.NewAssertion(t) + a.IsFalse(injectionutils.DetectXSS("")) + a.IsFalse(injectionutils.DetectXSS("abc")) + a.IsTrue(injectionutils.DetectXSS("")) + + runtime.GOMAXPROCS(4) + + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + _ = injectionutils.DetectXSS("RequestId: 1234567890") + } + }) +} diff --git a/internal/waf/rule.go b/internal/waf/rule.go index 066b69e..13174bc 100644 --- a/internal/waf/rule.go +++ b/internal/waf/rule.go @@ -590,6 +590,28 @@ func (this *Rule) Test(value any) bool { default: return injectionutils.DetectSQLInjection(this.stringifyValue(value)) } + case RuleOperatorContainsXSS: + if value == nil { + return false + } + switch xValue := value.(type) { + case []string: + for _, v := range xValue { + if injectionutils.DetectXSS(v) { + return true + } + } + return false + case [][]byte: + for _, v := range xValue { + if injectionutils.DetectXSS(string(v)) { + return true + } + } + return false + default: + return injectionutils.DetectXSS(this.stringifyValue(value)) + } case RuleOperatorContainsBinary: data, _ := base64.StdEncoding.DecodeString(this.stringifyValue(this.Value)) if this.IsCaseInsensitive { diff --git a/internal/waf/rule_operator.go b/internal/waf/rule_operator.go index faabcc9..95117a5 100644 --- a/internal/waf/rule_operator.go +++ b/internal/waf/rule_operator.go @@ -26,6 +26,7 @@ const ( RuleOperatorContainsAllWords RuleOperator = "contains all words" RuleOperatorNotContainsAnyWord RuleOperator = "not contains any word" RuleOperatorContainsSQLInjection RuleOperator = "contains sql injection" + RuleOperatorContainsXSS RuleOperator = "contains xss" RuleOperatorInIPList RuleOperator = "in ip list" RuleOperatorHasKey RuleOperator = "has key" // has key in slice or map RuleOperatorVersionGt RuleOperator = "version gt"