CVE-2021-30080: Insufficient Information (NVD-CWE-noinfo)
value: An issue was discovered in the route lookup process in beego before 1.12.11 that allows attackers to bypass access control.
CWE description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
CVSS v3 base score: 9.8
Published: 2022-04-05T16:15Z
Repository: https://github.com/beego/beego
Language: Go
Files changed: CHANGELOG.md, server/web/tree.go, server/web/tree_test.go
Commit date: 2021-01-26 00:18:41 +0800
Commit message: Fix BUG: /abc.html/aaa match /abc/aaa

Diff: diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6041f5bc..22c6d0ab 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,7 @@
 # developing
+- Fix: /abc.html/aaa match /abc/aaa. [4459](https://github.com/beego/beego/pull/4459)
 - ORM mock. [4407](https://github.com/beego/beego/pull/4407)
 - Add sonar check and ignore test. [4432](https://github.com/beego/beego/pull/4432) [4433](https://github.com/beego/beego/pull/4433)
 - Update changlog.yml to check every PR to develop branch.[4427](https://github.com/beego/beego/pull/4427)
 - Fix 4396: Add context.param module into adapter. [4398](https://github.com/beego/beego/pull/4398)
 - Remove `duration` from prometheus labels. [4391](https://github.com/beego/beego/pull/4391)
diff --git a/server/web/tree.go b/server/web/tree.go
index 5a765fd3..863d23ed 100644
--- a/server/web/tree.go
+++ b/server/web/tree.go
@@ -340,12 +340,13 @@ func (t *Tree) match(treePattern string, pattern string, wildcardValues []string
 		}
 	}
 	if runObject == nil && len(t.fixrouters) > 0 {
 		// Filter the .json .xml .html extension
 		for _, str := range allowSuffixExt {
-			if strings.HasSuffix(seg, str) {
+			if strings.HasSuffix(seg, str) && strings.HasSuffix(treePattern, seg){
 				for _, subTree := range t.fixrouters {
+					// strings.HasSuffix(treePattern, seg) avoid cases: /aaa.html/bbb could access /aaa/bbb
 					if subTree.prefix == seg[:len(seg)-len(str)] {
 						runObject = subTree.match(treePattern, pattern, wildcardValues, ctx)
 						if runObject != nil {
 							ctx.Input.SetParam(":ext", str[1:])
 						}
diff --git a/server/web/tree_test.go b/server/web/tree_test.go
index 43511ad8..0885ffe8 100644
--- a/server/web/tree_test.go
+++ b/server/web/tree_test.go
@@ -15,10 +15,11 @@
 package web
 
 import (
 	"strings"
 	"testing"
+	"time"
 
 	"github.com/beego/beego/v2/server/web/context"
 )
 
 type testInfo struct {
@@ -47,11 +48,11 @@ func notMatchTestInfo(pattern, url string) testInfo {
 		shouldMatchOrNot: false,
 	}
 }
 
 func init() {
-	routers = make([]testInfo, 0)
+	routers = make([]testInfo, 0, 128)
 	// match example
 	routers = append(routers, matchTestInfo("/topic/?:auth:int", "/topic", nil))
 	routers = append(routers, matchTestInfo("/topic/?:auth:int", "/topic/123", map[string]string{":auth": "123"}))
 	routers = append(routers, matchTestInfo("/topic/:id/?:auth", "/topic/1", map[string]string{":id": "1"}))
 	routers = append(routers, matchTestInfo("/topic/:id/?:auth", "/topic/1/2", map[string]string{":id": "1", ":auth": "2"}))
@@ -106,25 +107,36 @@ func init() {
 	// https://github.com/beego/beego/v2/issues/3865
 	routers = append(routers, notMatchTestInfo("/read_:id:int\\.htm", "/read_222htm"))
 	routers = append(routers, notMatchTestInfo("/read_:id:int\\.htm", "/read_222_htm"))
 	routers = append(routers, notMatchTestInfo("/read_:id:int\\.htm", " /read_262shtm"))
 
+	// test .html, .json not suffix
+	const abcHtml = "/suffix/abc.html"
+	routers = append(routers, notMatchTestInfo(abcHtml, "/suffix.html/abc"))
+	routers = append(routers, matchTestInfo("/suffix/abc", abcHtml, nil))
+	routers = append(routers, matchTestInfo("/suffix/*", abcHtml, nil))
+	routers = append(routers, notMatchTestInfo("/suffix/*", "/suffix.html/a"))
+	const abcSuffix = "/abc/suffix/*"
+	routers = append(routers, notMatchTestInfo(abcSuffix, "/abc/suffix.html/a"))
+	routers = append(routers, matchTestInfo(abcSuffix, "/abc/suffix/a", nil))
+	routers = append(routers, notMatchTestInfo(abcSuffix, "/abc.j/suffix/a"))
+
 }
 
 func TestTreeRouters(t *testing.T) {
 	for _, r := range routers {
-		shouldMatch := r.shouldMatchOrNot
 
+		shouldMatch := r.shouldMatchOrNot
 		tr := NewTree()
 		tr.AddRouter(r.pattern, "astaxie")
 		ctx := context.NewContext()
 		obj := tr.Match(r.requestUrl, ctx)
 		if !shouldMatch {
 			if obj != nil {
 				t.Fatal("pattern:", r.pattern, ", should not match", r.requestUrl)
 			} else {
-				return
+				continue
 			}
 		}
 		if obj == nil || obj.(string) != "astaxie" {
 			t.Fatal("pattern:", r.pattern+", can't match obj, Expect ", r.requestUrl)
 		}
@@ -136,10 +148,11 @@ func TestTreeRouters(t *testing.T) {
 					t.Fatal(r.pattern + "    " + r.requestUrl + " get param empty:" + k)
 				}
 			}
 		}
 	}
+	time.Sleep(time.Second)
 }
 
 func TestStaticPath(t *testing.T) {
 	tr := NewTree()
 	tr.AddRouter("/topic/:id", "wildcard")

Vulnerable code: 			if strings.HasSuffix(seg, str) {
	routers = make([]testInfo, 0)
		shouldMatch := r.shouldMatchOrNot
				return
Fixed code: - Fix: /abc.html/aaa match /abc/aaa. [4459](https://github.com/beego/beego/pull/4459)
			if strings.HasSuffix(seg, str) && strings.HasSuffix(treePattern, seg){
					// strings.HasSuffix(treePattern, seg) avoid cases: /aaa.html/bbb could access /aaa/bbb
	"time"
	routers = make([]testInfo, 0, 128)
	// test .html, .json not suffix
	const abcHtml = "/suffix/abc.html"
	routers = append(routers, notMatchTestInfo(abcHtml, "/suffix.html/abc"))
	routers = append(routers, matchTestInfo("/suffix/abc", abcHtml, nil))
	routers = append(routers, matchTestInfo("/suffix/*", abcHtml, nil))
	routers = append(routers, notMatchTestInfo("/suffix/*", "/suffix.html/a"))
	const abcSuffix = "/abc/suffix/*"
	routers = append(routers, notMatchTestInfo(abcSuffix, "/abc/suffix.html/a"))
	routers = append(routers, matchTestInfo(abcSuffix, "/abc/suffix/a", nil))
	routers = append(routers, notMatchTestInfo(abcSuffix, "/abc.j/suffix/a"))

		shouldMatch := r.shouldMatchOrNot
				continue
	time.Sleep(time.Second)