WAF操作符增加“包含XSS注入-严格模式”

This commit is contained in:
GoEdgeLab
2024-01-04 14:54:17 +08:00
parent e7fb706d8a
commit 84e9068381
8 changed files with 99 additions and 48 deletions

View File

@@ -58,7 +58,7 @@ int libinjection_sqli(const char* s, size_t slen, char fingerprint[]);
* \return 1 if XSS found, 0 if benign * \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 LIBINJECTION_END_DECLS

View File

@@ -16,7 +16,7 @@ typedef enum attribute {
static attribute_t is_black_attr(const char* s, size_t len); 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 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 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); 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[] = { static const char* BLACKTAG[] = {
"APPLET" "APPLET"
/* , "AUDIO" */ /* , "AUDIO" */
@@ -515,7 +544,6 @@ static const char* BLACKTAG[] = {
, "STYLE" , "STYLE"
/* , "VIDEO" */ /* , "VIDEO" */
, "VMLFRAME" , "VMLFRAME"
, "XML"
, "XSS" , "XSS"
, NULL , NULL
}; };
@@ -606,7 +634,7 @@ static int htmlencode_startswith(const char *a, const char *b, size_t n)
return (*a == 0) ? 1 : 0; 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; const char** black;
@@ -614,7 +642,11 @@ static int is_black_tag(const char* s, size_t len)
return 0; return 0;
} }
if (strictMode == 1) {
black = STRICT_BLACKTAG;
} else {
black = BLACKTAG; black = BLACKTAG;
}
while (*black != NULL) { while (*black != NULL) {
if (cstrcasecmp_with_null(*black, s, len) == 0) { if (cstrcasecmp_with_null(*black, s, len) == 0) {
/* printf("Got black tag %s\n", *black); */ /* printf("Got black tag %s\n", *black); */
@@ -729,7 +761,7 @@ static int is_black_url(const char* s, size_t len)
return 0; 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; h5_state_t h5;
attribute_t attr = TYPE_NONE; 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) { if (h5.token_type == DOCTYPE) {
return 1; return 1;
} else if (h5.token_type == TAG_NAME_OPEN) { } 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; return 1;
} }
} else if (h5.token_type == ATTR_NAME) { } 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; return 1;
} }
if (libinjection_is_xss(s, slen, VALUE_NO_QUOTE)) { if (libinjection_is_xss(s, slen, VALUE_NO_QUOTE, strictMode)) {
return 1; return 1;
} }
if (libinjection_is_xss(s, slen, VALUE_SINGLE_QUOTE)) { if (libinjection_is_xss(s, slen, VALUE_SINGLE_QUOTE, strictMode)) {
return 1; return 1;
} }
if (libinjection_is_xss(s, slen, VALUE_DOUBLE_QUOTE)) { if (libinjection_is_xss(s, slen, VALUE_DOUBLE_QUOTE, strictMode)) {
return 1; return 1;
} }
if (libinjection_is_xss(s, slen, VALUE_BACK_QUOTE)) { if (libinjection_is_xss(s, slen, VALUE_BACK_QUOTE, strictMode)) {
return 1; return 1;
} }

View File

@@ -13,7 +13,7 @@ extern "C" {
#include <string.h> #include <string.h>
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 #ifdef __cplusplus
} }

View File

@@ -3,4 +3,4 @@
#include "libinjection/src/libinjection_xss.c" #include "libinjection/src/libinjection_xss.c"
#include "libinjection/src/libinjection_html5.c" #include "libinjection/src/libinjection_html5.c"
#define GOEDGE_VERSION "23" // last version is for GoEdge change #define GOEDGE_VERSION "24" // last version is for GoEdge change

View File

