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
*
*/
int libinjection_xss(const char* s, size_t slen);
int libinjection_xss(const char* s, size_t slen, int strictMode);
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 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;
}

View File

@@ -13,7 +13,7 @@ extern "C" {
#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
}

View File

@@ -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
#define GOEDGE_VERSION "24" // last version is for GoEdge change

View File

@@ -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
}

View File

@@ -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("<script>"))
a.IsTrue(injectionutils.DetectXSS("<link>"))
a.IsFalse(injectionutils.DetectXSS("<html><span>"))
a.IsFalse(injectionutils.DetectXSS("&lt;script&gt;"))
a.IsTrue(injectionutils.DetectXSS("/path?onmousedown=a"))
a.IsTrue(injectionutils.DetectXSS("/path?onkeyup=a"))
a.IsTrue(injectionutils.DetectXSS("onkeyup=a"))
a.IsTrue(injectionutils.DetectXSS("<iframe scrolling='no'>"))
a.IsFalse(injectionutils.DetectXSS("<html><body><span>RequestId: 1234567890</span></body></html>"))
a.IsTrue(injectionutils.DetectXSS("name=s&description=%3Cscript+src%3D%22a.js%22%3Edddd%3C%2Fscript%3E"))
a.IsFalse(injectionutils.DetectXSS("", true))
a.IsFalse(injectionutils.DetectXSS("abc", true))
a.IsTrue(injectionutils.DetectXSS("<script>", true))
a.IsTrue(injectionutils.DetectXSS("<link>", true))
a.IsFalse(injectionutils.DetectXSS("<html><span>", true))
a.IsFalse(injectionutils.DetectXSS("&lt;script&gt;", true))
a.IsTrue(injectionutils.DetectXSS("/path?onmousedown=a", true))
a.IsTrue(injectionutils.DetectXSS("/path?onkeyup=a", true))
a.IsTrue(injectionutils.DetectXSS("onkeyup=a", true))
a.IsTrue(injectionutils.DetectXSS("<iframe scrolling='no'>", true))
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", true))
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:Description rdf:about=""
@@ -31,11 +31,21 @@ func TestDetectXSS(t *testing.T) {
<tiff:Orientation>1</tiff:Orientation>
</rdf:Description>
</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) {
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 {
b.Fatal("'result' should not be 'true'")
}
@@ -44,13 +54,13 @@ func BenchmarkDetectXSS_MISS(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
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) {
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 {
b.Fatal("'result' should not be 'true'")
}
@@ -59,13 +69,13 @@ func BenchmarkDetectXSS_MISS_Cache(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
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) {
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 {
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("<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:
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))

View File

@@ -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"