Skip to content

[Security] Password-reset and signup throttle bypass via spoofable client-IP header #9906

@Sachinart

Description

@Sachinart

Prerequisites

  • I have searched for related issues in the issues list.
  • This is an issue with the Halo project itself. If it is not an issue with the project itself(For example: Installation and deployment issues.), it is recommended to submit it in the Discussions.
  • I have tried disabling all plugins to rule out plugins as the cause of the problem.
  • If it is an issue with plugins and themes, please submit it in the respective plugin and theme repositories.

System information

Hi Halo security team,

Reporting a second vulnerability closely related to the login brute-force bypass I sent separately — the same spoofable client-IP issue also nullifies the throttling on the password-reset and signup endpoints.

  • Affected by Halo version(s): latest
  • Vulnerability self-scoring [1-10]: 6
  • Would you like to be attributed? Yes — please credit:
    Name: Chirag Artani
    Link: https://3rag.com/

Affected sinks

All three use getClientIp() as the per-IP rate-limit key:

  • PreAuthEmailPasswordResetEndpoint.java:183 — send-reset-email throttle
  • PreAuthEmailPasswordResetEndpoint.java:191 — reset-token verification throttle
  • PreAuthSignUpEndpoint.java:158 — signup throttle

Root cause is the same as the login brute-force report: getClientIp() returns the first (attacker-controlled) value from headers like X-Forwarded-For, X-Real-IP, CF-Connecting-IP, Proxy-Client-IP, with no verification that RemoteAddr belongs to a trusted reverse proxy.

Impact

  • Unlimited password-reset emails from a single host — email-bomb / harassment / deliverability damage to the Halo instance's sending domain.
  • Signup flooding — unlimited account creation, spam/abuse pressure, database growth.
  • Removes the only throttle in front of the reset-token verification endpoint. Token entropy (380-bit alphanumeric, SHA-512 hashed per EmailPasswordRecoveryServiceImpl.java:165-167) keeps raw brute-force infeasible, but defense-in-depth is eroded — any future weakening of token generation, logging, or hashing would become directly exploitable.

CWE

  • CWE-345: Insufficient Verification of Data Authenticity
  • CWE-307: Improper Restriction of Excessive Authentication Attempts (applies to the reset-token verification endpoint)
  • CWE-799: Improper Control of Interaction Frequency

Suggested remediation

The login fix — addressing IpAddressUtils.getClientIp() resolves all three sinks at once:

  1. Configurable trusted-proxy CIDR allowlist; only honor forwarding headers when RemoteAddr is on the list.
  2. Parse X-Forwarded-For right-to-left, stopping at the first untrusted hop.
  3. Default trusted list to empty — fresh installs use RemoteAddr only.
  4. For password-reset specifically, consider an additional per-email-address throttle so attribution does not depend solely on client IP.

What is the project operation method?

Source Code

What happened?

Summary

The same IpAddressUtils.getClientIp() weakness (trusting client-supplied forwarding headers without a trusted-proxy check) is also used as the rate-limit key for the password-reset and signup flows. An unauthenticated attacker can rotate the spoofed header per request to defeat throttling on all three endpoints.

Reproduce Steps

Proof of concept

Rotate the spoofed header per request, each attempt lands in a fresh bucket:

POST /password-reset/email HTTP/1.1
Host: <target>
X-Forwarded-For: 1.2.3.<random>
Content-Type: application/x-www-form-urlencoded

email=<victim>@[example.com](http://example.com/)

Same pattern works against /password-reset/verify and the signup endpoint.

Relevant log output

Additional information

Best regards,
Chirag Artani
https://3rag.com/

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions