diff --git a/internal/waf/injectionutils/libinjection/src/libinjection.h b/internal/waf/injectionutils/libinjection/src/libinjection.h index 6b40b1d..0b6723b 100644 --- a/internal/waf/injectionutils/libinjection/src/libinjection.h +++ b/internal/waf/injectionutils/libinjection/src/libinjection.h @@ -58,7 +58,7 @@ int libinjection_sqli(const char* s, size_t slen, char fingerprint[]); * \return 1 if XSS found, 0 if benign * */ -int libinjection_xss(const char* s, size_t slen); +int libinjection_xss(const char* s, size_t slen, int strictMode); LIBINJECTION_END_DECLS diff --git a/internal/waf/injectionutils/libinjection/src/libinjection_xss.c b/internal/waf/injectionutils/libinjection/src/libinjection_xss.c index c135a36..6c9f515 100644 --- a/internal/waf/injectionutils/libinjection/src/libinjection_xss.c +++ b/internal/waf/injectionutils/libinjection/src/libinjection_xss.c @@ -16,7 +16,7 @@ typedef enum attribute { static attribute_t is_black_attr(const char* s, size_t len); -static int is_black_tag(const char* s, size_t len); +static int is_black_tag(const char* s, size_t len, int strictMode); static int is_black_url(const char* s, size_t len); static int cstrcasecmp_with_null(const char *a, const char *b, size_t n); static int html_decode_char_at(const char* src, size_t len, size_t* consumed); @@ -492,6 +492,35 @@ static stringtype_t BLACKATTR[] = { }; */ +// GoEdge: change BLACKTAG to STRICT_BLACKTAG +static const char* STRICT_BLACKTAG[] = { + "APPLET" + , "AUDIO" + , "BASE" + , "COMMENT" /* IE http://html5sec.org/#38 */ + , "EMBED" + , "FORM" + , "FRAME" + , "FRAMESET" + , "HANDLER" /* Opera SVG, effectively a script tag */ + , "IFRAME" + , "IMPORT" + , "ISINDEX" + , "LINK" + , "LISTENER" + /* , "MARQUEE" */ + , "META" + , "NOSCRIPT" + , "OBJECT" + , "SCRIPT" + , "STYLE" + , "VIDEO" + , "VMLFRAME" + , "XML" + , "XSS" + , NULL +}; + static const char* BLACKTAG[] = { "APPLET" /* , "AUDIO" */ @@ -515,7 +544,6 @@ static const char* BLACKTAG[] = { , "STYLE" /* , "VIDEO" */ , "VMLFRAME" - , "XML" , "XSS" , NULL }; @@ -606,7 +634,7 @@ static int htmlencode_startswith(const char *a, const char *b, size_t n) return (*a == 0) ? 1 : 0; } -static int is_black_tag(const char* s, size_t len) +static int is_black_tag(const char* s, size_t len, int strictMode) { const char** black; @@ -614,7 +642,11 @@ static int is_black_tag(const char* s, size_t len) return 0; } - black = BLACKTAG; + if (strictMode == 1) { + black = STRICT_BLACKTAG; + } else { + black = BLACKTAG; + } while (*black != NULL) { if (cstrcasecmp_with_null(*black, s, len) == 0) { /* printf("Got black tag %s\n", *black); */ @@ -729,7 +761,7 @@ static int is_black_url(const char* s, size_t len) return 0; } -int libinjection_is_xss(const char* s, size_t len, int flags) +int libinjection_is_xss(const char* s, size_t len, int flags, int strictMode) { h5_state_t h5; attribute_t attr = TYPE_NONE; @@ -743,7 +775,7 @@ int libinjection_is_xss(const char* s, size_t len, int flags) if (h5.token_type == DOCTYPE) { return 1; } else if (h5.token_type == TAG_NAME_OPEN) { - if (is_black_tag(h5.token_start, h5.token_len)) { + if (is_black_tag(h5.token_start, h5.token_len, strictMode)) { return 1; } } else if (h5.token_type == ATTR_NAME) { @@ -835,21 +867,21 @@ int libinjection_is_xss(const char* s, size_t len, int flags) * * */ -int libinjection_xss(const char* s, size_t slen) +int libinjection_xss(const char* s, size_t slen, int strictMode) { - if (libinjection_is_xss(s, slen, DATA_STATE)) { + if (libinjection_is_xss(s, slen, DATA_STATE, strictMode)) { return 1; } - if (libinjection_is_xss(s, slen, VALUE_NO_QUOTE)) { + if (libinjection_is_xss(s, slen, VALUE_NO_QUOTE, strictMode)) { return 1; } - if (libinjection_is_xss(s, slen, VALUE_SINGLE_QUOTE)) { + if (libinjection_is_xss(s, slen, VALUE_SINGLE_QUOTE, strictMode)) { return 1; } - if (libinjection_is_xss(s, slen, VALUE_DOUBLE_QUOTE)) { + if (libinjection_is_xss(s, slen, VALUE_DOUBLE_QUOTE, strictMode)) { return 1; } - if (libinjection_is_xss(s, slen, VALUE_BACK_QUOTE)) { + if (libinjection_is_xss(s, slen, VALUE_BACK_QUOTE, strictMode)) { return 1; } diff --git a/internal/waf/injectionutils/libinjection/src/libinjection_xss.h b/internal/waf/injectionutils/libinjection/src/libinjection_xss.h index 3443d6e..92e9bef 100644 --- a/internal/waf/injectionutils/libinjection/src/libinjection_xss.h +++ b/internal/waf/injectionutils/libinjection/src/libinjection_xss.h @@ -13,7 +13,7 @@ extern "C" { #include - int libinjection_is_xss(const char* s, size_t len, int flags); + int libinjection_is_xss(const char* s, size_t len, int flags, int strictMode); #ifdef __cplusplus } diff --git a/internal/waf/injectionutils/libinjection_xss.c b/internal/waf/injectionutils/libinjection_xss.c index 72c861f..e61e26f 100644 --- a/internal/waf/injectionutils/libinjection_xss.c +++ b/internal/waf/injectionutils/libinjection_xss.c @@ -3,4 +3,4 @@ #include "libinjection/src/libinjection_xss.c" #include "libinjection/src/libinjection_html5.c" -#define GOEDGE_VERSION "23" // last version is for GoEdge change \ No newline at end of file +#define GOEDGE_VERSION "24" // last version is for GoEdge change \ No newline at end of file diff --git a/internal/waf/injectionutils/utils_xss.go b/internal/waf/injectionutils/utils_xss.go index 7a9c55c..eac1aad 100644 --- a/internal/waf/injectionutils/utils_xss.go +++ b/internal/waf/injectionutils/utils_xss.go @@ -19,7 +19,7 @@ import ( "unsafe" ) -func DetectXSSCache(input string, cacheLife utils.CacheLife) bool { +func DetectXSSCache(input string, isStrict bool, cacheLife utils.CacheLife) bool { var l = len(input) if l == 0 { @@ -27,17 +27,20 @@ func DetectXSSCache(input string, cacheLife utils.CacheLife) bool { } if cacheLife <= 0 || l < 512 || l > utils.MaxCacheDataSize { - return DetectXSS(input) + return DetectXSS(input, isStrict) } var hash = xxhash.Sum64String(input) var key = "WAF@XSS@" + strconv.FormatUint(hash, 10) + if isStrict { + key += "@1" + } var item = utils.SharedCache.Read(key) if item != nil { return item.Value == 1 } - var result = DetectXSS(input) + var result = DetectXSS(input, isStrict) if result { utils.SharedCache.Write(key, 1, fasttime.Now().Unix()+cacheLife) } else { @@ -47,12 +50,12 @@ func DetectXSSCache(input string, cacheLife utils.CacheLife) bool { } // DetectXSS detect XSS in string -func DetectXSS(input string) bool { +func DetectXSS(input string, isStrict bool) bool { if len(input) == 0 { return false } - if detectXSSOne(input) { + if detectXSSOne(input, isStrict) { return true } @@ -63,22 +66,22 @@ func DetectXSS(input string) bool { var args = input[argsIndex+1:] unescapeArgs, err := url.QueryUnescape(args) if err == nil && args != unescapeArgs { - return detectXSSOne(args) || detectXSSOne(unescapeArgs) + return detectXSSOne(args, isStrict) || detectXSSOne(unescapeArgs, isStrict) } else { - return detectXSSOne(args) + return detectXSSOne(args, isStrict) } } } else { unescapedInput, err := url.QueryUnescape(input) if err == nil && input != unescapedInput { - return detectXSSOne(unescapedInput) + return detectXSSOne(unescapedInput, isStrict) } } return false } -func detectXSSOne(input string) bool { +func detectXSSOne(input string, isStrict bool) bool { if len(input) == 0 { return false } @@ -86,5 +89,9 @@ func detectXSSOne(input string) bool { var cInput = C.CString(input) defer C.free(unsafe.Pointer(cInput)) - return C.libinjection_xss(cInput, C.size_t(len(input))) == 1 + var isStrictInt = 0 + if isStrict { + isStrictInt = 1 + } + return C.libinjection_xss(cInput, C.size_t(len(input)), C.int(isStrictInt)) == 1 } diff --git a/internal/waf/injectionutils/utils_xss_test.go b/internal/waf/injectionutils/utils_xss_test.go index 4bec531..752f7dd 100644 --- a/internal/waf/injectionutils/utils_xss_test.go +++ b/internal/waf/injectionutils/utils_xss_test.go @@ -12,18 +12,18 @@ import ( func TestDetectXSS(t *testing.T) { var a = assert.NewAssertion(t) - a.IsFalse(injectionutils.DetectXSS("")) - a.IsFalse(injectionutils.DetectXSS("abc")) - a.IsTrue(injectionutils.DetectXSS("") + var result = injectionutils.DetectXSS("RequestId: 1234567890", false) if !result { b.Fatal("'result' should not be 'false'") } @@ -74,7 +84,7 @@ func BenchmarkDetectXSS_HIT(b *testing.B) { b.RunParallel(func(pb *testing.PB) { for pb.Next() { - _ = injectionutils.DetectXSS("RequestId: 1234567890") + _ = injectionutils.DetectXSS("RequestId: 1234567890", false) } }) } diff --git a/internal/waf/rule.go b/internal/waf/rule.go index 0fcef92..0857ed2 100644 --- a/internal/waf/rule.go +++ b/internal/waf/rule.go @@ -600,27 +600,28 @@ func (this *Rule) Test(value any) bool { default: return injectionutils.DetectSQLInjectionCache(this.stringifyValue(value), this.cacheLife) } - case RuleOperatorContainsXSS: + case RuleOperatorContainsXSS, RuleOperatorContainsXSSStrictly: if value == nil { return false } + var isStrict = this.Operator == RuleOperatorContainsXSSStrictly switch xValue := value.(type) { case []string: for _, v := range xValue { - if injectionutils.DetectXSSCache(v, this.cacheLife) { + if injectionutils.DetectXSSCache(v, isStrict, this.cacheLife) { return true } } return false case [][]byte: for _, v := range xValue { - if injectionutils.DetectXSSCache(string(v), this.cacheLife) { + if injectionutils.DetectXSSCache(string(v), isStrict, this.cacheLife) { return true } } return false default: - return injectionutils.DetectXSSCache(this.stringifyValue(value), this.cacheLife) + return injectionutils.DetectXSSCache(this.stringifyValue(value), isStrict, this.cacheLife) } case RuleOperatorContainsBinary: data, _ := base64.StdEncoding.DecodeString(this.stringifyValue(this.Value)) diff --git a/internal/waf/rule_operator.go b/internal/waf/rule_operator.go index 95117a5..d6d8496 100644 --- a/internal/waf/rule_operator.go +++ b/internal/waf/rule_operator.go @@ -27,6 +27,7 @@ const ( RuleOperatorNotContainsAnyWord RuleOperator = "not contains any word" RuleOperatorContainsSQLInjection RuleOperator = "contains sql injection" RuleOperatorContainsXSS RuleOperator = "contains xss" + RuleOperatorContainsXSSStrictly RuleOperator = "contains xss strictly" RuleOperatorInIPList RuleOperator = "in ip list" RuleOperatorHasKey RuleOperator = "has key" // has key in slice or map RuleOperatorVersionGt RuleOperator = "version gt"