> ## Content Index
> Fetch the complete content index at: https://filamentmastery.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# PHP2Shell: Why Laravel didn’t protect your app, and how I patched mine
- URL: https://filamentmastery.com/articles/php2shell-why-laravel-didnt-protect-your-app-and-how-i-patched-mine/
- Published: 2026-10-28T06:18:54.000Z
- Updated: 2026-10-28T06:18:54.000Z
- Description: PHP2Shell hit the PHP engine before Laravel could run a single line. Here’s how I patched my clients’ Dockerized apps, why PECL became the bottleneck, and how I added defense-in-depth mitigations to the Filament Mastery starters.
- Author: Yebor974
- Tags: Security, Advanced, Filament V5, Filament V4, Filament V3

All my client apps running PHP 8.4 in Docker were on 8.4.27, tested and redeployed, in under four hours. Getting there was mostly a waiting game, and the slowest part had nothing to do with the code.

If you missed the last week: PHP shipped emergency fixes for two unauthenticated remote code execution bugs, nicknamed PHP2Shell. Two days later it disclosed a third, still unpatched. Here is the timeline.

| Date (UTC)    | What happened                                                                                 |
| ------------- | --------------------------------------------------------------------------------------------- |
| Oct 26        | CVE-2026-109618 and CVE-2026-109619 added to the CISA Known Exploited Vulnerabilities catalog |
| Oct 23        | PHP discloses CVE-2026-115560, no fix yet, mitigation by configuration                        |
| Oct 21, 14:00 | PHP releases 8.5.12, 8.4.27, 8.3.36 and 8.2.35                                                |
| Oct 21, 07:43 | Cloudflare reports the first attacks, six hours before the fix                                |
| Oct 20        | Details leak on social media ahead of the planned disclosure                                  |

Both October 21 bugs are rated 9.2 (CVSS v4.0) and affect every PHP version back to 5.4\. No login, no specific app code needed.

## Why Laravel did not protect you

The attack hits the PHP engine while it parses the incoming request, before your first line of Laravel code runs. Your middleware, your policies and your validation rules never get a chance to see it.

A common reflex is: "Laravel has its own session driver, so session bugs don't concern me, right?" They do. The known exploit path goes through PHP's session upload progress feature, which is handled by the engine during multipart parsing. Laravel's session driver sits one layer above and is irrelevant here.

Two consequences for a Laravel or Filament app:

