Skip to content

Latest commit

 

History

History
301 lines (220 loc) · 13.8 KB

File metadata and controls

301 lines (220 loc) · 13.8 KB

Gitea User Enumeration + Brute Force Attack Chain Vulnerability Report

1. Attack Chain Overview

Field Value
Chain Name User Enumeration + Brute Force = Account Takeover (3 vulnerabilities)
CWE IDs CWE-203 (x2) + CWE-307
CVSS 4.0 Score 8.7 (High)
CVSS 4.0 Vector CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:N/SC:N/SI:N/SA:N
Affected Software Gitea
Affected Versions <= 1.25.4
Tested Version 1.25.4 - 369830bada

2. Attack Chain Description

Gitea <= 1.25.4 is vulnerable to a user enumeration and brute force attack chain. Attackers can enumerate valid usernames through two methods: (1) accessing the /{username} user profile endpoint, where HTTP 200 indicates existence and 404 indicates non-existence; (2) submitting existing usernames to the /user/sign_up registration endpoint, which returns the error message "The username is already taken". Subsequently, attackers exploit the lack of rate limiting on the login endpoint to perform password brute force attacks at 280+ attempts/second, ultimately achieving arbitrary account takeover.

3. Exploit Prerequisites

Condition Type Required Notes
User profile publicly accessible Configuration Partial Default configuration allows anonymous access, enumeration method one depends on this
Registration enabled Configuration Partial DISABLE_REGISTRATION=false (default), enumeration method two depends on this
Login CAPTCHA not enabled Configuration Yes REQUIRE_CAPTCHA_FOR_LOGIN=false (default)
No external WAF/IDS protection Environment Partial External protection may mitigate attacks
Target user uses weak password User Action Yes Attack success depends on password strength

4. Attack Chain Steps

Step 1: User Enumeration - Method A (VULN-001)

The attacker uses a username dictionary, sequentially accessing the http://target/{username} endpoint. The system returns HTTP 200 status code and displays the user profile page for existing users, and returns HTTP 404 for non-existing users. By analyzing response status codes, the attacker can quickly enumerate all valid usernames in the system.

Step 1: User Enumeration - Method B (VULN-002)

The attacker submits each username from the dictionary to the /user/sign_up registration endpoint. When a username already exists, the system returns the error message "The username is already taken"; when a username is available, the system attempts to create an account or returns other errors. By analyzing error message differences, the attacker can enumerate valid usernames. This method remains effective when user profile access is restricted.

Step 2: Rate Limiting Test (VULN-003)

Before starting brute force, the attacker sends 20 rapid login requests to test whether the system has rate limiting. If there is no HTTP 429 response or "too many requests" error message, it confirms the system has not implemented effective brute force protection.

Step 3: Password Brute Force (VULN-003)

For each enumerated user, the attacker sends login requests to the /user/login endpoint using a password dictionary. The system returns the same error message for each failed login attempt, does not record failure counts, does not lock accounts, allowing the attacker to continue attempts at 200+ per second.

Step 4: Account Takeover

When login succeeds (HTTP 302/303 redirect to non-login page), the attacker obtains a valid session. Subsequently accessing /user/settings confirms login status, accessing /admin checks if it's an administrator account, completing the account takeover.

5. Proof of Concept (PoC)

Please check the POC_CODE_HERE

The POC Execute Result

poc

6. Component Vulnerabilities

6.1 VULN-001: User Enumeration via Profile Access

Field Value
Vulnerability ID VULN-001
Vulnerability Name User Enumeration via Public Profile Access
Vulnerability Type Information Disclosure
CWE-ID CWE-203 (Observable Discrepancy)
CVSS 4.0 Score 5.3 (Medium)
CVSS 4.0 Vector CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N

Description

Gitea defaults to allowing anonymous access to user profile pages /{username}. When the requested username exists, the server returns HTTP 200 and displays the user profile page; when the username does not exist, it returns HTTP 404. Attackers can exploit this discrepancy to quickly enumerate valid usernames in the system, providing a target list for subsequent brute force or social engineering attacks.

SINK Location

File: routers/web/user/home.go:715-768

