サポート切れのPHPもPHP2Shellから守りたい! 有志のバックポートでソースからビルドする手順

【追記:2026年10月28日】php-src-backportsには、PHP2Shellの修正が入っています。開発チームがサポート切れのバージョン向けに出した最小パッチで、7.4〜8.1は10月19日、7.0〜7.3は10月24日に入りました。
ただし、タグの名前(8.1.34など)は変わらずに中身だけが更新され、NEWSにも書かれていません。
それより前にダウンロードしたソースには入っていないので、「修正済みのCVEを確かめる」の方法で、手もとのソースを確かめてください。

PHPの脆弱性「PHP2Shell」(CVE-2026-109618/CVE-2026-109619)の公式の修正版は、8.2以降の4つだけ。8.1以前のPHPには、公式の修正版は出ていません。

ところが、わたくしが面倒を見ているサーバーには、7.0や7.1のまま動いているサイトがまだあるんです。全部すぐに8.2へ上げられたら、苦労しないんですけどね…。

そこで使えるのが、有志がサポート切れのPHPにセキュリティ修正を移植している「php-src-backports」。この記事では、これを使ってソースからPHPを入れ替える手順と、実際にハマったエラーの直し方をまとめました。

先に結論を書いておきます。

  • 8.1以前の選択肢は「8.2以降へ上げる」「開発チームの最小パッチ」「有志のバックポート」の3つ
  • ビルドの前に、session.upload_progress.enabled = 0で確認済みの経路をふさいでおく
  • バックポートを使うなら、直したいCVEが入っているかを必ず確かめる
  • GitHubのソースなので、ビルドにはbisonとre2cが必要
  • バックポートはあくまでつなぎ。本命は8.2以降への移行

8.1以前のPHP2Shell対策

使っているPHPのバージョンで、取れる手が変わります。

使っているPHP取れる手
8.2〜8.5公式の修正版(8.2.35・8.3.36・8.4.27・8.5.12)へ更新
7.4・8.0・8.1有志のバックポート(開発チームの最小パッチ入り)を使う、またはパッチを自分で当てる
7.3以前有志のバックポートを使う(7.0〜7.3は10月24日に修正入り)

8.2以降なら、迷わず公式の修正版へ。php.netから取得して、いつもどおりビルドするだけです。

開発チームの最小パッチは、今回の穴だけを直すもの。一方のバックポートは、サポートが切れたあとに見つかったほかの穴の修正も、まとめて移植されているのがポイントです(全部とは限りませんが)。

バックポートは信用できる?

有志のものと聞くと、ちょっと心配になりますよね。わたくしも、そこがいちばん気になりました。

php-src-backportsを作っているのは、Shivam Mathurさん。GitHub ActionsでPHPを入れる定番ツール「setup-php」の作者で、PHPの世界ではよく知られた人です。

ただし、個人のプロジェクトなので、次の点は覚えておきたいところ。

  • 全部の穴が直るとはかぎらない(古いバージョンにうまく移せない修正もある)
  • いつ直るかの約束はない
  • 1人の作者に頼ることになる

なので、直したいCVEが入っているかを、自分の目で確かめてから使う。これが鉄則です。

php.iniで応急処置する

ビルドの前に、まずやっておきたいのがsession.upload_progress.enabledを止めること。ファイルのアップロード中に、進み具合をセッションに書き込む機能です。

PHP2Shellでは、この機能が確認済みの攻撃の経路になっています。設定を1行変えて再起動するだけなので、修正版が出ていない古いPHPでも、すぐに塞ぐことができるんです。

session.upload_progress.enabled = 0

PHP-FPMなら、プールの設定に書く方法もあります。

php_admin_flag[session.upload_progress.enabled] = off
# 再起動する
systemctl restart php-fpm      # mod_php なら apachectl graceful

# php.ini に書いたときの確認(Local Value が Off ならOK)
/usr/local/php/bin/php -i | grep session.upload_progress.enabled

FPMのプール設定に書いたときは、コマンドのphp -iには出てきません。phpinfo()を一時的に置いて確かめ、終わったらすぐに消しましょう。

