[GH-ISSUE #4875] Security: Prevent user enumeration by checking authorization before user existence #10304

Open
opened 2026-08-05 01:25:20 -04:00 by saavagebueno · 0 comments
Owner

Originally created by @tkloda on GitHub (Nov 29, 2025).
Original GitHub issue: https://github.com/netbirdio/netbird/issues/4875

Summary

The current API endpoints may be vulnerable to user enumeration attacks. The server appears to return different error responses depending on whether a user exists or not, which allows an attacker to discover valid usernames/accounts.

Current Behavior

When an unauthorized request is made to certain endpoints:

  • If the user does not exist → returns one error type (e.g., 404 Not Found)
  • If the user exists but is unauthorized → returns a different error type (e.g., 403 Forbidden)

This difference in responses allows attackers to enumerate valid users by observing the error codes returned.

Expected Behavior

For any unauthorized attempt, the server should:

  1. Perform the authorization check first, before checking if the user/resource exists
  2. Return a consistent error response (e.g., 403 Forbidden or a generic "Access Denied") regardless of whether the user exists

This prevents attackers from learning anything about user existence from the response.

Security Impact

An attacker could use this vulnerability to:

  • Build a list of valid user accounts
  • Target those accounts for credential stuffing or phishing attacks
  • Gain reconnaissance information about the organization

Suggested Fix

In the authentication/authorization middleware, restructure the logic to:

// Pseudocode example
func handleRequest(req Request) Response {
    // 1. Check authorization FIRST
    if !isAuthorized(req) {
        return ForbiddenResponse("Access Denied")  // Generic response
    }
    
    // 2. Only then check if resource/user exists
    user, err := getUser(req.UserID)
    if err != nil {
        return ForbiddenResponse("Access Denied")  // Same generic response
    }
    
    // 3. Process the request
    return processRequest(user, req)
}

Environment

  • NetBird Version: 0.59.13
  • Deployment: Self-hosted
  • Discovered during: Security hardening / penetration testing

Additional Context

This was identified during security hardening of our NetBird deployment. We've already implemented generic error pages at the reverse proxy level (Caddy), but the underlying application logic still reveals user existence through differing response codes.

Originally created by @tkloda on GitHub (Nov 29, 2025). Original GitHub issue: https://github.com/netbirdio/netbird/issues/4875 ## Summary The current API endpoints may be vulnerable to user enumeration attacks. The server appears to return different error responses depending on whether a user exists or not, which allows an attacker to discover valid usernames/accounts. ## Current Behavior When an unauthorized request is made to certain endpoints: - If the user **does not exist** → returns one error type (e.g., 404 Not Found) - If the user **exists but is unauthorized** → returns a different error type (e.g., 403 Forbidden) This difference in responses allows attackers to enumerate valid users by observing the error codes returned. ## Expected Behavior For any unauthorized attempt, the server should: 1. Perform the **authorization check first**, before checking if the user/resource exists 2. Return a **consistent error response** (e.g., `403 Forbidden` or a generic "Access Denied") regardless of whether the user exists This prevents attackers from learning anything about user existence from the response. ## Security Impact - **Attack Type:** User Enumeration - **Severity:** Low to Medium - **OWASP Reference:** [WSTG-IDNT-04 - Testing for Account Enumeration](https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/03-Identity_Management_Testing/04-Testing_for_Account_Enumeration_and_Guessable_User_Account) An attacker could use this vulnerability to: - Build a list of valid user accounts - Target those accounts for credential stuffing or phishing attacks - Gain reconnaissance information about the organization ## Suggested Fix In the authentication/authorization middleware, restructure the logic to: ``` // Pseudocode example func handleRequest(req Request) Response { // 1. Check authorization FIRST if !isAuthorized(req) { return ForbiddenResponse("Access Denied") // Generic response } // 2. Only then check if resource/user exists user, err := getUser(req.UserID) if err != nil { return ForbiddenResponse("Access Denied") // Same generic response } // 3. Process the request return processRequest(user, req) } ``` ## Environment - NetBird Version: 0.59.13 - Deployment: Self-hosted - Discovered during: Security hardening / penetration testing ## Additional Context This was identified during security hardening of our NetBird deployment. We've already implemented generic error pages at the reverse proxy level (Caddy), but the underlying application logic still reveals user existence through differing response codes.
saavagebueno added the triage-needed label 2026-08-05 01:25:20 -04:00
Sign in to join this conversation.
No Label triage-needed
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: DYNR/netbird#10304