PHP2Shell на PHP 7.1–7.4 после EOL: официальный бэкпорт, патчи sury и RPM для CentOS 6
Короткий ответ: для 7.4 есть официальный минимальный бэкпорт — коммит 712e60f9edfd в ветке PHP-7.4-CVE-2026-109618-109619, 17 файлов, +240/−59. На 7.3 и ниже он не ложится, и для них исправление берётся из пакетов sury: php7.3 7.3.33-35, php7.2 7.2.34-66, php7.1 7.1.33-81. Каждый фикс там требует патчей-зависимостей из той же серии — без патча ZSTR_MAX_LEN сборка падает на компиляции, хотя patch --dry-run проходит чисто. Накладывать надо на последний патч-релиз ветки: на 7.4.5 патч не ложится, а на 7.1.8 или 7.2.23 под PHP2Shell лежит ещё одна RCE — CVE-2019-11043.
PHP2Shell — две RCE с CVSS 9.2, раскрытые 21 октября 2026: тело запроса разбирается до кода приложения, поэтому авторизация, CSRF и maintenance-режим на этом пути не стоят. Затронуты версии от 5.4. Для поддерживаемых ветвей вышли релизы; для ветки 7 релизов не будет — 7.4 закончилась в ноябре 2022, остальные раньше.
Эта статья — про то, как получить исправленный PHP 7.x той же ветки, не переезжая на 8.x. Общий разбор уязвимости, таблица источников по всем ветвям и смягчения — в статье-хабе. Ветка 8.x и ловушки Docker-тегов — отдельно.
Коротко (TL;DR)
- 7.4 — официальный бэкпорт. Один
git format-patch 712e60f9edfd, ложится наphp-7.4.33чисто: 17 строкpatching file …безoffsetиfuzz. - 7.3, 7.2, 7.1 — патчи sury. Фикс плюс зависимости: 7.3 —
0079+0107+0139; 7.2 —0105+0135+0159; 7.1 —0167+0186+0187+0188. - Версия пакета sury имеет значение. Для 7.1 исправление от 21 октября было неполным: пропущенный лимит
max_multipart_body_partsдоехал только в7.1.33-81на следующий день, после внешнего сообщения об ошибке. patch --dry-runчистый ≠ соберётся. Без патча0107компиляция 7.3 падает:zend_vm_execute.h:4971: error: 'ZSTR_MAX_LEN' undeclared. Макроса в 7.x нет, патч бэкпортирован с 8.2.35.- Патчить последний релиз ветки. На ранние версии патч или не ложится (7.4.5), или ложится, но оставляет открытой CVE-2019-11043 — RCE в php-fpm за nginx, исправленную в 7.1.33 и 7.2.24.
- Теги
php:7.*на Docker Hub живые и древние.php:7.1-fpmпоследний раз собран 22 ноября 2019, иtag_statusу него до сих порactive.docker pullотработает и ничего не скажет. - Грабля: assert после
patchнадо брать по строке, которой в ванильном дереве нет. Первая выбранная мной метка была в исходниках и до патча — assert проходил всегда. - CentOS 6: свой RPM поверх спека того же поставщика. Откатываться не на что — старых RPM в сети больше нет, резерв делается вручную до установки.
Почему для ветки 7 нет обычного пути обновления
Все четыре обычных канала доставки исправлений для этой ветки закрыты, причём давно. Это стоит показать числами, потому что «PHP 7.4 EOL» звучит абстрактно, а даты сборок — нет.
Официальные образы php:7.* не пересобираются. Вот что отдаёт Docker Hub на 26 октября 2026:
| Тег | Последняя сборка | tag_status |
|---|---|---|
php:7.4-fpm | 2022-11-15 | active |
php:7.3-fpm | 2022-03-18 | active |
php:7.2-fpm-alpine | 2020-12-17 | active |
php:7.2-fpm-buster | 2020-12-11 | active |
php:7.1-fpm | 2019-11-22 | active |
Главное тут — правая колонка. Тег четырёхлетней давности Docker Hub считает активным: docker pull php:7.1-fpm отработает, образ запустится, и ни одна команда не сообщит, что внутри PHP из 2019 года. Эту механику я разбирал на примере 8.x — там замерзают теги на старых ОС; здесь замерзла ветка целиком.
Остальные каналы в том же состоянии: remi для EL6 заморожен в 2020, webtatic — раньше, PPA ondrej/sury убрал bionic в 2023. А переезд старого Laravel или Yii2 на 8.x — это не обновление, а отдельный проект на недели, который нельзя сделать «к утру, пока эксплойт в природе».
Поэтому задача формулируется так: остаться на своей ветке, взять последний патч-релиз ветки и наложить на него исправление. Поведение языка не меняется, приложение не правится, в Dockerfile проекта меняется одна строка FROM.
Где взять исправление для каждой ветки 7.x
Источник зависит от ветки, и для 7.4 он официальный, а для остальных — пакеты Debian-мейнтейнера PHP:
| Ветка | Источник | Что брать |
|---|---|---|
| 7.4 | php-src, ветка PHP-7.4-CVE-2026-109618-109619 | коммит 712e60f9edfd поверх тега php-7.4.33 |
| 7.3 | sury php7.3 7.3.33-35 | патчи 0079 (max_multipart_body_parts) + 0107 (ZSTR_MAX_LEN) + 0139 (фикс) |
| 7.2 | sury php7.2 7.2.34-66 | 0105 + 0135 + 0159 |
| 7.1 | sury php7.1 7.1.33-81 | 0167 + 0186 + 0187 + 0188 |
Для 7.1 набор на один патч длиннее: 0187 — это «Fix repeated warning for file uploads limit exceeding» (Jakub Zelenka, 19 января 2023), парный к 0188 в апстриме. Я сначала решил, что это жёсткая зависимость: в 0188 кусок для main/rfc1867.c помечен index 4aef7ed..1eb8182, то есть сделан с файла, уже прошедшего через 0187. Проверил — 0188 ложится на дерево без 0187 чисто, тремя кусками со смещением +12. Хеши блобов в заголовке — метаданные из дерева мейнтейнера, patch их не смотрит. Так что 0187 надо брать не потому, что иначе не соберётся, а потому что это вторая половина одного апстрим-исправления.
Про официальный бэкпорт: 17 файлов, +240/−59, затронуты Zend/*, main/rfc1867.c, main/SAPI.c, main/main.c, main/php_variables.c и ext/session/session.c. SAPI fpm и apache2handler он не трогает вовсе — это важно, потому что снимает вопрос «а не надо ли пересобирать что-то ещё». Автор — Arnaud Le Blanc, co-author — Ilija Tovilo, сообщение коммита: «Avoid state corruption via bailout. Minimal mitigation for EOL version».
Про sury: это не сторонние патчи. Я скачал debian.tar.xz из пула и посмотрел заголовок 0139-Avoid-state-corruption-via-bailout.patch — там From: Arnaud Le Blanc <arnaud.lb@gmail.com> и Date: Sun, 18 Oct 2026 20:09:54 +0200, та же секунда, что у официального коммита 7.4. То есть для старых ветвей порт сделал тот же человек, что и официальный бэкпорт, с пояснением в теле патча:
«Backported from the PHP 7.4 version: … “|| 1” instead of “|| true” in the session upload progress callback (no stdbool.h)»
И CVE прописаны в changelog пакета явно: php7.2 (7.2.34-66) … * Backported from 8.2.35 + [CVE-2026-109618, CVE-2026-109619]: Avoid state corruption via bailout.
Версии пакетов, которые я проверял, собраны 21–22 октября 2026 — то есть в день выхода релизов и на следующий день. Для EOL-ветвей это быстрее, чем у многих поддерживаемых дистрибутивов.
Почему номер пакета sury важен: исправление 21 октября было неполным
Для 7.1 первая версия пакета закрывала не всё, и узнать об этом можно было только из трекера. В заголовке патча-фикса для 7.2 и 7.3 есть строка, которая выглядит успокаивающей:
- The max_multipart_body_parts hunk of the 7.4 patch is left out, thecode is already there.
Для 7.2 и 7.3 она правдива: лимит на число частей multipart-тела там действительно уже есть — отдельным патчем серии (0105 и 0079 соответственно). Для 7.1 и ниже — нет: лимит появился в PHP только в феврале 2023 вместе с исправлением CVE-2023-0662, а до старых ветвей sury его не доводил. Строка в заголовке переехала из патча в патч по цепочке портирования и в конце цепочки перестала соответствовать дереву.
Расхождение заметил и оформил внешний читатель — issue #146 в трекере deb.sury.org, 21 октября. Мейнтейнер Ondřej Surý объяснил механику ошибки прямо:
«Basically, the upstream PHP 7.4 minimal patch bundled CVE-2023-0662 and that part was dropped when applying the patch to php7.4; then going from php7.4 -> php7.3 -> php7.2 -> php7.1 -> php7.0 -> php5.6, it was not reintroduced again for php7.1 and below.»
Issue закрылся на следующий день сообщением «Fixed in: php5.6_5.6.40-105, php7.0_7.0.33-93, php7.1_7.1.33-81» — от создания до закрытия меньше суток. Пропущенный лимит доехал в 7.1 тремя патчами: 0187, 0188 и 0189 (последний добавляет CVE-2023-0662 в NEWS).
Проверить, какая версия пакета перед вами, можно по той же строке заголовка. В пакетах 7.2 и 7.3 она осталась прежней, а в 7.1.33-81 переписана:
- The max_multipart_body_parts hunk of the 7.4 patch is left out; thelimit comes with the CVE-2023-0662 backport (Introducemax_multipart_body_parts INI).
Практический вывод не про конкретный патч. Заголовок патча — это комментарий, а не проверка. Если в нём написано «код уже есть», надо сходить в дерево и убедиться:
| |
Если единственное совпадение — сама строка из заголовка патча-фикса, кода нет. Ровно это и увидел автор issue #146. И второй вывод: пакет, в changelog которого появился нужный CVE, — ещё не обязательно финальная версия. Перед сборкой стоит заглянуть в пул и взять последний Release ветки.
Почему официальный 7.4-патч нельзя взять для 7.3 и ниже
Я попробовал — он не ложится, и чем дальше ветка, тем хуже. git apply --reject официального 7.4-патча:
- на 7.3.33 — отклонены куски в
zend_alloc.c/zend_alloc.h,zend_execute_API.c,zend_vm_def.h; - на 7.2.34 — 46 отклонённых кусков, вся виртуальная машина;
- на 7.1 — та же картина.
Это ожидаемо: между 7.1 и 7.4 аллокатор и VM переписывались. Поэтому для каждой ветки нужен свой порт, и именно их делает sury.
Пара слов про отвергнутые варианты, потому что оба находятся поиском быстрее, чем пакеты sury.
Набор патчей vperetti для 5.4–7.3 — неполный. В нём 8 файлов из примерно семнадцати: нет zend_hash.c, нет php_variables.c, нет изменений в VM. Тестировался, по описанию автора, только на встроенном CLI-сервере, а подтверждённый вектор эксплуатации — FPM и mod_php. Ставить такое — закрыть часть путей и считать вопрос решённым.
Независимый порт mpyw для 7.3 (MIT, с готовыми образами на ghcr.io) выглядит аккуратнее и может быть вполне рабочим. Я всё равно выбрал sury, и критерий был простой: патчи sury делает мейнтейнер пакетов PHP для Debian, CVE прописаны в changelog пакета, а сам пакет собран инфраструктурой, которой пользуются миллионы установок. Для кода, который попадёт в веб-сервер на проде, проверяемое происхождение важнее удобства.
Платные варианты существуют и для части случаев будут правильным ответом: TuxCare выпустил alt-php72 для EL6 22 октября, у Freexian ELTS есть php7.4 для Debian 11, Ubuntu Pro ESM чинит системный php7.2 для 18.04. Docker-образов ни у кого из них нет.
Грабля первая: patch --dry-run чистый, а сборка падает
--dry-run проверяет текст патча и ничего не знает про его зависимости. Для 7.3 пара 0079 + 0139 ложится на ванильный 7.3.33 без единого конфликта — а компиляция падает:
| |
Причина: фикс бэкпортирован с 8.2.35 и использует макрос ZSTR_MAX_LEN, которого в 7.x нет. В пакете sury этот макрос вводится отдельным, более ранним патчем серии — 0107-backport-ZSTR_MAX_LEN, автор Remi Collet, ноябрь 2024, три строки в Zend/zend_string.h. Патч лежит в серии задолго до фикса PHP2Shell и к нему формально не относится, поэтому при выборочном копировании патчей теряется первым.
Проверять надо до сборки, а не после двадцати минут компиляции:
| |
Общее правило, которое я вывел из этого: для каждого идентификатора, который патч использует, но не определяет, надо найти, где он определён. В исходниках пакета sury это один grep по debian/patches/. В серии патчей от любого мейнтейнера зависимости неявные — порядок задан файлом debian/patches/series, и выдёргивать оттуда один файл по имени CVE нельзя.
На 7.1 ровно то же: 0186 ложится на ванильный php-7.1.33 чисто — 16 файлов, только смещения, --fuzz=0 не нужен, — при том что ZSTR_MAX_LEN в нём используется двадцать раз, а в Zend/zend_string.h ветки 7.1 этого макроса нет вообще. Честная оговорка: до ошибки компилятора я доводил дело только на 7.3; на 7.1 я сравнил патч с деревом, но сборку без 0167 не запускал.
Грабля вторая: assert, который ничего не проверял
Assert после patch надо строить на строке, которой в непатченом дереве нет, — иначе он проходит всегда и создаёт ложное чувство проверенности. У меня в Dockerfile после наложения патчей стоял grep -q по метке, и сборка его проходила. Метка была zend_objects_store_mark_destructed — она есть и в ванильных исходниках. То есть проверка не проверяла ничего, и я узнал об этом, только когда специально прогнал grep -c по чистому дереву.
Правило: grep -c по ванильному дереву должен дать 0, после патча — хотя бы 1. Метки, которые этому удовлетворяют:
| Ветка | Метка | Файл |
|---|---|---|
| 7.3 | zend_new_array_singleton | Zend/zend_hash.h |
| 7.2, 7.1 | array_init_singleton | Zend/zend_API.h |
| 7.4 | по поведению | — |
Для 7.4 удобной новой строки я не нашёл, так что там assert — только поведенческий тест на собранном образе.
И отдельно: статический маркер не заменяет проверку поведением. Он говорит, что патч попал в дерево, а не что собранный бинарник работает по-новому. php -v на патченой и непатченой сборке 7.4.33 отвечает одинаково, php -i на патченой честно показывает session.upload_progress.enabled => On, потому что выключение сделано в коде, а не в ini. Три уровня проверки — «лёг», «собрался», «работает» разобраны в хабе; для 7.x есть ещё один дешёвый признак:
| |
В непатченых 7.1–7.4 директивы max_multipart_body_parts нет — она приезжает вместе с патчем. На 8.0.30 этот маркер не работает: там директива есть и без патча, она появилась в 8.0.28.
Docker: свой базовый образ из последнего Dockerfile docker-library
Рецепт для 7.4, 7.3 и 7.2 один: взять последний Dockerfile из docker-library, где ветка ещё существовала, наложить патчи после docker-php-source extract и собрать свою базу. Смысл — в раскладке: /usr/local/etc/php, docker-php-ext-install, pecl, zz-docker.conf остаются точно как у официального образа, и в Dockerfile каждого проекта меняется ровно одна строка FROM. Одна база на ветку обслуживает все проекты этой ветки.
Коммиты, которые я брал за основу:
| Ветка | Коммит docker-library | Путь | Что это за коммит |
|---|---|---|---|
| 7.4 | b9f17156 | 7.4/bullseye/fpm | последнее состояние дерева 7.4 |
| 7.3 | 35d0a696 | 7.3/bullseye/fpm, 7.3/alpine3.15/fpm | «Update 8.1-rc», 18 марта 2022 — родитель удаления 7.3 |
| 7.2 | 3dc9a698 | 7.2/alpine3.12/fpm, 7.2/buster/fpm | «Add –with-pic», 5 ноября 2020 — перед удалением 7.2 |
Дата сборки тега php:7.3-fpm на Docker Hub — 18 марта 2022 — совпадает с датой коммита 35d0a696, так что это действительно последнее дерево, из которого ветка собиралась.
Вариант ОС выбирается по тому, что было, а не по тому, что новее. Alpine-проекты остаются на Alpine: переход на Debian меняет libc с musl на glibc и подтягивает другую сборку ImageMagick — для проекта, который обрабатывает картинки, это отдельный риск, никак не связанный с уязвимостью. Debian-проекты остаются на Debian, с одним вынужденным шагом: buster целиком уехал на archive.debian.org, поэтому 7.4 и 7.3 я собирал на bullseye. У bullseye тут есть неожиданный плюс — OpenSSL ещё 1.1.1, а не 3.0, и legacy-шифры платёжных интеграций продолжают работать без правок конфигурации TLS.
Фрагмент Dockerfile на примере 7.3:
| |
Строка с tar — та, про которую легко забыть. Официальный Dockerfile в конце делает docker-php-source delete, а docker-php-ext-install в образе приложения позже заново распаковывает /usr/src/php.tar.xz — оригинальный тарбол без патча, и расширение соберётся из непатченых исходников. Перепаковка дерева сразу после patch это закрывает. Оговорка: реального сбоя из-за этого я не наблюдал, мера защитная, стоит одну строку.
Образ приложения после этого выглядит так:
| |
Ini здесь избыточен — патч и так выключает upload progress в коде. Я его всё равно кладу: он делает смягчение видимым для аудита, который смотрит на php -i, а не собирает образ.
Старый Dockerfile не собирается, и без сборки нет патча
Dockerfile 2020–2022 года ломается не на PHP, а на инфраструктуре вокруг — и это надо починить до того, как дело дойдёт до патчей. Две поломки, обе быстрые:
apt-get updateотдаёт 404.busterиbullseye-securityпереехали наarchive.debian.org. Правкаsources.listплюсgrep -qрядом как assert: если формат файла в базовом образе изменится,sedтихо ничего не заменит, и сборка должна упасть на этом же слое.- Импорт ключа подписи тарбола падает. В старых Dockerfile ключи берутся с
ha.pool.sks-keyservers.net— сеть SKS закрыта. Замена —keyserver.ubuntu.com; подпись тарбола при этом по-прежнему проверяется, отключать проверку не нужно.
Alpine 3.12 и 3.15 обошлись без правок: пакеты на зеркалах ещё лежат, сборка прошла как есть.
CentOS 6: свой RPM поверх спека того же поставщика
Для машин, где PHP стоит пакетами из webtatic или remi, рецепт тот же по смыслу: взять спек того же поставщика, добавить патчи sury, собрать пакет с Release на единицу новее установленного. Приложение и раскладка не меняются вовсе — меняются только пакеты.
Для 7.1 это SRPM webtatic php71w-7.1.33-1.w6.src.rpm. Он до сих пор лежит на repo.webtatic.com/yum/el6/SRPMS/ и отдаётся по HTTP 200 — я проверял сегодня; подпись проверена ключом webtatic. Патчи sury 0167, 0186, 0187, 0188 добавляются в спек как Patch400–403, Release становится 1.1, и итоговые пакеты 7.1.33-1.1.w6 оказываются новее установленных по правилам сравнения версий rpm. Для 7.2 то же самое со спеком php72.spec из git.remirepo.net — беру коммит, из которого remi собирал 7.2.34-1.el6.remi.
| |
Три детали в этих шести строках существенны. Вне %if-блоков — потому что спеки remi полны условий по версии дистрибутива, и патч, случайно попавший внутрь неактивной ветки, просто не применится, а сборка пройдёт. --fuzz=0 — чтобы patch не пытался угадать смещение и не прикладывал кусок не туда. grep -q сразу после — тот самый assert по строке, которой до патча в дереве нет.
Сборочное окружение — контейнер centos:6 с репозиториями vault.centos.org и архивом EPEL6, без remi. Это принципиально: зависимости собранных пакетов должны остаться такими же, как у оригинала, то есть только base и EPEL. Подключив remi в сборщик, легко получить пакет, который на целевой машине не установится.
Две древности, на которые ушло время:
buildconfдля 7.2 требует autoconf ≥ 2.64, в EL6 — 2.63. Ответ:autoconf268из EPEL плюсPHP_AUTOCONF=autoconf268 PHP_AUTOHEADER=autoheader268в окружении сборки.- Зависимость
oniguruma5php(libonig.so.105) на старых установках remi отсутствует — пакет берётся из архива remi для el6.
| |
На одном стенде PHP оказался установлен мимо базы rpm, и там установка пошла через rpm -Uvh --nodeps. Это не изящно, но лучше, чем оставить уязвимый FPM на машине, которая смотрит в интернет.
Откат, которого нет: резерв делается руками до установки
Откатиться через пакеты на CentOS 6 не получится — старых RPM больше нет ни у webtatic, ни в зеркалах. Для 7.1.8, 7.1.11 и 7.1.17 я их не нашёл нигде, то есть обычный yum downgrade в этой ситуации не работает в принципе. Резерв приходится собирать вручную, до установки:
| |
Откат при этом — распаковка архива обратно, и у него есть известный дефект: база rpm останется на новой версии, то есть система будет считать установленным то, чего на диске нет. Для аварийного возврата работоспособности этого хватает, для порядка в системе — нет. Лучше знать про это заранее, чем выяснять в момент, когда откат уже нужен.
Приятная мелочь напоследок: rpm -Uvh сам перезапускает php-fpm через condrestart. Отдельный рестарт не нужен, простой — порядка секунды.
Где исправления нет совсем
Один случай в парке закрыть было нечем: PHP 7.4 из PPA ondrej/sury на Ubuntu 18.04. PPA убрал bionic в 2023 году, то есть пакета с патчем просто не существует; Ubuntu Pro ESM чинит системный php7.2, а не php7.4 из PPA. Варианты сводились к трём: пересобрать пакет самому, переехать на поддерживаемую ОС или закрыть сайт снаружи.
Я закрыл его паролем на уровне nginx, а PHP не трогал. Это честное «не исправлено, но недоступно», и в отчёте оно так и записано — не «закрыто». Разница существенная: смягчение держится на конфиге nginx, который кто-нибудь однажды поправит, не зная, зачем там basic-авторизация. Поэтому в самом конфиге рядом с auth_basic стоит комментарий с номерами CVE.
Как проверялась каждая сборка
Проверка одна для всех ветвей, и она не про версию. Патч меняет способ выхода при исчерпании memory_limit, и это видно по коду выхода и по вызову shutdown-функции:
| |
Этот тест прошёл на каждом собранном образе и каждом собранном RPM по всем четырём ветвям, и расхождений с ожидаемым не было ни разу. Кроме него я сравнивал старую и новую сборку по трём срезам: список собранных модулей (php -m) — должен совпадать строка в строку; вывод php-fpm -tt — тоже; итоговый ini — здесь допустимы ровно два отличия, session.upload_progress.enabled и появившийся max_multipart_body_parts.
Где сайт позволял, добавлялся смоук: прогон списка URL со сравнением кода ответа и размера тела до и после. Там, где приложение детерминировано, ответы совпадали байт в байт — для смены патч-версии PHP это хороший знак.
Что получилось по ветвям:
| Ветка | Источник исправления | Итоговая версия | ОС |
|---|---|---|---|
| 7.4 | официальный бэкпорт 712e60f9edfd | 7.4.33 + патч | Debian 10 → 11 |
| 7.3 | sury 0079 + 0107 + 0139 | 7.3.33 + патчи | Debian 11, Alpine 3.15 |
| 7.2 | sury 0105 + 0135 + 0159 | 7.2.34 + патчи | Alpine 3.12, buster; CentOS 6 (RPM 7.2.34-1.1.el6.remi) |
| 7.1 | sury 0167 + 0186 + 0187 + 0188 | 7.1.33 + патчи | CentOS 6 (RPM 7.1.33-1.1.w6) |
Состав — настоящий, количества машин и контейнеров опущены: это периметр заказчика. Разброс внутри одной ветки при этом измеряется годами — от 7.4.5 (апрель 2020) до 7.4.33 (ноябрь 2022). Поэтому «обновить 7.4» — это не одна операция, а разные: часть установок едет на 7.4.33 с патчем в один шаг, а часть сначала должна пройти путь из двадцати восьми патч-версий.
Не всё закрылось в один заход, и причины были не техническими. Часть проектов на ранних 7.4 владельцы отложили: патч на их версию не ложится, а прыжок сразу на 7.4.33 они захотели протестировать отдельно. Часть машин CentOS 6 обновлял разработчик проекта сам, по тому же рецепту. А неиспользуемые установки 7.1 я просто остановил — это самый дешёвый способ закрыть уязвимость, и проверять его стоит первым: прежде чем собирать патченый образ, надо спросить, нужен ли вообще этот сервис.
Итог
«Нет релиза» не означает «нет фикса». Для 7.4 есть официальный бэкпорт, для 7.1–7.3 — патчи того же автора в пакетах sury. Поверх последнего Dockerfile docker-library или спека того же поставщика RPM они дают исправленный PHP той же ветки, и проекты меняют одну строку FROM либо получают пакеты новее на один Release.
Патчить надо последний патч-релиз ветки, а не свою версию. Два независимых аргумента: на ранних версиях патч может не лечь (7.4.5), а на 7.1.8 или 7.2.23 под PHP2Shell лежит ещё одна RCE — CVE-2019-11043 в php-fpm за nginx с fastcgi_split_path_info, исправленная в 7.1.33 и 7.2.24. Закрыть новую уязвимость, оставив старую, — не результат.
Серия патчей мейнтейнера — это серия, а не набор файлов. Выдернуть из debian/patches/ один файл по имени CVE и ждать, что соберётся, нельзя: порядок и зависимости заданы файлом series, а patch --dry-run про это не знает. Самая дорогая ошибка на этой задаче была ровно такой — двадцать минут компиляции до ZSTR_MAX_LEN undeclared, и ещё полчаса на понимание, что искать надо не ошибку компилятора, а третий патч.
Остальное по серии: ветка 8.x и ловушки Docker-тегов, PHP 5.6, общий разбор уязвимости и смягчений.
Первоисточники: GHSA-w8hw-w9fx-j79w и GHSA-6wmf-4rv5-p3rp (advisory PHP), коммит 712e60f9edfd в php-src, пул пакетов sury, issue #146 в трекере deb.sury.org, репозиторий docker-library/php, отчёт по CVE-2019-11043 в багтрекере PHP, ChangeLog PHP 7, спеки remi.