気をつけたいのは、ini_set()や.user.iniでは効かないこと。リクエストを読む前に効く場所(php.iniやFPMのプール設定)に書く必要があります。

困るのは、アップロードの進み具合を表示する機能(PHP_SESSION_UPLOAD_PROGRESS)を使っているアプリだけ。ふつうのサイトなら、まず影響はありません。

ただし、塞ぐことのできるのは確認済みの経路だけ。ほかのやり方の攻撃は残るので、あくまで入れ替えまでのつなぎです。更新したあとも、0のままにしておくのがおすすめとのこと。

ほかの応急処置も含めて、こちらにまとめています。

bisonとre2cを用意する

php.netの配布ファイルとちがって、GitHubから取ったソースにはconfigureが入っていません。自分で作るので、次のツールが必要です。

ツール必要なバージョン(PHP 8.x)確かめ方
bison3.0.0以上bison --version
re2c1.0.3以上re2c --version
autoconf2.68以上autoconf --version

OSのパッケージで足りるかは、OSのバージョン次第です。

OSbisonre2c
CentOS 6× 2.4.1 → ソース× → ソース
CentOS 7○ 3.0.4× 0.14.3(EPEL) → ソース
RHEL 8系○ 3.0.4× 0.14.3(PowerTools) → ソース
RHEL 9系○ 3.7.4○ 2.2(EPEL)
Debian 8 jessie○ 3.0.2× 0.13.5 → ソース
Debian 9 stretch○ 3.0.4× 0.16 → ソース
Debian 10 buster○ 3.3.2○ 1.1.1
Debian 11 bullseye○ 3.7.5○ 2.0.3
Debian 12 bookworm○ 3.8.2○ 3.0
Debian 13 trixie○ 3.8.2○ 4.1
Ubuntu 14.04○ 3.0.2× 0.13.5 → ソース
Ubuntu 16.04○ 3.0.4× 0.16 → ソース
Ubuntu 18.04○ 3.0.4× 1.0.1 → ソース
Ubuntu 20.04○ 3.5.1○ 1.3
Ubuntu 22.04○ 3.8.2○ 3.0
Ubuntu 24.04○ 3.8.2○ 3.1

○はOSのパッケージ(yum・dnf・apt-get)で足りるもの、×は足りないのでソースからビルドするものです。Ubuntu 18.04のre2cは1.0.1で、あと少しだけ足りないのが惜しいところ。

表は、PHP 8.xでの話です。PHP 7.xは必要なバージョンがちがうので、足りないときはconfigureのエラーに出るバージョンに合わせてください。

Debian・Ubuntuなら、ビルドに使うものをまとめて入れておくと楽です。入ったら、bison --versionとre2c --versionで、上の表のバージョンを満たしているか確かめましょう。

apt-get install build-essential autoconf pkg-config bison re2c

足りないものは、OSのものは置き換えずに、別の場所に入れます。bisonのビルドにはm4が必要なので、最小構成のOSなら先に入れておきます。re2cのソースは、GitHubのリリースのページから取得します。

# bison(先に m4 を入れておく)
cd /usr/local/src
curl -LO https://ftp.gnu.org/gnu/bison/bison-3.0.5.tar.gz
tar xzf bison-3.0.5.tar.gz && cd bison-3.0.5
./configure --prefix=/usr/local/bison-3.0.5 && make && make install

# re2c(https://github.com/skvadrik/re2c/releases から取得)
cd /usr/local/src
tar xf re2c-1.3.tar.xz && cd re2c-1.3
./configure --prefix=/usr/local/re2c-1.3 && make && make install

このあとの作業は、PATHを通したターミナルで行います。bisonとre2cは、configureのときだけでなく、makeの途中でも使うからです。

export PATH=/usr/local/bison-3.0.5/bin:/usr/local/re2c-1.3/bin:$PATH

yumやaptが通らないとき

サポートが終わったOSは、パッケージの置き場所が移っています。リポジトリの設定が古いままだと、yum installやapt-get updateがエラーになるんです。

CentOS 7は、vault.centos.orgに向け直します。

