railsのsqlインジェクションの対策方法

sqlインジェクションという、昔ながらの攻撃技があります。

内容

アプリケーションが入力値を適切にエスケープしないままSQL中に展開することで発生する。 次のようなSQLを発行することを考える。 SELECT * FROM users WHERE name = '(入力値)'; ここで入力値に "t' OR 't' = 't" という文字列を与えた場合を考えると、SQL文は次のように展開される。 SELECT * FROM users WHERE name = 't' OR 't' = 't'; このSQL文では条件が常に真となるため、nameカラムの値にかかわらず、全レコードが選択される。このような攻撃は、入力値が1つだけのものには限られない。

SQLインジェクション - Wikipedia

実現方法

こんなメソッドがあったとします。

class User < ActiveRecord::Base
  class << self
    def search(email)
      User.where("email = '#{email}'")
    end
  end
end

emailを検索するメソッドですね。

email: "' or 1=1 --'"を入れてあげます。

要素 攻撃の効果
' 文字列定数の外に出る
OR 1=1 常にTRUEとなるよう、検索条件をねじ曲げる
-- それ以降の内容をコメントとして無視させる

参考: [ThinkIT] 第5回:インジェクション攻撃 (1/4)

User.search(email: "' or 1=1 --'")
  User Load (0.3ms)  SELECT `users`.* FROM `users` WHERE (email = '{:email=>"' or 1=1 --'"}')
=> #<ActiveRecord::Relation [#<User id: 1, email: "hoge2@hoge.com", crypted_password: "$2a$10$A3WpMNx9p0XpWEaT2a19w.0jvXv7FX0pvAdXhnnnAC4...", salt: "GfJfQSHHE2fTxR68xfM8", created_at: "2016-04-26 14:26:06", updated_at: "2016-04-26 14:26:06", admin: false>, #<User id: 2, email: "hoge@hoge.com", crypted_password: "$2a$10$5md8zPPhrYyG3UKTi.JJd.Ckjvha.DuYlbTcLgbspeq...", salt: "GZyzTgZ1Yr7eNj4YyVn4", created_at: "2016-04-26 14:26:27", updated_at: "2016-04-26 14:26:27", admin: false>]>

全部の結果を返してくれます。

1=1でtrueってのが、sqlでは利用されるようなので、消せないんでしょうね。

逆にそれを狙われて使っているってことです。

対策

プレースホルダーで書く

User.where(email: "#{email}")
→
User.search(email: "' or 1=1 --'")
  User Load (0.3ms)  SELECT `users`.* FROM `users` WHERE `users`.`email` = '{:email=>\"\' or 1=1 --\'\"}'
=> #<ActiveRecord::Relation []>
User.where("email = ?", "#{email}")→
User.search(email: "' or 1=1 --'")
  User Load (0.2ms)  SELECT `users`.* FROM `users` WHERE (email = '{:email=>\"\' or 1=1 --\'\"}')
=> #<ActiveRecord::Relation []>

まとめ

今でも起こっているようです。

原理を分かっておかないと、対策が打てないんですが、railsから入ってきた人は生sqlを書くことが少ないと思うので、注意して書きましょう。

何も考えずに、値を=で直に渡したら、危ないです。

これをきっかけにsqlをきちんとやらないとな・・・