# CVE-2026-23524: Laravel Reverb RCE via Insecure Deserialization (CVSS 9.8)

> Laravel Reverb versions 1.6.3 and below have a critical insecure deserialization vulnerability. When horizontal scaling is enabled, Reverb passes Redis channel data directly into unserialize() without class restrictions. If your Redis is unauthenticated, attackers can achieve full remote code execution. Here is how to check and fix it.

**Author:** Matt King | **Published:** May 19, 2026 | **Category:** Security

---

A critical vulnerability has been disclosed in Laravel Reverb, the official WebSocket server for Laravel applications. Tracked as CVE-2026-23524 with a CVSS score of 9.8, this flaw allows an unauthenticated attacker to execute arbitrary code on your server by sending a specially crafted payload through Redis when horizontal scaling is enabled. If you are running Reverb 1.6.3 or below with `REVERB_SCALING_ENABLED=true`, you should treat this as urgent. The fix is a single version bump, but understanding what went wrong is worth your time.

---

## What Is Laravel Reverb?

Laravel Reverb is the first-party WebSocket server for Laravel, introduced to give Laravel developers a native, self-hosted alternative to third-party services like Pusher. It integrates tightly with Laravel Echo and Laravel's broadcasting system, making it easy to add real-time features like notifications, live dashboards, and presence channels to any Laravel application.

Because it is an official Laravel package, it sees wide adoption. Estimates put roughly 7 million applications in the potentially affected range, which is part of what makes this CVE particularly significant.

When horizontal scaling is required (for example, running Reverb across multiple nodes behind a load balancer), Reverb uses Redis as a pub/sub backend to synchronise state between nodes. That Redis integration is exactly where the vulnerability lives.

---

## How the Vulnerability Works

When `REVERB_SCALING_ENABLED=true`, Reverb subscribes to Redis channels to share WebSocket state across nodes. As messages arrive from Redis, Reverb needs to reconstruct PHP objects from the channel data. In versions 1.6.3 and below, it did this with a direct call to PHP's native `unserialize()` function, passing in raw Redis payload data without any class restriction.

The relevant behaviour, simplified, looks like this:

```php
// Reverb <= 1.6.3 (simplified illustration of the vulnerable pattern)
$data = $redis->get($channel);
$object = unserialize($data); // No allowed_classes restriction
```

`unserialize()` without an `allowed_classes` option will happily instantiate any class that exists in the PHP runtime, including classes from Laravel itself, Symfony components, Guzzle, or any other package in your `vendor/` directory. Attackers exploit this by crafting serialized payloads that chain together existing classes in unexpected ways, a technique known as PHP Object Injection or a "property-oriented programming" (POP) chain.

The attack chain looks like this:

1. The attacker gains write access to your Redis instance. Because Redis is commonly deployed on internal networks without authentication (and sometimes exposed publicly), this step is often the easiest part.
2. The attacker writes a malicious serialized PHP payload to the Redis channel that Reverb is subscribed to.
3. Reverb deserializes the payload. During deserialization, PHP executes magic methods like `__wakeup()` or `__destruct()` on the instantiated objects.
4. By chaining the right classes together, the attacker triggers code execution, typically landing a shell or exfiltrating environment variables.

This is a textbook remote code execution path. No authentication is required against Reverb itself. If Redis is reachable and unauthenticated (which is a very common misconfiguration), exploitation is straightforward.

---

## Am I Affected?

You are potentially affected if all three of the following are true:

- You are running `laravel/reverb` version 1.6.3 or below
- Horizontal scaling is enabled in your environment
- Your Redis instance is accessible from an untrusted network, or is unauthenticated

**Check your Reverb version:**

```bash
composer show laravel/reverb
```

Look for the `versions` line in the output. If it shows `1.6.3` or lower, you are running a vulnerable version.

**Check whether scaling is enabled:**

```bash
grep REVERB_SCALING_ENABLED .env
```

If this returns `REVERB_SCALING_ENABLED=true`, the vulnerable code path is active.

**Check whether Redis requires authentication:**

```bash
redis-cli ping
```

If you get `PONG` without supplying a password, your Redis instance is unauthenticated. Combine that with a network-accessible Redis and you have a critical exposure.

---

## How to Fix It

**Step 1: Upgrade Reverb to 1.7.0**

```bash
composer update laravel/reverb
```

Then verify the installed version:

```bash
composer show laravel/reverb | grep versions
```

Confirm you are on `1.7.0` or above before redeploying.

**Step 2: Secure your Redis instance**

Even after upgrading, running an unauthenticated Redis accessible beyond localhost is a bad posture. Add a strong password to your Redis configuration:

