GHSA-w6f5-v2h6-g786: CRLF Injection

Published Sep 8, 2026
·
Updated

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 $<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\n1\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), EVALRO 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):

<?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' => ['readwritetimeout' => 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) { httpresponsecode(500); echo getclass($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%0A1%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 Software

1 affected componentFixes available
composer/predis/predis>=3.0.0-RC1<3.3.0
3.3.0

Remediation

Recommended actions to resolve this vulnerability, in priority order.

  1. Upgrade

    Upgrade composer/predis/predis to a version that resolves this vulnerability.

    Fixed in 3.3.0
  2. Upgrade

    Upgrade predis/predis to a version that resolves this vulnerability.

    Fixed in 3.3.0
  3. Configuration

    For predis/predis versions in the affected range (3.0.0-RC1 through 3.2.0), avoid calling pipeline() on cluster/replication (aggregate) connections with attacker-influenced keys/values, since only pipeline() reaches the vulnerable sink.

    Predis client pipeline usage pipeline() on aggregate connections = avoid/disable usage
  4. Compensating control

    If you cannot upgrade immediately, treat any attacker-controlled inputs that could become pipeline commands/arguments as unsafe: ensure pipeline is not constructed from attacker-influenced keys/values (e.g., do not use URL slugs/values containing \r\n).

Event History

Sep 8, 2026
Advisory Published
via GitHub·08:57 PM
Data Sourced
via GitHub·08:57 PM
DescriptionSeverityWeaknessAffected Software

Frequently Asked Questions

1

What application inputs need to be treated as exploit-relevant?

Any attacker-influenced argument sent through a pipeline can be relevant, including both command values and keys. A URL slug used as a cache key is explicitly identified as an example.

2

Which connection configurations have the most serious impact?

Cluster connections, including client-side sharding enabled with the cluster option, permit arbitrary Redis command smuggling. Replication connections using the replication option are affected by a reliable, repeatable denial of service when a value contains CRLF.

3

What could command smuggling enable on a cluster connection?

The described outcomes include shard-wide FLUSHDB, targeted DEL or SET operations, same-slot key retrieval with GET, cache poisoning, and a possible node or cluster outage.

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203