GitHub Advisoryadvisory · Packagist

GHSA-w6f5-v2h6-g786 Predis: Redis command injection and denial of service via CRLF smuggling in pipelined commands on aggregate connections

CRITICAL Published 2026-09-08 Modified 2026-09-08 Official page ↗
Aliases
CVE-2026-84372
Ecosystem
Packagist
Packages
predis/predis
Severity
Critical
CVSS vector
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
Weaknesses
CWE-93
Reviewed
2026-09-08

Affected packages

• Packagist predis/predis: >= 3.0.0-RC1, < 3.3.0 → fixed in 3.3.0

Description

Summary An improper CRLF neutralization flaw in Predis' pipeline handling on aggregate connections lets an unauthenticated attacker who can influence any pipelined argument — a value or a key, e.g. a URL slug used as a cache key — smuggle arbitrary Redis commands into the connection. • On cluster connections (`cluster` option, incl. client-side sharding) this is remote command injection: shard-wide `FLUSHDB`, targeted `DEL`/`SET`, same-slot key theft via `GET`, cache poisoning, and possible node/cluster outage. • On replication connections (`replication` option) it is a reliable, repeatable denial of service (uncaught fatal error) triggered by any value containing `\r\n`. Details When a pipeline is executed over an aggregate connection, `AbstractAggregateConnection::write()` re-parses the already-serialized pipeline buffer with `explode("\r\n")` instead of honoring RESP length prefixes: • https://github.com/predis/predis/blob/v3.2.0/src/Connection/AbstractAggregateConnection.php#L78-L94 • splits the buffer on `\r\n`, ignoring `$` bulk lengths, • rebuilds each chunk via `Command::deserializeCommand()` (https://github.com/predis/predis/blob/v3.2.0/src/Command/Command.php#L157) to decide routing, • writes each chunk to the connection chosen for that (fake) command. RESP is length-prefixed, so the Redis server parses the original stream correctly — but this second, client-side parser treats attacker-controlled `\r\n` sequences as command boundaries. An argument such as: PAD\r\n*1\r\n$7\r\nFLUSHDB is a single data value to the server, but a complete, valid `FLUSHDB` command to the re-parser. The consequence depends on the connection type: • Replication: a pipeline forces `switchToMaster()`, so all chunks go to the master and the byte stream stays intact — but the misaligned chunk makes `deserializeCommand()` throw an uncaught `UnexpectedValueException: Invalid serializing format`. Any value containing `\r\n` (binary serializers such as igbinary/msgpack, or multi-line text) reliably crashes the request. This is the crash tracked in #1574 — an unauthenticated, repeatable DoS. • Cluster: chunks are routed to different nodes by slot, so the byte stream is split across sockets. The smuggled command arrives on a node whose stream is clean and is executed, though the application never sent it: • `FLUSHDB` wipes an entire shard. It has no key but is routable because `ClusterStrategy::getFakeKey()` hardcodes the fake key `'key'` (https://github.com/predis/predis/blob/v3.2.0/src/Cluster/ClusterStrategy.php#L56 and #L243-L246), so the smuggled command always lands on the node serving `slot('key')`. • `INFO` (same fake-key routing) leaks server configuration via orphaned responses; `CLUSTER FLUSHSLOTS` can take a node down. • Same-slot `GET`/`SET`/`DEL` allow key theft (the reply is attributed to the application's own later command on that slot), cache poisoning and targeted data destruction; junk-key floods can exhaust node memory (OOM / mass eviction of legitimate keys). • Lua execution is not reachable: `EVAL` cannot be reconstructed (the class is `EVAL_` due to the PHP reserved word), `EVAL_RO` fails the `Keys` trait validation, and `EVALSHA` requires a pre-loaded script. This is accidental, not a designed mitigation, and does not reduce severity — `FLUSHDB`/`DEL`/`SET` alone already permit full cache wipes and data destruction. Affected versions. Introduced in v3.0.0 by PR #1438 ("Improved pipeline abstractions"). Affected range: 3.0.0-RC1 through 3.2.0 (v3.0.0-alpha1 is not affected — the vulnerable code was added after it). v1.x and v2.x are not affected; their pipelines write per-command via `writeRequest()` and the vulnerable code path does not exist. Only `pipeline()` reaches the vulnerable sink; `transaction()` / `MULTI` paths do not. Proof of concept Two plain `redis:8` containers acting as two shards (PredisCluster shards client-side, so Redis itself need not be in cluster mode); a PHP app on a vulnerable Predis checkout (e.g. v3.2.0). `docker-compose.yml`: services: redis1: image: redis:8 ports: ["6391:6379"] redis2: image: redis:8 ports: ["6392:6379"] `index.php` (a normal-looking app — slug from URL → cache lookup): 'predis', 'parameters' => ['read_write_timeout' => 2]]); if (isset($_GET['seed'])) { for ($i = 1; $i set("user:$i", "data$i"); } exit('seeded'); } $slug = $_GET['slug'] ?? ''; try { [$doc] = $client->pipeline()->get("slug:$slug")->execute(); echo $doc ?: 'no such slug'; } catch (Throwable $e) { http_response_code(500); echo get_class($e); } Run: composer require predis/predis:3.2.0 docker compose up -d php -S 127.0.0.1:8080 -t . curl 'http://127.0.0.1:8080/?seed' # 100 keys Attack (smuggled `FLUSHDB` inside the slug): curl 'http://127.0.0.1:8080/?slug=PAD4%0D%0A*1%0D%0A%247%0D%0AFLUSHDB' The slug's first line must hash to a different shard than the fake key `'key'` (otherwise the truncated bytes swallow the injection and the request simply 404s). With two shards this is ~50% per attempt — retry `PAD0`, `PAD1`, … until the request returns 500. More shards make the attack easier: the per-attempt hit probability is `(N-1)/N`, so on production clusters with many shards the first request succeeds with near-certainty. Verified result: `dbsize` across both shards drops 100 → 62; one shard was wiped by a `FLUSHDB` the application never issued (it only ever ran `GET`/`SET` on normal keys). The fix was confirmed A/B: the same PoC wipes a shard on the parent of commit `053cb4b6` and fails on `053cb4b6`. Impact CWE-93 (Improper Neutralization of CRLF Sequences) leading to Redis command injection / protocol smuggling and denial of service. Any application on predis/predis 3.0.0-RC1 – 3.2.0 that calls `pipeline()` on a cluster or replication connection and includes attacker-influenced data (values or keys — e.g. cache keys built from URL slugs) in the pipelined commands is affected. This is a common pattern for cache lookups, sessions and queued writes. • Cluster: unauthenticated remote command injection — shard-wide cache wipe (`FLUSHDB`), targeted destruction (`DEL`), cache poisoning (`SET`), same-slot key theft (`GET`), node memory exhaustion (key flood), possible cluster outage (`CLUSTER FLUSHSLOTS`). • Replication: reliable unauthenticated DoS on every affected request. Remediation Upgrade to predis/predis 3.3.0 or later. The fix (PR #1586, commit `053cb4b6`) makes pipelines on aggregate connections write each command using the real `Command` object, eliminating the second, byte-splitting parser. Users who cannot upgrade immediately should avoid calling `pipeline()` on aggregate (cluster / replication) connections with any attacker-influenced keys or values; there is no reliable in-application way to neutralize the embedded `\r\n` while the second parser remains in the code path.

Related

Alias · CVE (1)

  • CVE-2026-84372 Predis is a flexible and feature-complete Redis and Valkey client for PHP. vulnerability

Weakness · CWE (1)

  • CWE-93 Improper Neutralization of CRLF Sequences weakness

References