cp -p /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.orig
sed -i -e 's|^mirrorlist=|#mirrorlist=|' \
       -e 's|^#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|' \
       /etc/yum.repos.d/CentOS-Base.repo
yum clean all
yum install bison m4

CentOS 6は、同じように書き換えたうえで、$releaseverを6.10にします。

Debianのサポートが終わったバージョン(9 stretchなど)は、archive.debian.orgへ。

# /etc/apt/sources.list(Debian 9 の例)
deb http://archive.debian.org/debian stretch main
deb http://archive.debian.org/debian-security stretch/updates main
apt-get -o Acquire::Check-Valid-Until=false update

Ubuntuは、サポートが完全に終わったバージョンがold-releases.ubuntu.comへ移ります。16.04や18.04は、有料のESMがある間はふつうの置き場所に残っています。

sed -i -e 's|//[a-z.]*archive.ubuntu.com|//old-releases.ubuntu.com|' \
       -e 's|//security.ubuntu.com|//old-releases.ubuntu.com|' /etc/apt/sources.list
apt-get update

ただし、こうした置き場所のパッケージには、もうセキュリティの更新が来ません。ビルドの道具を入れるためだけに使う、と割り切りましょう。

ソースからビルドする手順

ソースは、GitHubのshivammathur/php-src-backportsから取得します。PHP本体のソースをコピーして、サポート切れのバージョンに修正を移しているリポジトリです。

バージョンごとにブランチがあって、ダウンロードに使うタグは次のとおり。READMEにも同じ一覧があります。

PHPタグ(NEW に入れる)ブランチ
8.18.1.34PHP-8.1-security-backports
8.08.0.30PHP-8.0-security-backports
7.47.4.33PHP-7.4-security-backports
7.37.3.33PHP-7.3-security-backports
7.27.2.34PHP-7.2-Security-backports
7.17.1.33PHP-7.1-security-backports
7.07.0.33PHP-7.0-security-backports

ダウンロードのURLは、最後のタグの部分を変えるだけです。

https://github.com/shivammathur/php-src-backports/archive/8.1.34.tar.gz

タグの名前は、公式の最後のバージョンの番号のまま。修正が移されても、名前は変わらずに中身だけが更新されます。手もとのソースが新しいかは、このあとの「修正済みのCVEを確かめる」で確かめます。
取り直すときは、古いtar.gzと展開したフォルダを先に消しておきましょう。ファイル名もフォルダ名も前回と同じなので、うっかり古いほうでビルドしてしまいます。wgetは、同じ名前のファイルがあると「.1」を付けて別に保存するので要注意です。

ここからは、8.1.7を8.1.34に上げる例で書きます。ほかのバージョンなら、上の表のタグをNEWに入れるだけです。

OLD=8.1.7          # 今のバージョン(php -v で確認)
NEW=8.1.34         # インストールするバージョン(php-src-backports のタグ)
SRC=/usr/local/src/PHP

ビルドの全体の流れ

まずは、全体の流れです。わたくしが7.4.30を7.4.33に上げたときも、ほぼこのとおりでした。

cd $SRC
rm -rf php-$NEW-backports.tar.gz php-src-backports-$NEW   # 取り直すときは、古いものを先に消す
curl -L -o php-$NEW-backports.tar.gz https://github.com/shivammathur/php-src-backports/archive/$NEW.tar.gz
tar xzf php-$NEW-backports.tar.gz && cd php-src-backports-$NEW

# 修正が入っているか(1つ目で行が出て、2つ目が1以上ならOK)
grep -nE 'rfc1867_enabled\) \|\| (true|1)\)' ext/session/session.c
grep -c 'php_mime_should_save_hdr' main/rfc1867.c

cp -p $SRC/php-$OLD/config.nice .   # 前回と同じ条件でビルドする
./buildconf --force
./config.nice
make -j$(nproc)
make install
systemctl restart php-fpm           # mod_php なら apachectl graceful

wgetを使うなら、wget -O php-$NEW-backports.tar.gzのように、保存するファイル名を指定しておくと安心です。

