diff options
author | zeripath <art27@cantab.net> | 2021-03-15 21:52:11 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-03-15 17:52:11 -0400 |
commit | 6e423d5573c20b78d6e21cb044e8f4d5de5b288a (patch) | |
tree | 61d2e282bc652b8254271fdd9e19b87a386b5dc7 /modules/migrations | |
parent | f268b4896b1030761b28f1f8923d77d87adb8f0b (diff) | |
download | gitea-6e423d5573c20b78d6e21cb044e8f4d5de5b288a.tar.gz gitea-6e423d5573c20b78d6e21cb044e8f4d5de5b288a.zip |
Ensure validation occurs on clone addresses too (#14994)
* Ensure validation occurs on clone addresses too
Fix #14984
Signed-off-by: Andrew Thornton <art27@cantab.net>
* fix lint
Signed-off-by: Andrew Thornton <art27@cantab.net>
* fix test
Signed-off-by: Andrew Thornton <art27@cantab.net>
* Fix api tests
Signed-off-by: Andrew Thornton <art27@cantab.net>
Co-authored-by: techknowlogick <techknowlogick@gitea.io>
Diffstat (limited to 'modules/migrations')
-rw-r--r-- | modules/migrations/migrate.go | 59 | ||||
-rw-r--r-- | modules/migrations/migrate_test.go | 36 |
2 files changed, 71 insertions, 24 deletions
diff --git a/modules/migrations/migrate.go b/modules/migrations/migrate.go index 656b78a584..619b572a3f 100644 --- a/modules/migrations/migrate.go +++ b/modules/migrations/migrate.go @@ -10,6 +10,7 @@ import ( "fmt" "net" "net/url" + "path/filepath" "strings" "code.gitea.io/gitea/models" @@ -17,6 +18,7 @@ import ( "code.gitea.io/gitea/modules/matchlist" "code.gitea.io/gitea/modules/migrations/base" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/util" ) // MigrateOptions is equal to base.MigrateOptions @@ -34,39 +36,60 @@ func RegisterDownloaderFactory(factory base.DownloaderFactory) { factories = append(factories, factory) } -func isMigrateURLAllowed(remoteURL string) error { +// IsMigrateURLAllowed checks if an URL is allowed to be migrated from +func IsMigrateURLAllowed(remoteURL string, doer *models.User) error { + // Remote address can be HTTP/HTTPS/Git URL or local path. u, err := url.Parse(strings.ToLower(remoteURL)) if err != nil { - return err + return &models.ErrInvalidCloneAddr{IsURLError: true} } - if strings.EqualFold(u.Scheme, "http") || strings.EqualFold(u.Scheme, "https") { - if len(setting.Migrations.AllowedDomains) > 0 { - if !allowList.Match(u.Host) { - return &models.ErrMigrationNotAllowed{Host: u.Host} - } - } else { - if blockList.Match(u.Host) { - return &models.ErrMigrationNotAllowed{Host: u.Host} - } + if u.Scheme == "file" || u.Scheme == "" { + if !doer.CanImportLocal() { + return &models.ErrInvalidCloneAddr{Host: "<LOCAL_FILESYSTEM>", IsPermissionDenied: true, LocalPath: true} + } + isAbs := filepath.IsAbs(u.Host + u.Path) + if !isAbs { + return &models.ErrInvalidCloneAddr{Host: "<LOCAL_FILESYSTEM>", IsInvalidPath: true, LocalPath: true} + } + isDir, err := util.IsDir(u.Host + u.Path) + if err != nil { + log.Error("Unable to check if %s is a directory: %v", u.Host+u.Path, err) + return err + } + if !isDir { + return &models.ErrInvalidCloneAddr{Host: "<LOCAL_FILESYSTEM>", IsInvalidPath: true, LocalPath: true} } + + return nil + } + + if u.Scheme == "git" && u.Port() != "" && (strings.Contains(remoteURL, "%0d") || strings.Contains(remoteURL, "%0a")) { + return &models.ErrInvalidCloneAddr{Host: u.Host, IsURLError: true} } - if u.Host == "" { - if !setting.ImportLocalPaths { - return &models.ErrMigrationNotAllowed{Host: "<LOCAL_FILESYSTEM>"} + if u.Opaque != "" || u.Scheme != "" && u.Scheme != "http" && u.Scheme != "https" && u.Scheme != "git" { + return &models.ErrInvalidCloneAddr{Host: u.Host, IsProtocolInvalid: true, IsPermissionDenied: true, IsURLError: true} + } + + if len(setting.Migrations.AllowedDomains) > 0 { + if !allowList.Match(u.Host) { + return &models.ErrInvalidCloneAddr{Host: u.Host, IsPermissionDenied: true} + } + } else { + if blockList.Match(u.Host) { + return &models.ErrInvalidCloneAddr{Host: u.Host, IsPermissionDenied: true} } - return nil } if !setting.Migrations.AllowLocalNetworks { addrList, err := net.LookupIP(strings.Split(u.Host, ":")[0]) if err != nil { - return &models.ErrMigrationNotAllowed{Host: u.Host, NotResolvedIP: true} + return &models.ErrInvalidCloneAddr{Host: u.Host, NotResolvedIP: true} } for _, addr := range addrList { if isIPPrivate(addr) || !addr.IsGlobalUnicast() { - return &models.ErrMigrationNotAllowed{Host: u.Host, PrivateNet: addr.String()} + return &models.ErrInvalidCloneAddr{Host: u.Host, PrivateNet: addr.String(), IsPermissionDenied: true} } } } @@ -76,7 +99,7 @@ func isMigrateURLAllowed(remoteURL string) error { // MigrateRepository migrate repository according MigrateOptions func MigrateRepository(ctx context.Context, doer *models.User, ownerName string, opts base.MigrateOptions) (*models.Repository, error) { - err := isMigrateURLAllowed(opts.CloneAddr) + err := IsMigrateURLAllowed(opts.CloneAddr, doer) if err != nil { return nil, err } diff --git a/modules/migrations/migrate_test.go b/modules/migrations/migrate_test.go index e8b71bb325..be119d32d3 100644 --- a/modules/migrations/migrate_test.go +++ b/modules/migrations/migrate_test.go @@ -5,41 +5,65 @@ package migrations import ( + "path/filepath" "testing" + "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/setting" "github.com/stretchr/testify/assert" ) func TestMigrateWhiteBlocklist(t *testing.T) { + assert.NoError(t, models.PrepareTestDatabase()) + + adminUser := models.AssertExistsAndLoadBean(t, &models.User{Name: "user1"}).(*models.User) + nonAdminUser := models.AssertExistsAndLoadBean(t, &models.User{Name: "user2"}).(*models.User) + setting.Migrations.AllowedDomains = []string{"github.com"} assert.NoError(t, Init()) - err := isMigrateURLAllowed("https://gitlab.com/gitlab/gitlab.git") + err := IsMigrateURLAllowed("https://gitlab.com/gitlab/gitlab.git", nonAdminUser) assert.Error(t, err) - err = isMigrateURLAllowed("https://github.com/go-gitea/gitea.git") + err = IsMigrateURLAllowed("https://github.com/go-gitea/gitea.git", nonAdminUser) assert.NoError(t, err) setting.Migrations.AllowedDomains = []string{} setting.Migrations.BlockedDomains = []string{"github.com"} assert.NoError(t, Init()) - err = isMigrateURLAllowed("https://gitlab.com/gitlab/gitlab.git") + err = IsMigrateURLAllowed("https://gitlab.com/gitlab/gitlab.git", nonAdminUser) assert.NoError(t, err) - err = isMigrateURLAllowed("https://github.com/go-gitea/gitea.git") + err = IsMigrateURLAllowed("https://github.com/go-gitea/gitea.git", nonAdminUser) + assert.Error(t, err) + + err = IsMigrateURLAllowed("https://10.0.0.1/go-gitea/gitea.git", nonAdminUser) assert.Error(t, err) + setting.Migrations.AllowLocalNetworks = true + err = IsMigrateURLAllowed("https://10.0.0.1/go-gitea/gitea.git", nonAdminUser) + assert.NoError(t, err) + old := setting.ImportLocalPaths setting.ImportLocalPaths = false - err = isMigrateURLAllowed("/home/foo/bar/goo") + err = IsMigrateURLAllowed("/home/foo/bar/goo", adminUser) assert.Error(t, err) setting.ImportLocalPaths = true - err = isMigrateURLAllowed("/home/foo/bar/goo") + abs, err := filepath.Abs(".") + assert.NoError(t, err) + + err = IsMigrateURLAllowed(abs, adminUser) + assert.NoError(t, err) + + err = IsMigrateURLAllowed(abs, nonAdminUser) + assert.Error(t, err) + + nonAdminUser.AllowImportLocal = true + err = IsMigrateURLAllowed(abs, nonAdminUser) assert.NoError(t, err) setting.ImportLocalPaths = old |