PHPのコードインジェクションを防ぐ5つの方法
DeveloperSteve Coochin
2021年6月29日
0 分で読めます前回の投稿PHP Composerのセキュリティ脆弱性をテストするに続き、今回はPHPのコードインジェクションを防ぎ、より安全なアプリケーションを開発するために役立つ内容をご紹介します。開発者として、私たちはエンドユーザーの暮らしを便利にするアプリを開発しています。エンターテインメント、職場、ソーシャルネットワーク向けのアプリケーションなど、どのようなものでも、コードにセキュリティを組み込むことで、ユーザーを守ることが最終的な目標です。セキュアコーディングの実践は、エンドユーザーの安全を守るだけでなく、手戻りを防いで開発時間とコストを削減することにもつながります。
コードインジェクションとは?
コードインジェクションとは、脆弱な攻撃経路を通じて悪意のあるコードを送り込む攻撃です。目的は、標的となるアプリケーションの完全性を損なうことです。攻撃者は、アプリケーションの実行環境側、またはエンドユーザーのブラウザー内で実行されるPHPコードやJavaScriptを送信できます。PHPにはJavaScript専用のエスケープ機能がないため、問題につながる可能性がある点にも注意が必要です。この脆弱性はPHPの実行環境で処理され、サーバー側または何も知らないユーザーのブラウザー内で実行されます。
PHPでコードインジェクションを防ぐ方法
コード開発のセキュリティは、セキュアコーディング規約の導入から始まります。外部に公開する入力と、その処理方法に注意してください。すべての入力をサニタイズし、アプリケーション内での保存方法にも気を配りましょう。
基本的なルールとして、どのようなアプリケーションでも動的なコード実行は決して許可すべきではありません。たとえば、可能な限り、OSレベルで実行されるshell_exec()やexec()などのPHPのコア機能は使用しないでください。
Snyk Open Sourceのようなオープンソースのセキュリティスキャンツールを常に利用することを強くおすすめします。パッケージマネージャーをスキャンして、ライブラリや依存関係に潜在する脆弱性を特定できます。私が毎日行っているのは、composer版Snyk Vulnerability Databaseを確認し、どのような手法が使われているかを調べることです。詳細なレポートページからコードレベルのリポジトリにたどり着き、脆弱性がどのように特定されたかを確認できます。
PHPアプリケーション開発でコードインジェクションを防ぐ5つの方法
exec()、shell_exec()、system()、passthru()を使用しないサニタイズに
strip_tags()を使用しないコードのシリアライズ
PHPセキュリティリンターを使う
SASTツールでコードインジェクションの問題を特定する
1. exec()、shell_exec()、system()、passthru()を使用しない
「ここに竜あり」という言葉があります。基本的には、可能な限り、PHPから実行環境を直接呼び出せる機能は使わないでください。攻撃経路の観点から見ると、こうした機能は、システムにさまざまな問題を直接引き起こす可能性があります。
従来、exec()、shell_exec()、system(),、passthru()などの関数は、ファイルの圧縮・展開、cronジョブの作成、OS上のファイルやフォルダーの操作などに使われてきました。これらはコードのサニタイズ機能を組み込みで備えていないため、特に検証やサニタイズをしていないユーザー入力を直接渡すと問題が発生します。
PHPには、入力をエスケープする機能を組み込んだ演算子escapeshellcmd()とescapeshellarg()があります。関数呼び出しの一環として、入力に一定のエスケープとサニタイズを施します。同じ機能をより安全に実現する方法もいくつかあります。
PHP 7.4以降では、PHPのコンパイル時に--with-zipフラグを有効にすると、ZipArchiveクラスでアーカイブを処理できます。ただし、パストラバーサル攻撃を可能にするおそれがあるため、引き続き注意が必要です。
ファイルシステムとのやり取りは、入力を通じて直接操作できる方法を最小限に抑え、プログラムから行いましょう。PHPには、OSを直接呼び出さずに使えるファイル関数があります。たとえば、指定したディレクトリ内のファイル一覧はscandir()関数で取得できます。返された配列を使って、コード内でファイルを開いたり参照したりできます。公開された入力によって作成されたファイルにアクセスしないよう注意してください。また、chmod()やchown()など、ファイルのアクセス権限に関する関数を使う際にも気をつけましょう。さらに、偽装できるコンテンツヘッダーを信用せず、ファイルの種類を検証することも重要です。
cronなどのComposerライブラリを使って、cronジョブを動的に作成しましょう。これは実際にはcron経由で実行され、cronの実行中に独自のcronを実行します。実行する内容には引き続き注意が必要ですが、コアOSへのアクセスを公開せず、よりプログラム的にプロセスを作成できます。
2. サニタイズにstrip_tags()を使用しない
ユーザー入力のサニタイズと処理には、常に十分な注意が必要です。ここでの目標は、有効なユーザー入力を受け入れ、適切に保存・処理することで、アプリケーションが脆弱にならないようにすることです。入力は、悪意ある攻撃者がアプリケーションに働きかけるための攻撃経路になり得ます。入力データを常に適切に処理するため、時間をかけて慎重に対応しましょう。
strip_tags()関数は、主に入力からHTMLとPHPのみを削除するためのものです。そのため、JavaScriptやSQLなど、悪意のある可能性がある入力は、この関数によって有効なものとみなされます。htmlentities()も入力のサニタイズに使われる選択肢で、UTF文字セットを指定できますが、入力を完全にサニタイズするものではない点に注意してください。
preg_replace('/[^a-zA-Z0-9]+/', '', $text)のような正規表現関数を使って、データを取り除く方法もあります。UTF-8文字セットの場合、入力から文字と数字のみを返します。ユーザー入力をクリーンアップする際は、mb_strtolower()などの関数にも注意が必要です。これまでに、CVE-2020-7065のように、UTF-32LEエンコーディングが原因で範囲外への書き込みが発生する、マルチバイト文字列関数(mb_)の脆弱性が見つかった例があります。スタックバッファーの上書きによるクラッシュを引き起こし、コード実行につながる可能性があります。
代わりにfilter_var()などを使い、定義したフィルターオプションに基づいて入力を検証・サニタイズしましょう。たとえば、ユーザー入力とともにFILTER_SANITIZE_STRING、FILTER_FLAG_STRIP_HIGHをfilter_var()関数に渡すと、すべてのHTMLタグとASCII値が> 127の文字を削除できます。
入力のサニタイズによく使われるComposerライブラリもいくつかあります。たとえばHTML purifierは、HTML仕様に準拠したサニタイズ機能を提供し、許可する入力データの種類をアプリケーションに合わせてカスタマイズできる、比較的優れた許可リスト機能を備えています。
3. PHPでunserialize()を避ける
このテーマだけでブログ記事が1本書けるほどで、長年にわたり、PHPの開発者の間でも盛んに議論されてきました。PHPのマニュアルでは、unserialize()関数を使う危険性が明確に示されています。特に「信頼できないユーザー入力を渡してはならない」とされており、そうした入力によって「コードが読み込まれ、実行される」可能性があります。
Unserialize()は、クラスを文字列に変換し、保存して他の関数に渡したり、後で使うためにキャッシュしたりする目的で使われます。単体では比較的無害に思えますが、unserialize()のデータをメモリーに保存する際の基盤となるCコードの動作を理解すると、問題の大半がどこで発生し得るかがわかります。
json_decode()やjson_encode()を使ったJSONなど、標準的なデータ形式の利用を強くおすすめします。シリアライズされたデータをユーザーとの間で安全に送受信するための、サニタイズされた安全な方法になります。
4. PHPセキュリティリンターを使う
開発ワークフローに適切なツールを取り入れることで、最初から安全で機能性の高いコードを作成できます。リンターは、PHPアプリケーションの開発中のエラーや潜在的な問題を減らし、ソースコードの脆弱性を低減するのに役立ちます。
PHP.iniの設定では、エラー表示をデフォルトで無効にしておくことも強くおすすめします。error_reporting = E_ALL、~E_NOTICE、~E_WARNINGを無効にすると、アプリケーションの環境や設定情報の特定に悪用される可能性があるエラー出力をなくせます。
PHP言語自体にリンターが組み込まれており、検証時に非常に詳細なエラーメッセージを表示します。CLI(またはテストフレームワークの一部)でPHP -lに続けてチェック対象のファイルを指定すると実行できます。リンターとして使う場合の欠点は、一度に1ファイルしかチェックできないことです。ただし、ループの一部として実行すれば、1回の実行で複数のファイルを処理できます。
複数のファイルをすばやくチェックする方法として、PHPlintは堅牢で広く知られている選択肢です。PHPLintはCLIから利用できるほか、Composerで導入するライブラリとしても使えます。Dockerイメージに組み込むのも比較的簡単です。PHPLintはPHP 7とPHP 8のチェックに対応し、出力はかなり詳細ですが、複数のリンティング処理で問題を特定できます。
もう一つよく使われるリンターはPHP-Parrallel-Lintです。PHP 5.3からPHP 8.0までに対応し、echo "hello world"と入力するよりも速く、複数のリンタープロセスを実行できます。PHP-Parrallel-Lintは前述の選択肢より詳細な出力を提供しますが、現時点ではCLIオプションや、すぐに使えるDockerソリューションには対応していません。
5. 静的アプリケーションセキュリティテストツールでコードインジェクションの問題を特定する
PHP開発で広く使われているIDEの一つにPHPStormがあり、便利な静的コード解析機能が組み込まれています。前述のリンターに加えて、PHPStormにはXdebugとのインテグレーション機能があり、コードのリンティング、プロファイリング、そして名前のとおりデバッグを行ってエラーを減らすのに役立ちます。セキュアコーディングのワークフローでは、特定のコーディングルールやPSR標準を適用できるため、PCI準拠のアプリケーションを開発する際にも便利です。
コードのセキュリティを高めるために、SnykにはPHPStorm用プラグインがあり、コードやライブラリに含まれるコード品質の問題や脆弱性をスキャンできます。インストールは簡単で、すぐに使い始められます。アプリ内のプラグインセクションから利用できます。

