diff --git a/internal/waf/injectionutils/utils_sqli.go b/internal/waf/injectionutils/utils_sqli.go index 85f8282..a8acdc5 100644 --- a/internal/waf/injectionutils/utils_sqli.go +++ b/internal/waf/injectionutils/utils_sqli.go @@ -16,11 +16,12 @@ import ( "net/url" "strconv" "strings" + "unicode/utf8" "unsafe" ) // DetectSQLInjectionCache detect sql injection in string with cache -func DetectSQLInjectionCache(input string, cacheLife utils.CacheLife) bool { +func DetectSQLInjectionCache(input string, isStrict bool, cacheLife utils.CacheLife) bool { var l = len(input) if l == 0 { @@ -28,7 +29,7 @@ func DetectSQLInjectionCache(input string, cacheLife utils.CacheLife) bool { } if cacheLife <= 0 || l < 128 || l > utils.MaxCacheDataSize { - return DetectSQLInjection(input) + return DetectSQLInjection(input, isStrict) } var hash = xxhash.Sum64String(input) @@ -38,7 +39,7 @@ func DetectSQLInjectionCache(input string, cacheLife utils.CacheLife) bool { return item.Value == 1 } - var result = DetectSQLInjection(input) + var result = DetectSQLInjection(input, isStrict) if result { utils.SharedCache.Write(key, 1, fasttime.Now().Unix()+cacheLife) } else { @@ -48,11 +49,23 @@ func DetectSQLInjectionCache(input string, cacheLife utils.CacheLife) bool { } // DetectSQLInjection detect sql injection in string -func DetectSQLInjection(input string) bool { +func DetectSQLInjection(input string, isStrict bool) bool { if len(input) == 0 { return false } + if !isStrict { + if len(input) > 1024 { + if !utf8.ValidString(input[:1024]) && !utf8.ValidString(input[:1023]) && !utf8.ValidString(input[:1022]) { + return false + } + } else { + if !utf8.ValidString(input) { + return false + } + } + } + if detectSQLInjectionOne(input) { return true } diff --git a/internal/waf/injectionutils/utils_sqli_test.go b/internal/waf/injectionutils/utils_sqli_test.go index 08bab8c..476a62d 100644 --- a/internal/waf/injectionutils/utils_sqli_test.go +++ b/internal/waf/injectionutils/utils_sqli_test.go @@ -15,21 +15,23 @@ import ( func TestDetectSQLInjection(t *testing.T) { var a = assert.NewAssertion(t) - a.IsTrue(injectionutils.DetectSQLInjection("' UNION SELECT * FROM myTable")) - a.IsTrue(injectionutils.DetectSQLInjection("id=1 ' UNION select * from a")) - a.IsTrue(injectionutils.DetectSQLInjection("asdf asd ; -1' and 1=1 union/* foo */select load_file('/etc/passwd')--")) - a.IsFalse(injectionutils.DetectSQLInjection("' UNION SELECT1 * FROM myTable")) - a.IsFalse(injectionutils.DetectSQLInjection("1234")) - a.IsFalse(injectionutils.DetectSQLInjection("")) - a.IsTrue(injectionutils.DetectSQLInjection("id=123 OR 1=1&b=2")) - a.IsTrue(injectionutils.DetectSQLInjection("id=123&b=456&c=1' or 2=2")) - a.IsFalse(injectionutils.DetectSQLInjection("?")) - a.IsFalse(injectionutils.DetectSQLInjection("/hello?age=22")) - a.IsTrue(injectionutils.DetectSQLInjection("/sql/injection?id=123 or 1=1")) - a.IsTrue(injectionutils.DetectSQLInjection("/sql/injection?id=123%20or%201=1")) - a.IsTrue(injectionutils.DetectSQLInjection("https://example.com/sql/injection?id=123%20or%201=1")) - a.IsTrue(injectionutils.DetectSQLInjection("id=123%20or%201=1")) - a.IsTrue(injectionutils.DetectSQLInjection("https://example.com/' or 1=1")) + for _, isStrict := range []bool{true, false} { + a.IsTrue(injectionutils.DetectSQLInjection("' UNION SELECT * FROM myTable", isStrict)) + a.IsTrue(injectionutils.DetectSQLInjection("id=1 ' UNION select * from a", isStrict)) + a.IsTrue(injectionutils.DetectSQLInjection("asdf asd ; -1' and 1=1 union/* foo */select load_file('/etc/passwd')--", isStrict)) + a.IsFalse(injectionutils.DetectSQLInjection("' UNION SELECT1 * FROM myTable", isStrict)) + a.IsFalse(injectionutils.DetectSQLInjection("1234", isStrict)) + a.IsFalse(injectionutils.DetectSQLInjection("", isStrict)) + a.IsTrue(injectionutils.DetectSQLInjection("id=123 OR 1=1&b=2", isStrict)) + a.IsTrue(injectionutils.DetectSQLInjection("id=123&b=456&c=1' or 2=2", isStrict)) + a.IsFalse(injectionutils.DetectSQLInjection("?", isStrict)) + a.IsFalse(injectionutils.DetectSQLInjection("/hello?age=22", isStrict)) + a.IsTrue(injectionutils.DetectSQLInjection("/sql/injection?id=123 or 1=1", isStrict)) + a.IsTrue(injectionutils.DetectSQLInjection("/sql/injection?id=123%20or%201=1", isStrict)) + a.IsTrue(injectionutils.DetectSQLInjection("https://example.com/sql/injection?id=123%20or%201=1", isStrict)) + a.IsTrue(injectionutils.DetectSQLInjection("id=123%20or%201=1", isStrict)) + a.IsTrue(injectionutils.DetectSQLInjection("https://example.com/' or 1=1", isStrict)) + } } func BenchmarkDetectSQLInjection(b *testing.B) { @@ -37,7 +39,7 @@ func BenchmarkDetectSQLInjection(b *testing.B) { b.RunParallel(func(pb *testing.PB) { for pb.Next() { - _ = injectionutils.DetectSQLInjection("asdf asd ; -1' and 1=1 union/* foo */select load_file('/etc/passwd')--") + _ = injectionutils.DetectSQLInjection("asdf asd ; -1' and 1=1 union/* foo */select load_file('/etc/passwd')--", false) } }) } @@ -47,7 +49,7 @@ func BenchmarkDetectSQLInjection_URL(b *testing.B) { b.RunParallel(func(pb *testing.PB) { for pb.Next() { - _ = injectionutils.DetectSQLInjection("/sql/injection?id=123 or 1=1") + _ = injectionutils.DetectSQLInjection("/sql/injection?id=123 or 1=1", false) } }) } @@ -57,7 +59,7 @@ func BenchmarkDetectSQLInjection_Normal_Small(b *testing.B) { b.RunParallel(func(pb *testing.PB) { for pb.Next() { - _ = injectionutils.DetectSQLInjection("a/sql/injection?id=1234") + _ = injectionutils.DetectSQLInjection("a/sql/injection?id=1234", false) } }) } @@ -67,7 +69,7 @@ func BenchmarkDetectSQLInjection_URL_Normal_Small(b *testing.B) { b.RunParallel(func(pb *testing.PB) { for pb.Next() { - _ = injectionutils.DetectSQLInjection("/sql/injection?id=" + types.String(rands.Int64()%10000)) + _ = injectionutils.DetectSQLInjection("/sql/injection?id="+types.String(rands.Int64()%10000), false) } }) } @@ -77,7 +79,7 @@ func BenchmarkDetectSQLInjection_URL_Normal_Middle(b *testing.B) { b.RunParallel(func(pb *testing.PB) { for pb.Next() { - _ = injectionutils.DetectSQLInjection("/search?q=libinjection+fingerprint&newwindow=1&sca_esv=589290862&sxsrf=AMwHvKnxuLoejn2XlNniffC12E_xc35M7Q%3A1702090118361&ei=htvzzebfFZfo1e8PvLGggAk&ved=0ahUKEwjTsYmnq4GDAxUWdPOHHbwkCJAQ4ddDCBA&uact=5&oq=libinjection+fingerprint&gs_lp=Egxnd3Mtd2l6LXNlcnAiGIxpYmluamVjdGlvbmBmaW5nKXJwcmludTIEEAAYHjIGVAAYCBgeSiEaUPkRWKFZcAJ4AZABAJgBHgGgAfoEqgwDMC40uAEGyAEA-AEBwgIKEAFYTxjWMuiwA-IDBBgAVteIBgGQBgI&sclient=gws-wiz-serp#ip=1") + _ = injectionutils.DetectSQLInjection("/search?q=libinjection+fingerprint&newwindow=1&sca_esv=589290862&sxsrf=AMwHvKnxuLoejn2XlNniffC12E_xc35M7Q%3A1702090118361&ei=htvzzebfFZfo1e8PvLGggAk&ved=0ahUKEwjTsYmnq4GDAxUWdPOHHbwkCJAQ4ddDCBA&uact=5&oq=libinjection+fingerprint&gs_lp=Egxnd3Mtd2l6LXNlcnAiGIxpYmluamVjdGlvbmBmaW5nKXJwcmludTIEEAAYHjIGVAAYCBgeSiEaUPkRWKFZcAJ4AZABAJgBHgGgAfoEqgwDMC40uAEGyAEA-AEBwgIKEAFYTxjWMuiwA-IDBBgAVteIBgGQBgI&sclient=gws-wiz-serp#ip=1", false) } }) } @@ -87,7 +89,7 @@ func BenchmarkDetectSQLInjection_URL_Normal_Small_Cache(b *testing.B) { b.RunParallel(func(pb *testing.PB) { for pb.Next() { - _ = injectionutils.DetectSQLInjectionCache("/sql/injection?id="+types.String(rands.Int64()%10000), utils.CacheMiddleLife) + _ = injectionutils.DetectSQLInjectionCache("/sql/injection?id="+types.String(rands.Int64()%10000), false, utils.CacheMiddleLife) } }) } @@ -100,7 +102,7 @@ func BenchmarkDetectSQLInjection_Normal_Large(b *testing.B) { b.RunParallel(func(pb *testing.PB) { for pb.Next() { - _ = injectionutils.DetectSQLInjection("a/sql/injection?id=" + types.String(rands.Int64()%10000) + "&s=" + s + "&v=%20") + _ = injectionutils.DetectSQLInjection("a/sql/injection?id="+types.String(rands.Int64()%10000)+"&s="+s+"&v=%20", false) } }) } @@ -112,7 +114,7 @@ func BenchmarkDetectSQLInjection_Normal_Large_Cache(b *testing.B) { b.RunParallel(func(pb *testing.PB) { for pb.Next() { - _ = injectionutils.DetectSQLInjectionCache("a/sql/injection?id="+types.String(rands.Int64()%10000)+"&s="+s, utils.CacheMiddleLife) + _ = injectionutils.DetectSQLInjectionCache("a/sql/injection?id="+types.String(rands.Int64()%10000)+"&s="+s, false, utils.CacheMiddleLife) } }) } @@ -122,7 +124,7 @@ func BenchmarkDetectSQLInjection_URL_Unescape(b *testing.B) { b.RunParallel(func(pb *testing.PB) { for pb.Next() { - _ = injectionutils.DetectSQLInjection("/sql/injection?id=123%20or%201=1") + _ = injectionutils.DetectSQLInjection("/sql/injection?id=123%20or%201=1", false) } }) } diff --git a/internal/waf/rule.go b/internal/waf/rule.go index 0857ed2..2bdf7cf 100644 --- a/internal/waf/rule.go +++ b/internal/waf/rule.go @@ -578,27 +578,28 @@ func (this *Rule) Test(value any) bool { return runes.ContainsAllWords(this.stringifyValue(value), this.stringValues, this.IsCaseInsensitive) case RuleOperatorNotContainsAnyWord: return !runes.ContainsAnyWordRunes(this.stringifyValue(value), this.stringValueRunes, this.IsCaseInsensitive) - case RuleOperatorContainsSQLInjection: + case RuleOperatorContainsSQLInjection, RuleOperatorContainsSQLInjectionStrictly: if value == nil { return false } + var isStrict = this.Operator == RuleOperatorContainsSQLInjectionStrictly switch xValue := value.(type) { case []string: for _, v := range xValue { - if injectionutils.DetectSQLInjectionCache(v, this.cacheLife) { + if injectionutils.DetectSQLInjectionCache(v, isStrict, this.cacheLife) { return true } } return false case [][]byte: for _, v := range xValue { - if injectionutils.DetectSQLInjectionCache(string(v), this.cacheLife) { + if injectionutils.DetectSQLInjectionCache(string(v), isStrict, this.cacheLife) { return true } } return false default: - return injectionutils.DetectSQLInjectionCache(this.stringifyValue(value), this.cacheLife) + return injectionutils.DetectSQLInjectionCache(this.stringifyValue(value), isStrict, this.cacheLife) } case RuleOperatorContainsXSS, RuleOperatorContainsXSSStrictly: if value == nil { diff --git a/internal/waf/rule_operator.go b/internal/waf/rule_operator.go index d6d8496..299cd73 100644 --- a/internal/waf/rule_operator.go +++ b/internal/waf/rule_operator.go @@ -4,35 +4,36 @@ type RuleOperator = string type RuleCaseInsensitive = string const ( - RuleOperatorGt RuleOperator = "gt" - RuleOperatorGte RuleOperator = "gte" - RuleOperatorLt RuleOperator = "lt" - RuleOperatorLte RuleOperator = "lte" - RuleOperatorEq RuleOperator = "eq" - RuleOperatorNeq RuleOperator = "neq" - RuleOperatorEqString RuleOperator = "eq string" - RuleOperatorNeqString RuleOperator = "neq string" - RuleOperatorMatch RuleOperator = "match" - RuleOperatorNotMatch RuleOperator = "not match" - RuleOperatorWildcardMatch RuleOperator = "wildcard match" - RuleOperatorWildcardNotMatch RuleOperator = "wildcard not match" - RuleOperatorContains RuleOperator = "contains" - RuleOperatorNotContains RuleOperator = "not contains" - RuleOperatorPrefix RuleOperator = "prefix" - RuleOperatorSuffix RuleOperator = "suffix" - RuleOperatorContainsAny RuleOperator = "contains any" - RuleOperatorContainsAll RuleOperator = "contains all" - RuleOperatorContainsAnyWord RuleOperator = "contains any word" - RuleOperatorContainsAllWords RuleOperator = "contains all words" - 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" - RuleOperatorVersionLt RuleOperator = "version lt" - RuleOperatorVersionRange RuleOperator = "version range" + RuleOperatorGt RuleOperator = "gt" + RuleOperatorGte RuleOperator = "gte" + RuleOperatorLt RuleOperator = "lt" + RuleOperatorLte RuleOperator = "lte" + RuleOperatorEq RuleOperator = "eq" + RuleOperatorNeq RuleOperator = "neq" + RuleOperatorEqString RuleOperator = "eq string" + RuleOperatorNeqString RuleOperator = "neq string" + RuleOperatorMatch RuleOperator = "match" + RuleOperatorNotMatch RuleOperator = "not match" + RuleOperatorWildcardMatch RuleOperator = "wildcard match" + RuleOperatorWildcardNotMatch RuleOperator = "wildcard not match" + RuleOperatorContains RuleOperator = "contains" + RuleOperatorNotContains RuleOperator = "not contains" + RuleOperatorPrefix RuleOperator = "prefix" + RuleOperatorSuffix RuleOperator = "suffix" + RuleOperatorContainsAny RuleOperator = "contains any" + RuleOperatorContainsAll RuleOperator = "contains all" + RuleOperatorContainsAnyWord RuleOperator = "contains any word" + RuleOperatorContainsAllWords RuleOperator = "contains all words" + RuleOperatorNotContainsAnyWord RuleOperator = "not contains any word" + RuleOperatorContainsSQLInjection RuleOperator = "contains sql injection" + RuleOperatorContainsSQLInjectionStrictly RuleOperator = "contains sql injection strictly" + 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" + RuleOperatorVersionLt RuleOperator = "version lt" + RuleOperatorVersionRange RuleOperator = "version range" RuleOperatorContainsBinary RuleOperator = "contains binary" // contains binary RuleOperatorNotContainsBinary RuleOperator = "not contains binary" // not contains binary