💭

sanitize_sql_likeを簡単に読んでみるメモ

2022/10/31に公開約2,100字

3行まとめ

  • Railsのsanitize_sql_likeを読む
  • %と_をサニタイズしてくれる関数
  • where本体のサニタイズについても今後読んでいきたい

SQLのサニタイズ

Webアプリで、SQLインジェクションを防ぐというのはセキュリティ上とても大事ですよね。
趣味開発の中でインジェクションについて調べているとこんな文章を見かけました。
https://railsguides.jp/active_record_querying.html#条件でlikeを使う

上の例は、ユーザーが指定した文字列で始まるタイトルに一致することを意図しています。しかし、>params[:title]に含まれる%または_はワイルドカードとして扱われるため、意外な結果をもたらします。状況によっては、データベースがインデックスを使用できなくなるため、クエリが大幅に遅くなる可能性があります。
これらの問題を回避するには、sanitize_sql_likeを使用して関連する引数のワイルドカード文字をエスケープします。

サニタイズについて整理するため、sanitize_sql_likeのコードを読んでみようと思います。

と言っても、たったの3行です。
https://github.com/rails/rails/blob/8015c2c2cf5c8718449677570f372ceb01318a32/activerecord/lib/active_record/sanitization.rb#L109

      def sanitize_sql_like(string, escape_character = "\\")
        pattern = Regexp.union(escape_character, "%", "_")
        string.gsub(pattern) { |x| [escape_character, x].join }
      end

Regexp.unionについて

https://docs.ruby-lang.org/ja/latest/class/Regexp.html#S_UNION

例えばソースコードにあるような、

escape_character = "!"

とした場合は、

[1] pry(main)> pattern = Regexp.union("!", "%", "_")
=> /!|%|_/

デフォルトなら

[2] pry(main)> pattern = Regexp.union("\\", "%", "_")
=> /\\|%|_/

エスケープする文字列を正規表現で列挙していますね。

gsub

gsubについてドキュメントはこちら
https://docs.ruby-lang.org/ja/latest/method/String/i/gsub.html
gsubでさらにブロック引数を渡す場合については、

文字列中で pattern にマッチした部分を順番にブロックに渡し、その実行結果で置き換えた文字列を生成して返します。ブロックなしの場合と違い、ブロックの中からは組み込み変数 $1, $2, $3, ... を問題なく参照できます。

とのことです。実際に動かしてみると、

[7] pry(main)> "snake_cased_string".gsub(Regexp.union("\\", "%", "_"))  { |x| ["\\", x].join }
=> "snake\\_cased\\_string"

となります。

["\\", x].join

については、ただの文字列の結合ですね。xに対して、その前に escape_characterをくっつけて返す(つまり該当の文字を用いてエスケープする)というものです。

[8] pry(main)> ["\\","_"].join
=> "\\_"

まとめ

これらのことから、

sanitize_sql_likeは、エスケープ文字(デフォルトは"\")を持ちいて、"%", "_", そしてエスケープ文字そのものを、エスケープしてくれる
ことが分かりました。

引数はSQLインジェクションを防ぐために自動的にエスケープされますが、SQL LIKEワイルドカード(つまり、%と_)はエスケープされません。

とのことなので、 sanitize_sql_likeは%と_に特化したサニタイズの関数というわけですね。
他のサニタイズしたいような文字列(例えば"--"とかでしょうか)は、その前

Book.where("title LIKE ?", params[:title])

のような書き方の時点で弾かれてそうですね。ここを読もうとするともう少し力を入れなければならなそうですが、いつか読みたいです。

Discussion

ログインするとコメントできます