Compare commits

...

13 Commits

Author SHA1 Message Date
Joe Chen
1ce5171ae1 Do not run tests with race detection on Windows 2022-08-02 11:01:04 +08:00
Joe Chen
47297c0510 gofmt 2022-08-02 10:44:36 +08:00
Joe Chen
a2113bd13a release: update version to 0.12.10 2022-08-02 10:42:47 +08:00
Joe Chen
7be49a8f2f netutil: allow using * to match any hostname (#7111)
# Conflicts:
#	internal/netutil/netutil_test.go
2022-08-02 10:38:46 +08:00
Joe Chen
7f147eb573 webhook: validate against hostname instead of full URL (#7075)
# Conflicts:
#	CHANGELOG.md
2022-08-02 10:37:45 +08:00
Joe Chen
012a1ba19e release: update version to 0.12.9 2022-06-07 21:29:32 +08:00
Joe Chen
a24b22c909 http: clean request path from Git endpoints (#7022) 2022-06-07 21:26:15 +08:00
Joe Chen
20923a8829 pathutil: check both styles of os.PathSeparator (#7020) 2022-06-07 21:25:51 +08:00
E99p1ant
4c02b480dc issues: sanitize DisplayName (#7009)
* issues: display issue poster’s `Name` instead of `DisplayName`

* sanitize display name

* update changelog
2022-06-07 21:24:56 +08:00
Joe Chen
deeb3f73e4 repo_editor: check both styles of os.PathSeparator in all systems (#7005)
# Conflicts:
#	CHANGELOG.md
2022-06-07 21:24:37 +08:00
Joe Chen
1bc379f4d4 pull: ignore PR status check if head repository is missing (#7004)
# Conflicts:
#	internal/db/pull.go
2022-06-07 21:23:38 +08:00
Joe Chen
8e8b185ea1 http: fix non-sense NotFound call (#7003) 2022-06-07 21:21:29 +08:00
Joe Chen
42ad18a245 repo: remove hard-coded branch name in init (#6999) 2022-06-07 21:20:54 +08:00
15 changed files with 188 additions and 56 deletions

View File

@@ -54,7 +54,7 @@ jobs:
strategy:
matrix:
go-version: [ 1.14.x, 1.15.x, 1.16.x, 1.17.x, 1.18.x ]
platform: [ ubuntu-latest, macos-latest, windows-latest ]
platform: [ ubuntu-latest, macos-latest ]
runs-on: ${{ matrix.platform }}
steps:
- name: Install Go
@@ -86,3 +86,43 @@ jobs:
The job "${{ github.job }}" of ${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }} completed with "${{ job.status }}".
View the job run at: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
# Running tests with race detection consumes too much memory on Windows,
# see https://github.com/golang/go/issues/46099 for details.
test-windows:
name: Test
strategy:
matrix:
go-version: [ 1.14.x, 1.15.x, 1.16.x, 1.17.x, 1.18.x ]
platform: [ windows-latest ]
runs-on: ${{ matrix.platform }}
steps:
- name: Install Go
uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go-version }}
- name: Checkout code
uses: actions/checkout@v2
- name: Run tests with coverage
run: go test -v -coverprofile=coverage -covermode=atomic ./...
- name: Upload coverage report to Codecov
uses: codecov/codecov-action@v1.5.0
with:
file: ./coverage
flags: unittests
- name: Send email on failure
uses: dawidd6/action-send-mail@v3
if: ${{ failure() && github.event_name == 'push' && github.ref == 'refs/heads/main' }}
with:
server_address: smtp.mailgun.org
server_port: 465
username: ${{ secrets.SMTP_USERNAME }}
password: ${{ secrets.SMTP_PASSWORD }}
subject: GitHub Actions (${{ github.repository }}) job result
to: github-actions-8ce6454@unknwon.io
from: GitHub Actions (${{ github.repository }})
reply_to: noreply@unknwon.io
body: |
The job "${{ github.job }}" of ${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }} completed with "${{ job.status }}".
View the job run at: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}

View File

@@ -19,13 +19,13 @@ All notable changes to Gogs are documented in this file.
- MSSQL as database backend is deprecated, installation page no longer shows it as an option. Existing installations and manually craft configuration file continue to work. [#6295](https://github.com/gogs/gogs/pull/6295)
- Use [Task](https://github.com/go-task/task) as the build tool. [#6297](https://github.com/gogs/gogs/pull/6297)
- The required Go version to compile source code changed to 1.16.
- Access tokens are now stored using their SHA256 hashes instead of raw values. [#7008](https://github.com/gogs/gogs/pull/7008)
- Support using `[security] LOCAL_NETWORK_ALLOWLIST = *` to allow all hostnames. [#7111](https://github.com/gogs/gogs/pull/7111)
### Fixed
- _Security:_ XSS in cookies. [#6953](https://github.com/gogs/gogs/issues/6953)
- _Security:_ OS Command Injection in file uploading. [#6968](https://github.com/gogs/gogs/issues/6968)
- _Security:_ Remote Command Execution in file editing. [#6555](https://github.com/gogs/gogs/issues/6555)
- Unable to use LDAP authentication on ARM machines. [#6761](https://github.com/gogs/gogs/issues/6761)
- Unable to send webhooks to local network addresses after configured `[security] LOCAL_NETWORK_ALLOWLIST`. [#7074](https://github.com/gogs/gogs/issues/7074)
### Removed
@@ -47,6 +47,30 @@ All notable changes to Gogs are documented in this file.
- Configuration option `[database] PASSWD` is no longer used, please use `[database] PASSWORD`.
- Remove option to use Makefile as the build tool. [#6980](https://github.com/gogs/gogs/pull/6980)
## 0.12.9
### Fixed
- _Security:_ OS Command Injection in file editor. [#7000](https://github.com/gogs/gogs/issues/7000)
- _Security:_ Sanitize `DisplayName` in repository issue list. [#7009](https://github.com/gogs/gogs/pull/7009)
- _Security:_ Path Traversal in file editor on Windows. [#7001](https://github.com/gogs/gogs/issues/7001)
- _Security:_ Path Traversal in Git HTTP endpoints. [#7002](https://github.com/gogs/gogs/issues/7002)
- Unable to init repository during creation on Windows. [#6967](https://github.com/gogs/gogs/issues/6967)
- Mysterious panic on `Value not found for type *repo.HTTPContext`. [#6963](https://github.com/gogs/gogs/issues/6963)
## 0.12.8
### Changed
- All users (including admins) need to use the configuration option `[security] LOCAL_NETWORK_ALLOWLIST` to allow repository migration and webhooks to be able to access local network addresses, which is a comma separated list of hostnames. [#6988](https://github.com/gogs/gogs/pull/6988)
### Fixed
- _Security:_ SSRF in webhook. [#6901](https://github.com/gogs/gogs/issues/6901)
- _Security:_ XSS in cookies. [#6953](https://github.com/gogs/gogs/issues/6953)
- _Security:_ OS Command Injection in file uploading. [#6968](https://github.com/gogs/gogs/issues/6968)
- _Security:_ Remote Command Execution in file editing. [#6555](https://github.com/gogs/gogs/issues/6555)
## 0.12.7
### Fixed

View File

@@ -170,6 +170,7 @@ ENABLE_LOGIN_STATUS_COOKIE = false
; The cookie name to store user login status.
LOGIN_STATUS_COOKIE_NAME = login_status
; A comma separated list of hostnames that are explicitly allowed to be accessed within the local network.
; Use "*" to allow all hostnames.
LOCAL_NETWORK_ALLOWLIST =
[email]

View File

@@ -19,7 +19,7 @@ import (
)
func init() {
conf.App.Version = "0.12.8"
conf.App.Version = "0.12.10"
}
func main() {

View File

@@ -621,11 +621,6 @@ func (pr *PullRequest) UpdateCols(cols ...string) error {
// UpdatePatch generates and saves a new patch.
func (pr *PullRequest) UpdatePatch() (err error) {
if pr.HeadRepo == nil {
log.Trace("PullRequest[%d].UpdatePatch: ignored cruppted data", pr.ID)
return nil
}
headGitRepo, err := git.Open(pr.HeadRepo.RepoPath())
if err != nil {
return fmt.Errorf("open repository: %v", err)
@@ -759,6 +754,11 @@ func (prs PullRequestList) LoadAttributes() error {
func addHeadRepoTasks(prs []*PullRequest) {
for _, pr := range prs {
if pr.HeadRepo == nil {
log.Trace("addHeadRepoTasks[%d]: missing head repository", pr.ID)
continue
}
log.Trace("addHeadRepoTasks[%d]: composing new test task", pr.ID)
if err := pr.UpdatePatch(); err != nil {
log.Error("UpdatePatch: %v", err)

View File

@@ -912,7 +912,7 @@ func initRepoCommit(tmpPath string, sig *git.Signature) (err error) {
if _, stderr, err = process.ExecDir(-1,
tmpPath, fmt.Sprintf("initRepoCommit (git push): %s", tmpPath),
"git", "push", "origin", "master"); err != nil {
"git", "push"); err != nil {
return fmt.Errorf("git push: %s", stderr)
}
return nil

View File

@@ -455,10 +455,12 @@ type UploadRepoFileOptions struct {
// path of the repository.
func isRepositoryGitPath(path string) bool {
return strings.HasSuffix(path, ".git") ||
strings.Contains(path, ".git"+string(os.PathSeparator)) ||
strings.Contains(path, ".git/") ||
strings.Contains(path, `.git\`) ||
// Windows treats ".git." the same as ".git"
strings.HasSuffix(path, ".git.") ||
strings.Contains(path, ".git."+string(os.PathSeparator))
strings.Contains(path, ".git./") ||
strings.Contains(path, `.git.\`)
}
func (repo *Repository) UploadRepoFiles(doer *User, opts UploadRepoFileOptions) error {

View File

@@ -5,7 +5,6 @@
package db
import (
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
@@ -16,23 +15,37 @@ func Test_isRepositoryGitPath(t *testing.T) {
path string
wantVal bool
}{
{path: filepath.Join(".", ".git"), wantVal: true},
{path: filepath.Join(".", ".git", ""), wantVal: true},
{path: filepath.Join(".", ".git", "hooks", "pre-commit"), wantVal: true},
{path: filepath.Join(".git", "hooks"), wantVal: true},
{path: filepath.Join("dir", ".git"), wantVal: true},
{path: ".git", wantVal: true},
{path: "./.git", wantVal: true},
{path: ".git/hooks/pre-commit", wantVal: true},
{path: ".git/hooks", wantVal: true},
{path: "dir/.git", wantVal: true},
{path: filepath.Join(".", ".git."), wantVal: true},
{path: filepath.Join(".", ".git.", ""), wantVal: true},
{path: filepath.Join(".", ".git.", "hooks", "pre-commit"), wantVal: true},
{path: filepath.Join(".git.", "hooks"), wantVal: true},
{path: filepath.Join("dir", ".git."), wantVal: true},
{path: ".gitignore", wantVal: false},
{path: "dir/.gitkeep", wantVal: false},
{path: filepath.Join(".gitignore"), wantVal: false},
{path: filepath.Join("dir", ".gitkeep"), wantVal: false},
// Windows-specific
{path: `.git\`, wantVal: true},
{path: `.git\hooks\pre-commit`, wantVal: true},
{path: `.git\hooks`, wantVal: true},
{path: `dir\.git`, wantVal: true},
{path: `.\.git.`, wantVal: true},
{path: `.\.git.\`, wantVal: true},
{path: `.git.\hooks\pre-commit`, wantVal: true},
{path: `.git.\hooks`, wantVal: true},
{path: `dir\.git.`, wantVal: true},
{path: "./.git.", wantVal: true},
{path: "./.git./", wantVal: true},
{path: ".git./hooks/pre-commit", wantVal: true},
{path: ".git./hooks", wantVal: true},
{path: "dir/.git.", wantVal: true},
{path: `dir\.gitkeep`, wantVal: false},
}
for _, test := range tests {
t.Run("", func(t *testing.T) {
t.Run(test.path, func(t *testing.T) {
assert.Equal(t, test.wantVal, isRepositoryGitPath(test.path))
})
}

View File

@@ -11,6 +11,7 @@ import (
"encoding/hex"
"fmt"
"io/ioutil"
"net/url"
"strings"
"time"
@@ -689,8 +690,13 @@ func TestWebhook(repo *Repository, event HookEventType, p api.Payloader, webhook
}
func (t *HookTask) deliver() {
if netutil.IsBlockedLocalHostname(t.URL, conf.Security.LocalNetworkAllowlist) {
t.ResponseContent = "Payload URL resolved to a local network address that is implicitly blocked."
payloadURL, err := url.Parse(t.URL)
if err != nil {
t.ResponseContent = fmt.Sprintf(`{"body": "Cannot parse payload URL: %v"}`, err)
return
}
if netutil.IsBlockedLocalHostname(payloadURL.Hostname(), conf.Security.LocalNetworkAllowlist) {
t.ResponseContent = `{"body": "Payload URL resolved to a local network address that is implicitly blocked."}`
return
}

View File

@@ -52,7 +52,7 @@ func init() {
// allowlist).
func IsBlockedLocalHostname(hostname string, allowlist []string) bool {
for _, allow := range allowlist {
if hostname == allow {
if hostname == allow || allow == "*" {
return false
}
}

View File

@@ -29,8 +29,10 @@ func TestIsLocalHostname(t *testing.T) {
{hostname: "google.com", want: false},
{hostname: "165.232.140.255", want: false},
{hostname: "192.168.123.45", allowlist: []string{"10.0.0.17"}, want: true},
{hostname: "gogs.local", allowlist: []string{"gogs.local"}, want: false},
{hostname: "192.168.123.45", allowlist: []string{"10.0.0.17"}, want: true}, // #11
{hostname: "gogs.local", allowlist: []string{"gogs.local"}, want: false}, // #12
{hostname: "192.168.123.45", allowlist: []string{"*"}, want: false}, // #13
}
for _, test := range tests {
t.Run("", func(t *testing.T) {

View File

@@ -9,7 +9,9 @@ import (
"strings"
)
// Clean cleans up given path and returns a relative path that goes straight down.
// Clean cleans up given path and returns a relative path that goes straight
// down to prevent path traversal.
func Clean(p string) string {
p = strings.ReplaceAll(p, `\`, "/")
return strings.Trim(path.Clean("/"+p), "/")
}

View File

@@ -12,38 +12,73 @@ import (
func TestClean(t *testing.T) {
tests := []struct {
path string
expVal string
path string
wantVal string
}{
{
path: "../../../readme.txt",
expVal: "readme.txt",
path: "../../../readme.txt",
wantVal: "readme.txt",
},
{
path: "a/../../../readme.txt",
expVal: "readme.txt",
path: "a/../../../readme.txt",
wantVal: "readme.txt",
},
{
path: "/../a/b/../c/../readme.txt",
expVal: "a/readme.txt",
path: "/../a/b/../c/../readme.txt",
wantVal: "a/readme.txt",
},
{
path: "/a/readme.txt",
expVal: "a/readme.txt",
path: "../../objects/info/..",
wantVal: "objects",
},
{
path: "/",
expVal: "",
path: "/a/readme.txt",
wantVal: "a/readme.txt",
},
{
path: "/",
wantVal: "",
},
{
path: "/a/b/c/readme.txt",
expVal: "a/b/c/readme.txt",
path: "/a/b/c/readme.txt",
wantVal: "a/b/c/readme.txt",
},
// Windows-specific
{
path: `..\..\..\readme.txt`,
wantVal: "readme.txt",
},
{
path: `a\..\..\..\readme.txt`,
wantVal: "readme.txt",
},
{
path: `\..\a\b\..\c\..\readme.txt`,
wantVal: "a/readme.txt",
},
{
path: `\a\readme.txt`,
wantVal: "a/readme.txt",
},
{
path: `..\..\..\../README.md`,
wantVal: "README.md",
},
{
path: `\`,
wantVal: "",
},
{
path: `\a\b\c\readme.txt`,
wantVal: `a/b/c/readme.txt`,
},
}
for _, test := range tests {
t.Run("", func(t *testing.T) {
assert.Equal(t, test.expVal, Clean(test.path))
t.Run(test.path, func(t *testing.T) {
assert.Equal(t, test.wantVal, Clean(test.path))
})
}
}

View File

@@ -23,6 +23,7 @@ import (
"gogs.io/gogs/internal/conf"
"gogs.io/gogs/internal/db"
"gogs.io/gogs/internal/lazyregexp"
"gogs.io/gogs/internal/pathutil"
"gogs.io/gogs/internal/tool"
)
@@ -99,7 +100,7 @@ func HTTPContexter() macaron.Handler {
!strings.Contains(action, "info/") &&
!strings.Contains(action, "HEAD") &&
!strings.Contains(action, "objects/") {
c.NotFound()
c.Error(http.StatusBadRequest, fmt.Sprintf("Unrecognized action %q", action))
return
}
@@ -402,15 +403,21 @@ func HTTP(c *HTTPContext) {
}
if route.method != c.Req.Method {
c.NotFound()
c.Error(http.StatusNotFound)
return
}
file := strings.TrimPrefix(reqPath, m[1]+"/")
dir, err := getGitRepoPath(m[1])
cleaned := pathutil.Clean(m[1])
if m[1] != "/"+cleaned {
c.Error(http.StatusBadRequest, "Request path contains suspicious characters")
return
}
file := strings.TrimPrefix(reqPath, cleaned)
dir, err := getGitRepoPath(cleaned)
if err != nil {
log.Warn("HTTP.getGitRepoPath: %v", err)
c.NotFound()
c.Error(http.StatusNotFound)
return
}
@@ -429,5 +436,5 @@ func HTTP(c *HTTPContext) {
return
}
c.NotFound()
c.Error(http.StatusNotFound)
}

View File

@@ -113,7 +113,7 @@
{{end}}
<p class="desc">
{{$.i18n.Tr "repo.issues.opened_by" $timeStr .Poster.HomeLink .Poster.DisplayName | Safe}}
{{$.i18n.Tr "repo.issues.opened_by" $timeStr .Poster.HomeLink .Poster.DisplayName | Sanitize | Safe}}
{{if .Milestone}}
<a class="milestone" href="{{$.Link}}?type={{$.ViewType}}&state={{$.State}}&labels={{$.SelectLabels}}&milestone={{.Milestone.ID}}&assignee={{$.AssigneeID}}">
<span class="octicon octicon-milestone"></span> {{.Milestone.Name | Sanitize}}