Це переказ статті команди Laravel Post Mortem: Patching the PHP and Node.js Vulnerabilities (4 листопада 2026). Про самі вразливості ми вже писали в матеріалі «Критичні RCE-вразливості в PHP: що сталося і що зробити зараз», а про те, як їх розкривали, - у погляді зсередини. Тут - інший бік історії: що робив хостинг, коли патч треба було розгорнути на тисячах застосунків менш ніж за добу.
Увечері 20 жовтня команда Laravel отримала попередження: PHP і Node.js от-от випустять виправлення критичних вразливостей. Менш ніж за 24 години виправлення стали публічними, і PHP треба було оновити на кожному застосунку Laravel Cloud. За словами команди, жодних ознак того, що когось із клієнтів атакували через ці вразливості, не знайшли.
Що сталося
21 жовтня PHP і Node.js оголосили випуски з виправленнями безпеки. Випуски PHP закривали дві критичні вразливості віддаленого виконання коду (RCE), а випуски Node.js - десять вразливостей, серед них ваду в декодері Brotli, яка теж могла дати віддалене виконання коду. Офіційне оголошення PHP - у бюлетені php.net.
Проєкт PHP надіслав попереднє оголошення о 19:58 UTC 20 жовтня, публічні випуски вийшли о 13:38 UTC наступного дня. Подробиць свідомо не розкривали - так і має працювати відповідальне розкриття. Але самого факту, що вразливість існує, достатньо, щоб її почали шукати, а чутки вже розходилися мережею.
Чому звичний підхід не підійшов
Зазвичай на CVE команда реагує так: оновлює базові образи й повідомляє клієнтам, що треба зробити деплой, щоб підтягнути свіжий образ з виправленням. Примусових передеплоїв вони уникають з двох причин. Перебудова всього парку повільна і в критичний момент може тягнутися годинами. До того ж застосунки виконують власні команди розгортання - міграції бази, сповіщення, зміни файлів, - і хостинг не може знати, що з цього безпечно запускати від імені клієнта.
Цього разу вразливість була настільки серйозною, що пропатчити хотіли кожне робоче навантаження, яким керує Laravel. Тож знадобився другий сценарій.
Спершу - додатковий шар захисту
Поки готували справжнє виправлення, ввімкнули захист, який від патча не залежав. О 08:55 UTC 21 жовтня, за години до публічного випуску PHP, на доменах Cloud увімкнули керований набір правил файрвола Cloudflare, зокрема новіші правила, що розпізнають у вхідних запитах спроби віддаленого та аномального виконання коду. До 10:24 UTC менший набір цих правил додали для Forge і виділених зон.
Файрвол не перекриває всіх шляхів до такої вразливості, тож правила лише зменшували ризик, а не замінювали патч. Але їх вирішили залишити: вони перевіряють трафік ще до того, як він дійде до застосунку, і дають кожному застосунку, пропатченому чи ні, ще один рубіж проти таких запитів.
Як отримати PHP вчасно
Зазвичай PHP береться з офіційних образів Docker Hub. Але коли всі одночасно кинуться завантажувати PHP і Node.js, не було певності, як швидко ці образи перезберуть.
Тому підготували два шляхи і домовилися йти тим, що спрацює першим. Docker Hub відкриває спосіб, яким збирає свої образи, тож можна було повторити процес компіляції і зібрати пропатчений PHP самостійно, щойно вийде випуск. Власний образ був готовий о 14:24 UTC - і саме ним і скористалися. Пропатчені образи на Docker Hub з'явилися лише через кілька годин після випуску, а деякі сторонні хости завантаження, на які спираються збірки образів, частину дня взагалі не працювали.
Живе патчення робочих навантажень
Laravel Cloud працює на Kubernetes, а Kubernetes побудований на узгодженні стану (reconciliation): цикл порівнює бажаний стан кожного робочого навантаження з поточним і вносить зміни, доки вони не збіжаться. Свою логіку в цей цикл команда додає через Kubernetes-оператори, і один з них - оператор застосунків - керує застосунками клієнтів у кожному кластері.
Саме туди й поклали патч. Оператор застосунків оновили так, щоб під час старту кожного робочого навантаження він підкладав у нього пропатчені бінарники PHP - замість перебудови й передеплою образів клієнтів. Робочі навантаження в Kubernetes незмінні, тож оператор перезапускає кожне з них, щоб застосувати зміну, а платформа поглинає рестарт так, що застосунки клієнтів не мають простою.
Вбудований пейсер не дає оператору перезапустити все одночасно - це захищає залежні системи, зокрема головне джерело даних платформи і білінг. Для цього розгортання пейсер прискорили вп'ятеро: за оцінками, близько 35 хвилин розтягування рестартів замість приблизно трьох годин. Повністю його не вимкнули - лавина рестартів могла б перевантажити ці системи.
Механізм спершу перевірили в dev- і staging-середовищах, а о 11:17 UTC, ще до публічного випуску PHP, запустили канарковий прогін у двох регіонах з найменшою щільністю. Перша хвиля почалася о 14:31 UTC з Канади й Токіо і спершу була перевірена на невеликій групі. Приблизно з 15:30 UTC патч хвилями пішов по решті регіонів - щоб помітити збої раніше, ніж вони дійдуть до великих регіонів, і щоб рестарти не прийшлися на один момент. Такий поетапний підхід виловив проблеми до того, як вони зачепили більшість клієнтів. О 18:05 UTC патч був у 100% регіонів. На цей час нові деплої в Cloud призупинили приблизно на три з половиною години, з 13:54 до 17:33 UTC.
Планування, за словами команди, забрало більше часу, ніж код: будівельні блоки вже були.
Таймлайн
Увесь час - UTC. Позначки «~» відновлені із записів дзвінків і точні приблизно до 10 хвилин.
| Час |
Подія |
| 20 жовт., 19:58 |
Попереднє оголошення PHP про випуск безпеки передано всередину команди. |
| 20 жовт., 19:58-20:56 |
Інженери планують тихе виправлення до розкриття; о 20:56 відкривають канал інциденту. |
| 21 жовт., 02:14 |
План погоджено: патчити PHP усередині запущених подів, а не перебудовувати образи клієнтів. |
| 21 жовт., 08:55 |
Увімкнено керовані правила файрвола Cloudflare; до 10:24 поширено на Forge і виділені зони. |
| 21 жовт., 11:17 |
Канарковий прогін механізму патчення в Канаді й Токіо. |
| 21 жовт., 12:38 |
Опубліковано випуски безпеки Node.js. |
| 21 жовт., 13:38 |
Опубліковано випуски безпеки PHP. |
| 21 жовт., 13:52 |
Forge деградує під напливом завдань оновлення. Envoyer виведено з роботи для оновлення серверів. |
| 21 жовт., 13:54 |
Деплої в Cloud призупинено. |
| 21 жовт., 14:31 |
Почалося розгортання патча середовища виконання в Cloud. |
| 21 жовт., 15:05 |
Vapor: оновлено нативні середовища виконання. |
| 21 жовт., 16:43 |
Forge: деплої падають, у черзі близько 48 000 завдань. |
| 21 жовт., 17:33 |
Деплої в Cloud відновлено. |
| 21 жовт., ~17:45 |
Forge: клієнтам повідомляють, що черга оброблена і деплої мають працювати. Це було передчасно. |
| 21 жовт., 18:05 |
Патч у 100% регіонів Cloud. |
| 21 жовт., 18:47 |
Інцидент у Cloud закрито. Базові образи перезібрано з пропатченими PHP і Node.js. |
| 21 жовт., 19:25 |
Vapor: клієнтів просять передеплоїти застосунки самостійно. |
| 21 жовт., 19:30-21:27 |
Forge: близько 4 800 деплоїв скасовано через задовге очікування в черзі. |
| 21 жовт., 21:18 |
Forge: виправлення випущено; до 21:27 деплої працюють нормально. |
| 21 жовт., 22:58 |
Envoyer знову працює. |
| 21 жовт., 23:27 |
Інцидент у Forge закрито, оновлення в реальному часі відновлено. |
| 22 жовт., 13:53 |
Forge і Envoyer підтверджено в нормальному режимі. |
Forge, Envoyer і Vapor: складніше
Cloud був простішим випадком, бо середовище виконання контролює сама Laravel. В інших продуктах клієнти оновлюють власні сервери чи застосунки, тож роботи було інакше і день вийшов довшим.
У Forge клієнти мають оновлювати PHP на кожному своєму сервері, і випуск спричинив приблизно вдесятеро більше завдань оновлення, ніж зазвичай. Багато з них падали й повторювалися через нестабільні репозиторії пакетів і застарілі сторонні джерела. Деплої ділили з ними одну чергу, тож чекали, доки не вичерпували тайм-аут. Панель Forge більшу частину дня працювала повільно, а оновлення інтерфейсу в реальному часі зависли - щоб їх відновити, їх перенесли на кластер Reverb у Laravel Cloud.
Сервери Envoyer пропатчити на місці не вдалося, тож о 13:52 UTC сервіс вивели з роботи й оновили сервери. О 22:58 UTC він повернувся, а перевірки стану деплоїв відновили 22 жовтня.
Для Vapor знадобилися нові середовища виконання й образи, і клієнтам довелося передеплоїти застосунки, щоб їх отримати.
Що спрацювало
Клієнтам Laravel Cloud не довелося робити нічого: патч дістався кожного запущеного застосунку без передеплою менш ніж за 5 годин, а канарковий прогін дав упевненість ще до справжнього випуску. Власна збірка PHP прибрала залежність від чужого графіка публікацій, а перевірка на невеликій групі виловила проблеми раніше, ніж вони дійшли до більшості клієнтів. Загалом реакція тривала близько 27 годин і передавалася між часовими поясами - за цей час випустили патчі і для PHP, і для Node.js.
Що пішло не так
Найбільший промах стався у Forge. Близько 17:45 UTC клієнтам повідомили, що чергу розчищено і деплої мають працювати. Приблизно за півтори години черга знову почала рости, і з 19:30 до 21:27 UTC близько 4 800 деплоїв скасували через задовге очікування. Цим клієнтам довелося робити деплой самостійно - повідомлення про відновлення прийшло зарано.
Клієнтам Vapor спершу пообіцяли, що Laravel сама передеплоїть їхні застосунки, а о 19:25 UTC змінили вказівки і попросили зробити це самостійно.
За цими помилками стояло кілька причин:
- На початку інциденту в кожного каналу комунікації не було чіткого відповідального, звідси - суперечливі повідомлення. Це виправили, закріпивши за кожним каналом одну людину.
- Ту саму невелику групу людей перша реакція тримала близько 27 годин, а відповідальні змінювалися разом з часовими поясами, не завжди маючи всю історію.
- Команди Forge, Envoyer і Vapor менші й мають слабший моніторинг, ніж Cloud, а частина їхніх систем працювала на ПЗ з вичерпаним терміном підтримки (end-of-life), яке довелося перебудовувати, а не патчити.
- Не було надійного обліку того, які версії ПЗ працюють у кожному застосунку.
Що команда змінює
У комунікаціях дещо спрацювало і залишиться:
- Погодження з керівником інцидента. Кожне повідомлення клієнтам проходило через керівника інцидента (incident commander). Цей фільтр зберігають і додають до нього ретельнішу звірку кожного твердження з реальним станом продукту.
- Сталі відповідальні за канали. Після плутанини на початку кожному каналу дали одного відповідального і звели заміни до мінімуму.
Нове:
- Період витримки перед оголошенням відновлення. Перш ніж повідомити, що щось відновилося, чекатимуть визначений час, щоб переконатися, що відновлення справжнє.
- Записані «воєнні кімнати». Кожна нарада щодо інцидентів SEV1 і SEV2 проходитиме у дзвінку з транскриптом, і це закріплять у регламенті.
Зміни за продуктами:
- Cloud: механізм патчення на місці стає стандартним інструментом, правила файрвола Cloudflare залишаються. Точне середовище виконання кожного застосунку відстежуватимуть суворіше, а версії з вичерпаною підтримкою приберуть з платформи.
- Forge: деплої тепер мають власну чергу, тож наплив інших завдань їх не задушить. Полагодили джерела пакетів і ключі репозиторіїв, припинили повторювати завдання для записів, яких уже немає, і додали потужностей та механізмів контролю під пікові хвилі оновлень.
- Envoyer: триматимуть на підтримуваних ОС і версії PHP, щоб наступний патч застосовувався без простою, і відновлюють та осучаснюють перевірки стану деплоїв.
- Vapor: застаріле середовище виконання
al2 позначили в документації як непідтримуване, клієнтам, які досі на ньому, пишуть окремо.
Окрім того, переглядають політику передеплоїв, щоб мати письмову відповідь на питання, що робити, коли робоче навантаження не можна ні пропатчити, ні автоматично передеплоїти.
Команда прямо попереджає: таке повторюватиметься. Розкриття подібних вразливостей стали частими, а AI скорочує час між оприлюдненням вразливості і першими спробами її експлуатувати. Якщо ви на Forge, Vapor чи Envoyer і ще не оновили PHP, база знань Forge пояснює, як перевірити свою версію.
Що з цього взяти собі
Якщо ви самі тримаєте PHP у Docker чи Kubernetes, ось що варто підготувати ще до наступного екстреного випуску:
- Тимчасовий захист на рівні WAF. Знайте заздалегідь, як увімкнути правила проти RCE-запитів у вашому CDN чи WAF. Між попередженням і патчем можуть минути години - це перший рубіж, хоч і не заміна оновленню.
- План B для образів. Не покладайтеся лише на те, що Docker Hub вчасно опублікує пропатчений образ. Майте відпрацьований скрипт збірки PHP з вихідного коду (офіційні Dockerfile відкриті) і власний реєстр, куди його покласти. Перевірте, від яких сторонніх хостів залежить збірка, - вони теж можуть лягти.
- Спосіб оновити середовище виконання без повного передеплою. Розділіть базовий образ і код застосунку так, щоб перебудувати й розкотити новий PHP можна було без запуску міграцій та інших кроків деплою.
- Контрольовані рестарти. Обмежуйте, скільки подів перезапускається одночасно (наприклад, через
maxUnavailable і PodDisruptionBudget), щоб оновлення не поклало бази, черги та інші залежні системи.
- Канарка і хвилі. Спершу staging, потім невелика частка трафіку чи найменш навантажений регіон, далі - хвилями з перевіркою метрик між ними.
- Облік версій. Знайте, яка версія PHP і базового образу працює в кожному сервісі. Без цього не відповісте на головне питання інциденту: «що в нас ще не пропатчено?».
- Окрема черга для деплоїв. Не давайте масовим службовим завданням витіснити деплої з черги.
- Дисципліна комунікацій. Один відповідальний за кожен канал і пауза перед оголошенням «все відновлено», поки не переконаєтеся, що це правда.