Ruby on Rails(3.2.9, 3.1.8, 3.0.17以前)のfind_by_*メソッドにSQLインジェクション脆弱性が見つかりました(CVE-2012-5664)。このエントリではその概要と対策について説明します。
概要
Ruby on Railsのfind_by_*メソッドの引数としてハッシュを指定することで、任意のSELECT文を実行できる脆弱性があります。
検証
Ruby on Rails3.2.9の環境を用意して、以下の2つのモデルを用意しました。
$ rails g scaffold user name:string email:string
$ rails g scaffold book author:string title:string
モデルUserは個人情報を保持しており、自分自身の情報のみが閲覧できるという想定です。モデルBookは書誌データベースであり、すべて公開情報であるとします。
自動的に生成されたコントローラのshowメソッドは下記となっています。
def show
@book = Book.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.json { render json: @book }
end
end
これが生成するSQL文は下記となります。
SELECT "books".* FROM "books" WHERE "books"."id" = ? LIMIT 1 [["id", "1"]]
プレースホルダを用いてSQL文が生成されているので、いい感じです。
画面表示は下記となります。
次に、findメソッドをfind_by_idに変更してみます。
@book = Book.find_by_id(params[:id])
生成されるSQL文は以下となります。プレースホルダを使っていないので嫌な感じです。
SELECT "books".* FROM "books" WHERE "books"."id" = 1 LIMIT 1
念のため、1の代わりに、1 or 1 = 1を指定してみましょう。検証のため、条件をハードコードします。
@book = Book.find_by_id('1 or 1 = 1')
生成されるSQL文は下記となります。大丈夫ですね。
SELECT "books".* FROM "books" WHERE "books"."id" = 1 LIMIT 1
ところが、以下のように、find_by_idの引数をハッシュにすることで、任意のSQL文を指定できます。
@book = Book.find_by_id({:select =>"name as author, email as title, id from users limit 1 --"})
生成されるSQLは以下の通りです。列名を合わせるために、SELECT文で別名を指定しています。本来のSQL文は--によりコメントアウトされています。
SELECT name as author, email as title, id from users limit 1 -- FROM "books" WHERE "books"."id" IS NULL LIMIT 1
この際の表示は下記となります。本来、書誌情報が表示されるべきところに、個人情報が表示されてます。
以上のように、find_by_*のパラーメタとしてハッシュを指定することが出来れば、任意のテーブルの情報を取り出すことができることがわかりました。
攻撃経路について
私自身がRuby on Railsに詳しくないので、find_by_idの引数にハッシュを指定できる可能性がどれくらいあるのか評価できないのですが、この問題を最初に取り上げたブログ「
Let Me Github That For You」によると、なんらかの理由でsecret_token.rbに定義されたsecret_token値が既知である場合(公開されたソースを修正しないでそのまま使っている場合など)に、セッションCookieを改変してuser_credentials_idに上記のハッシュをシリアライズしたものを埋め込む可能性を指摘しています。
しかし、secret_token 値が既知だという前提では、セッションの内容を第三者が改変できてしまうため、これ自体が重大な問題です。このため、この想定は少々乱暴なように思えます。それに、先のブログでは以下のように指摘されています。
The simple problem is, that most developers are simply not aware of the confidentiality of this file , and in result they 'll happly(happilyのtypoか?) check it into Github or other online repositories
試訳
単純な問題点は、大部分の開発者がこのファイル(注: secret_token.rb)を機密にすべきことを単に承知しておらず、その結果、開発者たちはそのファイルをご機嫌でGithubその他のオンラインレポジトリにチェックインして(その結果公開して)しまうことだ。
すなわち、ブログ「
Let Me Github That For You」の趣旨は、Ruby on RailsのSQLインジェクションがあることの指摘ではなく、多くのアプリケーションがsecret_tokenを適切に秘匿していないことを問題視しているように私は感じました。
対策
既にRuby on Railsの対策版が公開されています(3.2.10, 3.1.9, 3.0.18)。Ruby on Rails 3.2.10で先と同じことをすると、生成されるSQL文は下記となります。
SELECT "books".* FROM "books" WHERE "id"."select" = 'name as author, email as title, id from users limit 1 --' LIMIT 1
WHEREの後の"id"."select"が微妙な感じではありますが、実害はないでしょう。これにより、SQLインジェクション攻撃を防止しています。
また、すぐにRuby on Railsのバージョンアップが難しい場合の回避策が、SECLISTS.ORGに
紹介されています。
find_by_*を呼び出している箇所を探して、以下のようなメソッド呼び出しがあれば、
Post.find_by_id(params[:id])
以下のように文字列に変換します。これにより、ハッシュがfind_by_*メソッドに渡されることを防止できます。
Post.find_by_id(params[:id].to_s)
まとめ
Ruby on Railsのfind_by_*メソッドのSQLインジェクション脆弱性について説明しました。find_by_*メソッドの引数にハッシュが渡る可能性の評価については、私にはできませんので、Ruby on Railsに詳しい方の評価を待ちたいと思います。
追記(2013/1/4 2:10)
twitterにて、
@hasimoさんから、この問題の優れた解説「
Rails SQL injection vulnerability: hold your horses, here are the facts」を紹介いただきました。これによると、クエリ文字列 id[select]=xxxx の形でパラメタidにハッシュを与えることはできるが、ハッシュのキーが文字列になるためにSQLインジェクションには至らないということでした。ためしてみると、確かにそうなります。
アプリケーションに以下のクエリ文字列を指定して呼び出した場合、
id[select]=name+as+author,+email+as+title,+id+from+users+limit+1+--
Ruby on Railsアプリケーションの受け取る値は下記となります。
{"select"=>"name as author, email as title, id from users limit 1 --"}
一見、先のPoCと似ていますが、ハッシュのキーが、PoCではシンボル :select であるのに対して、クエリ文字列の場合は、キーが文字列 "select" です。そして、キーが文字列の場合は、SQLインジェクションにはなりません。
上記から考えても、CVE-2012-5664によってSQLインジェクションの攻撃を受けるシナリオというのは、ゼロではないにしても、相当レアなケースであると考えられます。
しかしながら、SQLインジェクション攻撃の影響は甚大です。できる限り、Ruby on Railsの対策版へのバージョンアップか、上記回避策の導入を推奨いたします。