今更ながらmagic_quotes_gpcの欠点

今更ながらですが、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にしたシステムで、生じうる副作用の例を挙げます(ほんの一例です)。

  1. Formで値を持ちまわる際に、「\」が増殖していく。
  2. メールアドレスなどの形式チェックができない。

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つほど問題があります。

  1. 文字エンコーディング
    PHPマニュアルには記載されていませんが)latin1を前提としてエスケープ処理を行ないます。それ以外のエンコーディングには対応していません。
  2. 対応するDBMS
    (これもPHPマニュアルには明記されていませんが)MySQLエスケープ方式をベースに作られているようです*1。しかし実際には、MySQLの行なうエスケープとは、細部で異なっています。

特に前者の問題は深刻で、Shift_JISEUC-JP、UTF-8といったエンコーディングでは、安全にエスケープされる保証はありません。実際にShift_JIS+addslashesでは、SQLインジェクションされてしまうことが知られています(Shift_JIS環境でaddslashesを使うことは稀だと思いますが)。

DBMSが提供するエスケープ関数や、バインド関数、もしくはそれらを使いやすいようにラップするPear::DBなどを使うようにするのが望ましいです。

まとめ

  • magic_quotes_gpcはOffにする
  • SQLを構成する際にエスケープする
  • エスケープ時にはRDBMS正規の関数を使用する

もし、既に稼動中のAPがmagic_quotes_gpc=Onを前提としているならば、前述したエスケープ対象外の変数に着目して、エスケープ漏れの有無を調べてみることをお勧めします。

ちなみにPHP6ではmagic_quotes_gpcは無くなるようです*2

*1:実際には、magic_quotes_sybaseという設定により、動作は変わります。

*2:それでも、magic_quotes_gpcをエミュレートするコードが出回りそうですが。