CVE-2023-26046: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: teler-waf is a Go HTTP middleware that provides teler IDS functionality to protect against web-based attacks. In teler-waf prior to version 0.1.1 is vulnerable to bypassing common web attack rules when a specific HTML entities payload is used. This vulnerability allows an attacker to execute arbitrary JavaScript code on the victim's browser and compromise the security of the web application. The vulnerability exists due to teler-waf failure to properly sanitize and filter HTML entities in user input. An attacker can exploit this vulnerability to bypass common web attack threat rules in teler-waf and launch cross-site scripting (XSS) attacks. The attacker can execute arbitrary JavaScript code on the victim's browser and steal sensitive information, such as login credentials and session tokens, or take control of the victim's browser and perform malicious actions. This issue has been fixed in version 0.1.1.


CWE description: The product does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users.
CVSS v3 base score: 6.1
Published: 2023-03-02T01:15Z
Repository: https://github.com/kitabisa/teler-waf
Language: Go
Files changed: README.md, analyze.go, utils.go
Commit date: 2023-01-11 10:17:56 +0700
Commit message: feat: unescape any HTML entities

Any HTML entities of URL-decoded characters should be unescape before
perform the checks.

$ curl localhost:3000 -so /dev/null -w "%{http_code}\n" -A X -d "body=%22autofocus%20onFocUs=%27%26%2397%3blert()%27"
403

Diff: diff --git a/README.md b/README.md
index c84727b..2882488 100644
--- a/README.md
+++ b/README.md
@@ -192,26 +192,26 @@ Here are some limitations of using teler-waf:
 $ go test -bench . -cpu=4
 goos: linux
 goarch: amd64
 pkg: github.com/kitabisa/teler-waf
 cpu: 11th Gen Intel(R) Core(TM) i9-11900H @ 2.50GHz
-BenchmarkTelerDefaultOptions-4               	    4530	    265197 ns/op	   35710 B/op	    1690 allocs/op
-BenchmarkTelerCommonWebAttackOnly-4          	   32484	     35325 ns/op	    5949 B/op	     118 allocs/op
-BenchmarkTelerCVEOnly-4                      	    6248	    187397 ns/op	   33402 B/op	    1647 allocs/op
-BenchmarkTelerBadIPAddressOnly-4             	   20649	     54890 ns/op	    5974 B/op	      86 allocs/op
-BenchmarkTelerBadReferrerOnly-4              	   48594	     22629 ns/op	    5548 B/op	      87 allocs/op
-BenchmarkTelerBadCrawlerOnly-4               	   41832	     26891 ns/op	    5634 B/op	      85 allocs/op
-BenchmarkTelerDirectoryBruteforceOnly-4      	   48087	     22008 ns/op	    5554 B/op	      84 allocs/op
-BenchmarkTelerCustomRule-4                   	   50428	     21523 ns/op	    5323 B/op	      84 allocs/op
-BenchmarkTelerWithoutCommonWebAttack-4       	    5133	    230608 ns/op	   34619 B/op	    1654 allocs/op
-BenchmarkTelerWithoutCVE-4                   	   15229	     75995 ns/op	    7169 B/op	     124 allocs/op
-BenchmarkTelerWithoutBadIPAddress-4          	    5677	    211478 ns/op	   34602 B/op	    1685 allocs/op
-BenchmarkTelerWithoutBadReferrer-4           	    4875	    240689 ns/op	   35127 B/op	    1684 allocs/op
-BenchmarkTelerWithoutBadCrawler-4            	    4922	    238995 ns/op	   35000 B/op	    1686 allocs/op
-BenchmarkTelerWithoutDirectoryBruteforce-4   	    4894	    242973 ns/op	   35241 B/op	    1687 allocs/op
+BenchmarkTelerDefaultOptions-4               	    4396	    266918 ns/op	   35944 B/op	    1696 allocs/op
+BenchmarkTelerCommonWebAttackOnly-4          	   30795	     35602 ns/op	    5990 B/op	     118 allocs/op
+BenchmarkTelerCVEOnly-4                      	    6171	    194193 ns/op	   33533 B/op	    1652 allocs/op
+BenchmarkTelerBadIPAddressOnly-4             	   20464	     55957 ns/op	    5986 B/op	      86 allocs/op
+BenchmarkTelerBadReferrerOnly-4              	   48403	     23128 ns/op	    5551 B/op	      87 allocs/op
+BenchmarkTelerBadCrawlerOnly-4               	   42002	     27165 ns/op	    5633 B/op	      85 allocs/op
+BenchmarkTelerDirectoryBruteforceOnly-4      	   50103	     23074 ns/op	    5535 B/op	      84 allocs/op
+BenchmarkTelerCustomRule-4                   	   49483	     22086 ns/op	    5332 B/op	      84 allocs/op
+BenchmarkTelerWithoutCommonWebAttack-4       	    5156	    228950 ns/op	   34683 B/op	    1658 allocs/op
+BenchmarkTelerWithoutCVE-4                   	   15295	     76501 ns/op	    7167 B/op	     124 allocs/op
+BenchmarkTelerWithoutBadIPAddress-4          	    5484	    216523 ns/op	   34820 B/op	    1691 allocs/op
+BenchmarkTelerWithoutBadReferrer-4           	    4894	    240202 ns/op	   35133 B/op	    1689 allocs/op
+BenchmarkTelerWithoutBadCrawler-4            	    5012	    239976 ns/op	   34995 B/op	    1691 allocs/op
+BenchmarkTelerWithoutDirectoryBruteforce-4   	    4736	    247549 ns/op	   35496 B/op	    1693 allocs/op
 PASS