@@ -19,7 +19,7 @@ import (
"unsafe" "unsafe"
) )
func DetectXSSCache(input string, cacheLife utils.CacheLife) bool { func DetectXSSCache(input string, isStrict bool, cacheLife utils.CacheLife) bool {
var l = len(input) var l = len(input)
if l == 0 { if l == 0 {
@@ -27,17 +27,20 @@ func DetectXSSCache(input string, cacheLife utils.CacheLife) bool {
} }
if cacheLife <= 0 || l < 512 || l > utils.MaxCacheDataSize { if cacheLife <= 0 || l < 512 || l > utils.MaxCacheDataSize {
return DetectXSS(input) return DetectXSS(input, isStrict)
} }
var hash = xxhash.Sum64String(input) var hash = xxhash.Sum64String(input)
var key = "WAF@XSS@" + strconv.FormatUint(hash, 10) var key = "WAF@XSS@" + strconv.FormatUint(hash, 10)
if isStrict {
key += "@1"
}
var item = utils.SharedCache.Read(key) var item = utils.SharedCache.Read(key)
if item != nil { if item != nil {
return item.Value == 1 return item.Value == 1
} }
var result = DetectXSS(input) var result = DetectXSS(input, isStrict)
if result { if result {
utils.SharedCache.Write(key, 1, fasttime.Now().Unix()+cacheLife) utils.SharedCache.Write(key, 1, fasttime.Now().Unix()+cacheLife)
} else { } else {
@@ -47,12 +50,12 @@ func DetectXSSCache(input string, cacheLife utils.CacheLife) bool {
} }
// DetectXSS detect XSS in string // DetectXSS detect XSS in string
func DetectXSS(input string) bool { func DetectXSS(input string, isStrict bool) bool {
if len(input) == 0 { if len(input) == 0 {
return false return false
} }
if detectXSSOne(input) { if detectXSSOne(input, isStrict) {
return true return true
} }
@@ -63,22 +66,22 @@ func DetectXSS(input string) bool {
var args = input[argsIndex+1:] var args = input[argsIndex+1:]
unescapeArgs, err := url.QueryUnescape(args) unescapeArgs, err := url.QueryUnescape(args)
if err == nil && args != unescapeArgs { if err == nil && args != unescapeArgs {
return detectXSSOne(args) || detectXSSOne(unescapeArgs) return detectXSSOne(args, isStrict) || detectXSSOne(unescapeArgs, isStrict)
} else { } else {
return detectXSSOne(args) return detectXSSOne(args, isStrict)
} }
} }
} else { } else {
unescapedInput, err := url.QueryUnescape(input) unescapedInput, err := url.QueryUnescape(input)
if err == nil && input != unescapedInput { if err == nil && input != unescapedInput {
return detectXSSOne(unescapedInput) return detectXSSOne(unescapedInput, isStrict)
} }
} }
return false return false
} }
func detectXSSOne(input string) bool { func detectXSSOne(input string, isStrict bool) bool {
if len(input) == 0 { if len(input) == 0 {
return false return false
} }
@@ -86,5 +89,9 @@ func detectXSSOne(input string) bool {
var cInput = C.CString(input) var cInput = C.CString(input)
defer C.free(unsafe.Pointer(cInput)) 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
} }

View File

@@ -12,18 +12,18 @@ import (
func TestDetectXSS(t *testing.T) { func TestDetectXSS(t *testing.T) {
var a = assert.NewAssertion(t) var a = assert.NewAssertion(t)
a.IsFalse(injectionutils.DetectXSS("")) a.IsFalse(injectionutils.DetectXSS("", true))
a.IsFalse(injectionutils.DetectXSS("abc")) a.IsFalse(injectionutils.DetectXSS("abc", true))
a.IsTrue(injectionutils.DetectXSS("<script>")) a.IsTrue(injectionutils.DetectXSS("<script>", true))
a.IsTrue(injectionutils.DetectXSS("<link>")) a.IsTrue(injectionutils.DetectXSS("<link>", true))
a.IsFalse(injectionutils.DetectXSS("<html><span>")) a.IsFalse(injectionutils.DetectXSS("<html><span>", true))
a.IsFalse(injectionutils.DetectXSS("&lt;script&gt;")) a.IsFalse(injectionutils.DetectXSS("&lt;script&gt;", true))
a.IsTrue(injectionutils.DetectXSS("/path?onmousedown=a")) a.IsTrue(injectionutils.DetectXSS("/path?onmousedown=a", true))
a.IsTrue(injectionutils.DetectXSS("/path?onkeyup=a")) a.IsTrue(injectionutils.DetectXSS("/path?onkeyup=a", true))
a.IsTrue(injectionutils.DetectXSS("onkeyup=a")) a.IsTrue(injectionutils.DetectXSS("onkeyup=a", true))
a.IsTrue(injectionutils.DetectXSS("<iframe scrolling='no'>")) a.IsTrue(injectionutils.DetectXSS("<iframe scrolling='no'>", true))
a.IsFalse(injectionutils.DetectXSS("<html><body><span>RequestId: 1234567890</span></body></html>")) a.IsFalse(injectionutils.DetectXSS("<html><body><span>RequestId: 1234567890</span></body></html>", true))
a.IsTrue(injectionutils.DetectXSS("name=s&description=%3Cscript+src%3D%22a.js%22%3Edddd%3C%2Fscript%3E")) a.IsTrue(injectionutils.DetectXSS("name=s&description=%3Cscript+src%3D%22a.js%22%3Edddd%3C%2Fscript%3E", true))
a.IsFalse(injectionutils.DetectXSS(`<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 6.0.0"> a.IsFalse(injectionutils.DetectXSS(`<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 6.0.0">
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<rdf:Description rdf:about="" <rdf:Description rdf:about=""
@@ -31,11 +31,21 @@ func TestDetectXSS(t *testing.T) {
<tiff:Orientation>1</tiff:Orientation> <tiff:Orientation>1</tiff:Orientation>
</rdf:Description> </rdf:Description>
</rdf:RDF> </rdf:RDF>
</x:xmpmeta>`)) // included in some photo files </x:xmpmeta>`, true)) // included in some photo files
a.IsFalse(injectionutils.DetectXSS(`<xml>
</xml>`, true))
}
func TestDetectXSS_Strict(t *testing.T) {
var a = assert.NewAssertion(t)
a.IsFalse(injectionutils.DetectXSS(`<xml>
</xml>`, false))
} }
func BenchmarkDetectXSS_MISS(b *testing.B) { func BenchmarkDetectXSS_MISS(b *testing.B) {
var result = injectionutils.DetectXSS("<html><body><span>RequestId: 1234567890</span></body></html>") var result = injectionutils.DetectXSS("<html><body><span>RequestId: 1234567890</span></body></html>", false)
if result { if result {
b.Fatal("'result' should not be 'true'") b.Fatal("'result' should not be 'true'")
} }
@@ -44,13 +54,13 @@ func BenchmarkDetectXSS_MISS(b *testing.B) {
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
for pb.Next() { for pb.Next() {
_ = injectionutils.DetectXSS("<html><body><span>RequestId: 1234567890</span></body></html>") _ = injectionutils.DetectXSS("<html><body><span>RequestId: 1234567890</span></body></html>", false)
} }
}) })
} }
func BenchmarkDetectXSS_MISS_Cache(b *testing.B) { func BenchmarkDetectXSS_MISS_Cache(b *testing.B) {
var result = injectionutils.DetectXSS("<html><body><span>RequestId: 1234567890</span></body></html>") var result = injectionutils.DetectXSS("<html><body><span>RequestId: 1234567890</span></body></html>", false)
if result { if result {
b.Fatal("'result' should not be 'true'") b.Fatal("'result' should not be 'true'")
} }
@@ -59,13 +69,13 @@ func BenchmarkDetectXSS_MISS_Cache(b *testing.B) {
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
for pb.Next() { for pb.Next() {
_ = injectionutils.DetectXSSCache("<html><body><span>RequestId: 1234567890</span></body></html>", utils.CacheMiddleLife) _ = injectionutils.DetectXSSCache("<html><body><span>RequestId: 1234567890</span></body></html>", false, utils.CacheMiddleLife)
} }
}) })
} }
func BenchmarkDetectXSS_HIT(b *testing.B) { func BenchmarkDetectXSS_HIT(b *testing.B) {
var result = injectionutils.DetectXSS("<html><body><span>RequestId: 1234567890</span><script src=\"\"></script></body></html>") var result = injectionutils.DetectXSS("<html><body><span>RequestId: 1234567890</span><script src=\"\"></script></body></html>", false)
if !result { if !result {
b.Fatal("'result' should not be 'false'") b.Fatal("'result' should not be 'false'")
} }
@@ -74,7 +84,7 @@ func BenchmarkDetectXSS_HIT(b *testing.B) {
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
for pb.Next() { for pb.Next() {
_ = injectionutils.DetectXSS("<html><body><span>RequestId: 1234567890</span><script src=\"\"></script></body></html>") _ = injectionutils.DetectXSS("<html><body><span>RequestId: 1234567890</span><script src=\"\"></script></body></html>", false)
} }
}) })
} }

