Fixed CRLF command smuggling and node misrouting in AbstractAggregateConnection::write() and CommandInterface::deserializeCommand() (#1728)

* Fixed CRLF command injection / smuggling in AbstractAggregateConnection::write()

* Revert "Fixed CRLF command injection / smuggling in AbstractAggregateConnection::write()"

This reverts commit a4720b03ee.

* Fixed CRLF command smuggling and node misrouting in AbstractAggregateConnection::write() and CommandInterface::deserializeCommand()

* Revert change
This commit is contained in:
Vladyslav Vildanov
2026-09-16 10:01:26 +03:00
committed by GitHub
parent 37490865cc
commit 3edc442e55
10 changed files with 421 additions and 37 deletions
@@ -1676,4 +1676,77 @@ class RedisClusterTest extends PredisTestCase
$cluster->write($command1->serializeCommand() . $command2->serializeCommand() . $command3->serializeCommand());
}
/**
* Regression guard for GHSA-w6f5-v2h6-g786 (CWE-93): a CRLF embedded in a
* bulk string's own value must not be mistaken for a command boundary
* (splitting one command into a smuggled extra command), and must not
* corrupt the argument list used to pick the target node.
*
* @group disconnected
*/
public function testWriteHandlesCRLFEmbeddedInBulkStringValue(): void
{
$command = new Command\Redis\SET();
$command->setArguments(['victim-key', "PAD\r\n*1\r\n\$7\r\nFLUSHDB"]);
$factory = $this->getMockBuilder(FactoryInterface::class)->getMock();
$connection1 = $this->getMockConnection('tcp://127.0.0.1:7001');
$connection2 = $this->getMockConnection('tcp://127.0.0.1:7002');
$connection3 = $this->getMockConnection('tcp://127.0.0.1:7003');
$cluster = new RedisCluster($factory, new Parameters());
$cluster->add($connection1);
$cluster->add($connection2);
$cluster->add($connection3);
$expectedConnection = $cluster->getConnectionByCommand($command);
foreach ([$connection1, $connection2, $connection3] as $connection) {
if ($connection === $expectedConnection) {
$connection->expects($this->once())->method('write')->with($command->serializeCommand());
} else {
$connection->expects($this->never())->method('write');
}
}
$cluster->write($command->serializeCommand());
}
/**
* Regression guard for GHSA-w6f5-v2h6-g786 (CWE-93): a CRLF embedded in a
* bulk string KEY must not corrupt the argument list used to pick the
* target node, which would silently route the command to the wrong node.
*
* @group disconnected
*/
public function testWriteHandlesCRLFEmbeddedInBulkStringKey(): void
{
$command = new Command\Redis\SET();
$command->setArguments(["victim\r\n*1\r\n\$4\r\nEVIL", 'somevalue']);
$factory = $this->getMockBuilder(FactoryInterface::class)->getMock();
$connection1 = $this->getMockConnection('tcp://127.0.0.1:7001');
$connection2 = $this->getMockConnection('tcp://127.0.0.1:7002');
$connection3 = $this->getMockConnection('tcp://127.0.0.1:7003');
$cluster = new RedisCluster($factory, new Parameters());
$cluster->add($connection1);
$cluster->add($connection2);
$cluster->add($connection3);
$expectedConnection = $cluster->getConnectionByCommand($command);
foreach ([$connection1, $connection2, $connection3] as $connection) {
if ($connection === $expectedConnection) {
$connection->expects($this->once())->method('write')->with($command->serializeCommand());
} else {
$connection->expects($this->never())->method('write');
}
}
$cluster->write($command->serializeCommand());
}
}