このあとは、1つずつ詳しく見ていきます。本番のサーバーなら、バックアップとインストール前の確認は省かないでください。

修正済みのCVEを確かめる

修正が入っているかは、ダウンロードして展開したソースの中身で確かめるのがいちばん手軽です。gitがなくても確かめられます。

# 展開したフォルダ(php-src-backports-$NEW)の中で
grep -nE 'rfc1867_enabled\) \|\| (true|1)\)' ext/session/session.c
grep -c 'php_mime_should_save_hdr' main/rfc1867.c

1つ目で行が出て、2つ目が1以上なら、修正は入っています。どちらも出なければ、修正が入る前にダウンロードしたソースなので、ダウンロードし直しましょう。

2つのgrepで見ているのは、修正で書き換わった次の2か所です。

ファイル何が変わったか探す文字
ext/session/session.cアップロードの進み具合をセッションに書き込む機能を、まるごと止めた7.4〜8.1は|| true
7.0〜7.3は|| 1
main/rfc1867.cアップロードのヘッダーの読み方を直したphp_mime_should_save_hdr

1つ目は、バージョンによって書き方がちがいます。grep -Eで、どちらでも見つかるようにしました。

ダウンロードする前に知りたいなら、コミットの説明の全文で確かめる方法もあります。PHP2Shellの修正は「Avoid state corruption via bailout」というコミットで、説明の中にCVEの番号が書いてあります。

git log --onelineは1行目しか出さないので、CVEの番号では見つかりません。NEWSにも書かれていないので要注意です。

# コミットの説明(全文)に CVE の番号があるか
cd $SRC
git clone --filter=tree:0 --no-checkout --branch $NEW https://github.com/shivammathur/php-src-backports.git php-$NEW-check
git -C php-$NEW-check log -i --grep='CVE-2026-10961' --format='%h %cd %s' --date=short

GitHubのページで、ブラウザから確かめることもできます。

直したいCVEの番号が出てこなければ、そのバージョンに上げても穴は残ります。ここで止めましょう。

config.niceを残す

# 前回ビルドしたフォルダの config.nice を残す
cp -p $SRC/php-$OLD/config.nice /root/php-$OLD-config.nice

# 今のモジュールの一覧(あとで比べる)
/usr/local/php/bin/php -m > /root/php-$OLD-modules.txt

# 今の PHP を丸ごと残す
cp -a /usr/local/php /usr/local/php-$OLD
cp -a /etc/php /etc/php-$OLD          # --sysconfdir の場所に合わせる

PHPは、configureするたびにconfig.niceというファイルを作ります。中身は、そのときのconfigureのコマンドそのもの。CFLAGSなどの環境変数も残るので、これをコピーして実行すれば、前回と同じ条件でビルドし直せます。めっちゃ便利です。

前回のビルドのフォルダを消してしまったときは、/usr/local/php/bin/php-config --configure-optionsで、オプションだけは取り出せます。

config.niceでビルドする

cd $SRC
curl -L -o php-$NEW-backports.tar.gz \
  https://github.com/shivammathur/php-src-backports/archive/$NEW.tar.gz
tar xzf php-$NEW-backports.tar.gz
cd php-src-backports-$NEW

./buildconf --force                                 # configure を作る
cp -p /root/php-$OLD-config.nice ./config.nice
./config.nice                                       # 前回と同じ条件で configure
make -j$(nproc)

インストール前に確かめる

sapi/cli/php -v                                       # 新しいバージョンになっているか
sapi/cli/php -m | diff /root/php-$OLD-modules.txt -   # モジュールが減っていないか

diffで<の付いた行が出たら、そのモジュールが新しいPHPに入っていません。configureのオプションを見直しましょう。

make installで入れ替える

make install

# PHP-FPM の場合
systemctl restart php-fpm

# mod_php の場合(LoadModule が重複して書き足されていないかも見る)
grep -n 'LoadModule.*php' /path/to/httpd.conf
apachectl configtest && apachectl graceful

php.iniとphp-fpm.confは、make installでは上書きされません。同じ系列(8.1→8.1など)の中で上げるなら、peclで入れた拡張もそのまま使えます。