View File

@@ -600,27 +600,28 @@ func (this *Rule) Test(value any) bool {
default: default:
return injectionutils.DetectSQLInjectionCache(this.stringifyValue(value), this.cacheLife) return injectionutils.DetectSQLInjectionCache(this.stringifyValue(value), this.cacheLife)
} }
case RuleOperatorContainsXSS: case RuleOperatorContainsXSS, RuleOperatorContainsXSSStrictly:
if value == nil { if value == nil {
return false return false
} }
var isStrict = this.Operator == RuleOperatorContainsXSSStrictly
switch xValue := value.(type) { switch xValue := value.(type) {
case []string: case []string:
for _, v := range xValue { for _, v := range xValue {
if injectionutils.DetectXSSCache(v, this.cacheLife) { if injectionutils.DetectXSSCache(v, isStrict, this.cacheLife) {
return true return true
} }
} }
return false return false
case [][]byte: case [][]byte:
for _, v := range xValue { for _, v := range xValue {
if injectionutils.DetectXSSCache(string(v), this.cacheLife) { if injectionutils.DetectXSSCache(string(v), isStrict, this.cacheLife) {
return true return true
} }
} }
return false return false
default: default:
return injectionutils.DetectXSSCache(this.stringifyValue(value), this.cacheLife) return injectionutils.DetectXSSCache(this.stringifyValue(value), isStrict, this.cacheLife)
} }
case RuleOperatorContainsBinary: case RuleOperatorContainsBinary:
data, _ := base64.StdEncoding.DecodeString(this.stringifyValue(this.Value)) data, _ := base64.StdEncoding.DecodeString(this.stringifyValue(this.Value))

View File

@@ -27,6 +27,7 @@ const (
RuleOperatorNotContainsAnyWord RuleOperator = "not contains any word" RuleOperatorNotContainsAnyWord RuleOperator = "not contains any word"
RuleOperatorContainsSQLInjection RuleOperator = "contains sql injection" RuleOperatorContainsSQLInjection RuleOperator = "contains sql injection"
RuleOperatorContainsXSS RuleOperator = "contains xss" RuleOperatorContainsXSS RuleOperator = "contains xss"
RuleOperatorContainsXSSStrictly RuleOperator = "contains xss strictly"
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"