| « 2.5' HDDで320GB | 意図が伝わってないのかな? » |
SET NAMESは禁止
MySQLには文字エンコーディングを変更する「SET NAMES」SQL文が用意されています。(PostgreSQLも同様のSQL文、SET CLIENT_ENCODINGがあります)この機能はSQLコンソールからは使ってよい機能ですが、アプリケーションからは使ってはならない機能です。SQLインジェクションに脆弱になる場合があります。
Ruby on Railsの本を読んでいて、ActiveRecordを説明している部分にMySQLの文字エンコーディングを変更する場合の例としてSET NAMESが利用されていました。アプリケーションからはSET NAMESは使ってはならない事を周知させるのは結構時間が必要かなと思いました。
PHPも5.2の途中からMySQLモジュールにlibmysqlの文字エンコーディング設定APIのラッパー関数が追加されていたりするので、たまたま最近読んだRoRの本だけでなく、多くの開発向け情報ソースにSET NAMESを利用した例が載っていると思います。
ストアドプロシージャだけ使っていれば安全ですが、アプリケーションからDBMSの文字エンコーディングを設定する場合、SQL文ではなく必ず文字エンコーディング設定APIを利用するよう紹介しなければならないです。MySQL4はストアドプロシージャが使えないので、フレームワークなどではエミュレートしています。ストアドプロシージャだけ使って防御している「つもり」で防御になっていない場合もあります。これもフレームワークを使っていてもアプリケーションが脆弱になる良い例ですね。
脆弱性の説明は面倒ですが注意事項は簡単です。「DBMSをアプリケーションから利用する場合、文字エンコーディング設定は必ずAPIを利用する」つまり「SET NAMES(SET CLIENT_ENCODING等も)は禁止」です。
16 comments
# と書いておかないと困る方もいるかもしれないので念のため。
特にアプリ側でUTF-8を使っていて、DB側では他のコードを使っていると、バインド機能使っていても攻撃が成立することが場合によってはあるみたいです。
このエントリの脆弱性の理由って、このあたり
http://blog.ohgaki.net/index.php/yohgaki/2006/02/13/addslashesa_la_a_a_ua_sa_pa_fa_a_bc_a_ma#comments
と関連してると思うのですが。
で、PHPの標準MySQL関数ってクライアントのエンコーディングを設定する関数がないですよね。MySQLi使えればありますが、普通のレンタルサーバでは入れてないかと思います。
ということは、
MySQL5+PHP5+MySQLi
でない限り、使うなってことでしょうか?多くのPHPユーザーの現実に反している気がします。
実はPHP5.2.3には追加されています。セキュリティフィックスなのでPHP4.4ユーザにも提供されるべきなのですがまだです... 追加されてリリースされるのか分かりません。
PHP 4.4向けにバックポートしたパッチを書こうかな、と思っていますが時間がない.......
PostgreSQL でも 7.3 までは psql で使ってはいけないコマンドでした。代わりに psql の \encoding ディレクティブを使います。
7.4 からは SET CLIENT_ENCODING を psql が認識して内部エンコーディングを変更するようになりました。
私が書くまでもなく探せば見つかると思いますが、参考になるURLとしては
http://www.postgresql.org/docs/techdocs.50
などがあります。PostgreSQLの文書ですがMySQLでも同じです。MySQLの接続構造体にもencodingを保持するメンバがあり、PostgreSQLの文字エンコーディング設定/エスケープAPIで同じような使われ方としています。
テストケースは私もセミナーで話をした事があるので私のスライドも見つかるかも知れませんが上記のURLの方が詳しく解説しているハズなので参考になると思います。
実は、Xoopsのモジュールを作成していて、
文字コードを「SET NAMES」で指定したい
場面が生じてしまいました。
そこでもう少し詳しく確認したいのですが、
よろしいでしょうか。
ご指定のサイト
http://www.postgresql.org/docs/techdocs.50
にアクセスしたところ、
http://wiki.postgresql.org/wiki/Main_Page
に飛んでしまいました。
そこで、問題の「SET CLIENT_ENCODING」で
検索したところ、それらしい記事では
「SET NAMES」とかによる文字コードの設定
というよりも、「SJIS, BIG5, GBK, GB18030,UHC」
とかでのエスケープ処理の問題ばかりが
でてきました。
http://wiki.postgresql.org/wiki/8.1.4_et._al._Security_Release_Technical_Info
http://wiki.postgresql.org/wiki/8.1.4_et._al._Security_Release_User%27s_Guide
そして同様の検索ででてきたFAQ
http://wiki.postgresql.org/wiki/8.1.4_et._al._Security_Release_FAQ
では、正規表現やaddslashes()やmagic_quotesで
エスケープしても、問題がでてくる、と書いてありました。
そして解決法として、
1. pg_escape_string() (but look for a driver update soon)
2. PEAR-DB (safe against this exploit, but may be vulnerable to future issues as it uses ad-hoc escaping)
3. NOT PEAR-MDB2 (according to the PEAR development team, MDB2 is currently using a custom escape routine which is currently unsafe. Expect an update next week.)
4. PDO
5. If you're using magic_quotes_gpc, test using magic_quotes_sybase instead (and plan a refactor -- magic_quotes will not be in PHP 6.0)
が挙げられていて、新しいものならば
pg_escape_string() で処理しても
よさそうに読めてしまうのですが・・・。
ですので、yohgakiさんのおっしゃる
「SET NAMESは禁止」というのは、「一人の
開発者がaddslashesとかで
エスケープ処理をしている場合、
別の開発者が知らないところで、
「SET NAMES」を使ってSJISとか問題のある
文字コードに変更するロジックを入れてしまう際に
問題が生じるので、「SET NAMES」の
利用には注意すること」と
いうことで、よろしいでしょうか。
また、上でも書きましたとおり、Xoopsですので
MySQLなのですが、mysql_real_escape_stringは、
エンコーディングを考慮したエスケープを
してくれるそうなのですが、これを
大丈夫ということで、よろしいでしょうか。
SET NAMESによって文字エンコーディングを変更するとC言語などで書かれたエスケープAPI (libmysql, libpqなど)が想定しているエンコーディングと実際のエンコーディングが異なる状況が発生します。この違いにより、環境によっては文字エンコーディングを利用したSQLインジェクション攻撃が可能になります。
つまり、APIによって文字エンコーディング情報を変更しないと接続情報が更新されず、エスケープAPIを利用していても正しくエスケープできない場合発生する、ということです。
SET NAMES, SET CLIENT ENCODINGを利用しないで、mysql_set_charset, pg_set_client_encodingを利用すれば、このような不整合が発生しないので問題も発生しなくなります。
SET NAMES等を使っても安全な場合もあります。それは元々接続がSJISだった場合です。ISO8859-1だった物をSET NAMESでSJISに変更してしまうとmysql_real_escape_stringを利用していてもSQLイジェクションが可能になります。
addslashesによるエスケープは論外です。絶対に行ってはなりません。
>エスケープAPI (libmysql, libpqなど)が想定しているエンコーディング
ということとの関連ですが、
新しく見つけたサイト
http://d.hatena.ne.jp/t_komura/20060122#1137944280
によると、むしろ「SET NAMES」を
使用して、「SET NAMES binary」と設定
して、エスケープを必ず専用エスケープ
関数(mysql_real_escape_stringなど)
で行うことで問題を回避する、
という手法があるそうです。
上のような手法が有効なのはやはり、
binaryならば基本的にどんな状態の
APIでも想定しているから、
ということでしょうか?
1. pg_escape_string() (but look for a driver update soon)
2. PEAR-DB (safe against this exploit, but may be vulnerable to future issues as it uses ad-hoc escaping)
3. NOT PEAR-MDB2 (according to the PEAR development team, MDB2 is currently using a custom escape routine which is currently unsafe. Expect an update next week.)
4. PDO
5. If you're using magic_quotes_gpc, test using magic_quotes_sybase instead (and plan a refactor -- magic_quotes will not be in PHP 6.0)
少なくとも、5の対策は対策と呼ぶには十分とは言えません。sybaseはSQL準拠のエスケープ方式だったのでaddslashesでも'は'', nullは¥nullにエスケープしています。しかし、文字エンコーディングを考慮していないので壊れた文字エンコーディングを作って攻撃する事も可能です。
# 対策済みのPostgreSQLならこのタイプの壊れた文字エンコーディング
# をすべて検出できるので、これでも大丈夫とも言えます。スラッシュ
# でエスケープする変わりに'でエスケープされる様になることは解説
# してあるのか気になっています。
> http://d.hatena.ne.jp/t_komura/20060122#1137944280
> によると、むしろ「SET NAMES」を
> 使用して、「SET NAMES binary」と設定
> して、エスケープを必ず専用エスケープ
> 関数(mysql_real_escape_stringなど)
> で行うことで問題を回避する、
> という手法があるそうです。
これは対策になりません。不正な文字エンコーディングでもデータベースに登録できるようになるだけです。
SQLインジェクションだけ防いでも意味が在りません。不正な文字エンコーディングはHTML/XML/JavaScriptインジェクション等にも利用できるからです。絶対にこのような運用はしてはなりません。
- APIを利用して文字エンコーディングを設定
- APIを利用して文字列をエスケープ
すれば良いだけです。もし、自分が使っている言語のライブラリにこれらの関数がないなら、セキュリティ上の脆弱性問題としてレポートして対処してもらうのが一番です。
このエントリではデータベースとアプリケーションの間でのセキュリティ対策を解説しているので、
- APIを利用して文字エンコーディングを設定
- APIを利用して文字列をエスケープ
を対策として紹介しています。多重のセキュリティ対策の意味もあるので、全ての入力の文字エンコーディングをチェックしていてもデータベースとアプリケーション間でもこれらを使って処理すべきです。また条件によっては攻撃可能な状態も発生します。e.g. SJIS文字列をaddslashesでエスケープ
全てのプログラマにどのような状態の時に攻撃可能となるか、解説してもらい、理解して、実践してもらうのは無理があります。
最も確実かつ簡単な方法(APIを利用した文字エンコーディング設定とエスケープ。実装は簡単ではないかもしれませんが)を採用するのが一番良いと思います。
私自身も論点をぶらすような投稿を
少ししてしまったようなので、
論点を整理すると、「SET NAMESは禁止」
という論題で特に問題になるのはむしろ、
「中途半端にAPIを利用して、セキュリティー
ホールをつくってしまう」という
ことでしょうか。
問題になるケースというのは、一見して
非常に安全に見える「文字コードを考慮した
エスケープをしてくれる関数」
(mysql_real_escape_stringやpg_escape_stringなどで
特に旧いバージョンのもの)を利用したけれども、
「SET NAMES」や「SET CLIENT ENCODING」
で文字コードを変更する
場合ということですよね。
この場合には、例えば
mysql_real_escape_stringで考慮されている
文字コードと入ってくるデータが食い違って
しまって、エスケープの関数がざるになる
ケースがある。そして、「SET NAMES sjis」
した場合では問題の実例も報告されている。
==================
例
---SET NAMES---
DB接続直後のデフォルト状態
実際のコード ujis、API想定コード ujis
↓
「SET NAMES sjis」を実行
実際のコード sjis、API想定コードujis
↓
エスケープ関数実行
入ってくるデータはsjisだが、ujisの基準で
エスケープ。結果、攻撃が可能に
---APIで変更---
DB接続直後のデフォルト状態
実際のコード ujis、API想定コード ujis
↓
「mysql_set_charset(sjis)」を実行
実際のコード sjis、API想定コードsjis
↓
エスケープ関数実行
入ってくるデータはsjisで、sjisの基準で
エスケープ。(万々歳)
(上のようなことを考えると例えば、
http://php.morva.net/manual/ja/function.mysql-set-charset.php
にあるJanez R.さんのような運用は、
問題がある。)
====================
ここで問題になってくるのが、mysql_set_charset
がPHPの5.2.3以降となっているところで、
これより旧いバージョンで運用している
ところが多い(pg_set_client_encodingは
結構古くからある)。
ということで、仕方なく「SET NAMES」を使う
場合があるのですが、この場合には開発者間
できちんを申し合わせをしておく必要がある。
実際に問題のあるな実例が
確認されているのは、「SET NAMES sjis」あたり。
(http://wiki.postgresql.org/からすると、
utf8とかならば、安全でしょうか。)
そして、
http://d.hatena.ne.jp/t_komura/20060122#1137944280
の対策は、SQLインジェクションは防げても、
登録データに変なものが入りやすくなってしまう。
よって、登録されたデータを活用する際に
セキュリティーのための手間をかけなくては
ならないので、
可能であれば断然「APIで文字コード変更+
APIでエスケープ」がよい。
お話をまとめると、上のような感じでしょうか。
はい。
普段MySQLを利用していないので指摘いただくまで忘れていました。mysql_set_charsetは新しい関数なので、古いPHP(ホスティング環境など)ではアップグレードが出来ないです。
SET NAMESを利用していても、安全に運用したい方は以下のようにすればよいと思います。PHP以外の言語を利用されている方でも意味を理解いただければ、同じように対策できるはずです。
■EUC-JPかUTF-8エンコーディング
この場合は話は簡単で入力時点で文字列がEUC-JPまたはUTF-8で正しくエンコーディングされているかチェックするだけです。アプリケーションの入力チェック処理としてmb_check_encoding(無い場合はmb_convert_encoding)を利用して文字エンコーディングが正しいかチェックすれば大丈夫です。そのまま、mysql_escape_stringを使ってエスケープすればほとんどの環境で大丈夫だと思います。
# 例えば元のエンコーディングがISO-8859-1で
# EUC-JP, UTF-8に変更した場合は安全。
蛇足ですが、文字エンコーディングのチェックはWebアプリケーションセキュリティ対策の基本です。例えば、htmlspecialchars/htmlentitiesが不正なマルチバイト文字に対して脆弱な問題が最近修正されていますが、入力チェックで文字エンコーディングが正しいかチェックしれていれば問題ありません。libxml2等よく利用されているライブラリにも文字エンコーディング関連の脆弱性が発見されていますが文字エンコーディングが正しければ影響を受けないものがほとんどです。
■SJISエンコーディング(その1)
この場合、文字エンコーディングに配慮した置換ができる関数でエスケープするしかありません。例えば、mb_regex関数を利用して\と'をエスケープする自前の関数を作成して利用します。(マニュアルを見ないで書いています。mysqlでエスケープが必要な文字はMySQLマニュアルを参照してください)
このエスケープ処理を行うだけでは壊れた文字エンコーディング攻撃に脆弱になるので、アプリケーション全体の入力チェック処理として、文字エンコーディングが正しいか確認する処理も追加します。
■SJISエンコーディング(その2)
komuraさんのブログに書かれている通り、EUCやUTF-8にはSJISにある問題が無いです。なので以下の対策も有効です。
> 非効率ですが、データベースの文字コード
> が Shift_JIS でも、クライアントの文字
> コードを EUC-JP にして、SQL 文を EUC-JP
> に変換してから発行することは可能だと思
> います。
文字列をmb_convert_encoding関数でSJISからEUCかUTF-8に変換し、変換後にmysql_escpae_stringでエスケープ処理行い、エスケープ済み文字列をまたSJISに変換してデータベースに送信すればSQLインジェクション出来なくなります。
他の対策同様、アプリケーションレベルでの文字エンコーディングチェックも行うべきです。