11/28 に Haskell で MySQL の Xプロトコルを実装したという話が聴ける Club MySQL というイベントがあったので参加してきました。
MySQLのプロトコルの話ということで、平日の夜とは言え東京で参加者9人(発表者含む)というマニアックな集まりでした。
自分も1年前に Ruby で MySQL Xプロトコルを実装していたのですが、このツイートを最後に中断していたのでした。
MySQL X Protocol で Collection の追加はできるようになったが、検索がめんどくさい。条件文字列のパースはクライアントで行う必要があるんだな。
— とみたまさひろ💎🐬 (@tmtms) 2017年2月20日
今回話を聞いて、無理に謎条件式文字列をパースするんじゃなくて、処理系で書きやすいように書けばいいんだという方式に目からウロコでした。
もしかしたらまたRubyでの実装を再開するかもしれません。
で、このイベントで私も喋ってきました。本当は前座で話す予定だったんですが、到着が遅れてしまったので合間の休憩時間で話すことになりました。スイマセン。
www.slideshare.net
以下がしゃべった内容です。
MySQLの参照系のクエリのパケットはこんな感じです。クエリの応答としてフィールド数と、フィールド情報と、レコードデータが返されます。
更新系のクエリはもっと簡単です。クエリの応答として更新の結果情報が返されます。
クエリをパースするのはサーバーなので、クライアントはクエリが参照か更新かは知りません。
クライアントはクエリの応答のパケットでクエリが参照だったか更新だったかを知ります。
ところで、LOAD DATA LOCAL INFILE という特殊なクエリがあって、これはローカルにあるファイルをテーブルにロードします。
が、クエリをパースするのはサーバーなので、クエリ中のファイル名もサーバーから返してもらいます。
ということは、クライアントから送ったクエリで指定したファイル名とは異なるファイル名がサーバーから指定されても、クライアントはそれを信じてサーバーから指定されたファイルのデータを送ってしまいます。
当日は、クライアントとサーバーの間のプロキシとして動くようなテキトーに作ったプログラムを使って、LOAD DATA LOCAL INFILE のときだけファイル名を差し替えるデモをしました。
この場合は、テーブルに登録されたデータを見れば自分が意図したものではないことがわかります。
クエリをパースするのはサーバー側ということは、INSERT, UPDATE 等の更新系のクエリを発行した時に LOAD DATA LOCAL INFILE だと偽ってクライアントにファイルを送らせることもできるわけです。
この場合はテーブルに登録されるのはクライアントが発行したクエリ通りのデータなので、プロキシによってファイルの内容が取られてしまったことはわかりません。
こわい!
対策としては、
基本的に信頼できない MySQL サーバーには繋がない。
サーバーは信頼できるんだけど、途中のネットワークが信頼できない場合はSSL接続を使う。
クライアントライブラリで LOAD DATA LOCAL INFILE を使わないというフラグを設定する。
mysql コマンドの場合は --local-infile=false
を指定できます。この場合はサーバーからファイルを送るように指示されてもクライアントライブラリが不正なパケットとして振る舞います。