CVEReports
CVEReports

Automated vulnerability intelligence platform. Comprehensive reports for high-severity CVEs generated by AI.

Product

  • Home
  • Sitemap
  • RSS Feed

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CVEReports. All rights reserved.

Made with love by Amit Schendel & Alon Barad



CVE-2026-20904

Gitea OpenID Visibility Toggle IDOR: The "Trust Me, Bro" Update Query

Alon Barad
Alon Barad
Software Engineer

Jan 24, 2026·6 min read·83 visits

Executive Summary (TL;DR)

Gitea developers forgot the golden rule of access control: verify ownership. By sending a request to the OpenID visibility toggle endpoint and iterating through IDs, an attacker could hide or show OpenID connections for every user on the instance. The fix involved adding a simple `AND uid = ?` clause to the SQL query.

A classic Insecure Direct Object Reference (IDOR) vulnerability in Gitea versions prior to 1.25.4 allowed authenticated users to toggle the visibility of OpenID credentials belonging to any other user. The flaw stemmed from a database update query that checked the record ID but failed to verify the record owner.

The Hook: Who Turned Out the Lights?

In the world of web application security, there are sophisticated memory corruption bugs, complex race conditions, and then there are the "oops" moments. CVE-2026-20904 falls squarely into the latter category. It is a story about Gitea, a delightful self-hosted Git service, and how it trusted user input a little too much.

OpenID Connect is a fantastic feature. It lets users log in with their Google, GitHub, or generic OIDC providers. Gitea allows users to manage these linked accounts, including a privacy setting: a simple toggle to "Show" or "Hide" the OpenID URI on their public profile. It seems harmless enough. A boolean flag. On or off.

But here is the kicker: the mechanism controlling that switch didn't care whose hand was on it. It was like a light switch in a hotel hallway that, instead of controlling the light above it, accepted a room number as input and toggled the lights in that room. If you knew the room numbers (or just guessed them), you could throw a disco party in someone else's suite without ever stepping inside.

The Flaw: A Case of Missing Identity

The vulnerability is a textbook Insecure Direct Object Reference (IDOR), or as I like to call it, "Database Roulette." The issue lived in models/user/openid.go within the ToggleUserOpenIDVisibility function.

When a user clicked that toggle button, the frontend sent a request containing the ID of the OpenID record. The backend received this ID and immediately constructed a SQL query to update the database. The logic was dangerously simple: "Find the row with this ID, and flip the show bit."

Here is where the logic fell apart. The database schema relies on an auto-incrementing integer for the primary key (id). However, the table also contains a uid column, which links the record to a specific user. The vulnerable function completely ignored the uid. It assumed that if you were asking to toggle record #1337, you must own record #1337.

> [!NOTE] > IDORs are particularly embarrassing because they aren't failures of technology; they are failures of logic. The code did exactly what it was told to do. It just wasn't told to check user permissions.

The Code: The Smoking Gun

Let's look at the code. This is the difference between "working software" and "secure software."

The Vulnerable Code (Pre-1.25.4):

// ToggleUserOpenIDVisibility toggles visibility of an openid address of given user.
func ToggleUserOpenIDVisibility(ctx context.Context, id int64) (err error) {
    // DANGER: Updates based solely on the Primary Key (id)
    _, err = db.GetEngine(ctx).Exec("update `user_open_id` set `show` = not `show` where `id` = ?", id)
    return err
}

Notice the function signature. It takes ctx and id. It doesn't even ask who is making the request. The SQL query is blind to ownership.

The Patched Code (1.25.4):

// ToggleUserOpenIDVisibility toggles visibility of an openid address of given user.
func ToggleUserOpenIDVisibility(ctx context.Context, id int64, user *User) error {
    // FIXED: Now checks both Primary Key (id) AND Foreign Key (uid)
    affected, err := db.GetEngine(ctx).Exec(
        "update `user_open_id` set `show` = not `show` where `id` = ? AND uid = ?", 
        id, 
        user.ID
    )
    if err != nil {
        return err
    }
    // If no rows were affected, it means the ID exists but belongs to someone else
    // (or doesn't exist at all).
    if n, _ := affected.RowsAffected(); n != 1 {
        return util.NewNotExistErrorf("OpenID is unknown")
    }
    return nil
}

The fix is elegant in its simplicity. They updated the signature to require the user object (the authenticated actor) and appended AND uid = ? to the SQL query. Now, if I try to toggle your record, the database says, "I found the ID, but the UID doesn't match," resulting in zero rows affected.

The Exploit: Flipping Switches

Exploiting this requires valid authentication on the Gitea instance, but any low-level user will do. Once logged in, the attack vector is trivial.

  1. Reconnaissance: The attacker toggles their own OpenID visibility and captures the request using a proxy like Burp Suite or Caido.
  2. Analysis: The request will likely look like POST /user/settings/security/openid/toggle with a body or query parameter id=105.
  3. Weaponization: The attacker sends the request to the Intruder (or writes a simple Python script) to iterate the id parameter from 1 to N.

Because OpenID record IDs are likely sequential integers (1, 2, 3...), the attacker doesn't even need to guess. They can simply brute-force the entire integer space.

The result? Chaos. Users who intended to keep their OpenID providers private suddenly have them exposed. Users who relied on them being public suddenly find them hidden. It's a low-tech Denial of Service on the configuration integrity of the platform.

The Impact: Privacy Roulette

While this isn't a Remote Code Execution (RCE) that burns the server to the ground, we shouldn't dismiss the impact.

1. Privacy Leakage: OpenID URIs can sometimes leak personal information. If a user configured a custom OpenID provider that includes their real name or personal domain in the URL, forced visibility exposes this to the public.

2. Data Integrity Loss: Security isn't just about confidentiality; it's about integrity. If an attacker can modify your settings without your consent, the system's integrity is compromised.

3. Social Engineering Prep: By toggling settings and observing the results, an attacker might be able to map out which users are active and which OpenID providers are most common on the target infrastructure, aiding in targeted phishing campaigns.

CVSS Score Analysis (6.5): The score reflects the fact that integrity (I:L) is violated. The attack is network-based (AV:N), requires low privileges (PR:L), and is easy to execute (AC:L).

The Fix: Trust No One

The mitigation here is straightforward: upgrade. Gitea version 1.25.4 patches this vulnerability effectively.

If you are a developer looking at this, let it be a lesson: Never rely on an object ID alone for database operations in a multi-user environment. Always scope your queries to the authenticated user.

Remediation Steps:

  1. Upgrade: Pull the latest docker image or binary for Gitea 1.25.4.
  2. Audit: If you suspect foul play, check your database logs (if enabled with high verbosity) for UPDATE user_open_id queries where the id sequence looks linear and rapid, originating from a single IP or session.
  3. Code Review: Grep your own codebases for UPDATE ... WHERE id = ?. If you find one, ask yourself: "Who owns this ID?"

Official Patches

GiteaOfficial Release Notes
GitHubPull Request #36346

Fix Analysis (1)

Technical Appendix

CVSS Score
6.5/ 10
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:L
EPSS Probability
0.02%
Top 97% most exploited

Affected Systems

Gitea < 1.25.4

Affected Versions Detail

Product
Affected Versions
Fixed Version
Gitea
Gitea
<= 1.25.31.25.4
AttributeDetail
CWE IDCWE-639
Attack VectorNetwork
CVSS v3.16.5
ImpactIntegrity Loss
Privileges RequiredLow (Authenticated)
Exploit StatusPoC Available

MITRE ATT&CK Mapping

T1565.001Data Manipulation: Stored Data Manipulation
Impact
T1596Search Open Technical Databases
Reconnaissance
CWE-639
Insecure Direct Object Reference (IDOR)

Authorization Bypass Through User-Controlled Key

Known Exploits & Detection

HypotheticalBurp Intruder iteration over 'id' parameter on toggle endpoint.

Vulnerability Timeline

Patch Merged (PR #36346)
2026-01-13
CVE-2026-20904 Published
2026-01-22
Gitea 1.25.4 Released
2026-01-23

References & Sources

  • [1]Gitea Advisory GHSA-jrpc-w85r-hgqx
  • [2]Gitea Blog: Release of 1.25.4

Attack Flow Diagram

Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.

More Reports

•1 day ago•CVE-2026-58197
8.8

CVE-2026-58197: Host Escape and Lateral Movement via Insecure Container Network Defaults in ToolHive

A high-severity access control vulnerability in ToolHive CLI before v0.30.1 and ToolHive Studio before v0.38.0 allows local containerized MCP servers to bypass network isolation. This enables malicious workloads to establish TCP/IP connections to administrative and control plane endpoints exposed on the host loopback interface.

Amit Schendel
Amit Schendel
8 views•8 min read
•1 day ago•CVE-2026-63405
5.9

CVE-2026-63405: Insufficient Verification of Data Authenticity in AnyCable Pusher REST API

AnyCable is a real-time communication server. Prior to version 1.6.15, its Pusher-compatible REST API suffered from an authentication bypass vulnerability because it failed to verify that the request body matched the signature-validated body_md5 parameter. This allows attackers to perform replay attacks with modified body contents.

Amit Schendel
Amit Schendel
9 views•5 min read
•2 days ago•CVE-2026-64847
6.8

CVE-2026-64847: Indefinite Denial of Service via Undrained Stderr in AnyIO Process Pool Workers

A denial-of-service vulnerability exists in AnyIO prior to version 4.14.2. Standard error streams of process-pool workers are connected to an operating system pipe that is never drained by the parent process. This allows a worker to fill the pipe buffer and deadlock indefinitely.

Amit Schendel
Amit Schendel
9 views•6 min read
•2 days ago•CVE-2026-63349
7.0

CVE-2026-63349: Privilege Dropping Bypass and Denial of Service in AnyIO Subprocess Module

CVE-2026-63349 is a critical privilege-dropping bypass vulnerability in the AnyIO asynchronous framework (versions 4.14.0 and 4.14.1) on POSIX platforms. Due to a variable assignment typo, supplementary groups specified by the developer are not correctly propagated to the execution backend, resulting in subprocesses retaining the parent process's elevated supplementary group permissions.

Alon Barad
Alon Barad
15 views•5 min read
•2 days ago•CVE-2026-63406
5.9

CVE-2026-63406: Information Disclosure via Insecure Telemetry and Hardcoded Credentials in AnyCable-Go

CVE-2026-63406 is an information disclosure vulnerability in AnyCable-go prior to version 1.6.15. The built-in telemetry client is enabled by default with a hardcoded public authentication token ('secret'). This client digests highly sensitive configuration parameters and command-line arguments, including JWT secrets and RPC secrets, into a stable SHA-256 fingerprint. This fingerprint is sent over public networks, exposing those administrative secrets to offline dictionary and brute-force attacks if intercepted.

Alon Barad
Alon Barad
7 views•5 min read
•2 days ago•CVE-2026-84992
6.1

CVE-2026-84992: Cross-Site Scripting (XSS) via Fenced Code Block Parsing in md-editor-v3

CVE-2026-84992 is a Cross-Site Scripting (XSS) vulnerability affecting md-editor-v3 before version 6.5.4. It occurs because the fenced-code block language parser directly interpolates unescaped language metadata into unquoted HTML attributes inside the custom rendering callback. This bypasses the built-in XSSPlugin which runs during the parsing phase, before rendering.

Amit Schendel
Amit Schendel
10 views•6 min read