0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

/etc/php 配下のファイル群を読み解く:php.ini・conf.d・php-fpm.d の役割と、脆弱性対応で見直す設定

0
Posted at

はじめに

php.ini を直したのに、設定が効かない。
そもそも cli / fpm / apache2 のどの php.ini を直せばいいのか分からない。

そんな経験はないだろうか。結論を先に書く。

php.ini は SAPI(cli / fpm / apache2)ごとに別。conf.d の断片が php.ini を上書きし、php-fpm では pool 設定がさらに上書きする。編集は php.ini 本体ではなく conf.d(または pool)に置くと、パッケージ更新で壊れない。脆弱性対応の本丸はパッケージ更新で、ini の堅牢化は攻撃面を減らす補助。

この記事で分かること:

  • /etc/php 配下のファイル群の役割と、読み込まれる順
  • 設定の編集をどこに置き、どう反映するか
  • 脆弱性対応・堅牢化で見直す設定

検証環境

  • Ubuntu 24.04 の php8.3(8.3.6-0ubuntu0.24.04.13)
  • AlmaLinux 9 の php:8.3 ストリーム(8.3.36)
  • どちらも Docker コンテナで確認した。出力は加工していない
  • Docker 公式の php:* イメージは配置が別なので、この記事では扱わない

呼び方

  • Debian 系:Debian / Ubuntu
  • RHEL 系:RHEL / AlmaLinux / Rocky Linux

全体像:Debian 系

まず Debian 系(今回は Ubuntu 24.04)のツリーを見る。

find /etc/php -maxdepth 2 | sort
/etc/php
/etc/php/8.3
/etc/php/8.3/cli
/etc/php/8.3/fpm
/etc/php/8.3/mods-available

👉 -maxdepth 2 で切っているので、php.ini などファイルの中身までは出ない。

/etc/php/<version>/ の下はこう分かれている。

