Prerequisites
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:
- Configurable trusted-proxy CIDR allowlist; only honor forwarding headers when
RemoteAddr is on the list.
- Parse
X-Forwarded-For right-to-left, stopping at the first untrusted hop.
- Default trusted list to empty — fresh installs use
RemoteAddr only.
- 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/
Prerequisites
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.
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 throttlePreAuthEmailPasswordResetEndpoint.java:191— reset-token verification throttlePreAuthSignUpEndpoint.java:158— signup throttleRoot cause is the same as the login brute-force report:
getClientIp()returns the first (attacker-controlled) value from headers likeX-Forwarded-For,X-Real-IP,CF-Connecting-IP,Proxy-Client-IP, with no verification thatRemoteAddrbelongs to a trusted reverse proxy.Impact
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
Suggested remediation
The login fix — addressing
IpAddressUtils.getClientIp()resolves all three sinks at once:RemoteAddris on the list.X-Forwarded-Forright-to-left, stopping at the first untrusted hop.RemoteAddronly.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:
Same pattern works against
/password-reset/verifyand the signup endpoint.Relevant log output
Additional information
Best regards,
Chirag Artani
https://3rag.com/