Compare commits

...

3 Commits

Author SHA1 Message Date
E99p1ant
b215ba6788 remove Err_CloneAddr
Signed-off-by: E99p1ant <i@github.red>
2026-04-04 15:07:27 +08:00
E99p1ant
e00e9af33a update changelog
Signed-off-by: E99p1ant <i@github.red>
2026-04-04 14:48:09 +08:00
E99p1ant
a6a16575d8 repository: validate remote address on mirror address update
Signed-off-by: E99p1ant <i@github.red>
2026-04-04 14:41:00 +08:00
5 changed files with 55 additions and 8 deletions

View File

@@ -4,6 +4,10 @@ All notable changes to Gogs are documented in this file.
## 0.15.0+dev (`main`)
### Fixed
- _Security:_ SSRF via mirror address update bypassing clone address validation.
### Removed
- The `gogs cert` subcommand. [#8153](https://github.com/gogs/gogs/pull/8153)

View File

@@ -52,12 +52,19 @@ func (f *MigrateRepo) Validate(ctx *macaron.Context, errs binding.Errors) bindin
return validate(errs, ctx.Data, f, ctx.Locale)
}
type ParseRemoteAddrOptions struct {
CloneAddr string
User *database.User
AuthUsername string
AuthPassword string
}
// ParseRemoteAddr checks if given remote address is valid,
// and returns composed URL with needed username and password.
// It also checks if given user has permission when remote address
// is actually a local path.
func (f MigrateRepo) ParseRemoteAddr(user *database.User) (string, error) {
remoteAddr := strings.TrimSpace(f.CloneAddr)
func ParseRemoteAddr(options ParseRemoteAddrOptions) (string, error) {
remoteAddr := strings.TrimSpace(options.CloneAddr)
// Remote address can be HTTP/HTTPS/Git URL or local path.
if strings.HasPrefix(remoteAddr, "http://") ||
@@ -72,15 +79,15 @@ func (f MigrateRepo) ParseRemoteAddr(user *database.User) (string, error) {
return "", database.ErrInvalidCloneAddr{IsBlockedLocalAddress: true}
}
if len(f.AuthUsername)+len(f.AuthPassword) > 0 {
u.User = url.UserPassword(f.AuthUsername, f.AuthPassword)
if len(options.AuthUsername)+len(options.AuthPassword) > 0 {
u.User = url.UserPassword(options.AuthUsername, options.AuthPassword)
}
// To prevent CRLF injection in git protocol, see https://github.com/gogs/gogs/issues/6413
if u.Scheme == "git" && (strings.Contains(remoteAddr, "%0d") || strings.Contains(remoteAddr, "%0a")) {
return "", database.ErrInvalidCloneAddr{IsURLError: true}
}
remoteAddr = u.String()
} else if !user.CanImportLocal() {
} else if !options.User.CanImportLocal() {
return "", database.ErrInvalidCloneAddr{IsPermissionDenied: true}
} else if !osx.IsDir(remoteAddr) {
return "", database.ErrInvalidCloneAddr{IsInvalidPath: true}

View File

@@ -251,7 +251,12 @@ func migrate(c *context.APIContext, f form.MigrateRepo) {
}
}
remoteAddr, err := f.ParseRemoteAddr(c.User)
remoteAddr, err := form.ParseRemoteAddr(form.ParseRemoteAddrOptions{
CloneAddr: f.CloneAddr,
User: c.User,
AuthUsername: f.AuthUsername,
AuthPassword: f.AuthPassword,
})
if err != nil {
if database.IsErrInvalidCloneAddr(err) {
addrErr := err.(database.ErrInvalidCloneAddr)

View File

@@ -170,7 +170,12 @@ func MigratePost(c *context.Context, f form.MigrateRepo) {
return
}
remoteAddr, err := f.ParseRemoteAddr(c.User)
remoteAddr, err := form.ParseRemoteAddr(form.ParseRemoteAddrOptions{
CloneAddr: f.CloneAddr,
User: c.User,
AuthUsername: f.AuthUsername,
AuthPassword: f.AuthPassword,
})
if err != nil {
if database.IsErrInvalidCloneAddr(err) {
c.Data["Err_CloneAddr"] = true

View File

@@ -121,7 +121,33 @@ func SettingsPost(c *context.Context, f form.RepoSetting) {
return
}
}
if err := c.Repo.Mirror.SaveAddress(f.MirrorAddress); err != nil {
remoteAddr, err := form.ParseRemoteAddr(form.ParseRemoteAddrOptions{
CloneAddr: f.MirrorAddress,
User: c.User,
})
if err != nil {
if database.IsErrInvalidCloneAddr(err) {
addrErr := err.(database.ErrInvalidCloneAddr)
switch {
case addrErr.IsURLError:
c.RenderWithErr(c.Tr("repo.migrate.clone_address")+c.Tr("form.url_error"), http.StatusBadRequest, tmplRepoSettingsOptions, &f)
case addrErr.IsPermissionDenied:
c.RenderWithErr(c.Tr("repo.migrate.permission_denied"), http.StatusForbidden, tmplRepoSettingsOptions, &f)
case addrErr.IsInvalidPath:
c.RenderWithErr(c.Tr("repo.migrate.invalid_local_path"), http.StatusBadRequest, tmplRepoSettingsOptions, &f)
case addrErr.IsBlockedLocalAddress:
c.RenderWithErr(c.Tr("repo.migrate.clone_address_resolved_to_blocked_local_address"), http.StatusForbidden, tmplRepoSettingsOptions, &f)
default:
c.Error(err, "unexpected error")
}
} else {
c.Error(err, "parse remote address")
}
return
}
if err := c.Repo.Mirror.SaveAddress(remoteAddr); err != nil {
c.Error(err, "save address")
return
}