CVE-2026-23603: Gitea Blind SSRF Vulnerability

LOW
Advisory Identifier: GHSA-x77v-q46j-393g
CVSS 3.1
CWE-918
Published
Advisory Summary

CVE-2026-23603 is a LOW (CVSS 3.1) blind ssrf vulnerability (CWE-918: Server-Side Request Forgery (SSRF)) affecting Gitea libraries in the Go ecosystem. The issue involves blind ssrf in oauth2 avatar synchronization via unvalidated oidc picture claim. Remediation is available by upgrading affected packages to version 1.27.0.

CVE-2026-23603 at a Glance

CVE Identifier CVE-2026-23603
GHSA Identifier GHSA-x77v-q46j-393g
Severity LOW
CVSS v3.1 Base Score 3.1
CWE Classification CWE-918: Server-Side Request Forgery (SSRF)
Affected Product Gitea
Package Ecosystem Go
Published Date
Last Updated

Security Impact

CVSS: 3.1

The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:L/I:N/A:N).

Attack Vector Network (AV:N)
Attack Complexity High (AC:H)
Privileges Required Low (PR:L)
User Interaction None (UI:N)
Scope Unchanged (S:U)
Confidentiality Low (C:L)
Integrity None (I:N)
Availability None (A:N)

How to Fix CVE-2026-23603

To remediate this vulnerability, upgrade all affected Gitea dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

code.gitea.io/gitea fixed: 1.27.0
Upgrade code.gitea.io/gitea to 1.27.0
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

inspect dependencies for code.gitea.io/gitea

How to Check if You Are Affected

To determine whether your application is affected by CVE-2026-23603, inspect your project dependencies for Gitea:

inspect dependencies for code.gitea.io/gitea

Analyze the terminal output to locate any version of Gitea in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 1.27.0), your application is affected.

Note: Verify declared build manifests and deployed runtime container environments.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-918: Server-Side Request Forgery (SSRF), where the web server receives a url or similar request from an upstream component and retrieves the contents of this url, but it does not sufficiently ensure that the request is being sent to the expected destination. In Gitea, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

### Summary When `[oauth2_client] UPDATE_AVATAR = true` is enabled, Gitea fetches the avatar URL received from an OAuth2/OIDC provider using Go's default HTTP client. The URL comes from the user's OAuth/OIDC avatar value, commonly the OIDC `picture` claim. The affected code path calls `http.Get(url)` without applying outbound host or IP restrictions. A low-privileged user who can influence their own `picture` claim under an already-configured OAuth2/OIDC source can cause the Gitea server to make arbitrary outbound HTTP GET requests. This includes requests to loopback addresses, RFC 1918 private network addresses, and IPv4 link-local addresses such as `169.254.169.254`. This is a blind SSRF by default. Impact can increase in deployments where the Gitea host can reach cloud metadata services, localhost-only services, or internal services that return valid image data. ### Details The vulnerable sink is in `routers/web/auth/oauth.go`: ```go func oauth2UpdateAvatarIfNeed(ctx *context.Context, url string, u *user_model.User) { if setting.OAuth2Client.UpdateAvatar && len(url) > 0 { resp, err := http.Get(url) if err == nil { defer func() { _ = resp.Body.Close() }() } if err == nil && resp.StatusCode == http.StatusOK { data, err := io.ReadAll(io.LimitReader(resp.Body, setting.Avatar.MaxFileSize+1)) if err == nil && int64(len(data)) <= setting.Avatar.MaxFileSize { _ = user_service.UploadAvatar(ctx, u, data) } } } } ``` The caller is in `routers/web/auth/oauth_signin_sync.go`: ```go func oauth2SignInSync(ctx *context.Context, authSourceID int64, u *user_model.User, gothUser goth.User) { oauth2UpdateAvatarIfNeed(ctx, gothUser.AvatarURL, u) ... } ``` `gothUser.AvatarURL` is derived from the OAuth2/OIDC provider's avatar value. For OIDC providers, this is commonly populated from the `picture` claim returned by the provider's userinfo endpoint or ID token. The issue is that this value can be attacker-influenced in some common IdP configurations, while Gitea fetches it server-side using `http.Get` with no host/IP validation and no restricted transport. Comparable outbound fetch paths in Gitea use `hostmatcher.NewDialContext` to enforce restrictions at TCP dial time. For example, repository migration uses an HTTP transport with host matching. The OAuth2 avatar synchronization path does not apply those restrictions. ### PoC #### Requirements - Local Gitea build or binary - Python 3 - Python packages: `requests`, `pyjwt`, `cryptography` - Gitea configured with OAuth2 avatar synchronization enabled Install Python dependencies: ```bash python3 -m pip install requests pyjwt cryptography ``` Configure `app.ini`: ```ini [oauth2_client] UPDATE_AVATAR = true ENABLE_AUTO_REGISTRATION = true USERNAME = userid ``` Run the fake OIDC provider: ```bash python3 fake_oidc.py http://127.0.0.1:8888/ 9999 ``` Run a listener for the SSRF target: ```bash nc -lvnp 8888 ``` Register an OAuth2 authentication source in Gitea: - Provider: `OpenID Connect` - Client ID: `gitea-client` - Client Secret: `gitea-secret` - OpenID Connect Auto Discovery URL: `http://127.0.0.1:9999/.well-known/openid-configuration` Then initiate login through the configured OAuth2 source. Observed request to the SSRF listener: ```http GET / HTTP/1.1 Host: 127.0.0.1:8888 User-Agent: Go-http-client/1.1 Accept-Encoding: gzip ``` Observe that Gitea fetched the OIDC `picture` claim URL from the server side using the default Go HTTP client. ### Impact When [oauth2_client] UPDATE_AVATAR = true is enabled, a low-privileged OAuth2/OIDC user who can influence their own picture claim can force the Gitea server to make outbound HTTP GET requests to attacker-selected URLs. This allows blind SSRF from the Gitea server’s network position, including requests to loopback addresses, RFC1918 private addresses, link-local addresses such as 169.254.169.254, and other internal services that may not be reachable from the public internet. In practical terms, this can enable internal service probing and interaction with localhost-only or private-network services depending on the deployment’s network access controls. The vulnerability is blind in the common case because non-image responses such as HTML, JSON, or plaintext are rejected during avatar processing and are not directly returned to the attacker. However, impact can increase in cloud or internal-network deployments where metadata services, internal admin panels, monitoring endpoints, or image-generating internal services are reachable from the Gitea host. If an internal endpoint returns a valid supported image format within the configured avatar size limit, the response may be stored as the attacker’s avatar, creating a limited response retrieval primitive.

