PHPとSET NAMES問題のまとめ

PHP データベース 2010-07-09 17:55:54
大垣さんの3年も前のエントリが元になっている議論ですが・・・。
http://blog.ohgaki.net/set_namesa_mcb_asc

意外にもネットで正しい結論が書かれていない(≒誤った理解が増えている)ので、まとめてみました。
最近どっかの本の著者がPEARを勧めて、それを鵜呑みにした人がPDOをPEAR::DBに乗り換えようとしているらしい? いや、それもう推奨されてないんですけど・・・。
変なPHPerのせいでPHPが叩かれる現象は永遠に繰り返されるのだろうか。

問題の発生条件

以下のすべて を満たすこと。
  1. データベースエンジンが mysql >= 4.1.3 である。
  2. プリペアードステートメントを使っていない。
  3. フォームから入力された値を使うSQL文が存在する。
  4. スクリプト中で SET NAMES を使って文字コードを変えている。
  5. PHP >= 5.2.3 にもかかわらずスクリプト中で mysql_set_charset を使っていない。

正しい考察手順

  1. mysql をやめる。
    • 最初にこの問題が mysql の不具合であることを認識するべきである。
    • ゆえに mysql を使わないのが最も正しい解決方法となる。どうせこの問題が無くてもmysqlは正しく4バイトのUTF-8を取り扱えないのだ。SET NAMES binary も正当な解決策ではないのだ。
    • たとえば postgresql >= 7.4であれば、この問題は起こらない。
    • mysql < 4.1.3 にはプリペアードステートメントが無いのでそもそも論外。
  2. プリペアードステートメントを使う。
    • PDOを使ってないのが理由なら、PDOを使うように変更しましょう。
    • PDO::ATTR_EMULATE_PREPARES は false にする(PHP >= 5.2.1のPDO_MYSQLではデフォルトtrueらしい。ショックだ)。
    • プ リペアードステートメントをエミュレーションするPHPライブラリには注意する。特にPHP4互換を謳うモダンフレームワークには要注意。
    • likeは WHERE name LIKE '%' || :name || '%' とか書くと安全だよ!
    • 当 たり前だけど、SQL文字列に連結する変数が全くエスケープされていないなら、SET NAMES 問題以前にセキュリティに大問題があります。
  3. バリデータを真面目に書く。
    • そもそも日本語文字列の自由記入が出来る箇所は限られている。
    • 結局プリペアードステートメントをまともに使っているかどうかの問題に帰結する。
  4. SET NAMES を使わないで済むようにmysqlクライアントとサーバの設定をする。
    • スクリプトとmysqlクライアントとmysqlサーバのエンコードを統一すれば済む問題です。
    • たとえそれがShift_JISであっても、 mysql_real_escape_string()を使えば済む問題です。
    • Shift_JISの文字化けを回避するためだけに、たとえばモバイル用サイトで「PHPとDBはEUCだけど表示はSJIS」のような構築をするのは本末転倒です。過去の亡霊にとらわれすぎ。
    • でも、UnicodeやEUC-JPが選択可能なシステムでわざわざShift_JISを選んでる人のことは「ブヲ」(侮辱の侮に半角のヲ)と呼んであげま しょう。
    • UnicodeとEUC-JPの選択で後者を選ぶ人は、サロゲートペアか文字列長か「波ダッシュ全角チルダ問題」のい ずれかで被害を受けて精神的に病んでしまった方です。いたわりの心をもって接しましょう。マジレスすると、PC専用サイトでEUC-JPに統一するのは個 人的にはアリだと思う。
  5. 以上すべての対策を満たせないときに、はじめて mysql_set_charset()を考える。
    • つまり「mysqlを使わないといけなくて」「PDOやプリペアードステートメントが使えなくて」「mysqlの設定もいじれない」状況。
    • 安物レンタルサーバで無料サンプルやら前任者の糞スクリプトを動かすような鬱になる仕事がこれに該当する。
    • mysql_query("SET NAMES 'utf8'") を mysql_set_charset('utf8') に変えるだけの簡単なお仕事です。

誤った考察

  • PHPをやめればいいと考えている。
    • ジョークや煽りならともかく真に受けてる人がいそうで恐ろしい。
    • あらゆる面でPHPを上回る代替手段を追い求めるのはロマンだから否定しない。
  • 古いmysqlを使っている。
    • mysql < 5.1ではプリペアードステートメントを使うとクエリキャッシュが効かなくなるらしい。
    • で、それを理由にプリペアードステートメント自体をやめてしまうらしい。バージョンあげろよ・・・。
  • そもそもPDOを使っていない事に疑問を抱いていない。
    • mysql関数のプリペアードステートメントは貧弱です。?しか使えません。それでいいの? いいならいいけど。
    • PHP4を使っていいのは小学生までだよねー!
    • そもそもPHP < 5.2.3には対策手段が無い。RHEL5とかでPHP 5.1.6を使ってる人はその時点で解決不能なのです。そんな環境でPHPを実行している事自体を疑問視すべき。
  • PDOは mysql_set_charset() が無いからセキュリティに不備があると考えている。
    • 「SET NAMES問題の直接的な解決策は mysql_set_charset() が最適である」という主張を見て、「mysql_set_charset()を使わない環境はすべてSET NAMES問題が起こる」と短絡しちゃった人が、こういう誤った結論に陥る。
    • どうしてもいくつかプリペアードステートメントを使えないSQL文字列を実行せざるを得ないのであれば、そのSQLだけ mysql_set_charset() を使って mysql_query() すれば良いのでは?
    • そういうSQLをなくす事のほうが重要だと思いませんか?
  • PDOよりPEAR::DB や PEAR::MDB2 のほうが良いソリューションであると考えている。
    • PEAR::DBは mysql_set_charset() に対応していません。最新版 1.7.13 (stable) was released on 2007-09-21を調べました。
    • PEAR::MDB2の最新版 2.4.1 (stable) was released on 2007-05-03 は mysql_set_charset() が実装されるPHP 5.2.3 (01-Jun-2007)よりも前にリリースされました。
    • なのでそもそも問題解決手段としては全く不適切。

結論

セキュリティ対策のために真面目にやらなければならないこと。
  • 最新安定版のPHP本体とライブラリを使うこと。
  • まともなデータベースエンジンを使うこと。もしくはデータベースエンジンを使わないこと。
  • 自分でインストールしたソフトウェアを使うか、信頼出来る人に依頼すること。
  • 誤った判断や選択をしないこと。自分の誤りを訂正できるようにしておくこと。
  • ケチらんでプロに頼んだほうがトータルで考えてお得だよ!
「セキュリティ過敏症」に掛かっている人ほど、セキュリティに関するリテラシーが低いと思う。
個人情報を実際に入力するお客様が不満を言っているならともかく、技術力の低いプログラマーが「なんちゃってセキュリティ対策」をやっちゃっても、決して誰も幸せにはならないのだ。