この続きです。 euglena1215.hatenablog.jp
検証方法が全く網羅的ではないということを踏まえた上でお読みください。
検証
XSS(クロスサイトスクリプティング)
方法
投稿フォームに以下のリンクに載っているXSSの例を片っ端から入力してみた。
XSS Filter Evasion Cheat Sheet - OWASP
結果
脆弱性は見つからなかった。
理由
rails3からデフォルトで文字列を出力するときにはエスケープされるようになっているため基本的にはXSSは通らない。
なので文字列をエスケープさせたくない場合には
<%= 文字列.html_safe %>
<%= raw 文字列 %>
<%== 文字列 %>
のどれかを使う。
この記事によると <%==
を使うのがベストらしい。
SQLインジェクション
方法
検索フォームに ');--
と入力する
結果
SELECT "users"."id" FROM "users" WHERE (username LIKE '%');--%’)
というクエリが生成され、userの全件取得ができてしまう。ということは…
方法
検索フォームに’ AND password LIKE ‘password’);—
と入力する
結果
SELECT "users"."id" FROM "users" WHERE (username LIKE '%’ AND password LIKE ‘password’);—%')
というクエリが発行されてpasswordが’password’のユーザが取得できる。
という風に利用者が任意のクエリを発行することができる。これは危ない。
※今回使用したDeviseは暗号化したパスワードをDBに保存しているのですぐにパスワードがバレるというわけではありません
普通に利用しているとユーザがアクセスできないデータにアクセスされる可能性がある。
対策
where("username LIKE '%"+username+"%’”)
を
where("username LIKE ?","%#{username}%")
に変更する。
以下のように修正する。
# before # app/models/post.rb scope :by_body_like, ->(body){ where("body LIKE '%"+body+"%'") } # app/models/user.rb scope :by_username_like, ->(username){ where("username LIKE '%"+username+"%'") }
# after # app/models/post.rb scope :by_body_like, ->(body){ where("body LIKE :body ESCAPE '\\'", { body: "%#{sanitize_sql_like(body)}%"}) } # app/models/user.rb scope :by_username_like, ->(username){ where("username LIKE :username ESCAPE '\\'", { username: "%#{sanitize_sql_like(username)}%"}) }
検索でLIKE演算子のワイルドカードである_や%が使われてもいいようにエスケープする必要がある。
Rails 4.2.1以降にはsanitize_sql_likeメソッドが実装されているのでそれを使う。
sanitize_sql_like (ActiveRecord::Sanitization::ClassMethods) - APIdock
?を使うとSQLインジェクションを防ぐことができるということは分かったものの、なぜこれでSQLインジェクションが防げるのかよく分からなかった。
なので今度Active recordを読み解いていこうと思う。
Active record whereメソッド :
rails/query_methods.rb at 0399b71dab8b270b4e40b2aff99194a8b8f2596c · rails/rails · GitHub
これはActive recordのwhereメソッドの仕様ではなくSQLのプレースホルダという機能だった。
プレースホルダとはパラメータを後で記述できるようにすることによって、SQLの構文を決定した後にバインドできるのでパラメータがリテラルの外にはみ出す現象が起こらなくなりSQLインジェクションが起こらなくなる機能だ。
普段SQL文を書いていないことが露呈してしまいました...基礎技術を勉強し直そうと思います。用途にもよるが、%や_のエスケープをしていないところが気になった / DROP "posts"はDROP TABLE "posts"では? / “Railsでセキュリティのことを考えずに作ったwebアプリは脆弱性があるのか調べて…” https://t.co/90vKBdLT5M
— 徳丸 浩 (@ockeghem) September 24, 2016
ご指摘ありがとうこざいます!
?を使うSQL文は色々なところで見たことはありましたがプレースホルダという名前があったのは知りませんでした。@ockeghem ?を使ったプレースホルダの対策をして、なぜこれでSQLインジェクションが防げるのかわからなかった、の時点でどうなんだろうか、、、と思ったけど、まだ学生なんですね。
— Kaneko Yukari (@yukapon_ririka) September 24, 2016
ご指摘のおかげでgoogle先生に聞くことができました。ありがとうございます!
方法
検索フォームに ‘); DROP “posts”;—
'); DROP TABLE “posts”;--
と入力する
SQLインジェクションが通る状態で複文はどうなるのか試してみた。
結果
SELECT "users"."id" FROM "users" WHERE (username LIKE '%‘); DROP “posts”;--%')
SELECT "users"."id" FROM "users" WHERE (username LIKE '%'); DROP TABLE “posts”;--%')
というクエリが発行されているもののpostsテーブルは削除されていなかった。
なので複文はサポートされてないみたい。(今回はSQLite3での実験だったので他のDBでどうなるかは試していません)
まとめ
- やはり自分の書いたコードに穴があった。
- XSSやSQLインジェクションのチートシートの存在を知ったことが一番の収穫だった。
- マサカリを投げて間違いを指摘してくれる人がいる。アウトプットの重要性を改めて実感した。
参考にさせていただきました
SQL Injection Cheat Sheet
Railsで検索をSQLで簡単に実装する方法 - Qiita
Railsのセキュリティ対策で調べた事 - Qiita
XSS Filter Evasion Cheat Sheet - OWASP
html_safe、raw、「<%==」の比較 - Qiita
https://www.ipa.go.jp/files/000017320.pdf
rails - クエリの基本 - そういうことだったんですね