CVE-2026-23603 Affected Gitea Versions

1 component(s)
Fixed Versions:
1.27.0
Product / Package Ecosystem Affected Versions Fixed Version
code.gitea.io/gitea
Go
< 1.27.0
1.27.0

References

Frequently Asked Questions

What is CVE-2026-23603?
CVE-2026-23603 is a security vulnerability in Gitea involving Blind SSRF in OAuth2 avatar synchronization via unvalidated OIDC picture claim under CWE-918: Server-Side Request Forgery (SSRF).
Which Gitea versions are affected?
Affected versions include releases prior to 1.27.0 across affected packages: code.gitea.io/gitea.
What version fixes CVE-2026-23603?
Upgrading to 1.27.0 (or the latest patched release for your respective branch) resolves the vulnerability.
Is code.gitea.io/gitea affected?
Yes, code.gitea.io/gitea is affected in releases prior to 1.27.0. Upgrade your dependency to 1.27.0 or later.
What is the CVSS score of CVE-2026-23603?
CVE-2026-23603 has a CVSS v3.1 base score of 3.1 (LOW) with vector string CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:L/I:N/A:N.
What CWE is associated with CVE-2026-23603?
CVE-2026-23603 is classified under CWE-918: Server-Side Request Forgery (SSRF) (The web server receives a URL or similar request from an upstream component and retrieves the contents of this URL, but it does not sufficiently ensure that the request is being sent to the expected destination.).
How can I check Go dependencies for CVE-2026-23603?
Run the dependency tree command: 'inspect dependencies for code.gitea.io/gitea'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-x77v-q46j-393g",
  "modified" : "2026-07-21T20:22:23Z",
  "published" : "2026-07-21T20:22:23Z",
  "aliases" : [ "CVE-2026-23603" ],
  "summary" : "Gitea: Blind SSRF in OAuth2 avatar synchronization via unvalidated OIDC picture claim",
  "details" : "### Summary\nWhen `[oauth2_client] UPDATE_AVATAR = true` is enabled, Gitea fetches the avatar URL received from an OAuth2/OIDC provider using Go's default HTTP client. The URL comes from the user's OAuth/OIDC avatar value, commonly the OIDC `picture` claim.\n\nThe affected code path calls `http.Get(url)` without applying outbound host or IP restrictions. A low-privileged user who can influence their own `picture` claim under an already-configured OAuth2/OIDC source can cause the Gitea server to make arbitrary outbound HTTP GET requests. This includes requests to loopback addresses, RFC 1918 private network addresses, and IPv4 link-local addresses such as `169.254.169.254`.\n\nThis is a blind SSRF by default. Impact can increase in deployments where the Gitea host can reach cloud metadata services, localhost-only services, or internal services that return valid image data.\n\n### Details\nThe vulnerable sink is in `routers/web/auth/oauth.go`:\n\n```go\nfunc oauth2UpdateAvatarIfNeed(ctx *context.Context, url string, u *user_model.User) {\n    if setting.OAuth2Client.UpdateAvatar && len(url) > 0 {\n        resp, err := http.Get(url)\n        if err == nil {\n            defer func() { _ = resp.Body.Close() }()\n        }\n        if err == nil && resp.StatusCode == http.StatusOK {\n            data, err := io.ReadAll(io.LimitReader(resp.Body, setting.Avatar.MaxFileSize+1))\n            if err == nil && int64(len(data)) <= setting.Avatar.MaxFileSize {\n                _ = user_service.UploadAvatar(ctx, u, data)\n            }\n        }\n    }\n}\n```\n\nThe caller is in `routers/web/auth/oauth_signin_sync.go`:\n\n```go\nfunc oauth2SignInSync(ctx *context.Context, authSourceID int64, u *user_model.User, gothUser goth.User) {\n    oauth2UpdateAvatarIfNeed(ctx, gothUser.AvatarURL, u)\n    ...\n}\n```\n\n`gothUser.AvatarURL` is derived from the OAuth2/OIDC provider's avatar value. For OIDC providers, this is commonly populated from the `picture` claim returned by the provider's userinfo endpoint or ID token.\n\nThe issue is that this value can be attacker-influenced in some common IdP configurations, while Gitea fetches it server-side using `http.Get` with no host/IP validation and no restricted transport.\n\nComparable outbound fetch paths in Gitea use `hostmatcher.NewDialContext` to enforce restrictions at TCP dial time. For example, repository migration uses an HTTP transport with host matching. The OAuth2 avatar synchronization path does not apply those restrictions.\n\n### PoC\n\n#### Requirements\n\n- Local Gitea build or binary\n- Python 3\n- Python packages: `requests`, `pyjwt`, `cryptography`\n- Gitea configured with OAuth2 avatar synchronization enabled\n\nInstall Python dependencies:\n\n```bash\npython3 -m pip install requests pyjwt cryptography\n```\n\nConfigure `app.ini`:\n\n```ini\n[oauth2_client]\nUPDATE_AVATAR = true\nENABLE_AUTO_REGISTRATION = true\nUSERNAME = userid\n```\n\nRun the fake OIDC provider:\n\n```bash\npython3 fake_oidc.py http://127.0.0.1:8888/ 9999\n```\n\nRun a listener for the SSRF target:\n\n```bash\nnc -lvnp 8888\n```\n\nRegister an OAuth2 authentication source in Gitea:\n\n- Provider: `OpenID Connect`\n- Client ID: `gitea-client`\n- Client Secret: `gitea-secret`\n- OpenID Connect Auto Discovery URL: `http://127.0.0.1:9999/.well-known/openid-configuration`\n\nThen initiate login through the configured OAuth2 source.\n\nObserved request to the SSRF listener:\n\n```http\nGET / HTTP/1.1\nHost: 127.0.0.1:8888\nUser-Agent: Go-http-client/1.1\nAccept-Encoding: gzip\n```\n\nObserve that Gitea fetched the OIDC `picture` claim URL from the server side using the default Go HTTP client.\n\n### Impact\nWhen [oauth2_client] UPDATE_AVATAR = true is enabled, a low-privileged OAuth2/OIDC user who can influence their own picture claim can force the Gitea server to make outbound HTTP GET requests to attacker-selected URLs. This allows blind SSRF from the Gitea server’s network position, including requests to loopback addresses, RFC1918 private addresses, link-local addresses such as 169.254.169.254, and other internal services that may not be reachable from the public internet. In practical terms, this can enable internal service probing and interaction with localhost-only or private-network services depending on the deployment’s network access controls.\n\nThe vulnerability is blind in the common case because non-image responses such as HTML, JSON, or plaintext are rejected during avatar processing and are not directly returned to the attacker. However, impact can increase in cloud or internal-network deployments where metadata services, internal admin panels, monitoring endpoints, or image-generating internal services are reachable from the Gitea host. If an internal endpoint returns a valid supported image format within the configured avatar size limit, the response may be stored as the attacker’s avatar, creating a limited response retrieval primitive.",
  "severity" : [ {
    "type" : "CVSS_V3",
    "score" : "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:L/I:N/A:N"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "Go",
      "name" : "code.gitea.io/gitea"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0"
      }, {
        "fixed" : "1.27.0"
      } ]
    } ]
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/go-gitea/gitea/security/advisories/GHSA-x77v-q46j-393g"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/go-gitea/gitea/pull/38406"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/go-gitea/gitea/pull/38426"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/go-gitea/gitea/commit/de4b8277e9cb576f2315fb03b5ab6478b42a1d31"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/go-gitea/gitea/commit/f69e15afe7496cc62e96dab244629c69eb31a7bf"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/go-gitea/gitea"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/go-gitea/gitea/releases/tag/v1.27.0"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-918" ],
    "severity" : "LOW",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-07-21T20:22:23Z",
    "nvd_published_at" : null
  }
}