CVE-2020-36627 (Go, published 2022-12-25T11:15Z)
Description: value: A vulnerability was found in Macaron i18n. It has been declared as problematic. Affected by this vulnerability is an unknown functionality of the file i18n.go. The manipulation leads to open redirect. The attack can be launched remotely. Upgrading to version 0.5.0 is able to address this issue. The name of the patch is 329b0c4844cc16a5a253c011b55180598e707735. It is recommended to upgrade the affected component. The identifier VDB-216745 was assigned to this vulnerability.
Severity: nan (CVSS v3 base score: 6.1)
Weakness: CWE-601 \u2014 URL Redirection to Untrusted Site ('Open Redirect')
A web application accepts a user-controlled input that specifies a link to an external site, and uses that link in a Redirect. This simplifies phishing attacks.
Repository: https://github.com/go-macaron/i18n
Files changed:
.gitignore
go.mod
go.sum
i18n.go
i18n_test.go
Commit message: security: fix Open Redirection vulnerability


Vulnerable code:
	github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337
	github.com/unknwon/com v0.0.0-20190804042917-757f69c95f3e
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg=
github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304 h1:Jpy1PXuP99tXNrhbq2BaPz9B+jNAvH1JPQQpG/9GCXY=
	"github.com/unknwon/com"
const _VERSION = "0.4.1"

