php2shell 回避策(パッチなし):PHP 設定とサーバ側設定
パッチを当てられない環境で、どの設定がどの段階で攻撃を阻止するか。実測はこのラボ(mod_php・1 worker・PHP 8.3 / 8.4 / 8.5)で行ったものだけを「実測」と書き、それ以外は未検証と明記する。
1. 実測で攻撃を阻止した設定
| 設定 | 攻撃を阻止できる段階 | 根拠 |
|---|---|---|
session.upload_progress.enabled=Off |
Stage 1 の heap leak が成立しない | 実測(upload_progress_off) |
session.use_strict_mode=1 |
同じく heap leak が失敗 | 実測(strict_mode_on) |
memory_limit=-1 |
OOM が起きないので Stage 1 が失敗 | 実測 |
post_max_size=8M |
12,655,437 B の圧力 POST が受理されない | 実測 |
display_errors=Off |
heap leak が失敗(理由は未特定) | 実測(display_errors_off) |
memory_limit は「OOM しない値まで上げる」という意味。-1 だけ実測で、128M などの中間値は未検証。上げると別の DoS 耐性は下がるので、用途に合わせて決める。
2. 109619 path 2(pre-RINIT callback)
専用ラボで 11 通りを実測した。結論は 「成立を阻止できるのは 2 つだけ」。
| 設定 / 状態 | path 2 の結果(実測) | 判定 |
|---|---|---|
.htaccess を無効化(AllowOverride None)または .htaccess 削除 |
unserialize_callback_func が空になり nonce なし。session_start() を呼ぶエンドポイントでも同じ。 |
止まる |
| セッションレコードを作らせない(Web から書けない保存先) | 未知クラスが存在せず nonce なし(callback は arm 済みでも成立しない)。 | 止まる |
session.upload_progress.enabled=Off |
アプリが session_start() を呼ばないエンドポイントでは止まる。呼ぶ場合は nonce が出る。 |
条件付き(自動トリガーを無効化するだけ) |
session.use_strict_mode=1 |
攻撃者が用意した SID のレコードはそのまま読まれ、nonce が出る。 | 止まらない |
アプリが ini_set('unserialize_callback_func', 'system') する |
.htaccess を無効化しても session_start() で成立(nonce あり)。ini_set が upload-progress の読み込みより後なら成立しない。 |
アプリ次第 |
path 2 の成立を阻止するのはこの 2 つ。 ① callback を arm させない(
.htaccessを無効化する・unserialize_callback_funcを設定しない)② セッションレコードを作らせない。upload_progressOff とstrict_modeは単独では不十分(実測)。.htaccessを無効化してもアプリのini_setで再び有効になるため、コード側も確認する。
3. 効果がなかった / 当てにしない設定
| 設定 | 結果 | 理由 |
|---|---|---|
disable_functions=system,exec,… |
止まらない(実測) | チェーンは zif_system のアドレスを直接呼ぶ。名前での無効化は関係しない。 |
session.auto_start=On だけ |
止まらない / 復活もしない | 必要なのは upload_progress。auto_start は代替にならない(実測)。 |
expose_php=Off・バージョン非表示 |
攻撃は止まらない | blind でもバージョンは推定できる。検知の補助にしかならない。 |
MaxRequestWorkers を増やす |
止まらない | worker pinning は「空きを 1 個」にすれば成立する。台数を増やすと狙いやすくなるだけ。 |
4. ini の例
; サーバ全体の php.ini に置く。.user.ini / .htaccess は Web から
; 書ける場合があり、攻撃側に書き戻されるため、当てにしない。
session.upload_progress.enabled = Off
session.use_strict_mode = 1
session.save_path = /var/lib/php/sessions
session.cookie_httponly = 1
open_basedir = /var/www/html:/var/lib/php/sessions:/tmp
post_max_size = 8M
memory_limit = 128M ; -1 は実測。中間値は未検証
display_errors = Off ; leak 失敗の実測あり(理由不明)
読み込み順に注意。 conf.d はファイル名順で後のものが勝つ。反映後は必ず Web の SAPI で値を確認する。
5. PHP の外側(Apache / FPM / ネットワーク)
| 設定 | 効果 | 状態 |
|---|---|---|
LimitRequestBody 8388608 |
12 MiB 級の POST を入口で拒否。PoC は本文サイズを変えてくるので恒久策ではない。 | Apache の仕様 |
RequestReadTimeout body=10,MinRate=500(mod_reqtimeout) |
worker pinning(Content-Length: 6000000 を宣言して 2 KB だけ送り保持)を阻止する。成功率を下げる。 |
Apache の仕様 |
FPM request_terminate_timeout / pm.max_children
|
長時間の保持と多重化を制限。pinning の土台を弱める。 | 設計上の効果(未検証) |
| 外向き通信の制限(egress) | 逆シェル(/dev/tcp)や第二段の取得を阻止する。 |
設計上の効果(未検証) |
/tmp の noexec・auditd EXECVE
|
攻撃コードの実行と実行後の痕跡確保。 | 設計上の効果(未検証) |
6. 位置づけ
これは回避策であり、修正ではない。 メモリ破壊そのものは残る。設定は「攻撃の成立条件(OOM・upload progress・セッション初期化)を排除する」だけで、バージョン・SAPI・アプリの実装次第では対策にならない。未検証の組み合わせを「安全」と断定しない。最終的にはベンダーパッチを当てる。