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.
Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

ptmkenny created an issue. See original summary.

  • ptmkenny committed 1893c495 on 1.0.x
    fix: #3605236 Security: rate limit bypass by setting uid
    
    By: ptmkenny
    
ptmkenny’s picture

Status: Active » Fixed

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.