```ini
# /etc/redis/redis.conf
requirepass your-strong-password-here
```

Then update your Laravel environment to match:

```ini
# .env
REDIS_PASSWORD=your-strong-password-here
```

**Step 3: Restrict Redis network exposure**

Redis should only be reachable by the services that need it. Bind it to a private interface, not `0.0.0.0`:

```ini
# /etc/redis/redis.conf
bind 127.0.0.1 10.0.0.5
```

Use firewall rules to block external access to port `6379`.

**Step 4: Rotate secrets**

If you have any reason to believe your Redis instance was accessible and unprotected before you applied these fixes, treat your application secrets as compromised. Rotate your `APP_KEY`, database credentials, API keys, and any other secrets stored in your environment.

---

## Why Insecure Deserialization Is So Dangerous

Insecure deserialization consistently earns a place in the OWASP Top 10 for a reason. PHP's `unserialize()` is a powerful function that reconstructs complex object graphs from a string. When you give it a string from a trusted source, like a signed cookie you control, that is fine. When you give it arbitrary input from an external system like Redis, you are handing an attacker a loaded weapon.

The danger is not that `unserialize()` runs arbitrary code directly. It is that PHP automatically calls magic methods (`__construct`, `__wakeup`, `__destruct`, `__toString`, and others) during the deserialization process. By carefully constructing a serialized string that chains together existing classes from your vendor directory, an attacker can build a sequence of method calls that ultimately executes a system command or writes a file.

These chains, called POP chains, do not require any application-specific code. They exploit legitimate classes doing legitimate things in the wrong order and context. Tools like PHPGGC exist specifically to generate these chains for common PHP frameworks and libraries.

The defence is simple in principle: never call `unserialize()` on data you do not fully control. PHP 7.0 added an `allowed_classes` option to restrict which classes can be instantiated during deserialization. Passing `['YourExpectedClass']` or `false` dramatically reduces the attack surface.

---

## What Changed in Reverb 1.7.0

The fix in Reverb 1.7.0 addresses the root cause directly. Rather than using PHP's native `unserialize()` with unrestricted class instantiation, Reverb now passes an explicit `allowed_classes` option:

```php
// Reverb 1.7.0 (simplified illustration of the fix)
$data = $redis->get($channel);
$object = unserialize($data, ['allowed_classes' => [ReverbChannelPayload::class]]);
```

With `allowed_classes` set to a specific list, PHP will throw an error rather than instantiate any class outside that list. This breaks every known POP chain because the attacker can no longer introduce arbitrary classes into the object graph during deserialization.

The fix is tracked under GitHub Advisory GHSA-m27r-m6rx-mhm4 and is a minimal, targeted change. There is no reason to delay the upgrade.

---

## Protect Your Laravel Application

CVE-2026-23524 is a good example of how a well-intentioned architectural decision (horizontal scaling via Redis) introduces a critical attack surface when one implementation detail is overlooked. The vulnerable code was not careless. It was just missing one option on one function call. That is how most real-world vulnerabilities work.

[Run a free StackShield scan](/free-scan) on your application to check for exposed Redis instances, insecure configurations, and the kinds of infrastructure weaknesses that make exploits like this one trivial to chain together.

---

## Frequently Asked Questions

### What is CVE-2026-23524?

CVE-2026-23524 is a critical remote code execution vulnerability in Laravel Reverb versions 1.6.3 and below. It has a CVSS score of 9.8. When horizontal scaling is enabled via Redis, Reverb passes channel data into PHP's unserialize() function without restricting which classes can be instantiated, allowing attackers to inject malicious objects and execute arbitrary code.

### Am I affected by the Reverb vulnerability?

You are affected if you are running laravel/reverb version 1.6.3 or below AND have REVERB_SCALING_ENABLED=true in your .env file. Run "composer show laravel/reverb" to check your version. If horizontal scaling is not enabled, the vulnerable code path is not active.

### How do I fix CVE-2026-23524?

Upgrade Laravel Reverb to version 1.7.0 or later by running "composer update laravel/reverb". Additionally, secure your Redis instance with authentication (requirepass in redis.conf) and restrict it to private network interfaces only. If Redis was previously unauthenticated, rotate all application secrets.

### What is insecure deserialization in PHP?

Insecure deserialization occurs when PHP's unserialize() function processes untrusted data without restricting which classes can be instantiated. Attackers craft serialized payloads that chain together existing classes (called POP chains) to trigger arbitrary code execution through magic methods like __wakeup() and __destruct(). The fix is to always pass an allowed_classes option to unserialize().

