Problem/Motivation
Identified by Opus 4.8:
Rate-limit bypass via client-supplied uid
The rate limiter is keyed on the session user, but the value it counts is a client-writable field.
- RateLimitPerUserValidator::isPostCountBelowLimit() counts stored entries where uid = $this->currentUser->id()
(RateLimitPerUserValidator.php:77,89).
- But the uid base field has no field-access restriction — setDisplayConfigurable('form', FALSE) only hides it from the
form; it does nothing for REST/JSON:API writes. There is no hook_entity_field_access, no setReadOnly, nothing (confirmed
by grep).
- Validation runs before preSave, against entities already in storage, so the entity being created is never counted in
its own check.
Attack: an authenticated attacker (say uid 100) includes "uid": 0 (anonymous — always a valid entity reference) in every
POST body. Each entry is stored owned by uid 0. The validator then runs count(... WHERE uid = 100), which is always 0,
so 0 < count_auth is always true. The limit never trips → unlimited log creation. The same trick spreads entries across
any valid uid to dodge both the count_auth and count_anon buckets.
This is exactly the bypass class the preSave comment describes for created ("never trust a client-supplied… the
constraint counts a user's entries by their created…"), but uid is also read by that same query and was left
client-controlled.
Comments
Comment #4
ptmkenny commented