Patching PHP2Shell on EOL PHP 7.1 / CentOS 7 by rebuilding the Webtatic RPMs (what worked, what bit us)
TL;DR:* ported upstream 712e60f9ed into the Webtatic php71w 7.1.33 SRPM, rebuilt on a CentOS 7 container, ~35 min to build, ~6 s of php-fpm downtime per host. Two gotchas at the bottom.
Someone in the earlier PHP2Shell thread asked if Ondřej's backports are Debian only. They are, and that was our problem. We still had a couple dozen CentOS 7 boxes on Webtatic 7.1.33, Webtatic has been dead for years, and the PHP 8 estimate for just one of the apps was 6 to 8 person-days. So we rebuilt the packages ourselves. Writing it down because I couldn't find anyone who had done it for Webtatic.
---
The patch. Upstream's fix for old branches is php-src 712e60f9ed ("Avoid state corruption via bailout"). It doesn't apply to 7.1 cleanly. I diffed four community 7.1 ports against upstream and Qsnh/php2shell-fixed was the closest. Sury's Debian patches depend on another one of his patches to even compile, so I skipped those.
The gist: an OOM in the allocator now kills the worker with _exit(1) instead of jumping back with half-built state, upload_progress is hard-disabled, rfc1867.c only keeps the part headers it needs and gets a max_multipart_body_parts limit, and request shutdown clears some globals.
I changed one line in the port. Codex caught it when I had it read the diff, then I checked the 7.1 source:
+/* 7.1 hash part is nTableSize uint32 slots (7.3+ uses 2x); keep >= 2 so Buckets stay 8-byte aligned */
#define array_init_singleton(arg) do { \
array_init(arg); \
- Z_ARRVAL_P(arg)->nTableSize = 1; \
+ Z_ARRVAL_P(arg)->nTableSize = 2; \
} while (0)
The build. podman centos:7 container (gcc 4.8.5) pointed at the vault repos. Verify the Webtatic SRPM signature and that the tarball inside matches php.net's sha256, then four spec edits:
Release: 1%{?rcver:.%{rcver}}%{?dist}.php2shell1
Patch400: php-7.1.33-php2shell-cve-2026-109618-109619.patch
%patch400 -p1 -b .php2shell
+ a %changelog entry
yum-builddep -y --define 'dist .w7' SPECS/php71.spec
rpmbuild -bb --define "dist .w7" SPECS/php71.spec
First run died on make: command not found because yum-builddep doesn't pull it in. Around 35 minutes of rpmbuild, the Webtatic spec builds several SAPIs. You get 33 RPMs, all 7.1.33-1.w7.php2shell1, and since 1.w7 sorts below that, a later yum update won't quietly undo it.
Testing. No public RCE PoC. I wrote a small checker instead:
1. 3000-field multipart should hit the parts limit
2. a 1 MB upload should still arrive intact
3. upload_progress shouldn't land in the session
4. OOM at memory_limit=32M should kill the worker (unpatched just says "Allowed memory size" and keeps going)
5. requests after that still return 200
Original 2/5, patched 5/5, back to 2/5 after rollback.
Then the php.net test suite, 9,454 tests against the installed packages:
| Build | Passed | Failed |
|---|--:|--:|
| original 1.w7 | 8,751 | 34 |
| patched | 8,742 | 43 |
The 9 new failures are 8 rfc1867 session tests plus one that expects the old OOM message, all expected. The port author reports the same 9.
Rollout. Save rpm -qa, pull the original NEVRAs from a Webtatic mirror so you can roll back, then:
yum --disablerepo='*' --assumeno upgrade ./php71w-*.rpm # must say only "Upgrade N Packages"
systemctl stop php-fpm
yum -y --disablerepo='*' upgrade ./php71w-*.rpm
php-fpm -t && systemctl start php-fpm
php-fpm was down about 6 seconds, the whole thing was 2 to 7 minutes a host with checks. Rollback is yum downgrade from the saved originals.
What bit us:
1. The spec resets /var/lib/php/session (and wsdlcache, opcache) to root:apache 770 on upgrade. Our pools don't run as apache, so session_start() broke for about 4 minutes on one box before we chowned it back. My container test missed it because the test pool did run as apache. Write down the owners first.
2. WorkerMan kept running the old binary, /proc/<pid>/exe showed (deleted). Restart your long-running CLI stuff too.
> For 7.4 I didn't build anything. exception0x876's php74-el7 packages are a Remi 7.4.33 rebuild with the same upstream commit. I checked their signed SHA256SUMS and diffed their SRPM against Remi's before trusting it.
This only closes this one CVE pair, 7.1 is still 7.1. It just gave us time to do the upgrade properly.
11 points · 22 comments
Mentions
Patching PHP2Shell on EOL PHP 7.1 / CentOS 7 by rebuilding the Webtatic RPMs (what worked, what bit us) TL;DR:* ported upstream 712e60f9ed into the Webtatic php71w 7.1.33 SRPM, rebuilt on a CentOS 7 container, ~35 min to build, ~6 s of php-fpm downtime per host. Two gotchas at the bottom. Someone in the earlier PHP2Shell thread asked if Ondřej's backports are Debian only. They are, and that was our problem. We still had a couple dozen CentOS 7 boxes on Webtatic 7.1.33, Webtatic has been dead for years, and the PHP 8 estimate for just one of the apps was 6 to 8 person-days. So we rebuilt the packages ourselves. Writing it down because I couldn't find anyone who had done it for Webtatic. --- The patch. Upstream's fix for old branches is php-src 712e60f9ed ("Avoid state corruption via bailout"). It doesn't apply to 7.1 cleanly. I diffed four community 7.1 ports against upstream and Qsnh/php2shell-fixed was the closest. Sury's Debian patches depend on another one of his patches to even compile, so I skipped those. The gist: an OOM in the allocator now kills the worker with _exit(1) instead of jumping back with half-built state, upload_progress is hard-disabled, rfc1867.c only keeps the part headers it needs and gets a max_multipart_body_parts limit, and request shutdown clears some globals. I changed one line in the port. Codex caught it when I had it read the diff, then I checked the 7.1 source: +/* 7.1 hash part is nTableSize uint32 slots (7.3+ uses 2x); keep >= 2 so Buckets stay 8-byte aligned */ #define array_init_singleton(arg) do { \ array_init(arg); \ - Z_ARRVAL_P(arg)->nTableSize = 1; \ + Z_ARRVAL_P(arg)->nTableSize = 2; \ } while (0) The build. podman centos:7 container (gcc 4.8.5) pointed at the vault repos. Verify the Webtatic SRPM signature and that the tarball inside matches php.net's sha256, then four spec edits: Release: 1%{?rcver:.%{rcver}}%{?dist}.php2shell1 Patch400: php-7.1.33-php2shell-cve-2026-109618-109619.patch %patch400 -p1 -b .php2shell + a %changelog entry yum-builddep -y --define 'dist .w7' SPECS/php71.spec rpmbuild -bb --define "dist .w7" SPECS/php71.spec First run died on make: command not found because yum-builddep doesn't pull it in. Around 35 minutes of rpmbuild, the Webtatic spec builds several SAPIs. You get 33 RPMs, all 7.1.33-1.w7.php2shell1, and since 1.w7 sorts below that, a later yum update won't quietly undo it. Testing. No public RCE PoC. I wrote a small checker instead: 1. 3000-field multipart should hit the parts limit 2. a 1 MB upload should still arrive intact 3. upload_progress shouldn't land in the session 4. OOM at memory_limit=32M should kill the worker (unpatched just says "Allowed memory size" and keeps going) 5. requests after that still return 200 Original 2/5, patched 5/5, back to 2/5 after rollback. Then the php.net test suite, 9,454 tests against the installed packages: | Build | Passed | Failed | |---|--:|--:| | original 1.w7 | 8,751 | 34 | | patched | 8,742 | 43 | The 9 new failures are 8 rfc1867 session tests plus one that expects the old OOM message, all expected. The port author reports the same 9. Rollout. Save rpm -qa, pull the original NEVRAs from a Webtatic mirror so you can roll back, then: yum --disablerepo='*' --assumeno upgrade ./php71w-*.rpm # must say only "Upgrade N Packages" systemctl stop php-fpm yum -y --disablerepo='*' upgrade ./php71w-*.rpm php-fpm -t && systemctl start php-fpm php-fpm was down about 6 seconds, the whole thing was 2 to 7 minutes a host with checks. Rollback is yum downgrade from the saved originals. What bit us: 1. The spec resets /var/lib/php/session (and wsdlcache, opcache) to root:apache 770 on upgrade. Our pools don't run as apache, so session_start() broke for about 4 minutes on one box before we chowned it back. My container test missed it because the test pool did run as apache. Write down the owners first. 2. WorkerMan kept running the old binary, /proc/<pid>/exe showed (deleted). Restart your long-running CLI stuff too. > For 7.4 I didn't build anything. exception0x876's php74-el7 packages are a Remi 7.4.33 rebuild with the same upstream commit. I checked their signed SHA256SUMS and diffed their SRPM against Remi's before trusting it. This only closes this one CVE pair, 7.1 is still 7.1. It just gave us time to do the upgrade properly.
用宝塔装了 PHP 7.x / 8.0 / 8.1 的朋友注意了!我和 AI 一起做了一套修复包,覆盖 PHP 7.0 – 8.1 10 月 21 日 PHP 官方披露了 PHP2Shell 漏洞( CVE-2026-109618 / CVE-2026-109619 ),CVSS 评分 9.2 ,无需登录即可远程执行代码,PHP-FPM 、Apache mod_php 都受影响。 我和 AI 一起做了一套修复包,覆盖 PHP 7.0 – 8.1: - 7.4 / 8.0 / 8.1:使用 PHP 官方补丁原件 - 7.0 / 7.1 / 7.2 / 7.3:基于官方 7.4 补丁逐版本移植 - 每个版本都升级到该分支的最后一个版本(比如 7.3.x → 7.3.33 ),顺带拿到这些年所有已发布的安全修复 配了一个一键脚本,下载后 build 、install 两条命令完成升级: - 自动沿用宝塔当初的编译参数重新编译,php.ini 、FPM 配置、redis 等扩展都不动 - 安装前自动完整备份,任何一步失败都会自动回滚;出问题也可以一条命令手动回滚 - 每个 PHP 版本的站点只会中断几秒钟 在 Ubuntu 16.04 和 CentOS 7 上,按宝塔的方式装好未打补丁的版本,逐个版本实测过:升级、回滚、FPM 行为测试,以及和未打补丁版本对比的数千个回归测试。 ⚠️ 说明: - 7.0 – 7.3 的移植补丁没有经过 PHP 官方审核,上线前请先在测试环境验证。 - PHP 8.2 及以上直接在宝塔面板里更新即可,不需要这个。 - 长期还是建议尽快迁移到 PHP 8.2+。 开源地址 👉 Qsnh/php2shell-fixed