今更ながらですが、PHPのmagic_quotes_gpcをOnにすべきでない理由を整理してみます。
世の中には、magic_quotes_gpcはOffにすべき、と書いた文章は多数あるのですが、その理由を(私が見る限りで)十分に説明しているものは無いからです。
以下では、
1. 対象外の変数の存在
2. 弊害(副作用)
3. エスケープの不完全さ
という三つの観点から記述します。
対象外の変数の存在
magic_quotes_gpcの、「gpc」はGET/POST/COOKIEを表しています。つまり、この3種類のリソースからの入力はエスケープされますが、それ以外は原則エスケープされません。
エスケープされているもの、されていないものを、以下にまとめてみます。
エスケープ済み
・$REQUEST
$_GET・$_POST・$_COOKIE
場合によってエスケープ済み
・$_SESSION(セッション)
GPC由来の変数の場合
・$_FILE(アップロードファイル)
ファイル名のみ
・DBから得たデータ
magic_quotes_runtime=Onの場合
・データファイル
GPC由来の変数の場合
・AP内で生成・加工されるデータ
ケースバイケース
通常エスケープされていない
・$_ENV(環境変数)
・$_SERVER(ヘッダ等)
・設定ファイル等で定義された変数
・他システムからのデータ
これを見れば判るように、magic_quotes_gpcをOnにした場合、エスケープされている変数とされていない変数がAP内に混在します。これは、漏れや二重エスケープといった誤りを招きやすい危うい状態といえます。
特に漏れの危険性が高いのは、$_SERVERのUser-Agentや、DBから得たデータ、AP内で入力値内の全角文字を半角化した値などです。
弊害(副作用)
magic_quotes_gpcをOnにしたシステムで、生じうる副作用の例を挙げます(ほんの一例です)。
- Formで値を持ちまわる際に、「\」が増殖していく。
- メールアドレスなどの形式チェックができない。
2についてちょっと補足すると、メールアドレスのlocalpart(@より前)では、「'」はOK、「\」はNGです。しかし、それに従った正規表現で入力値のチェックを行なうと、「'」を含むアドレスははじかれてしまいます。あらかじめ「'」が「\'」に変換されているからです。
つまり、magic_quotes_gpcをOnにした場合、「'」や「\」などの文字を使う可能性があるデータ ―― メールアドレス、パスワード、URL、住所、掲示板の記事 etc. ―― を正確に扱うことが難しくなります。
これらは、HTML出力前や形式チェック前に、stripslashesを掛ければ避けられる問題ではあります。しかし、アドホックなコードを継ぎ足していけば、エスケープ漏れや保守性の低下につながります。
根本的な問題は、magic_quotes_gpcがエスケープを行なうタイミングです。magic_quotes_gpcは、APの前処理の段階でエスケープを行ないます。しかし、形式チェックやHTML出力時には、変数がSQLエスケープされている必要はありません。むしろ、エスケープは邪魔でしかありません。
つまり、エスケープされていない「プレイン」な状態で変数を保持する方が、APにとって都合が良い訳です。そして、SQLエスケープはSQL文を組立てる際に行ないます。それは、SQLエスケープのそもそもの目的を考えても自然なことです。
SQLの組立て時に統一することで、前述したようなエスケープ済みとそうでない変数の混在や、漏れや二重エスケープといった事態を招くことも少なくなります。
エスケープの不完全さ
magic_quotes_gpcは、addslashesというPHPの関数を内部的に呼び出して、エスケープを行ないます。しかし、この関数には2つほど問題があります。
- 文字エンコーディング
(PHPマニュアルには記載されていませんが)latin1を前提としてエスケープ処理を行ないます。それ以外のエンコーディングには対応していません。 - 対応するDBMS
(これもPHPマニュアルには明記されていませんが)MySQLのエスケープ方式をベースに作られているようです*1。しかし実際には、MySQLの行なうエスケープとは、細部で異なっています。
特に前者の問題は深刻で、Shift_JIS、EUC-JP、UTF-8といったエンコーディングでは、安全にエスケープされる保証はありません。実際にShift_JIS+addslashesでは、SQLインジェクションされてしまうことが知られています(Shift_JIS環境でaddslashesを使うことは稀だと思いますが)。
DBMSが提供するエスケープ関数や、バインド関数、もしくはそれらを使いやすいようにラップするPear::DBなどを使うようにするのが望ましいです。