開発者、特にPHP開発者の間で採用が広がっているもう一つの開発ツールがVSCodeです。PHPStormと同様に、リンターが組み込まれており、PSR標準を適用できます。
VSCodeでSnykを使って脆弱性をスキャンするのは簡単です。VSCodeのターミナルを新しく開き、Snyk CLIでsnyk testを実行するか、snyk monitorを使ってコードベースの継続的なアラートを受け取ります。Snyk CLIとPHPを使い始める方法について詳しくは、以前のブログ記事SnykでPHP Composerのセキュリティ脆弱性をテストするをご覧ください。
更新:Snyk CodeでPHPをサポートする機能も最近リリースしました。詳しく知り、実際に試してみるには、ブログ記事PHPの脆弱性スキャンに対応するSnyk Codeがベータ版にをご覧ください。
まとめ
開発者として、私たちはエンドユーザーの日々の暮らしを支えるアプリケーションを開発しています。アプリケーションのセキュリティを確保することは、ユーザーのデータを守るだけでなく、ユーザーからの信頼にもつながります。
適切なツールとワークフローを最初から整えておけば、開発の初期段階でコードインジェクションの脆弱性を簡単に特定できます。
Capture the Flagを始めよう
オンデマンドのバーチャル入門ワークショップを見て、Capture the Flagの課題の解き方を学びましょう。