-ok  	github.com/kitabisa/teler-waf	23.207s
+ok  	github.com/kitabisa/teler-waf	23.660s
 ```
 
 > **Note**: It's important to note that the benchmarking results may vary and may not be consistent. Those results were obtained when there were **>1.5k** CVE templates and the [teler-resources](https://github.com/kitabisa/teler-resources) dataset may have increased since then, which may impact the results.
 
 - **Configuration complexity**: Configuring teler-waf to suit the specific needs of your application can be complex, and may require a certain level of expertise in web security. This can make it difficult for those who are not familiar with application firewalls and IDS systems to properly set up and use teler-waf.
diff --git a/analyze.go b/analyze.go
index 1a88b0f..bf8c0ff 100644
--- a/analyze.go
+++ b/analyze.go
@@ -108,12 +108,12 @@ func (t *Teler) analyzeRequest(w http.ResponseWriter, r *http.Request) (threat.T
 // If no custom rules are violated, the function returns nil.
 func (t *Teler) checkCustomRules(r *http.Request) error {
 	// Converts map of headers to RAW string
 	headers := headersToRawString(r.Header)
 
-	// Decode the URL-encoded request URI of the URL
-	uri := toURLDecode(r.URL.RequestURI())
+	// Decode the URL-encoded and unescape HTML entities request URI of the URL
+	uri := stringDeUnescape(r.URL.RequestURI())
 
 	// Declare byte slice for request body.
 	var body string
 
 	// Initialize buffer to hold request body.
@@ -128,12 +128,12 @@ func (t *Teler) checkCustomRules(r *http.Request) error {
 
 		// Convert the buffer to a string.
 		body = buf.String()
 	}
 
-	// Decode the URL-encoded of body
-	body = toURLDecode(body)
+	// Decode the URL-encoded and unescape HTML entities of body
+	body = stringDeUnescape(body)
 
 	// Iterate over the Customs field of the Teler struct, which is a slice of custom rules
 	for _, rule := range t.opt.Customs {
 		// Initialize the found match counter to zero
 		f := 0
@@ -199,12 +199,12 @@ func (t *Teler) checkCustomRules(r *http.Request) error {
 
 // checkCommonWebAttack checks if the request contains any patterns that match the common web attacks data.
 // If a match is found, it returns an error indicating a common web attack has been detected.
 // If no match is found, it returns nil.
 func (t *Teler) checkCommonWebAttack(r *http.Request) error {
-	// Decode the URL-encoded request URI of the URL
-	uri := toURLDecode(r.URL.RequestURI())
+	// Decode the URL-encoded and unescape HTML entities request URI of the URL
+	uri := stringDeUnescape(r.URL.RequestURI())
 
 	// Declare byte slice for request body.
 	var body string
 
 	// Initialize buffer to hold request body.
@@ -219,12 +219,12 @@ func (t *Teler) checkCommonWebAttack(r *http.Request) error {
 
 		// Convert the buffer to a string.
 		body = buf.String()
 	}
 
-	// Decode the URL-encoded of body
-	body = toURLDecode(body)
+	// Decode the URL-encoded and unescape HTML entities of body
+	body = stringDeUnescape(body)
 
 	// Iterate over the filters in the CommonWebAttack data stored in the t.threat.cwa.Filters field
 	for _, filter := range t.threat.cwa.Filters {
 		// Initialize a variable to track whether a match is found
 		var match bool
diff --git a/utils.go b/utils.go
index 8127b93..8ad55a9 100644
--- a/utils.go
+++ b/utils.go
@@ -1,9 +1,10 @@
 package teler
 
 import (
 	"fmt"
+	"html"
 	"strings"
 
 	"net/http"
 
 	"github.com/kitabisa/teler-waf/request"
@@ -67,15 +68,28 @@ func headersToRawString(headers http.Header) string {
 
 	// Returns the accumulated string of builder
 	return h.String()
 }
 
-// toURLDecode decode URL-decoded characters string using mdurl package
+// unescapeHTML to unescapes any HTML entities, i.e. &aacute;"
+// unescapes to "á", as does "&#225;" and "&#xE1;".
+func unescapeHTML(s string) string {
+	return html.UnescapeString(s)
+}
+
+// toURLDecode decode URL-decoded characters string using mdurl
 func toURLDecode(s string) string {
 	return mdurl.Decode(s)
 }
 
+// stringDeUnescape to decode URL-decoded characters, and
+// unescapes any HTML entities
+func stringDeUnescape(s string) string {
+	s = toURLDecode(s)
+	return unescapeHTML(s)
+}
+
 // isValidMethod check if the given request.Method is valid
 func isValidMethod(method request.Method) bool {
 	switch method {
 	case request.GET, request.HEAD, request.POST, request.PUT, request.PATCH:
 	case request.DELETE, request.CONNECT, request.OPTIONS, request.TRACE, request.ALL:

Vulnerable code: BenchmarkTelerDefaultOptions-4               	    4530	    265197 ns/op	   35710 B/op	    1690 allocs/op
BenchmarkTelerCommonWebAttackOnly-4          	   32484	     35325 ns/op	    5949 B/op	     118 allocs/op
BenchmarkTelerCVEOnly-4                      	    6248	    187397 ns/op	   33402 B/op	    1647 allocs/op
BenchmarkTelerBadIPAddressOnly-4             	   20649	     54890 ns/op	    5974 B/op	      86 allocs/op
BenchmarkTelerBadReferrerOnly-4              	   48594	     22629 ns/op	    5548 B/op	      87 allocs/op
BenchmarkTelerBadCrawlerOnly-4               	   41832	     26891 ns/op	    5634 B/op	      85 allocs/op
BenchmarkTelerDirectoryBruteforceOnly-4      	   48087	     22008 ns/op	    5554 B/op	      84 allocs/op
BenchmarkTelerCustomRule-4                   	   50428	     21523 ns/op	    5323 B/op	      84 allocs/op
BenchmarkTelerWithoutCommonWebAttack-4       	    5133	    230608 ns/op	   34619 B/op	    1654 allocs/op
BenchmarkTelerWithoutCVE-4                   	   15229	     75995 ns/op	    7169 B/op	     124 allocs/op
BenchmarkTelerWithoutBadIPAddress-4          	    5677	    211478 ns/op	   34602 B/op	    1685 allocs/op
BenchmarkTelerWithoutBadReferrer-4           	    4875	    240689 ns/op	   35127 B/op	    1684 allocs/op
BenchmarkTelerWithoutBadCrawler-4            	    4922	    238995 ns/op	   35000 B/op	    1686 allocs/op
BenchmarkTelerWithoutDirectoryBruteforce-4   	    4894	    242973 ns/op	   35241 B/op	    1687 allocs/op
ok  	github.com/kitabisa/teler-waf	23.207s
	// Decode the URL-encoded request URI of the URL
	uri := toURLDecode(r.URL.RequestURI())
	// Decode the URL-encoded of body
	body = toURLDecode(body)
	// Decode the URL-encoded request URI of the URL
	uri := toURLDecode(r.URL.RequestURI())
	// Decode the URL-encoded of body
	body = toURLDecode(body)
// toURLDecode decode URL-decoded characters string using mdurl package
Fixed code: BenchmarkTelerDefaultOptions-4               	    4396	    266918 ns/op	   35944 B/op	    1696 allocs/op
BenchmarkTelerCommonWebAttackOnly-4          	   30795	     35602 ns/op	    5990 B/op	     118 allocs/op
BenchmarkTelerCVEOnly-4                      	    6171	    194193 ns/op	   33533 B/op	    1652 allocs/op
BenchmarkTelerBadIPAddressOnly-4             	   20464	     55957 ns/op	    5986 B/op	      86 allocs/op
BenchmarkTelerBadReferrerOnly-4              	   48403	     23128 ns/op	    5551 B/op	      87 allocs/op
BenchmarkTelerBadCrawlerOnly-4               	   42002	     27165 ns/op	    5633 B/op	      85 allocs/op
BenchmarkTelerDirectoryBruteforceOnly-4      	   50103	     23074 ns/op	    5535 B/op	      84 allocs/op
BenchmarkTelerCustomRule-4                   	   49483	     22086 ns/op	    5332 B/op	      84 allocs/op
BenchmarkTelerWithoutCommonWebAttack-4       	    5156	    228950 ns/op	   34683 B/op	    1658 allocs/op
BenchmarkTelerWithoutCVE-4                   	   15295	     76501 ns/op	    7167 B/op	     124 allocs/op
BenchmarkTelerWithoutBadIPAddress-4          	    5484	    216523 ns/op	   34820 B/op	    1691 allocs/op
BenchmarkTelerWithoutBadReferrer-4           	    4894	    240202 ns/op	   35133 B/op	    1689 allocs/op
BenchmarkTelerWithoutBadCrawler-4            	    5012	    239976 ns/op	   34995 B/op	    1691 allocs/op
BenchmarkTelerWithoutDirectoryBruteforce-4   	    4736	    247549 ns/op	   35496 B/op	    1693 allocs/op
ok  	github.com/kitabisa/teler-waf	23.660s
	// Decode the URL-encoded and unescape HTML entities request URI of the URL
	uri := stringDeUnescape(r.URL.RequestURI())
	// Decode the URL-encoded and unescape HTML entities of body
	body = stringDeUnescape(body)
	// Decode the URL-encoded and unescape HTML entities request URI of the URL
	uri := stringDeUnescape(r.URL.RequestURI())
	// Decode the URL-encoded and unescape HTML entities of body
	body = stringDeUnescape(body)
	"html"
// unescapeHTML to unescapes any HTML entities, i.e. &aacute;"
// unescapes to "á", as does "&#225;" and "&#xE1;".
func unescapeHTML(s string) string {
	return html.UnescapeString(s)
}

// toURLDecode decode URL-decoded characters string using mdurl
// stringDeUnescape to decode URL-decoded characters, and
// unescapes any HTML entities
func stringDeUnescape(s string) string {
	s = toURLDecode(s)
	return unescapeHTML(s)
}