func Version() string {
	return _VERSION
		if com.IsFile(customPath) {
			ctx.Redirect(opt.SubURL + ctx.Req.RequestURI[:strings.Index(ctx.Req.RequestURI, "?")])
	. "github.com/smartystreets/goconvey/convey"
func Test_Version(t *testing.T) {
	Convey("Check package version", t, func() {
		So(Version(), ShouldEqual, _VERSION)
	})
}

func Test_I18n(t *testing.T) {
	Convey("Use i18n middleware", t, func() {
		Convey("No langauge", func() {
			defer func() {
				So(recover(), ShouldNotBeNil)
			}()
			m := macaron.New()
			m.Use(I18n(Options{}))
		})
		Convey("Languages and names not match", func() {
			defer func() {
				So(recover(), ShouldNotBeNil)
			}()
			m := macaron.New()
			m.Use(I18n(Options{
				Langs: []string{"en-US"},
			}))
		})
		Convey("Invalid directory", func() {
			defer func() {
				So(recover(), ShouldNotBeNil)
			}()

			m := macaron.New()
			m.Use(I18n(Options{
				Directory: "404",
				Langs:     []string{"en-US"},
				Names:     []string{"English"},
			}))
		})
		Convey("With correct options", func() {
			m := macaron.New()
			m.Use(I18n(Options{
				Files: map[string][]byte{"locale_en-US.ini": []byte("")},
				Langs: []string{"en-US"},
				Names: []string{"English"},
			}))
			m.Get("/", func() {})
			resp := httptest.NewRecorder()
			req, err := http.NewRequest("GET", "/", nil)
			So(err, ShouldBeNil)
			m.ServeHTTP(resp, req)
		Convey("Set by redirect of URL parameter", func() {
			m := macaron.New()
			m.Use(I18n(Options{
				Langs:    []string{"en-US"},
				Names:    []string{"English"},
				Redirect: true,
			}))
			m.Get("/", func() {})
			resp := httptest.NewRecorder()
			req, err := http.NewRequest("GET", "/?lang=en-US", nil)
			So(err, ShouldBeNil)
			req.RequestURI = "/?lang=en-US"
			m.ServeHTTP(resp, req)
		Convey("Set by Accept-Language", func() {
			m := macaron.New()
			m.Use(I18n(Options{
				Langs: []string{"en-US", "zh-CN", "it-IT"},
				Names: []string{"English", "简体中文", "Italiano"},
			}))
			m.Get("/", func(l Locale) {
				So(l.Language(), ShouldEqual, "it-IT")
			})
			req, err := http.NewRequest("GET", "/", nil)
			So(err, ShouldBeNil)
			req.Header.Set("Accept-Language", "it")
		})

		Convey("Set to default language", func() {
			m := macaron.New()
			m.Use(I18n(Options{
				Langs: []string{"en-US", "zh-CN", "it-IT"},
				Names: []string{"English", "简体中文", "Italiano"},
			}))
			m.Get("/", func(l Locale) {
				So(l.Language(), ShouldEqual, "en-US")
			})
			resp := httptest.NewRecorder()
			req, err := http.NewRequest("GET", "/", nil)
			So(err, ShouldBeNil)
			req.Header.Set("Accept-Language", "ru")
			m.ServeHTTP(resp, req)
	})

Fixed code:
/.idea
	github.com/stretchr/testify v1.4.0
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
	"os"
// isFile returns true if given path is a file,
// or returns false when it's a directory or does not exist.
func isFile(filePath string) bool {
	f, e := os.Stat(filePath)
	if e != nil {
		return false
	}
	return !f.IsDir()
		if isFile(customPath) {
			ctx.Redirect(opt.SubURL + path.Clean(ctx.Req.RequestURI[:strings.Index(ctx.Req.RequestURI, "?")]))
	"errors"
	"github.com/stretchr/testify/assert"
func TestI18n(t *testing.T) {
	t.Run("no language", func(t *testing.T) {
		defer func() {
			assert.Equal(t, "no language is specified", recover())
		}()
		m := macaron.New()
		m.Use(I18n(Options{}))
	})
	t.Run("languages and names not match", func(t *testing.T) {
		defer func() {
			assert.Equal(t, "length of langs is not same as length of names", recover())
		}()
		m := macaron.New()
		m.Use(I18n(Options{
			Langs: []string{"en-US"},
		}))
	})
	t.Run("invalid directory", func(t *testing.T) {
		defer func() {
			assert.Equal(t, errors.New("fail to set message file(en-US): open 404/locale_en-US.ini: no such file or directory"), recover())
		}()

		m := macaron.New()
		m.Use(I18n(Options{
			Directory: "404",
			Langs:     []string{"en-US"},
			Names:     []string{"English"},
		}))
	})
	t.Run("with correct options", func(t *testing.T) {
		m := macaron.New()
		m.Use(I18n(Options{
			Files: map[string][]byte{"locale_en-US.ini": []byte("")},
			Langs: []string{"en-US"},
			Names: []string{"English"},
		}))
		m.Get("/", func() {})

		resp := httptest.NewRecorder()
		req, err := http.NewRequest("GET", "/", nil)
		if err != nil {
			t.Fatal(err)
		}
		m.ServeHTTP(resp, req)
	})
	t.Run("set by Accept-Language", func(t *testing.T) {
		m := macaron.New()
		m.Use(I18n(Options{
			Langs: []string{"en-US", "zh-CN", "it-IT"},
			Names: []string{"English", "简体中文", "Italiano"},
		}))
		m.Get("/", func(l Locale) {
			assert.Equal(t, "it-IT", l.Language())
		resp := httptest.NewRecorder()
		req, err := http.NewRequest("GET", "/", nil)
		if err != nil {
			t.Fatal(err)
		}
		req.Header.Set("Accept-Language", "it")
		m.ServeHTTP(resp, req)
	})
	t.Run("set to default language", func(t *testing.T) {
		m := macaron.New()
		m.Use(I18n(Options{
			Langs: []string{"en-US", "zh-CN", "it-IT"},
			Names: []string{"English", "简体中文", "Italiano"},
		}))
		m.Get("/", func(l Locale) {
			assert.Equal(t, "en-US", l.Language())
		resp := httptest.NewRecorder()
		req, err := http.NewRequest("GET", "/", nil)
		if err != nil {
			t.Fatal(err)
		}
		req.Header.Set("Accept-Language", "ru")
		m.ServeHTTP(resp, req)
	})
}
func TestRedirect(t *testing.T) {
	m := macaron.New()
	m.Use(I18n(Options{
		Langs:    []string{"en-US"},
		Names:    []string{"English"},
		Redirect: true,
	}))
	m.Get("/", func() {})

	tests := []struct {
		url    string
		expURL string
	}{
		{
			url:    "/?lang=en-US",
			expURL: "/",
		}, {
			url:    "//example.com?lang=en-US",
			expURL: "/example.com",
		}, {
			url:    "/abc/../../../example.com?lang=en-US",
			expURL: "/example.com",
		}, {
			url:    "/../abc/../example.com?lang=en-US",
			expURL: "/example.com",
		},
	}
	for _, test := range tests {
		t.Run("", func(t *testing.T) {
			req, err := http.NewRequest("GET", test.url, nil)
			if err != nil {
				t.Fatal(err)
			}
			req.RequestURI = test.url
			assert.Equal(t, http.StatusFound, resp.Code)
			assert.Equal(t, "<a href=\""+test.expURL+"\">Found</a>.\n\n", resp.Body.String())
	}