注: この投稿を進めるには、PHP でのプログラミングの基本的な知識があることを前提としています。
この記事では、お気に入りの CMS やフレームワークの先頭で目にしたことがあるであろう PHP コード スニペットについて説明します。おそらく、セキュリティ上の理由から、開発するすべての PHP ファイルの先頭に常にこれを含める必要があるということを読んだことがあると思いますが、その理由については明確な説明はありません。このコードを参照しています:
<?php if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly }
このタイプのコードは、WordPress ファイルでは非常に一般的ですが、ほぼすべてのフレームワークと CMS で使用されます。たとえば、Joomla CMS の場合、唯一の変更点は、ABSPATH の代わりに JEXEC を使用することです。それ以外は、ロジックは同じままです。この CMS は、Mambo と呼ばれる以前のシステムから進化したもので、これも同様のコードを使用していましたが、定数として _VALID_MOS を使用していました。さらに遡ると、この種のコードを使用した最初の CMS は PHP-Nuke であることがわかります (最初の PHP ベースの CMS であると考える人もいます)。
PHP-Nuke (および今日のほとんどの CMS およびフレームワーク) の実行フローは、ユーザーまたは訪問者が Web サイト上で行ったアクションに応答するために複数のファイルを順次ロードすることで構成されていました。たとえば、example.net でホストされている当時の Web サイトにこの CMS がインストールされていると想像してください。ホームページがロードされるたびに、システムは一連のファイルを順番に実行します (これは単なる例であり、実際のシーケンスではありません)。 load_modules.php =>モジュール.php。このチェーンでは、index.php が最初にロードされ、次に、load_modules.php がロードされ、さらに modules.php がロードされました。
この実行チェーンは、必ずしも最初のファイル (index.php) から始まるわけではありません。実際、誰でも URL (http://example.net/load_modules.php や http://example.net/modules.php) を通じて他の PHP ファイルに直接アクセスすることで、フローの一部をバイパスできます。これは、後で説明するように、多くの場合に危険を伴う可能性があります。
この問題はどのように解決されましたか? セキュリティ対策が導入され、各ファイルの先頭に次のようなコードが追加されました。
<?php if (!eregi("modules.php", $HTTP_SERVER_VARS['PHP_SELF'])) { die ("You can't access this file directly..."); }
基本的に、このコードは modules.php という名前のファイルの先頭に配置され、modules.php が URL 経由で直接アクセスされているかどうかをチェックしました。存在する場合、実行は停止され、「このファイルに直接アクセスすることはできません…」というメッセージが表示されます。 $HTTP_SERVER_VARS['PHP_SELF'] に modules.php が含まれていない場合、通常の実行フローがアクティブであり、スクリプトを続行します。
ただし、このコードにはいくつかの制限がありました。まず、コードは挿入されるファイルごとに異なるため、複雑さが増していました。さらに、特定の状況では、PHP が $HTTP_SERVER_VARS['PHP_SELF'] に値を割り当てなかったため、有効性が制限されました。
それで、開発者は何をしたのでしょうか?彼らは、これらのコード スニペットをすべて、よりシンプルで効率的なバージョンに置き換えました。
<?php if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly }
PHP コミュニティで非常に人気になっていたこの新しいコードでは、定数の存在がチェックされました。この定数は、実行フローの最初のファイル (index.php、home.php、または同様のファイル) で定義され、値が割り当てられました。したがって、この定数がシーケンス内の他のファイルに存在しなかった場合は、誰かがindex.phpをバイパスして別のファイルに直接アクセスしようとしていたことを意味します。
この時点で、実行チェーンを切断することは非常に深刻な作業に違いないと考えているかもしれません。しかし、実際には、通常、それは大きな脅威にはなりません
。PHP エラーによってファイルへのパスが公開されると、リスクが発生する可能性があります。サーバーがエラーを抑制するように構成されている場合、これは気にする必要はありません。たとえエラーが隠蔽されなかったとしても、公開される情報は最小限であり、潜在的な攻撃者にほんのわずかな手がかりしか提供されません。
誰かが HTML フラグメント (ビュー) を含むファイルにアクセスし、コンテンツの一部が公開される可能性もあります。ほとんどの場合、これも心配する必要はありません。
最後に、開発者は、誤って、または経験不足により、外部依存関係のない危険なコードを実行フローの途中に配置する可能性があります。フレームワークまたは CMS コードは通常、実行時に他のクラス、関数、または外部変数に依存するため、これは非常にまれです。したがって、URL を通じてスクリプトを直接実行しようとすると、これらの依存関係が見つからないためエラーが発生し、実行は続行されません。
では、懸念する理由がほとんどないのに、なぜ定数コードを追加するのでしょうか?答えは次のとおりです。「この方法は、register globals 攻撃による偶発的な変数の挿入も防ぎ、実際にはアプリケーション内にないのに PHP ファイルがアプリケーション内にあると想定するのを防ぎます。」
PHP の初期の頃から、URL (GET) またはフォーム (POST) 経由で送信されたすべての変数は自動的にグローバル変数に変換されました。たとえば、download.php?filepath=/etc/passwd ファイルにアクセスした場合、download.php ファイル内 (および実行フロー内のそれに依存するファイル内) で echo $filepath; を使用できます。 /etc/passwd が出力されます。
download.php 内では、変数 $filepath が実行チェーン内の以前のファイルによって作成されたのか、それとも URL または POST 経由で改ざんされたのかを知る方法がありませんでした。これにより、重大なセキュリティ上の脆弱性が生じました。 download.php ファイルに次のコードが含まれていると仮定して、例を見てみましょう:
<?php if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly }
開発者は、コードにフロント コントローラー パターンを使用することを意図していた可能性があります。これは、すべての Web リクエストが単一のエントリ ファイル (index.php、home.php など) を通過することを意味します。このファイルはセッションの初期化を処理し、共通変数をロードし、最後にリクエストを特定のスクリプト (この場合は download.php) にリダイレクトしてファイルのダウンロードを実行します。
ただし、前述のように、攻撃者は download.php?filepath=/etc/passwd を呼び出すだけで、意図した実行シーケンスをバイパスする可能性があります。 PHP は、値 /etc/passwd を持つグローバル変数 $filepath を自動的に作成し、攻撃者がそのファイルをシステムからダウンロードできるようにします。深刻な問題です。
最小限の労力でさらに危険な攻撃が実行される可能性があるため、これは氷山の一角にすぎません。たとえば、次のようなコードでは、プログラマが未完成のスクリプトとして残している可能性があります。
<?php if (!eregi("modules.php", $HTTP_SERVER_VARS['PHP_SELF'])) { die ("You can't access this file directly..."); }
攻撃者は、リモート ファイル インクルージョン (RFI) 攻撃を使用して任意のコードを実行する可能性があります。攻撃者が、実行したいコードを含むファイル My.class.php を自分のサイト https://mysite.net に作成した場合、自分のドメイン「useful_code.php?base_path=https」を渡すことで、脆弱なスクリプトを呼び出すことができます。 //mysite.net にアクセスすると、攻撃は完了します。
別の例: 次のコードを含む、remove_file.inc.php という名前のスクリプト内:
<?php if (!defined('MODULE_FILE')) { die ("You can't access this file directly..."); }
攻撃者は、remove_file.inc.php?filename=/etc/hosts のような URL を使用してこのファイルを直接呼び出し、システムから /etc/hosts ファイルを削除しようとする可能性があります (システムが許可している場合、または他のファイルも削除しようとします)。削除する権限があります)。
内部的にグローバル変数も使用する WordPress のような CMS では、この種の攻撃は壊滅的でした。しかし、継続的な技術のおかげで、これらおよび他の PHP スクリプトは保護されました。最後の例を見てみましょう:
<?php if(file_exists($filepath)) { header('Content-Description: File Transfer'); header('Content-Type: application/octet-stream'); header('Content-Disposition: attachment; filename="'.basename($filepath).'"'); header('Expires: 0'); header('Cache-Control: must-revalidate'); header('Pragma: public'); header('Content-Length: ' . filesize($filepath)); flush(); // Flush system output buffer readfile($filepath); exit; }
誰かがremove_file.inc.php?filename=/etc/hostsにアクセスしようとすると、定数によってアクセスがブロックされます。論理的に、これが変数である場合、攻撃者がそれを挿入する可能性があるため、これは定数であることが重要です。
今では、これほど危険な機能であるのに、なぜ PHP がこの機能を保持していたのか不思議に思っているかもしれません。また、他のスクリプト言語 (JSP、Ruby など) を知っている場合は、それらに似たものがないことがわかるでしょう (そのため、定数テクニックも使用されません)。 PHP は当初 C ベースのテンプレート システムとして作成され、この動作により開発が容易になったことを思い出してください。幸いなことに、PHP のメンテナーは、それが引き起こす問題を考慮して、register_globals (デフォルトで有効) と呼ばれる php.ini ディレクティブを導入して、この機能を無効にできるようにしました。
しかし、問題が解決しないため、デフォルトで無効化されました。それでも、多くのホストは、クライアントのプロジェクトが動作しなくなるのではないかという懸念から、この変数を有効にし続けていました。当時のコードの多くは、GET/POST/... 値にアクセスするために推奨されている HTTP_*_VARS 変数を使用せず、むしろ使用していたためです。グローバル変数。
最終的に、状況が改善しないことを確認した彼らは、これらすべての問題を回避するために、PHP 5.4 でこの機能を削除するという思い切った決断を下しました。したがって、今日では、これまで見てきたようなスクリプト (定数を使用しない) は、特定のケースでの無害な警告/通知を除いて、通常 もはやリスクではありません。
今日でも、一定のテクニックが一般的です。しかし、残念な現実とこの記事の理由は、その使用の背後にある本当の理由を理解している開発者がほとんどいないということです。
過去の他のベスト プラクティス (参照に関する問題を回避するためにパラメーターを関数内のローカル変数にコピーする、プライベート変数でアンダースコアを使用して区別するなど) と同様に、多くの人は、単に誰かが一度そう言ったからといって、それを適用し続けています。それが今日でも価値をもたらすかどうかは疑問の余地なく、良い実践です。真実は、大多数の場合、このテクニックはもはや必要ではないということです。
この慣行が適切でなくなった理由は次のとおりです。
*register グローバルの削除: PHP 5.4 以降、PHP での GET および POST 変数のグローバルとしての自動登録は削除されました。 *register globals を使用しない場合、個々のスクリプトを直接実行しても無害であり、この手法の主な理由が取り除かれます。
より良いコード設計: PHP 5.4 より前のバージョンであっても、最新のコードは一般にクラスや関数の構造が改善されており、外部変数を介したアクセスや操作がより困難になっています。従来グローバル変数を使用していた WordPress でさえ、これらのリスクを最小限に抑えています。
*front-controllers の使用: 現在、ほとんどの Web アプリケーションは、適切に設計された *front-controllers を採用して、クラスと関数のコードが実行される場合にのみ実行されるようにしています。チェーンはメインエントリーポイントから始まります。したがって、誰かがファイルを単独でロードしようとした場合、フローが正しいエントリ ポイントから開始しない限り、ロジックはトリガーされません。
クラスの自動ロード: 最新の開発ではクラスの自動ロードが広く使用されているため、include または require の使用は大幅に減少しました。これにより、経験豊富な開発者の間で、これらの方法 (リモート ファイル インクルージョン や ローカル ファイル インクルージョン など) に関連するリスクが軽減されます。
パブリック コードとプライベート コードの分離: 多くの最新の CMS やフレームワークでは、パブリック コード (アセット など) がプライベート コード (ロジック) から分離されています。この措置は、サーバー上で PHP が失敗した場合に、PHP コード (定数テクニックを使用しているかどうかに関係なく) が公開されないようにするため、特に価値があります。この分離は、グローバルの登録を軽減するために特別に実装されたわけではありませんが、他のセキュリティ問題を防ぐのに役立ちます。
フレンドリー URL の広範な使用: 現在、フレンドリー URL を使用するようにサーバーを構成することは一般的であり、アプリケーション ロジックの単一のエントリ ポイントを確保しています。これにより、PHP ファイルを単独でロードすることはほぼ不可能になります。
本番環境でのエラー抑制: ほとんどの最新の CMS とフレームワークはデフォルトでエラー出力を無効にしているため、攻撃者はアプリケーションの内部動作に関する手がかりを見つけることができず、他の種類の攻撃を助長する可能性があります。
このテクニックは大多数の場合にはもう必要ありませんが、それは決して役に立たないという意味ではありません。プロの開発者として、それぞれの状況を分析し、一定のテクニックが作業している特定の状況に関連しているかどうかを判断することが不可欠です。この種の批判的思考は、いわゆるベスト プラクティスであっても、常に適用されるべきです。
一定のテクニックをいつ適用すべきかまだわからない場合は、次の推奨事項が参考になるかもしれません。
学び続けてください...
以上がフレームワークと CMS の奇妙な PHP コードの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。