- **Updating PHP is the only complete fix.** PHP says so explicitly in its release notes ([php-src 8.4.27](https://github.com/php/php-src/releases/tag/php-8.4.27?ref=filamentmastery.com)).
- **Turning off `session.upload_progress.enabled` blocks the known paths, not every variant.** It also has to be set in `php.ini` or the FPM pool: a `.user.ini` or an `ini_set()` call applies too late in the request. Laravel itself does not use this feature, so you lose nothing by turning it off.

## Why `php artisan down` protected nothing

I saw many Laravel sites go into maintenance mode on October 21\. It gave them a nice 503 page and zero protection.

Maintenance mode works through a `storage/framework/down` file that `public/index.php` checks. To run that check, the request has to reach PHP-FPM. And PHP parses the multipart body, upload progress included, while starting the request, before executing the first line of `index.php`. The attack lands before Laravel knows it is down.

What actually protects you is a request that never reaches PHP. Before the patch, the options were, from lightest to heaviest:

1. **`session.upload_progress.enabled = 0` in the PHP config.** Minutes to apply, site stays up, but it only blocks the known attack paths.
2. **A 503 served by the web server.** A `return 503;` in Nginx before `fastcgi_pass`, with a static page. Full protection, visitors still get a proper message.
3. **Taking the site offline entirely.** Same protection as option 2\. Several Dutch sites, the NCSC among them, did exactly that on October 21.

## How I patched my clients

Most of my clients run PHP 8.4, everything in Docker on `php:8.4-fpm-alpine`, with GitLab CI/CD. The target was 8.4.27\. Here is what the day looked like.

1. **Wait for the official image.** The PHP release was out, but the Docker image took a while to follow. At first the new tags were listed with no usable image behind them, as if they had been published and then rebuilt. I waited until a pull actually returned 8.4.27.
2. **Rebuild from the new base.** My build jobs always pull the base image, so with a floating tag like `8.4-fpm-alpine` the rebuild picked up 8.4.27 instead of a stale cached layer. Without that pull, a green pipeline can still ship the old PHP.
3. **Run the full CI pipeline, unchanged.** Security checks, unit tests, feature tests and GitLab container scanning. A PHP patch release should not break anything, but an emergency release is exactly when you want proof rather than faith.
4. **Deploy through the normal CD pipeline.** No manual hotfix on the servers. Recreating the containers also restarts queue workers and schedulers, which keep the old PHP in memory otherwise.

Total: under four hours for all of them.

**The real bottleneck was PECL.** During the rebuilds, pecl.php.net stopped responding. My guess, which I can't prove: half the PHP world was rebuilding images at the same moment, and every Dockerfile with a `pecl install xdebug` or `pecl install imagick` was hitting the same server. My pipelines could not finish until it came back.

## The October 23 CVE and the starters update

The Filament Mastery starters now ship with session upload progress turned off. A standard Laravel app is very unlikely to be exposed to the October 23 bug, and this change is defense in depth.

CVE-2026-115560 has no fix yet. PHP will publish it in its next regular release. Updating to 8.4.27 does not cover it. An app is exposed only if all three conditions hold:

1. `session.serialize_handler` is set to `php_binary` or `php_serialize`
2. the code switches it to `php` with `ini_set()` before calling `session_start()`
3. session upload progress is enabled, which is PHP's default

Laravel's standard session handling does not normally use PHP's native `session_start()` flow. However, packages or custom code may do so, so check your actual dependency stack before concluding that the application is not exposed. The fix PHP recommends in the meantime is the same setting already advised for CVE-2026-109619 on October 21\. One line covers both, so the Filament Mastery starters carry it by default.

```ini
; docker/common/php-fpm/conf.d/php.ini
; PHP2Shell (CVE-2026-109619) and CVE-2026-115560 mitigation
session.upload_progress.enabled=Off

```

Copied into the image:

```dockerfile
RUN if [ "$APP_ENV" = "local" ] ; then \
        cp "$PHP_INI_DIR/php.ini-development" "$PHP_INI_DIR/php.ini" ; \
    else \
        cp "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" ; \
    fi

COPY docker/common/php-fpm/conf.d/php.ini "$PHP_INI_DIR/conf.d/custom.ini"
```

The full setup, with this config already applied, is in my [production-ready Docker setup for Laravel Filament](https://filamentmastery.com/articles/production-ready-docker-setup-for-laravel-filament/), updated today.

The official image reads that directory for both CLI and FPM. If you are on PHP 8.5, also add `fatal_error_backtraces = Off`: PHP lists it as the second mitigation for CVE-2026-109619 on that version. Keep both settings even after patching, until PHP ships the fix for CVE-2026-115560.

**The Filament Mastery starters are updated and available now, with this config included by default:**

[Laravel Filament Backend Starter - Build your admin panel fastKickstart your Laravel v13 projects with Filament PHP v5\. Secure, customizable backend panel ready for unlimited projects.![](https://filamentmastery.com/content/images/icon/apple-touch-icon-2-4aa959bb-f9ba-4779-88e7-ddd6caa24429.png)Filament MasteryYebor974![](https://filamentmastery.com/content/images/thumbnail/01K0EN5YE8Z3092JVJRY1QC6DK-d34f6ffc-c9db-4f6a-9774-aad8323b9bbd.png)](https://filamentmastery.com/articles/laravel-filament-backend-starter-build-your-admin-panel-fast/)

[Laravel Filament Multipanel Starter - Build your app fastLaunch Laravel apps faster with this Filament PHP starter. Includes ready-to-use backend & member panels, user roles, Horizon, and more.![](https://filamentmastery.com/content/images/icon/apple-touch-icon-2-a7f28e41-f432-4939-be71-bd4a6251ddda.png)Filament MasteryYebor974![](https://filamentmastery.com/content/images/thumbnail/01K0Y6946TQMGF28ECT4BW0QHG-139ce4e2-39e5-4d1c-9fe3-895c05606cea.png)](https://filamentmastery.com/articles/laravel-filament-multipanel-starter-build-your-app-fast/)

[Laravel Filament Multi-Tenant Starter - Build your app fastBuilding a SaaS with Laravel Filament? This starter gives you team-based multi-tenancy out of the box. Backend panel, tenant member panel, team invitations, team switching, and tenant-aware permissions.![](https://filamentmastery.com/content/images/icon/apple-touch-icon-2-40f41c04-5546-4be8-8c5f-c1837f3e749c.png)Filament MasteryYebor974![](https://filamentmastery.com/content/images/thumbnail/laravel-filament-multi-tenant-starter-build-your-app-fast-69c949da-0ac0-468b-94da-399db912dd50.webp)](https://filamentmastery.com/articles/laravel-filament-multi-tenant-starter-build-your-app-fast/)

## Checklist for your Laravel and Filament apps

- \[ \] PHP is at 8.5.12, 8.4.27, 8.3.36 or 8.2.35: `docker compose exec php-fpm php -v`
- \[ \] On PHP 8.1 or older, `php -v` shows the same number before and after a distro backport: check the package revision instead, then plan the upgrade ([Laravel Forge](https://laravel.com/forge/docs/knowledge-base/php-security-release-october-2026?ref=filamentmastery.com))
- \[ \] Images rebuilt with a fresh pull of the base image, not from cache
- \[ \] Queue workers, Horizon, Octane and Reverb restarted (recreating containers does it)
- \[ \] Upload progress off, set in `php.ini` or `conf.d`, not `.user.ini`: `docker compose exec php-fpm php -i | grep session.upload_progress.enabled` returns `Off`
- \[ \] On PHP 8.5, `fatal_error_backtraces` is `Off`
- \[ \] Every PHP version on the server patched, including ones no site uses today