Vulnerability: Unrestricted Upload of File with Dangerous Type in Gogs ### Impact

The malicious user is able to upload a crafted `config` file into repository's `.git` directory with to gain SSH access to the server. All installations with [repository upload enabled (default)](https://github.com/gogs/gogs/blob/f36eeedbf89328ee70cc3a2e239f6314f9021f58/conf/app.ini#L127-L129) are affected.

### Patches

Repository file uploads are prohibited to its `.git` directory. Users should upgrade to 0.12.6 or the latest 0.13.0+dev.

### Workarounds

[Disable repository files upload](https://github.com/gogs/gogs/blob/f36eeedbf89328ee70cc3a2e239f6314f9021f58/conf/app.ini#L128-L129).

### References

https://huntr.dev/bounties/b4928cfe-4110-462f-a180-6d5673797902/

### For more information

If you have any questions or comments about this advisory, please post on #6833.
Vulnerability ID: GHSA-5gjh-5j4f-cpwv
CWE: CWE-434
Severity score: 8.8
Project: https://github.com/gogs/gogs
Published: 2022-03-28
File: internal/db/repo_editor.go
Commit message: repo_editor: check upload `TreePath` and file name (#6838)
Commit author: Joe Chen

Vulnerable version (before fix):
 import ( 	"strings" 	"time"  	gouuid "github.com/satori/go.uuid" 	"github.com/unknwon/com"  	"github.com/gogs/git-module"  	"gogs.io/gogs/internal/conf" 	"gogs.io/gogs/internal/cryptoutil" 	"gogs.io/gogs/internal/db/errors" 	"gogs.io/gogs/internal/gitutil" 	"gogs.io/gogs/internal/osutil" 	"gogs.io/gogs/internal/process" 	"gogs.io/gogs/internal/tool" ) func (repo *Repository) UpdateRepoFile(doer *User, opts UpdateRepoFileOptions) ( 	if opts.OldBranch != opts.NewBranch { 		// Directly return error if new branch already exists in the server 		if git.RepoHasBranch(repoPath, opts.NewBranch) { 			return errors.BranchAlreadyExists{Name: opts.NewBranch} 		}  		// Otherwise, delete branch from local copy in case out of sync func isRepositoryGitPath(path string) bool { 	return strings.HasSuffix(path, ".git") || strings.Contains(path, ".git"+string(os.PathSeparator)) }  func (repo *Repository) UploadRepoFiles(doer *User, opts UploadRepoFileOptions) (err error) { 	if len(opts.Files) == 0 { 		return nil 	}  	uploads, err := GetUploadsByUUIDs(opts.Files) 	if err != nil { 		return fmt.Errorf("get uploads by UUIDs[%v]: %v", opts.Files, err) func (repo *Repository) UploadRepoFiles(doer *User, opts UploadRepoFileOptions) 			continue 		}  		// Prevent copying files into .git directory, see https://gogs.io/gogs/issues/5558. 		if isRepositoryGitPath(upload.Name) { 			continue 		}

Fixed version (after fix):
 import ( 	"strings" 	"time"  	"github.com/pkg/errors" 	gouuid "github.com/satori/go.uuid" 	"github.com/unknwon/com"  	"github.com/gogs/git-module"  	"gogs.io/gogs/internal/conf" 	"gogs.io/gogs/internal/cryptoutil" 	dberrors "gogs.io/gogs/internal/db/errors" 	"gogs.io/gogs/internal/gitutil" 	"gogs.io/gogs/internal/osutil" 	"gogs.io/gogs/internal/pathutil" 	"gogs.io/gogs/internal/process" 	"gogs.io/gogs/internal/tool" ) func (repo *Repository) UpdateRepoFile(doer *User, opts UpdateRepoFileOptions) ( 	if opts.OldBranch != opts.NewBranch { 		// Directly return error if new branch already exists in the server 		if git.RepoHasBranch(repoPath, opts.NewBranch) { 			return dberrors.BranchAlreadyExists{Name: opts.NewBranch} 		}  		// Otherwise, delete branch from local copy in case out of sync func isRepositoryGitPath(path string) bool { 	return strings.HasSuffix(path, ".git") || strings.Contains(path, ".git"+string(os.PathSeparator)) }  func (repo *Repository) UploadRepoFiles(doer *User, opts UploadRepoFileOptions) error { 	if len(opts.Files) == 0 { 		return nil 	}  	// Prevent uploading files into the ".git" directory 	if isRepositoryGitPath(opts.TreePath) { 		return errors.Errorf("bad tree path %q", opts.TreePath) 	}  	uploads, err := GetUploadsByUUIDs(opts.Files) 	if err != nil { 		return fmt.Errorf("get uploads by UUIDs[%v]: %v", opts.Files, err) func (repo *Repository) UploadRepoFiles(doer *User, opts UploadRepoFileOptions) 			continue 		}  		upload.Name = pathutil.Clean(upload.Name)  		// Prevent uploading files into the ".git" directory 		if isRepositoryGitPath(upload.Name) { 			continue 		}

Patch:
patch: @@ -16,16 +16,18 @@ import (
 	"strings"
 	"time"
 
+	"github.com/pkg/errors"
 	gouuid "github.com/satori/go.uuid"
 	"github.com/unknwon/com"
 
 	"github.com/gogs/git-module"
 
 	"gogs.io/gogs/internal/conf"
 	"gogs.io/gogs/internal/cryptoutil"
-	"gogs.io/gogs/internal/db/errors"
+	dberrors "gogs.io/gogs/internal/db/errors"
 	"gogs.io/gogs/internal/gitutil"
 	"gogs.io/gogs/internal/osutil"
+	"gogs.io/gogs/internal/pathutil"
 	"gogs.io/gogs/internal/process"
 	"gogs.io/gogs/internal/tool"
 )
@@ -134,7 +136,7 @@ func (repo *Repository) UpdateRepoFile(doer *User, opts UpdateRepoFileOptions) (
 	if opts.OldBranch != opts.NewBranch {
 		// Directly return error if new branch already exists in the server
 		if git.RepoHasBranch(repoPath, opts.NewBranch) {
-			return errors.BranchAlreadyExists{Name: opts.NewBranch}
+			return dberrors.BranchAlreadyExists{Name: opts.NewBranch}
 		}
 
 		// Otherwise, delete branch from local copy in case out of sync
@@ -449,11 +451,16 @@ func isRepositoryGitPath(path string) bool {
 	return strings.HasSuffix(path, ".git") || strings.Contains(path, ".git"+string(os.PathSeparator))
 }
 
-func (repo *Repository) UploadRepoFiles(doer *User, opts UploadRepoFileOptions) (err error) {
+func (repo *Repository) UploadRepoFiles(doer *User, opts UploadRepoFileOptions) error {
 	if len(opts.Files) == 0 {
 		return nil
 	}
 
+	// Prevent uploading files into the ".git" directory
+	if isRepositoryGitPath(opts.TreePath) {
+		return errors.Errorf("bad tree path %q", opts.TreePath)
+	}
+
 	uploads, err := GetUploadsByUUIDs(opts.Files)
 	if err != nil {
 		return fmt.Errorf("get uploads by UUIDs[%v]: %v", opts.Files, err)
@@ -487,7 +494,9 @@ func (repo *Repository) UploadRepoFiles(doer *User, opts UploadRepoFileOptions)
 			continue
 		}
 
-		// Prevent copying files into .git directory, see https://gogs.io/gogs/issues/5558.
+		upload.Name = pathutil.Clean(upload.Name)
+
+		// Prevent uploading files into the ".git" directory
 		if isRepositoryGitPath(upload.Name) {
 			continue
 		}
path: internal/db/repo_editor.go