CVE-2022-1996 (Go, published 2022-06-08T13:15Z)
Description: value: Authorization Bypass Through User-Controlled Key in GitHub repository emicklei/go-restful prior to v3.8.0.
Severity: MEDIUM (CVSS v3 base score: 9.1)
Weakness: CWE-639 \u2014 Authorization Bypass Through User-Controlled Key
The system's authorization functionality does not prevent one user from gaining access to another user's data or record by modifying the key value identifying the data.
Repository: https://github.com/emicklei/go-restful
Files changed:
cors_filter.go
cors_filter_test.go
Commit message: use exact matching of allowed domain entries, issue #489 (#493)

* use exact matching of allowed domain entries, issue #489

* update doc, add testcases from PR conversation

* introduce AllowedDomainFunc #489

* more tests, fix doc

* lowercase origin before checking cors

Vulnerable code:
	ExposeHeaders  []string // list of Header names
	AllowedHeaders []string // list of Header names
	AllowedDomains []string // list of allowed values for Http Origin. An allowed value can be a regular expression to support subdomain matching. If empty all are allowed.
	allowed := false
		if domain == origin {
			allowed = true
			break

	if !allowed {
		if len(c.allowedOriginPatterns) == 0 {
			// compile allowed domains to allowed origin patterns
			allowedOriginRegexps, err := compileRegexps(c.AllowedDomains)
			if err != nil {
				return false
			}
			c.allowedOriginPatterns = allowedOriginRegexps
		}

		for _, pattern := range c.allowedOriginPatterns {
			if allowed = pattern.MatchString(origin); allowed {
				break
			}
		}

	return allowed

// Take a list of strings and compile them into a list of regular expressions.
func compileRegexps(regexpStrings []string) ([]*regexp.Regexp, error) {
	regexps := []*regexp.Regexp{}
	for _, regexpStr := range regexpStrings {
		r, err := regexp.Compile(regexpStr)
		if err != nil {
			return regexps, err
		}
		regexps = append(regexps, r)
	}
	return regexps, nil
}
			t.Fatal("expected to be accepted")
			t.Fatal("did not expect to be accepted")

Fixed code:
	ExposeHeaders []string // list of Header names

	// AllowedHeaders is alist of Header names. Checking is case-insensitive.
	// The list may contain the special wildcard string ".*" ; all is allowed
	AllowedHeaders []string

	// AllowedDomains is a list of allowed values for Http Origin.
	// The list may contain the special wildcard string ".*" ; all is allowed
	// If empty all are allowed.
	AllowedDomains []string

	// AllowedDomainFunc is optional and is a function that will do the check
	// when the origin is not part of the AllowedDomains and it does not contain the wildcard ".*".
	AllowedDomainFunc func(origin string) bool

	// AllowedMethods is either empty or has a list of http methods names. Checking is case-insensitive.
	lowerOrigin := strings.ToLower(origin)
		if c.AllowedDomainFunc != nil {
			return c.AllowedDomainFunc(lowerOrigin)
		}
	// exact match on each allowed domain
		if domain == ".*" || strings.ToLower(domain) == lowerOrigin {
			return true
	if c.AllowedDomainFunc != nil {
		return c.AllowedDomainFunc(origin)
	return false
			t.Error("expected to be accepted", each)
			t.Error("did not expect to be accepted")

func TestCORSFilter_AllowedDomainFunc(t *testing.T) {
	cors := CrossOriginResourceSharing{
		AllowedDomains: []string{"here", "there"},
		AllowedDomainFunc: func(origin string) bool {
			return "where" == origin
		},
	}
	if got, want := cors.isOriginAllowed("here"), true; got != want {
		t.Errorf("got [%v:%T] want [%v:%T]", got, got, want, want)
	}
	if got, want := cors.isOriginAllowed("HERE"), true; got != want {
		t.Errorf("got [%v:%T] want [%v:%T]", got, got, want, want)
	}
	if got, want := cors.isOriginAllowed("there"), true; got != want {
		t.Errorf("got [%v:%T] want [%v:%T]", got, got, want, want)
	}
	if got, want := cors.isOriginAllowed("where"), true; got != want {
		t.Errorf("got [%v:%T] want [%v:%T]", got, got, want, want)
	}
	if got, want := cors.isOriginAllowed("nowhere"), false; got != want {
		t.Errorf("got [%v:%T] want [%v:%T]", got, got, want, want)
	}
	// just func
	cors.AllowedDomains = []string{}
	if got, want := cors.isOriginAllowed("here"), false; got != want {
		t.Errorf("got [%v:%T] want [%v:%T]", got, got, want, want)
	}
	if got, want := cors.isOriginAllowed("where"), true; got != want {
		t.Errorf("got [%v:%T] want [%v:%T]", got, got, want, want)
	}
	// empty domain
	if got, want := cors.isOriginAllowed(""), false; got != want {
		t.Errorf("got [%v:%T] want [%v:%T]", got, got, want, want)
	}
}