Forums Bug Reports Thread

Portal new-ticket rate limit is trivially bypassable (per-email LIKE counter, no normalization)

Patrick Bass · Jun 6 · 8 · 1 Locked
[Minor] [Normal Priority] [Bug Fixed] [Always Reproduces]
🚀 OP Jun 6, 2026 6:25pm

Area: mobieusHelp (audit p7) · Surface: mobieusHelp /help/portal/new (PortalRateLimiter) · Dimension: security · Severity: minor

OWASP A04 Insecure Design / weak rate limiting on a public ticket-creation surface. Using a JSON LIKE scan over the audit table as the counter is both evadable (vary email) and a performance footgun as audit volume grows. Each unmatched submission still issues a verification email (PortalController::submit L182 PortalVerification::issueAndSend) so an attacker can use the tenant's outbound SMTP to send verification emails to arbitrary addresses, subject only to the 10/IP/hr cap.

Evidence

PortalRateLimiter::check (src/Services/Helpdesk/PortalRateLimiter.php L31-46) throttles 5/email/hr and 10/IP/hr by COUNTing helpdesk_audit_events rows whose after_json matches a LIKE pattern (countRecent L80-105: `after_json LIKE '%"email":"<value>"%'`). The email facet is attacker-chosen and unbounded (any unique address evades the per-email cap), and the IP cap of 10/hr is the only real limit. PortalController::submit (L125-127) reads `$ip = $_SERVER['REMOTE_ADDR']`. Captcha (MathCaptcha) gates anon submissions, which mitigates pure flooding, but the documented per-email throttle provides little protection.

Suggested fix. Add a normalized counter keyed on (facet,value,hour) instead of LIKE on JSON, lower/clamp the per-IP cap, and gate verification-email send behind the IP limit. Consider hashing the email facet and indexing it.

Filed by the automated tenant-app audit and adversarially evidence-verified. Status: verified. Open — not yet actioned.


Patrick Bass
@mobieus

🚀 Jun 7, 2026 5:38am

Resolved — fixed and deployed. Commit 89b2334003a7, shipped dev-first then to all tenants on 2026-06-06.

Hardened PortalRateLimiter: IP facet now counts against the indexed ip_address column (no JSON LIKE scan); email facet is SHA-256 hashed and queried by fixed-width hash, removing the fragile quote-escaping and substring false-positives and keeping no clear-text PII in counter rows; lowered PER_IP_HOUR from 10 to 3; reordered check() to enforce the IP cap first so the downstream verification-email send (which only runs after check() returns ok) is gated behind the IP limit. php -l passes.

Status: fixed. Thread closed and locked.


Patrick Bass
@mobieus

Log in or register to reply to this thread.