元のPHPに戻す方法

rm -rf /usr/local/php && cp -a /usr/local/php-$OLD /usr/local/php
systemctl restart php-fpm      # mod_php なら apachectl graceful

ビルドのエラーと対処法

実際にビルドしてみて、ハマったエラーは3つ。bisonが古い、ファイルの持ち主がちがう、OpenSSLが古い、です。

どれも、エラーの1行目を読めば原因の見当がつくものでした。出たエラーの文言に近い見出しを、このあとから探してみてください。

bison 3.0.0が必要と出る

configure: error: bison 3.0.0 is required to generate PHP parsers (excluded versions: none).

下準備をしないでconfigureすると、こう言われます。php.netの配布ファイルには、bisonで作ったファイルが最初から入っているので、ふだんは気にしなくていいんですけどね。GitHubのソースならではのエラーです。

Permission deniedが出る

rm: cannot remove 'ext/openssl/xp_ssl.loT': Permission denied

configureをrootで、makeを一般ユーザーで……と混ぜると、こうなります。configure・make・make installのユーザーは、どちらかにそろえましょう。

OpenSSL 1.0.2で止まる

これはバックポートではなく、公式の8.2.35をCentOS 7でビルドしたときの話です。8.2.11は通ったのに、8.2.35ではext/openssl/xp_ssl.cで止まりました。

warning: implicit declaration of function 'ASN1_STRING_get0_data'
error: subscripted value is neither array nor pointer nor vector

ASN1_STRING_get0_dataは、OpenSSL 1.1.0から入った関数。CentOS 7の標準のOpenSSLは1.0.2なので、この関数がないんです。

1.0.2には、同じ働きのASN1_STRING_dataがあります。そこで、次の3行を足して通しました。

#if OPENSSL_VERSION_NUMBER < 0x10100000L
#define ASN1_STRING_get0_data ASN1_STRING_data
#endif

最初は手で書き足したら、#ifdefの中に入ってしまって「unterminated #ifdef」で怒られました。なんでやねん…。最後の#include <openssl/...>の行の下に、コマンドで差し込むのが安全です。

cp -p ext/openssl/xp_ssl.c ext/openssl/xp_ssl.c.orig

cat > /tmp/xp_ssl_compat.txt <<'EOF'
#if OPENSSL_VERSION_NUMBER < 0x10100000L
#define ASN1_STRING_get0_data ASN1_STRING_data
#endif
EOF

n=$(grep -n '^#include <openssl/' ext/openssl/xp_ssl.c | tail -1 | cut -d: -f1)
sed -i "${n}r /tmp/xp_ssl_compat.txt" ext/openssl/xp_ssl.c
diff -u ext/openssl/xp_ssl.c.orig ext/openssl/xp_ssl.c   # 3行増えていればOK

ただ、これはソースを直接いじる応急処置。OpenSSL 1.0.2もCentOS 7も、すでにサポートが切れています。OSごと移る計画も、あわせて立てておきたいところです。

まとめ

  • PHP2Shellの公式の修正版は8.2以降だけ。8.1以前は、開発チームの最小パッチか有志のバックポート
  • まずはsession.upload_progress.enabled = 0で応急処置。ini_set()や.user.iniでは効かない
  • php-src-backportsを使うなら、直したいCVEが入っているかを先に確かめる
  • GitHubのソースなので、bisonとre2cが必要。PATHはmakeのときも通しておく
  • 前回のビルドのconfig.niceをコピーすれば、同じ条件でビルドし直せる
  • バックポートはつなぎ。8.2も2026年12月31日でサポートが終わるので、本命は8.3・8.4への移行

IT系フリーランスエンジニアとしては、まず穴をふさいで時間を作る。その間に移行の段取りを組む。これが現実的な落としどころだと思います。

修正版の情報は、こちらにまとめています。

攻撃の跡の探し方は、こちら。更新する前に入られていないか、あわせてチェックしておいてください。

参考にしたページ

関連記事一覧

  1. この記事へのコメントはありません。