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つだけのものには限られない。
実現方法
こんなメソッドがあったとします。
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をきちんとやらないとな・・・