func UsernameSubRoute(ctx *context.Context) {
    // WORKAROUND to support usernames with "." in it
    // https://github.com/go-chi/chi/issues/781
    username := ctx.PathParam("username")
    reloadParam := func(suffix string) (success bool) {
        ctx.SetPathParam("username", strings.TrimSuffix(username, suffix))
        context.UserAssignmentWeb()(ctx)
        if ctx.Written() {
            return false
        }
        // check view permissions
        if !user_model.IsUserVisibleToViewer(ctx, ctx.ContextUser, ctx.Doer) {
            ctx.NotFound(fmt.Errorf("%s", ctx.ContextUser.Name))
            return false
        }
        return true
    }

SOURCE Location

File: routers/web/web.go:820-823

m.Group("", func() {
    m.Get("/{username}", user.UsernameSubRoute)
    m.Methods("GET, OPTIONS", "/attachments/{uuid}", optionsCorsHandler(), repo.GetAttachment)
}, optSignIn)

Call Stack (SOURCE to SINK)

  1. SOURCE: HTTP GET request arrives at /{username} route (routers/web/web.go:821)
  2. Route matches to user.UsernameSubRoute handler function
  3. UsernameSubRoute extracts username parameter from URL path (routers/web/user/home.go:718)
  4. Calls context.UserAssignmentWeb() middleware (services/context/user.go:15)
  5. Middleware calls userAssignment function (services/context/user.go:61)
  6. userAssignment calls user_model.GetUserByName(ctx, username) to query database (services/context/user.go:68)
  7. SINK: If user does not exist, returns HTTP 404; if exists, continues to render user profile returning HTTP 200

Root Cause

Gitea's user profile pages are open to unauthenticated users by default, and return different HTTP status codes (200 vs 404) for existing and non-existing users. The system lacks username enumeration protection mechanisms and has not implemented a unified error response strategy, allowing attackers to distinguish valid and invalid usernames through simple HTTP requests.

Exploit Conditions

Condition Required Notes
User profile publicly accessible Yes Default configuration
No WAF/IDS protection Partial External protection may detect enumeration behavior

6.2 VULN-002: User Enumeration via Registration

Field Value
Vulnerability ID VULN-002
Vulnerability Name User Enumeration via Registration Error Messages
Vulnerability Type Information Disclosure
CWE-ID CWE-203 (Observable Discrepancy)
CVSS 4.0 Score 5.3 (Medium)
CVSS 4.0 Vector CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N

Description

Gitea's registration endpoint /user/sign_up returns the explicit error message "The username is already taken" when a username already exists, while returning other responses (registration success or other errors) when a username is available. Attackers can exploit this discrepancy to enumerate valid usernames in the system. This method remains effective when user profile access is restricted, serving as an alternative approach for user enumeration.

SINK Location

File: routers/web/auth/auth.go:579-586

// handle error with template
switch {
case user_model.IsErrUserAlreadyExist(err):
    ctx.Data["Err_UserName"] = true
    ctx.RenderWithErr(ctx.Tr("form.username_been_taken"), tpl, form)
case user_model.IsErrEmailAlreadyUsed(err):
    ctx.Data["Err_Email"] = true
    ctx.RenderWithErr(ctx.Tr("form.email_been_used"), tpl, form)
// ... other error cases ...
}

SOURCE Location

File: routers/web/auth/auth.go:452-526

// SignUpPost response for sign up information submission
func SignUpPost(ctx *context.Context) {
    form := web.GetForm(ctx).(*forms.RegisterForm)
    // ...
    u := &user_model.User{
        Name:   form.UserName,
        Email:  form.Email,
        Passwd: form.Password,
    }

    if !createAndHandleCreatedUser(ctx, tplSignUp, form, u, nil, nil) {
        // error already handled
        return
    }
}

Call Stack (SOURCE to SINK)

  1. SOURCE: HTTP POST request arrives at /user/sign_up route, carrying user_name parameter
  2. SignUpPost function handles the request (routers/web/auth/auth.go:453)
  3. Creates user_model.User struct containing user-submitted username
  4. Calls createAndHandleCreatedUser function (routers/web/auth/auth.go:523)
  5. Calls createUserInContext function (routers/web/auth/auth.go:535)
  6. Internally calls user_model.CreateUser to attempt user creation
  7. If username already exists, returns ErrUserAlreadyExist error
  8. SINK: createUserInContext catches error, renders "form.username_been_taken" message (routers/web/auth/auth.go:583)

Root Cause

Gitea's registration functionality returns explicit error messages when handling username conflicts, distinguishing between "username already exists" and other error types. The system has not implemented a unified registration error response strategy, allowing attackers to distinguish valid and invalid usernames by analyzing error messages. Additionally, registration functionality has no CAPTCHA protection by default, allowing automated batch enumeration.

Exploit Conditions

Condition Required Notes
Registration enabled Yes DISABLE_REGISTRATION=false (default)
Registration CAPTCHA not enabled Partial Can be bypassed via OCR or manual effort
No WAF/IDS protection Partial External protection may detect enumeration behavior

6.3 VULN-003: Login Brute Force (No Rate Limiting)

Field Value
Vulnerability ID VULN-003
Vulnerability Name Login Brute Force due to Missing Rate Limiting
Vulnerability Type Authentication Bypass
CWE-ID CWE-307 (Improper Restriction of Excessive Authentication Attempts)
CVSS 4.0 Score 7.5 (High)
CVSS 4.0 Vector CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N

Description

Gitea's login endpoint /user/login has CAPTCHA disabled by default, has not implemented login failure count limiting, has not implemented account lockout mechanism, and has not implemented IP-level rate limiting. Attackers can send hundreds of login requests per second, using password dictionaries to brute force target accounts until finding the correct password.

SINK Location

File: routers/web/auth/auth.go:196-243

// SignInPost response for sign in request
func SignInPost(ctx *context.Context) {
    // ... validation code ...

    if setting.Service.EnableCaptcha && setting.Service.RequireCaptchaForLogin {
        context.VerifyCaptcha(ctx, tplSignIn, form)
        if ctx.Written() {
            return
        }
    }

    u, source, err := auth_service.UserSignIn(ctx, form.UserName, form.Password)
    if err != nil {
        if errors.Is(err, util.ErrNotExist) || errors.Is(err, util.ErrInvalidArgument) {
            ctx.RenderWithErr(ctx.Tr("form.username_password_incorrect"), tplSignIn, &form)
            log.Warn("Failed authentication attempt for %s from %s: %v", form.UserName, ctx.RemoteAddr(), err)
        }
        // ... error handling continues without rate limiting ...
        return
    }

SOURCE Location

File: modules/setting/service.go:56-57,190-191

// Service settings structure
type ServiceSettings struct {
    // ...
    EnableCaptcha                           bool
    RequireCaptchaForLogin                  bool
    // ...
}

// Default configuration loading
Service.EnableCaptcha = sec.Key("ENABLE_CAPTCHA").MustBool(false)
Service.RequireCaptchaForLogin = sec.Key("REQUIRE_CAPTCHA_FOR_LOGIN").MustBool(false)

Call Stack (SOURCE to SINK)

  1. SOURCE: HTTP POST request arrives at /user/login route
  2. Request carries user_name and password parameters
  3. SignInPost function handles the request (routers/web/auth/auth.go:197)
  4. Checks RequireCaptchaForLogin configuration (default false, skips CAPTCHA)
  5. Calls auth_service.UserSignIn() to verify credentials (services/auth/signin.go:25)
  6. SINK: If credentials are wrong, only logs and returns error message, no restriction measures
  7. Attacker can immediately send next request, no delay or blocking

Root Cause

Gitea's login functionality was not designed with brute force protection as a default security policy. The RequireCaptchaForLogin configuration defaults to false, and the system does not enforce CAPTCHA. The code does not implement login failure counters, temporary account lockout, IP blacklisting, or request delays and other protection mechanisms. Login failures are only written to logs, triggering no defensive actions.

Exploit Conditions

Condition Required Notes
REQUIRE_CAPTCHA_FOR_LOGIN=false Yes Default configuration
ENABLE_CAPTCHA=false Partial Even if CAPTCHA is enabled, login may still not enforce it
No external rate limiting Yes No reverse proxy or WAF limiting
Target uses weak password Yes Attack success depends on password strength