パス 役割
<version>/cli/php.ini CLI(php コマンド)用の php.ini
<version>/fpm/php.ini php-fpm 用の php.ini
<version>/<sapi>/conf.d/ 追加の ini 置き場。中身は mods-available/ へのシンボリックリンク
<version>/mods-available/ 拡張ごとの ini の実体
<version>/fpm/php-fpm.conf php-fpm 全体の設定。pool.d/*.conf を include する
<version>/fpm/pool.d/www.conf pool(ワーカーの単位)の設定
  • conf.d/ の中身は、10-opcache.ini -> /etc/php/8.3/mods-available/opcache.ini のような mods-available/ へのシンボリックリンク
  • apache2/ は、libapache2-mod-php を入れると現れる。今回は入れていないので上のツリーには無い

👉 SAPI ごとに php.ini が別。php --ini が示すのは CLI の php.ini(/etc/php/8.3/cli/php.ini)で、php-fpm が読むのは /etc/php/8.3/fpm/php.ini。

php.ini-production と php.ini-development は、設定ディレクトリではなく /usr/lib/php/8.3/ にある。

ls /usr/lib/php/8.3/
php.ini-development
php.ini-production
php.ini-production.cli
sapi

全体像:RHEL 系

次に RHEL 系(今回は AlmaLinux 9)。

AlmaLinux 9 の AppStream には php のモジュールストリームが複数ある。今回は 8.3 ストリームを有効にして、Debian 系と同じ 8.3 系に揃えた。

dnf -q module list php
AlmaLinux 9 - AppStream
Name Stream  Profiles                   Summary               
php  8.1     common [d], devel, minimal PHP scripting language
php  8.2     common [d], devel, minimal PHP scripting language
php  8.3 [e] common [d], devel, minimal PHP scripting language

Hint: [d]efault, [e]nabled, [x]disabled, [i]nstalled

👉 [e] が付いた 8.3 が有効なストリーム。

ツリーを見る。

ls -la /etc/php.ini /etc/php-fpm.conf /etc/php.d /etc/php-fpm.d
-rw-r--r-- 1 root root  4908 Oct 21 21:25 /etc/php-fpm.conf
-rw-r--r-- 1 root root 62851 Oct 21 21:25 /etc/php.ini

/etc/php-fpm.d:
total 32
drwxr-xr-x 2 root root  4096 Oct 22 05:25 .
drwxr-xr-x 1 root root  4096 Oct 22 05:25 ..
-rw-r--r-- 1 root root 19491 Oct 21 21:25 www.conf

/etc/php.d:
total 56
drwxr-xr-x 2 root root 4096 Oct 22 05:25 .
drwxr-xr-x 1 root root 4096 Oct 22 05:25 ..
-rw-r--r-- 1 root root   44 Oct 21 21:25 20-bz2.ini
-rw-r--r-- 1 root root   54 Oct 21 21:25 20-calendar.ini
-rw-r--r-- 1 root root   48 Oct 21 21:25 20-ctype.ini
-rw-r--r-- 1 root root   46 Oct 21 21:25 20-curl.ini
-rw-r--r-- 1 root root   46 Oct 21 21:25 20-exif.ini
-rw-r--r-- 1 root root   54 Oct 21 21:25 20-fileinfo.ini
-rw-r--r-- 1 root root   44 Oct 21 21:25 20-ftp.ini
-rw-r--r-- 1 root root   52 Oct 21 21:25 20-gettext.ini
-rw-r--r-- 1 root root   48 Oct 21 21:25 20-iconv.ini
-rw-r--r-- 1 root root   46 Oct 21 21:25 20-phar.ini
-rw-r--r-- 1 root root   56 Oct 21 21:25 20-tokenizer.ini

php --ini の結果はこう。

php --ini
Configuration File (php.ini) Path: /etc
Loaded Configuration File:         /etc/php.ini
Scan for additional .ini files in: /etc/php.d
Additional .ini files parsed:      /etc/php.d/20-bz2.ini,
/etc/php.d/20-calendar.ini,
/etc/php.d/20-ctype.ini,
/etc/php.d/20-curl.ini,
/etc/php.d/20-exif.ini,
/etc/php.d/20-fileinfo.ini,
/etc/php.d/20-ftp.ini,
/etc/php.d/20-gettext.ini,
/etc/php.d/20-iconv.ini,
/etc/php.d/20-phar.ini,
/etc/php.d/20-tokenizer.ini

Debian 系との最大の違いは、php.ini が SAPI 共通で 1 つなこと。

  • php.ini は /etc/php.ini の 1 つだけ。SAPI ごとのファイルは無い
  • 追加の ini は /etc/php.d/ に直接並ぶ。上の ls -la では通常ファイル(-rw-r--r--)で、mods-available に相当するものは見当たらない
  • php-fpm の設定は /etc/php-fpm.conf と /etc/php-fpm.d/

php.ini-production の置き場所は、パッケージのファイル一覧で確認できる。

rpm -ql php-common | grep -E 'php\.ini'
/etc/php.ini
/usr/share/doc/php-common/php.ini-development
/usr/share/doc/php-common/php.ini-production

👉 /etc/php.ini が実際に読まれる設定で、php.ini-production / php.ini-development は /usr/share/doc/php-common/ にある見本。

対応表:Debian 系と RHEL 系

役割 Debian 系(Ubuntu 24.04) RHEL 系(AlmaLinux 9)
php.ini /etc/php/8.3/cli/php.ini、/etc/php/8.3/fpm/php.ini(SAPI ごと) /etc/php.ini(共通で 1 つ)
追加 ini /etc/php/8.3/<sapi>/conf.d/ /etc/php.d/
php-fpm.conf /etc/php/8.3/fpm/php-fpm.conf /etc/php-fpm.conf
pool /etc/php/8.3/fpm/pool.d/www.conf /etc/php-fpm.d/www.conf
php.ini-production /usr/lib/php/8.3/php.ini-production /usr/share/doc/php-common/php.ini-production

❌ 「Ubuntu の手順をそのまま AlmaLinux に当てはめる」
👉 パスの形が違う。RHEL 系には <version>/<sapi>/ の階層が無い。


読み込み順と上書きの優先度

設定は 1 か所では決まらない。後から読まれたものが、先に読まれたものを上書きする。

この記事では、優先順位を次の名前で呼ぶ。

php.ini → conf.d → pool(php_admin_value / php_value)→ .user.ini(fpm / CGI)または .htaccess(mod_php)→ ini_set()

👉 右へ行くほど後から読まれる。conf.d は php.ini の後に読まれる追加の ini ファイルで、php.ini の値を上書きできる(第 9 章で、php.ini only の expose_php を conf.d で変えた)。
👉 changeable mode が制限するのは、その後ろの層(pool / .user.ini / ini_set())で変えられるかどうか(後述の表)。
👉 php_admin_value / php_admin_flag で決めた値は ini_set() では変えられない(後述のマニュアルの記述)。.user.ini や .htaccess との優先関係は、この記事では扱わない。

conf.d はアルファベット順に読まれる

PHP マニュアルには次のとおり書かれている。

After the primary configuration file step, PHP may additionally scan for .ini files in the path specified by the PHP_INI_SCAN_DIR environment variable, or, if that is not set, by the --with-config-file-scan-dir compile time option.

Within each directory, PHP will scan all files ending in .ini in alphabetical order.

  • php.ini を読んだ後に、スキャンディレクトリ(conf.d)の .ini を読む
  • ファイル名のアルファベット順に読む。だから 10- 20- と数字の接頭辞が付く
  • 後から読まれる 99-local.ini のようなファイルが勝つ

changeable mode:どこで変えられるか

設定ごとに「どこで変えられるか」が決まっている。マニュアルの定義はこう。

mode どこで変えられるか
PHP_INI_USER ユーザースクリプト(ini_set() など)と .user.ini
PHP_INI_PERDIR php.ini、.htaccess、httpd.conf、.user.ini
PHP_INI_SYSTEM php.ini と httpd.conf
PHP_INI_ALL どこでも

👉 マニュアルでは PHP_ 接頭辞なしの INI_USER などで書かれている。この記事は PHP_INI_* で統一する。
👉 ディレクティブによっては、マニュアルの一覧で mode 欄が "php.ini only" と書かれている(第 7 章で出てくる expose_php と disable_functions)。ini_set() や .user.ini では変えられない。conf.d には書ける(第 9 章)。

.user.ini と .htaccess

  • .user.ini は CGI / FastCGI SAPI だけが処理する。php-fpm もこれに当たる
  • 認識されるのは PHP_INI_ALL / PHP_INI_PERDIR / PHP_INI_USER の設定だけ。PHP_INI_SYSTEM は置けない
  • mod_php では .htaccess を使う(上の PHP_INI_PERDIR の定義に .htaccess がある)

These files are processed only by the CGI/FastCGI SAPI.

👉 ファイル名は user_ini.filename(既定 .user.ini)、再読込の間隔は user_ini.cache_ttl(既定 300 秒)。変更の反映が最大 300 秒遅れることがある。

php-fpm の pool:php_value と php_admin_value

pool に書く php_value と php_admin_value は、ini_set() で上書きできるかが違う。

Settings defined with php_admin_value and php_admin_flag cannot be overridden with ini_set().

  • php_value / php_flag:以前の値を上書きする。changeable mode が PHP_INI_ALL などで ini_set() を許す設定なら、ini_set() で再び変えられる(前述の mode の表)
  • php_admin_value / php_admin_flag:ini_set() で上書きできない
  • 例外として、disable_functions を定義しても php.ini の値は上書きされず、追記される(同じページに記載)

👉 アプリに変えさせたくない値は php_admin_value に書く。

確認する:CLI と fpm は別の php.ini を読む

まず CLI。

php --ini
Configuration File (php.ini) Path: /etc/php/8.3/cli
Loaded Configuration File:         /etc/php/8.3/cli/php.ini
Scan for additional .ini files in: /etc/php/8.3/cli/conf.d
Additional .ini files parsed:      /etc/php/8.3/cli/conf.d/10-mysqlnd.ini,
/etc/php/8.3/cli/conf.d/10-opcache.ini,
/etc/php/8.3/cli/conf.d/10-pdo.ini,
/etc/php/8.3/cli/conf.d/20-calendar.ini,
/etc/php/8.3/cli/conf.d/20-ctype.ini,
/etc/php/8.3/cli/conf.d/20-exif.ini,
/etc/php/8.3/cli/conf.d/20-ffi.ini,
/etc/php/8.3/cli/conf.d/20-fileinfo.ini,
/etc/php/8.3/cli/conf.d/20-ftp.ini,
/etc/php/8.3/cli/conf.d/20-gettext.ini,
/etc/php/8.3/cli/conf.d/20-iconv.ini,
/etc/php/8.3/cli/conf.d/20-mysqli.ini,
/etc/php/8.3/cli/conf.d/20-pdo_mysql.ini,
/etc/php/8.3/cli/conf.d/20-phar.ini,
/etc/php/8.3/cli/conf.d/20-posix.ini,
/etc/php/8.3/cli/conf.d/20-readline.ini,
/etc/php/8.3/cli/conf.d/20-shmop.ini,
/etc/php/8.3/cli/conf.d/20-sockets.ini,
/etc/php/8.3/cli/conf.d/20-sysvmsg.ini,
/etc/php/8.3/cli/conf.d/20-sysvsem.ini,
/etc/php/8.3/cli/conf.d/20-sysvshm.ini,
/etc/php/8.3/cli/conf.d/20-tokenizer.ini

続いて php-fpm。

php-fpm8.3 -i | grep -E 'Loaded Configuration|Scan this dir|Additional .ini'
Loaded Configuration File => /etc/php/8.3/fpm/php.ini
Scan this dir for additional .ini files => /etc/php/8.3/fpm/conf.d
Additional .ini files parsed => /etc/php/8.3/fpm/conf.d/10-mysqlnd.ini,

👉 Additional .ini files parsed が 1 行目しか出ていないのは、grep が 1 行ずつ拾うため。

❌ 「php -i の結果 = Web で動いている設定」
👉 php は CLI の /etc/php/8.3/cli/php.ini を読む。php-fpm が読むのは /etc/php/8.3/fpm/php.ini。パスも conf.d も別。

確認コマンドの使い分け:

  • php --ini:CLI が読む php.ini と conf.d の一覧
  • php -i:CLI の設定全体
  • php-fpm8.3 -i:php-fpm が読む設定。上のように grep で絞る
  • Web 経由の phpinfo():実際にリクエストを処理するプロセスの設定

Debian 系の拡張モジュール管理

Debian 系の conf.d は、mods-available/ へのシンボリックリンクが並ぶ。

ls -la /etc/php/8.3/fpm/conf.d
total 8
drwxr-xr-x 2 root root 4096 Oct 22 05:24 .
drwxr-xr-x 4 root root 4096 Oct 22 05:24 ..
lrwxrwxrwx 1 root root   39 Oct 22 05:24 10-mysqlnd.ini -> /etc/php/8.3/mods-available/mysqlnd.ini
lrwxrwxrwx 1 root root   39 Oct 22 05:24 10-opcache.ini -> /etc/php/8.3/mods-available/opcache.ini
lrwxrwxrwx 1 root root   35 Oct 22 05:24 10-pdo.ini -> /etc/php/8.3/mods-available/pdo.ini
lrwxrwxrwx 1 root root   40 Oct 22 05:24 20-calendar.ini -> /etc/php/8.3/mods-available/calendar.ini
lrwxrwxrwx 1 root root   37 Oct 22 05:24 20-ctype.ini -> /etc/php/8.3/mods-available/ctype.ini
lrwxrwxrwx 1 root root   36 Oct 22 05:24 20-exif.ini -> /etc/php/8.3/mods-available/exif.ini
lrwxrwxrwx 1 root root   35 Oct 22 05:24 20-ffi.ini -> /etc/php/8.3/mods-available/ffi.ini
lrwxrwxrwx 1 root root   40 Oct 22 05:24 20-fileinfo.ini -> /etc/php/8.3/mods-available/fileinfo.ini
lrwxrwxrwx 1 root root   35 Oct 22 05:24 20-ftp.ini -> /etc/php/8.3/mods-available/ftp.ini
lrwxrwxrwx 1 root root   39 Oct 22 05:24 20-gettext.ini -> /etc/php/8.3/mods-available/gettext.ini
lrwxrwxrwx 1 root root   37 Oct 22 05:24 20-iconv.ini -> /etc/php/8.3/mods-available/iconv.ini
lrwxrwxrwx 1 root root   38 Oct 22 05:24 20-mysqli.ini -> /etc/php/8.3/mods-available/mysqli.ini
lrwxrwxrwx 1 root root   41 Oct 22 05:24 20-pdo_mysql.ini -> /etc/php/8.3/mods-available/pdo_mysql.ini
lrwxrwxrwx 1 root root   36 Oct 22 05:24 20-phar.ini -> /etc/php/8.3/mods-available/phar.ini
lrwxrwxrwx 1 root root   37 Oct 22 05:24 20-posix.ini -> /etc/php/8.3/mods-available/posix.ini
lrwxrwxrwx 1 root root   40 Oct 22 05:24 20-readline.ini -> /etc/php/8.3/mods-available/readline.ini
lrwxrwxrwx 1 root root   37 Oct 22 05:24 20-shmop.ini -> /etc/php/8.3/mods-available/shmop.ini
lrwxrwxrwx 1 root root   39 Oct 22 05:24 20-sockets.ini -> /etc/php/8.3/mods-available/sockets.ini
lrwxrwxrwx 1 root root   39 Oct 22 05:24 20-sysvmsg.ini -> /etc/php/8.3/mods-available/sysvmsg.ini
lrwxrwxrwx 1 root root   39 Oct 22 05:24 20-sysvsem.ini -> /etc/php/8.3/mods-available/sysvsem.ini
lrwxrwxrwx 1 root root   39 Oct 22 05:24 20-sysvshm.ini -> /etc/php/8.3/mods-available/sysvshm.ini
lrwxrwxrwx 1 root root   41 Oct 22 05:24 20-tokenizer.ini -> /etc/php/8.3/mods-available/tokenizer.ini

👉 各行が 10-opcache.ini -> /etc/php/8.3/mods-available/opcache.ini の形。実体は mods-available にある。

接頭辞の数字は、mods-available の ini にあるコメント ; priority= に対応している。同じ環境で 2 つの ini を見るとこうなる。

cat /etc/php/8.3/mods-available/opcache.ini /etc/php/8.3/mods-available/pdo_mysql.ini
; configuration for php opcache module
; priority=10
zend_extension=opcache.so
opcache.jit=off
; configuration for php mysql module
; priority=20
extension=pdo_mysql.so

👉 priority=10 が 10-opcache.ini、priority=20 が 20-pdo_mysql.ini に対応する。

phpenmod / phpdismod / phpquery

拡張の有効・無効は、リンクを手で張らずにコマンドで行う。phpenmod を引数なしで実行すると usage が出る。

phpenmod 2>&1 | head -20
WARNING: 
usage: phpenmod [ -v ALL|php_version ] [ -s ALL|sapi_name ] module_name [ module_name_2 ]
  • phpenmod:モジュールを有効にする
  • phpdismod:モジュールを無効にする
  • phpquery:モジュールの状態を調べる

オプションは 1 行ずつ。

  • -v:PHP のバージョンを指定する(phpenmod では ALL も可)
  • -s:SAPI を指定する(phpenmod では ALL も可)
  • -m:モジュール名を指定して状態を見る(phpquery。例:phpquery -v 8.3 -s fpm -m opcache)
  • -M:有効なモジュールの一覧を出す(phpquery。今回の環境では上の conf.d のリンクと同じ 22 個だったので、出力は載せない)

phpquery を引数なしで実行すると usage が出る。モジュール名を指定すると状態が見える。

phpquery 2>&1
WARNING: You need to specify at least one action.
usage: phpquery [ -d ] [ -q ] -v version_name -s sapi_name [ -m module_name ] [ -M ] [ -S ] [ -V ]
phpquery -v 8.3 -s fpm -m opcache
opcache (Enabled for fpm by maintainer script)

複数バージョンが入っているとき

PHP を複数バージョン入れている場合、php コマンドの向き先は update-alternatives で切り替える。

下は今回の環境で update-alternatives --display php を実行した出力。8.3 しか入れていないので、候補は 1 つだけ。切り替えの操作そのものは、この環境では実行していない。

update-alternatives --display php
php - auto mode
  link best version is /usr/bin/php8.3
  link currently points to /usr/bin/php8.3
  link php is /usr/bin/php
  slave php.1.gz is /usr/share/man/man1/php.1.gz
/usr/bin/php8.3 - priority 83
  slave php.1.gz: /usr/share/man/man1/php8.3.1.gz

👉 複数バージョンが並んだ出力ではない。

RHEL 系は直接並ぶ

RHEL 系は /etc/php.d/*.ini に通常ファイルが直接並ぶ(第 3 章の ls -la の出力)。

👉 自分の設定の置き場所(詳細は第 9 章):

  • Debian 系:mods-available/ に ini を置いて phpenmod するか、<sapi>/conf.d/ に 99-local.ini を置く
  • RHEL 系:/etc/php.d/99-local.ini を置く

php-fpm.conf と pool

php-fpm の設定は 2 層に分かれる。

  • php-fpm.conf:php-fpm 全体(グローバル)の設定
  • pool:ワーカーの単位。user や listen は pool ごとに決める

まず Debian 系のグローバル設定。コメントと空行を除いて見る。

grep -vE '^\s*(;|$)' /etc/php/8.3/fpm/php-fpm.conf
[global]
pid = /run/php/php8.3-fpm.pid
error_log = /var/log/php8.3-fpm.log
include=/etc/php/8.3/fpm/pool.d/*.conf

👉 最終行の include= が pool.d/*.conf を読み込んでいる。pool の設定ファイルを足すだけで pool が増える仕組みは、この実ファイルが根拠。

次に pool(www.conf)。

grep -vE '^\s*(;|$)' /etc/php/8.3/fpm/pool.d/www.conf
[www]
user = www-data
group = www-data
listen = /run/php/php8.3-fpm.sock
listen.owner = www-data
listen.group = www-data
pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3

👉 [www] が pool 名。以降のキーはその pool だけに効く。

主要キー

キー 置き場所 役割(出典の範囲)
pid グローバル pid ファイルの場所。既定値は書かない。Debian 系の実際の値は上の出力のとおり
error_log グローバル php-fpm 自体のログ。既定は #INSTALL_PREFIX#/log/php-fpm.log、syslog も指定できる
emergency_restart_threshold グローバル この数の子プロセスが、emergency_restart_interval の間に SIGSEGV / SIGBUS で終了すると FPM が再起動する。既定は 0(Off)
process_control_timeout グローバル 既定は 0
include グローバル 上の php-fpm.conf では pool.d/*.conf を指している(実ファイルで確認)
user / group pool ワーカーの実行ユーザー。user は必須。group は未設定なら既定ユーザーのグループ
listen pool FastCGI リクエストを受ける場所。pool ごとに必須。ip.add.re.ss:port、port、UNIX ソケットのパスが書ける
listen.owner / listen.mode pool UNIX ソケットのパーミッション。既定は実行ユーザーとグループ、mode は 0660
pm pool 必須。static / ondemand / dynamic
pm.* pool プロセス数の設定。チューニングはこの記事の範囲外
php_admin_value / php_admin_flag pool ini_set() で上書きできない
php_value / php_flag pool 以前の値を上書きする
security.limit_extensions pool FPM が解釈するメインスクリプトの拡張子。既定は .php .phar
request_terminate_timeout pool 1 リクエストの処理時間がこれを超えるとワーカーを終了する。既定は 0(Off)
slowlog pool スローログの出力先。既定は #INSTALL_PREFIX#/log/php-fpm.log.slow

👉 pid と pm の既定値は書かない。Debian 系・RHEL 系の実際の値は、実ファイルの出力で示した。
👉 disable_functions を php_value で定義しても php.ini の値は上書きされず、追記される(同じページに記載)。

listen は UNIX ソケット

Debian 系の listen は、パスで書かれている。

ls -la /run/php/ 2>&1; grep -nE '^listen' /etc/php/8.3/fpm/pool.d/www.conf
total 12
drwxr-xr-x 2 www-data www-data 4096 Oct 22 05:24 .
drwxr-xr-x 1 root     root     4096 Oct 22 05:24 ..
41:listen = /run/php/php8.3-fpm.sock
53:listen.owner = www-data
54:listen.group = www-data

👉 listen の値がファイルのパスなら UNIX ソケット。この出力にはソケットファイル自体は出ていない。確認できたのは listen の値と、/run/php/ の所有者が www-data なこと。

Web サーバーはこのソケットに接続する。マニュアルにはこうある。

In Linux, read/write permissions must be set in order to allow connections from a web server.

  • Web サーバーのユーザーが読み書きできるように、listen.owner / listen.group / listen.mode で合わせる
  • 上の出力では listen.owner = www-data、listen.group = www-data。mode は既定の 0660

RHEL 系の pool

RHEL 系の pool は /etc/php-fpm.d/www.conf。同じ方法で見る。

grep -vE '^\s*(;|$)' /etc/php-fpm.d/www.conf
[www]
user = apache
group = apache
listen = /run/php-fpm/www.sock
listen.acl_users = apache,nginx
listen.allowed_clients = 127.0.0.1
pm = dynamic
pm.max_children = 50
pm.start_servers = 5
pm.min_spare_servers = 5
pm.max_spare_servers = 35
slowlog = /var/log/php-fpm/www-slow.log
php_admin_value[error_log] = /var/log/php-fpm/www-error.log
php_admin_flag[log_errors] = on
php_value[session.save_handler] = files
php_value[session.save_path]    = /var/lib/php/session
php_value[soap.wsdl_cache_dir]  = /var/lib/php/wsdlcache

Debian 系との差:

  • user / group は apache(Debian 系は www-data)
  • listen は /run/php-fpm/www.sock(Debian 系は /run/php/php8.3-fpm.sock)
  • listen.owner / listen.group の代わりに listen.acl_users = apache,nginx と listen.allowed_clients = 127.0.0.1 がある
  • pm.max_children などの値が大きい(50 / 5 / 5 / 35)
  • slowlog、php_admin_value[error_log]、php_value[session.save_path] などが最初から入っている

👉 listen.acl_users は、POSIX ACL でソケットに接続できるユーザーを指定する。マニュアルには "When set, listen.owner and listen.group are ignored." とある。RHEL 系の www.conf に listen.owner が無いのは、これを使っているため。
👉 listen.allowed_clients は、接続を許す FastCGI クライアントの IP アドレス。マニュアルには "Makes sense only with a tcp listening socket." とある。この pool は UNIX ソケットで待ち受けているので、この行は意味を持たない。

独自 pool の設定例

www.conf を直さず、新しいファイルを pool.d/(RHEL 系は php-fpm.d/)に足す。www.conf を直接編集しないほうが、自分の変更を差分として管理しやすい。

以下は例(/etc/php/8.3/fpm/pool.d/app.conf)。

[app]
user = app
group = app
listen = /run/php/app.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
php_admin_value[disable_functions] = exec,passthru,shell_exec,system,proc_open,popen
php_admin_flag[display_errors] = off

👉 listen は UNIX ソケットのパス。TCP で待ち受ける場合は listen = 127.0.0.1:<port> とし、listen.allowed_clients = 127.0.0.1 を添える。マニュアルは、公開された FastCGI の待ち受けは任意コード実行を許すと警告し、listen.allowed_clients は空のままだと "connections will be accepted from any ip address" と書いている(install.fpm.configuration.php)。
👉 pool の php_admin_value / php_admin_flag で設定した値は、ini_set() で上書きできない(install.fpm.configuration.php)。disable_functions は pool に書くと php.ini の値に追記される(install.fpm.configuration.php)。

構文確認と反映

設定を足したら、まず構文を確認する。

php-fpm8.3 -t
[22-Oct-2026 05:25:56] NOTICE: configuration file /etc/php/8.3/fpm/php-fpm.conf test is successful

問題なければ反映する。Debian 系:

systemctl reload php8.3-fpm

RHEL 系:

systemctl reload php-fpm

👉 reload は設定を読み直す。出力は出ないので、反映の確認は第 9 章の方法で行う。


脆弱性対応・堅牢化で見直す ini パラメータ

各項目は「何が起きるか → 設定値 → どこに書くか」の順で書く。

  • 「changeable mode」は、第 4 章の PHP_INI_* の表記。マニュアルの一覧(ini.list.php)に書かれた値をそのまま使う
  • 「既定値」は PHP 本体の既定。ディストリビューションの php.ini や php.ini-production の値とは別
  • 設定値は「例」。アプリの要件に合わせて決める

まず php.ini-production との差を見る

Debian 系の見本は 2 つある。php.ini-production と php.ini-development で、関係する行を並べる。

grep -nE '^(display_errors|log_errors|expose_php|allow_url_include|allow_url_fopen|session\.cookie_httponly|session\.use_strict_mode|session\.cookie_samesite|session\.upload_progress\.enabled|memory_limit|post_max_size|disable_functions)\b' /usr/lib/php/8.3/php.ini-production /usr/lib/php/8.3/php.ini-development
/usr/lib/php/8.3/php.ini-production:333:disable_functions = 
/usr/lib/php/8.3/php.ini-production:410:expose_php = Off
/usr/lib/php/8.3/php.ini-production:445:memory_limit = 128M
/usr/lib/php/8.3/php.ini-production:518:display_errors = Off
/usr/lib/php/8.3/php.ini-production:537:log_errors = On
/usr/lib/php/8.3/php.ini-production:713:post_max_size = 8M
/usr/lib/php/8.3/php.ini-production:876:allow_url_fopen = On
/usr/lib/php/8.3/php.ini-production:880:allow_url_include = Off
/usr/lib/php/8.3/php.ini-production:1393:session.use_strict_mode = 0
/usr/lib/php/8.3/php.ini-production:1432:session.cookie_httponly =
/usr/lib/php/8.3/php.ini-production:1438:session.cookie_samesite =
/usr/lib/php/8.3/php.ini-development:339:disable_functions = 
/usr/lib/php/8.3/php.ini-development:414:expose_php = On
/usr/lib/php/8.3/php.ini-development:449:memory_limit = 128M
/usr/lib/php/8.3/php.ini-development:522:display_errors = On
/usr/lib/php/8.3/php.ini-development:541:log_errors = On
/usr/lib/php/8.3/php.ini-development:717:post_max_size = 8M
/usr/lib/php/8.3/php.ini-development:880:allow_url_fopen = On
/usr/lib/php/8.3/php.ini-development:884:allow_url_include = Off
/usr/lib/php/8.3/php.ini-development:1397:session.use_strict_mode = 0
/usr/lib/php/8.3/php.ini-development:1436:session.cookie_httponly =
/usr/lib/php/8.3/php.ini-development:1442:session.cookie_samesite =
  • この出力の範囲では、production と development で値が違うのは expose_php(Off / On)と display_errors(Off / On)
  • session.cookie_httponly と session.cookie_samesite は、どちらも値が空
  • session.use_strict_mode は、どちらも 0

👉 production を基準にして、自分の変更は conf.d の 99-local.ini に足す。

expose_php

マニュアルの説明:

Exposes to the world that PHP is installed on the server, which includes the PHP version within the HTTP header (e.g., X-Powered-By: PHP/5.3.7).

expose_php = Off
  • 既定値は "1"、changeable mode は "php.ini only"
  • php.ini-production では Off(development は On)
  • ini_set() や .user.ini では変えられない。conf.d の 99-local.ini に書く
  • conf.d に書いて効くことは、CLI で確認した(第 9 章)

👉 Web サーバー側でヘッダーを消す方法は、別の記事にまとめた。
https://qiita.com/gtakairo/items/2a7e08c618f802ca3b23

display_errors / log_errors / error_log

  • display_errors:既定 "1"、PHP_INI_ALL
  • log_errors:既定 "0"、PHP_INI_ALL
  • error_log:既定 null、PHP_INI_ALL
  • php.ini-production は display_errors = Off、log_errors = On(development は display_errors = On)

本番はエラーを画面に出さず、ログに残す。

display_errors = Off
log_errors = On
error_log = /var/log/php/app-error.log

👉 3 つとも PHP_INI_ALL なので、ini_set() でも変えられる。pool の php_admin_value / php_admin_flag で設定した値は、ini_set() で上書きできない(install.fpm.configuration.php)。RHEL 系の www.conf には php_admin_value[error_log] と php_admin_flag[log_errors] が最初から入っていた。

allow_url_include と allow_url_fopen

2 つは別の設定。

  • allow_url_fopen:マニュアルの説明は "This option enables the URL-aware fopen wrappers that enable accessing URL object like files."。既定 "1"、PHP_INI_SYSTEM
  • allow_url_include:マニュアルの説明は "This option allows the use of URL-aware fopen wrappers with the following functions: include, include_once, require, require_once."。既定 "0"、PHP_INI_SYSTEM。7.4.0 で非推奨
  • allow_url_include には "This setting requires allow_url_fopen to be on." という注記がある

allow_url_include = Off

👉 php.ini-production は allow_url_include = Off、allow_url_fopen = On。
👉 どちらも PHP_INI_SYSTEM なので、ini_set() と .user.ini では変えられない。conf.d に書く。

disable_functions

マニュアルの記述:

  • カンマ区切りの関数名を書く
  • 内部関数だけが対象
  • 8.0.0 以降は、無効化した関数の定義が消え、ユーザーランドで再定義できる
  • php.ini にだけ設定でき、httpd.conf には書けない(changeable mode は "php.ini only")
  • 既定値は ""

This directive can be circumvented and should not be considered a sufficient security measure for shared hosting environments.

例:

disable_functions = exec,passthru,shell_exec,system,proc_open,popen
  • pool の php_value / php_admin_value で disable_functions を定義すると、php.ini の値は上書きされず追記される(install.fpm.configuration.php)
  • 副作用:アプリが使う関数を切ると、そのアプリは動かなくなる。入れる前に、使っている関数を確認する
  • 書く場所は conf.d の 99-local.ini。conf.d の ini は、php.ini の後に読まれる追加の ini ファイル(第 4 章の "After the primary configuration file step")。同じ php.ini only の expose_php は、conf.d で上書きできた(第 9 章)
  • disable_functions 自体は本記事では実証していない

👉 マニュアル自身が「回避され得る」「十分なセキュリティ対策とは見なすべきでない」と書いている。入口を減らす補助として使う。

open_basedir

PHP がアクセスできるファイルを、指定したディレクトリツリーに限る設定。既定 null、PHP_INI_ALL。

open_basedir = /var/www/app:/tmp

👉 上は例。パスはアプリに合わせる。

マニュアルに書かれている制限と限界:

  • シンボリックリンクはすべて解決される。symlink では、この制限を回避できない
  • 実行時に ini_set() で狭めることはできる
  • 8.3.0 以降は、実行時に .. を含むパスを受け付けない
  • Unix ドメインソケットへのアクセスは制限しない。stream_socket_client() や fsockopen() で接続できる
  • .(作業ディレクトリ)は chdir() で変わるので、少し危険
  • 有効にすると realpath_cache_size が 0 になり、キャッシュが無効になる

open_basedir is just an extra safety net, that is in no way comprehensive, and can therefore not be relied upon when security is needed.

❌ 「open_basedir を設定すれば、ほかのディレクトリは見られない」
👉 マニュアル自身が、包括的ではなく、セキュリティが必要なときに頼ってはいけないと書いている。

session.cookie_httponly / session.cookie_secure / session.use_strict_mode / session.cookie_samesite

4 つとも PHP_INI_ALL。PHP 本体の既定値と、マニュアルの説明は次のとおり。

パラメータ 既定値 意味
session.cookie_httponly "0" クッキーを HTTP プロトコル経由だけで参照できるようにする。JavaScript などのスクリプトからは見えなくなる
session.cookie_secure "0" クッキーを安全な接続でだけ送るか。On にするとセッションは HTTPS 接続でしか動かない
session.use_strict_mode "0" 未初期化のセッション ID を受け付けない。ブラウザから未初期化の ID が送られると、新しい ID を返す
session.cookie_samesite ""(7.3.0 以降) クロスサイトのリクエストにクッキーを付けないよう指示する。空なら SameSite 属性を付けない。Lax と Strict はクロスドメインの POST に付けない。クロスドメインの GET には Lax は付け、Strict は付けない

https://www.php.net/manual/en/ini.list.php
https://www.php.net/manual/en/session.configuration.php

👉 session.cookie_secure を 1 にすると HTTP 接続ではセッションが動かない。HTTPS で動かしているアプリに限って入れる。

見直しの例:

session.cookie_httponly = 1
session.cookie_secure = 1
session.use_strict_mode = 1
session.cookie_samesite = Lax
  • 上は例。Lax は、cookie_samesite に指定できる値の 1 つ
  • php.ini-production の session.use_strict_mode は 0、session.cookie_httponly と session.cookie_samesite は空。production を基準にしても、この 3 つは自分で決める必要がある

👉 PHP_INI_ALL なので ini_set() でも変えられる。pool の php_admin_value / php_admin_flag で設定した値は、ini_set() で上書きできない(install.fpm.configuration.php)。

cgi.fix_pathinfo と security.limit_extensions

cgi.fix_pathinfo は、CGI の PATH_INFO / PATH_TRANSLATED を仕様に合わせる設定。

It is turned on by default.

  • 既定 "1"、PHP_INI_SYSTEM
  • ini_set() では変えられない。conf.d に書く

security.limit_extensions は pool の設定。FPM が解釈するメインスクリプトの拡張子を絞る。

You should only limit FPM to .php extensions to prevent malicious users to use other extensions to execute php code.

  • 既定値は .php .phar
  • Web サーバー側の設定ミスを防ぐ目的で使える、とマニュアルにある
security.limit_extensions = .php

👉 FPM 側で実行する拡張子を絞る、という位置づけ。.phar を使わないなら .php だけにする例。
👉 cgi.fix_pathinfo と Nginx の関係は、php.net の原文に記述が無い。この記事では扱わない。

file_uploads / upload_max_filesize / post_max_size / max_input_vars / memory_limit

受け付ける入力の上限と、メモリの上限。memory_limit のマニュアルの説明は "This sets the maximum amount of memory in bytes that a script is allowed to allocate."(https://www.php.net/manual/en/ini.core.php)。

パラメータ 既定値 changeable mode
file_uploads "1" PHP_INI_SYSTEM
upload_max_filesize "2M" PHP_INI_PERDIR
post_max_size "8M" PHP_INI_PERDIR
max_input_vars 1000 PHP_INI_PERDIR
memory_limit "128M" PHP_INI_ALL

file_uploads = Off
upload_max_filesize = 2M
post_max_size = 8M
max_input_vars = 1000
memory_limit = 128M

👉 上は例。ファイルアップロードを使わないアプリなら file_uploads = Off。使うなら、必要な大きさに合わせる。
👉 php.ini-production の memory_limit と post_max_size は、既定と同じ 128M と 8M。
👉 memory_limit / post_max_size は、第 8 章でも出てくる。第 8 章で示すのは 2026-10-21 の公式アドバイザリ向けの別の設定で、ここの例(128M / 8M)とは目的が違う。

session.upload_progress.enabled

既定 "1"、changeable mode は PHP_INI_PERDIR。

session.upload_progress.enabled = 0
  • PHP_INI_PERDIR なので、ini_set() では変えられない
  • conf.d か pool に書く
  • 書いたあと、reload で足りるか再起動が要るかは、この記事では検証していない。公式アドバイザリの記述は第 8 章で扱う

👉 この設定は、第 8 章の 2026-10-21 の公式アドバイザリでも暫定策として出てくる。暫定策だけでは安全にならず、完全な対処は更新のみ、という公式の記述と合わせて第 8 章で書く。

enable_dl

  • 既定 "1"、changeable mode は PHP_INI_SYSTEM
  • マニュアルの注記は、"This deprecated feature will certainly be removed in the future."
enable_dl = 0

👉 非推奨で、将来削除される機能。上は例。PHP_INI_SYSTEM なので conf.d に書く。動作の説明は、この記事では扱わない。

一覧表

パラメータ 推奨値(例) 書く場所 changeable mode
expose_php Off conf.d の 99-local.ini(CLI で効くことを第 9 章で確認) php.ini only
display_errors Off conf.d の 99-local.ini(変えさせたくなければ pool の php_admin_flag) PHP_INI_ALL
log_errors On conf.d の 99-local.ini(または pool の php_admin_flag) PHP_INI_ALL
error_log ログのパス conf.d の 99-local.ini(または pool の php_admin_value) PHP_INI_ALL
allow_url_include Off conf.d の 99-local.ini PHP_INI_SYSTEM
allow_url_fopen アプリが使わないなら Off conf.d の 99-local.ini PHP_INI_SYSTEM
disable_functions アプリが使わない関数だけ conf.d の 99-local.ini(本記事では未実証。pool の php_admin_value は追記) php.ini only
open_basedir アプリのディレクトリ conf.d の 99-local.ini(または pool の php_admin_value) PHP_INI_ALL
session.cookie_httponly 1 conf.d の 99-local.ini(または pool の php_admin_flag) PHP_INI_ALL
session.cookie_secure 1 conf.d の 99-local.ini(または pool の php_admin_flag) PHP_INI_ALL
session.use_strict_mode 1 conf.d の 99-local.ini(または pool の php_admin_flag) PHP_INI_ALL
session.cookie_samesite Lax conf.d の 99-local.ini(または pool の php_admin_value) PHP_INI_ALL
cgi.fix_pathinfo 既定値 1 のまま(変更する根拠は本記事では扱わない) conf.d の 99-local.ini PHP_INI_SYSTEM
security.limit_extensions .php pool 表に無い(pool の設定)
file_uploads 使わなければ Off conf.d の 99-local.ini PHP_INI_SYSTEM
upload_max_filesize 2M conf.d の 99-local.ini PHP_INI_PERDIR
post_max_size 8M conf.d の 99-local.ini PHP_INI_PERDIR
max_input_vars 1000 conf.d の 99-local.ini PHP_INI_PERDIR
memory_limit 128M conf.d の 99-local.ini PHP_INI_ALL
session.upload_progress.enabled 0 conf.d の 99-local.ini または pool PHP_INI_PERDIR
enable_dl 0 conf.d の 99-local.ini PHP_INI_SYSTEM

👉 「conf.d の 99-local.ini」は、Debian 系は /etc/php/8.3/fpm/conf.d/、RHEL 系は /etc/php.d/。
👉 ini は攻撃面を減らす補助。脆弱性対応の本丸はパッケージ更新(第 8 章)。


脆弱性対応の本丸はパッケージ更新

ini の堅牢化は、攻撃面を減らす補助。脆弱性対応の本丸はパッケージ更新。

設定で塞げるのは、特定の経路だけ。コードの欠陥そのものは、更新で直すしかない。

例 1:php2shell(CVE-2026-109618 / CVE-2026-109619)

「php2shell」は 2 件をまとめて呼ぶ通称で、公式のアドバイザリ(php.net の告知と GHSA)はこの名前を使っていない。

2026-10-21 に、PHP 公式が 2 件の critical な脆弱性の修正版を出した。

  • 修正版:8.5.12 / 8.4.27 / 8.3.36 / 8.2.35
  • 2 件とも、影響を受けるのは < 8.2.35、< 8.3.36、< 8.4.27、< 8.5.12
  • CVE-2026-109618:メモリ不足(OOM)の処理に起因する
  • CVE-2026-109619:古いグローバル状態が残ることに起因する。確認済みの経路は、初期化前の INI 診断とセッションのアップロード進捗

https://www.php.net/archive/2026.php
https://github.com/php/php-src/security/advisories/GHSA-w8hw-w9fx-j79w
https://github.com/php/php-src/security/advisories/GHSA-6wmf-4rv5-p3rp

公式の告知は、すぐ更新できない場合の暫定策を挙げている。ただし、原文はこう書いている。

If you cannot update immediately: while only an update is a complete solution, the following measures reduce exposure to specific attack paths but do not make an installation safe.

👉 暫定策は、特定の攻撃経路への露出を減らすだけ。導入環境を安全にはしない(原文: do not make an installation safe)。完全な解決は更新のみ。

告知が挙げている暫定策は 3 つ。

  • memory_limit=-1。アプリが上書きできない形(FPM pool の php_admin_value や Apache の設定)で。どこかで有限の値に戻していないかも確認する

    This removes the controllable PHP memory limit failure, but an underlying allocation failure can still reach the vulnerable path.

  • post_max_size を低くする。アップロード不要のアプリなら 8M、必要なら 32M。upload_max_filesize は post_max_size より小さくする

  • session.upload_progress.enabled=0 にして、全ワーカーを再起動する

    This blocks the confirmed session paths but not other variants.

最後の項目には、こうも添えられている。

Do not rely on ini_set() or FPM .user.ini.

👉 第 7 章の表で見たとおり、session.upload_progress.enabled は PHP_INI_PERDIR。ini_set() では変えられない。PHP_INI_PERDIR は .user.ini にも書ける(第 4 章の表)が、告知は頼るなと書いている。php.ini、conf.d、pool のように、リクエストの解析より前に効く場所に書く。

conf.d の 99-local.ini に書く形は、こうなる。以下は例。

; 例:php2shell の暫定策。これだけでは導入環境は安全にならない。更新が先
session.upload_progress.enabled = 0
post_max_size = 8M
upload_max_filesize = 2M

memory_limit=-1 は、アプリに戻させない形にする。memory_limit は PHP_INI_ALL なので、conf.d に書くだけだと ini_set() で変えられてしまう。pool に書く例(これも例):

php_admin_value[memory_limit] = -1

👉 memory_limit = -1 は上限なしの値。第 7 章の例(128M)とは目的が違う。公式の暫定策に従うときだけの値で、更新したら元に戻す判断をする。
👉 書いたあとに reload で足りるか再起動が要るかは、この記事では検証していない。公式の告知は「全ワーカーを再起動」と書いている。

❌ 「multipart のリクエストを弾けば安全」
👉 GHSA-w8hw-w9fx-j79w は、次のように書いている。

Several multipart parsing problems can let a request reach OOM while remaining within its apparent PHP limits:

multipart は OOM に至る経路の一部で、必須条件とは書かれていない。同じ GHSA は、アプリが multipart を使わないなら frontend で弾くよう書いている。

If the application does not use multipart form data, block such requests at the frontend before they reach PHP.

👉 これは経路を 1 つ減らす部分的な対策。しかも告知は、暫定策では導入環境は安全にならず、完全な解決は更新のみ、と明言している。

👉 Nginx 側で multipart を PHP の手前で弾く設定の読み解きは、別記事にまとめた:https://qiita.com/gtakairo/items/1ab335720f8a59205621

CVE-2026-109619 の GHSA は、こう書いている。

There is no complete generic configuration mitigation as an application, extension or server path may publish stale state

Updating to a release containing the complete state reset patch is the only reliable mitigation.

8.1 以前

PHP 8.1 and older no longer receive updates from the PHP project.

👉 8.1 以前には、上流の更新が無い。一部のディストリビューションや LTS ベンダーがバックポートを提供している。配布元に確認する(告知の記述)。

手元のパッケージに修正が入っているか

2026-10-22 に、Ubuntu 24.04 と AlmaLinux 9 のコンテナで確認した。まず Ubuntu。

php -v
PHP 8.3.6 (cli) (built: Oct 19 2026 15:16:58) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.3.6, Copyright (c) Zend Technologies
    with Zend OPcache v8.3.6, Copyright (c), by Zend Technologies
dpkg -l php8.3-cli | tail -1
ii  php8.3-cli     8.3.6-0ubuntu0.24.04.13 amd64        command-line interpreter for the PHP scripting language

php -v の 8.3.6 は上流の版番号。dpkg -l の 8.3.6-0ubuntu0.24.04.13 は、上流の版(8.3.6)に、配布元のリビジョン(-0ubuntu0.24.04.13)が付いた形。

changelog の先頭はこう。

zcat /usr/share/doc/php8.3-cli/changelog.Debian.gz | head -25
php8.3 (8.3.6-0ubuntu0.24.04.13) noble-security; urgency=medium

  * SECURITY UPDATE: Remote RCE via state corruption issue
    - debian/patches/CVE-2026-109618_109619.patch: Avoid state corruption via
      bailout in Zend/tests/bug40770.phpt, Zend/tests/bug54268.phpt,
      Zend/tests/bug76846.phpt,
      Zend/tests/debug_backtrace_sensitive_named_variadic.phpt,
      Zend/tests/fibers/get-return-after-bailout.phpt, Zend/tests/gh11189.phpt,
      Zend/tests/gh11189_1.phpt,
      Zend/tests/oom_SplFixedArray___unserialize.phpt,
      Zend/tests/oom_SplFixedArray___wakeup.phpt,
      Zend/tests/oom_SplFixedArray_copy_ctor.phpt,
      Zend/tests/oom_SplFixedArray_fromArray_001.phpt,
      Zend/tests/oom_SplFixedArray_fromArray_002.phpt,
      Zend/tests/oom_after_main_script.phpt, Zend/tests/oom_array_fill_001.phpt,
      Zend/tests/oom_array_fill_002.phpt, Zend/tests/oom_array_fill_003.phpt,
      Zend/tests/oom_array_fill_004.phpt, Zend/tests/oom_array_pad_001.phpt,
      Zend/tests/oom_array_pad_002.phpt, Zend/tests/oom_array_pad_003.phpt,
      Zend/tests/oom_array_pad_004.phpt, Zend/tests/oom_array_pad_005.phpt,
      Zend/tests/oom_array_pad_006.phpt, Zend/tests/oom_array_pad_007.phpt,
      Zend/tests/oom_array_pad_008.phpt, Zend/tests/oom_call_function.phpt,
      Zend/tests/oom_call_function_002.phpt,
      Zend/tests/oom_call_function_003.phpt,
      Zend/tests/oom_debug_backtrace.phpt,
      Zend/tests/oom_debug_backtrace_args.phpt,

👉 changelog の先頭に CVE-2026-109618_109619.patch という名前のパッチと "Remote RCE via state corruption issue" の記載がある。出力から読めるのはここまで。パッチの中身までは見ていない。

次に AlmaLinux 9。

php -v
PHP 8.3.36 (cli) (built: Oct 19 2026 14:00:34) (NTS gcc x86_64)
Copyright (c) The PHP Group
Zend Engine v4.3.36, Copyright (c) Zend Technologies
rpm -q php-cli
php-cli-8.3.36-1.module_el9.8.0+330+fdc30075.x86_64
rpm -q --changelog php-cli | grep -E '^- ' | head -15
- rebase to 8.3.36
- rebase to 8.3.33
- rebase to 8.3.32
- drop capstone dependency on i686
- rebase to 8.3.31
- rebase to 8.3.29
- rebase to 8.3.26
- rebase to 8.3.19
- rebase to 8.3.15
- rebase to 8.3.12 RHEL-62189
- enable command history in phpdbg
- backport Argon2 password hashing in OpenSSL ext
- build sockets extension statically
- switch to nikic/php-parser version 5
- openssl: always warn about missing curve_name

👉 この 2 つのコマンドは、別の AlmaLinux 9 コンテナ(dnf module install php:8.3/common で導入。php-cli は同じ 8.3.36-1)で取った。版は 8.3.36。上流の修正版(8.3.36)と同じ番号。2 つ目のコマンドは changelog のエントリ行(- で始まる行)だけを出す。先頭は rebase to 8.3.36。CVE や php2shell の記載は、この 15 行には出ていない。

どちらのディストリビューションについても、「完全に直った」とはこの記事では言わない。読めるのは、Ubuntu は changelog が両 CVE に触れていること、AlmaLinux は版が上流の修正版と同じこと、まで。

版番号だけで判断しない

配布元は、修正をバックポートする。バックポートした修正では、上流の版番号は変わらない。Red Hat はこう書いている。

For most products, our default practice is to backport security fixes.

just looking at the version number of a package will not tell them if they are vulnerable or not.

  • 8.3.6-0ubuntu0.24.04.13 のように、上流の版が古く見えても、配布元の修正が入っていることがある
  • 逆に、上流の版番号だけを見て「新しいから問題ない」とも言えない
  • 判断は、配布元のセキュリティ情報と changelog で行う

Ubuntu の security tracker:
https://ubuntu.com/security/cves?package=php8.3

👉 URL の案内だけ。どの CVE がどの版で直っているかは、tracker で確認する。

配布元の修正を待てない場合は、自前ビルドの手順を別の記事にまとめた。
https://qiita.com/gtakairo/items/5a655e618d28b5d274ad

例 2:CVE-2024-4577

設定ではなく、PHP のコード自体の欠陥の例をもう 1 つ。CVE-2024-4577 は、Windows で Apache と PHP-CGI を使い、特定の code page を使う設定のときに、Windows の "Best-Fit" 変換でコマンドラインの文字が PHP のオプションと誤解釈され得る問題。Linux には該当しない。修正は 8.1.29 / 8.2.20 / 8.3.8 への更新。


パッケージ更新で設定が壊れない運用

php.ini 本体を直接編集しない。 自分の設定は、読み込み順で後に来るファイルに足す。

置き場所
Debian 系 /etc/php/8.3/<sapi>/conf.d/99-local.ini(SAPI ごと)。全 SAPI に効かせるなら、mods-available/ に ini を置いて phpenmod -v 8.3 -s ALL <モジュール名>。最後に読ませるには、ini に ; priority=99 を書く(接頭辞の数字は ; priority= に対応する。第 5 章)
RHEL 系 /etc/php.d/99-local.ini

👉 99- は、アルファベット順で最後に読まれる(第 4 章)。php.ini の値を上書きできる(php.ini only の expose_php でも効いた。下の確認)。
👉 changeable mode が制限するのは、その後ろの層(pool / .user.ini / ini_set())での変更。

conf.d で上書きできることを確かめる

Ubuntu のコンテナで、expose_php を例にした。

php -i | grep -E '^expose_php'
expose_php => On => On
printf 'expose_php = Off\n' > /etc/php/8.3/cli/conf.d/99-local.ini

出力は無い。

php -i | grep -E '^expose_php'
expose_php => Off => Off

👉 On が Off に変わった。php.ini には触れていない。

配布版の php.ini は production のコピー

diff /usr/lib/php/8.3/php.ini-production /etc/php/8.3/fpm/php.ini; echo exit=$?
exit=0

👉 exit=0 で、差分は無い。Debian 系の /etc/php/8.3/fpm/php.ini は php.ini-production と同じ内容だった(インストール直後のコンテナで)。だから、production を基準に、差分を conf.d に足す運用になる。

更新したとき

Debian 系:php.ini は ucf で管理されている。

grep -n ucf /var/lib/dpkg/info/php8.3-cli.postinst | head -5
19:	ucf /usr/lib/php/8.3/php.ini-production.cli $phpini
21:	ucf /usr/lib/php/8.3/php.ini-production $phpini
23:    ucfr php8.3-cli $phpini
ucfq /etc/php/8.3/cli/php.ini
Configuration file                            Package             Exists Changed
/etc/php/8.3/cli/php.ini                      php8.3-cli          Yes     No
  • postinst が ucf を呼んでいる
  • ucfq で、/etc/php/8.3/cli/php.ini が php8.3-cli の管理下にあると分かる

👉 php.ini は ucf の管理下にある(更新時の扱いは ucf に従う)。99-local.ini に分けて php.ini を配布版のままにしておけば、自分の設定が更新で失われることはない。

RHEL 系:設定ファイルは、rpm -qc で見える。

rpm -qc php-common php-fpm | sort
/etc/httpd/conf.d/php.conf
/etc/logrotate.d/php-fpm
/etc/nginx/conf.d/php-fpm.conf
/etc/nginx/default.d/php.conf
/etc/php-fpm.conf
/etc/php-fpm.d/www.conf
/etc/php.d/20-bz2.ini
/etc/php.d/20-calendar.ini
/etc/php.d/20-ctype.ini
/etc/php.d/20-curl.ini
/etc/php.d/20-exif.ini
/etc/php.d/20-fileinfo.ini
/etc/php.d/20-ftp.ini
/etc/php.d/20-gettext.ini
/etc/php.d/20-iconv.ini
/etc/php.d/20-phar.ini
/etc/php.d/20-tokenizer.ini
/etc/php.ini
/etc/systemd/system/httpd.service.d/php-fpm.conf
/etc/systemd/system/nginx.service.d/php-fpm.conf

/etc/php.ini と /etc/php.d/*.ini、/etc/php-fpm.d/www.conf が設定ファイルとして並ぶ。RPM の %config(noreplace) について、マニュアルはこう書いている。

A locally modified %config(noreplace) is preserved as-is on package updates

the new content from the package is saved with .rpmnew suffix for reference.

  • 更新時、ローカルで変更した %config(noreplace) のファイルは、そのまま残る。新しい内容は .rpmnew で置かれる
  • .rpmsave は、変更した %config ファイルを削除したときの退避。更新時の話ではない

👉 上の rpm -qc は設定ファイルの一覧で、noreplace の指定までは出ない。/etc/php.ini が %config(noreplace) かどうかは、この出力だけでは確認できていない。

変更後の反映と確認

構文を確認する(Debian 系)。

php-fpm8.3 -t

反映する。

systemctl reload php8.3-fpm

RHEL 系は php-fpm。

systemctl reload php-fpm

👉 php2shell の暫定策(session.upload_progress.enabled=0)は、公式の告知が「全ワーカーを再起動」と書いている(第 8 章)。

mod_php(Apache)は再起動する。

systemctl restart apache2

反映の確認は、php-fpm の設定を絞って見る。

php-fpm8.3 -i | grep -E '^expose_php'

👉 実際にリクエストを処理するプロセスの値は、Web 経由の phpinfo() で確かめる。確認が済んだら phpinfo() のページは消す。


チェックリスト

確認項目 コマンド
どの php.ini を読んでいるか php --ini(CLI)、php-fpm8.3 -i | grep -E 'Loaded Configuration|Scan this dir'(fpm)
conf.d の読み込み順 ls -la /etc/php/8.3/fpm/conf.d(RHEL 系は ls -la /etc/php.d)
pool の上書き grep -vE '^\s*(;|$)' /etc/php/8.3/fpm/pool.d/www.conf(RHEL 系は /etc/php-fpm.d/www.conf)
expose_php / display_errors php-fpm8.3 -i | grep -E '^(expose_php|display_errors)'
session cookie php-fpm8.3 -i | grep -E '^session\.(cookie_httponly|cookie_secure|use_strict_mode|cookie_samesite)'
cgi.fix_pathinfo / limit_extensions php-fpm8.3 -i | grep cgi.fix_pathinfo、grep limit_extensions /etc/php/8.3/fpm/pool.d/*.conf(行が無ければ既定の .php .phar)
入力上限 php-fpm8.3 -i | grep -E '^(memory_limit|post_max_size|upload_max_filesize|max_input_vars|session.upload_progress.enabled)'
パッケージが修正版か dpkg -l php8.3-cli、zcat /usr/share/doc/php8.3-cli/changelog.Debian.gz | head -25(RHEL 系は rpm -q php-cli、rpm -q --changelog php-cli | grep -E '^- ' | head -15)
php.ini を直接編集していないか diff /usr/lib/php/8.3/php.ini-production /etc/php/8.3/fpm/php.ini
構文確認と reload php-fpm8.3 -t → systemctl reload php8.3-fpm(php2shell の暫定策は全ワーカーの再起動。第 8 章)

👉 pool の値は Web 経由の phpinfo() で確かめる。


まとめ

php.ini は SAPI(cli / fpm / apache2)ごとに別。conf.d の断片が php.ini を上書きし、php-fpm では pool 設定がさらに上書きする。編集は php.ini 本体ではなく conf.d(または pool)に置くと、パッケージ更新で壊れない。脆弱性対応の本丸はパッケージ更新で、ini の堅牢化は攻撃面を減らす補助。

  • 設定の変更は 99-local.ini(または pool)に足す。php.ini 本体は触らない
  • ini_set() や .user.ini で変えられるかは、changeable mode で決まる
  • 暫定策では導入環境は安全にならない。完全な解決は更新のみ
  • 版番号だけで判断せず、配布元の changelog とセキュリティ情報で確かめる

関連記事:

参考

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?