目次
はじめに
こんにちは、キラメックスでTechacademyの開発をしている久保田です。
キラメックスでは、徳丸本こと『安全なWebアプリケーションの作り方 第2版』の勉強会を2019年末から実施し、つい最近、完走いたしました。
徳丸本で学んだ知識を実践で活かすぞ!と息巻いていたものの弊社ではRuby on Railsで開発しており、そのおかげで代表的な脆弱性はすでに対策されていました。「Railsありがとう。」で終わらせたいところですが、どうやらレールに乗るだけで安全!とは言い切れないようなのでRailsでも気をつけたい脆弱性についてまとめてみました。
今回はSQLインジェクションに絞ってご紹介したいと思います。
SQLインジェクションとは
そもそもSQLインジェクションとは何でしょうか?
IPAでは次のように説明されています。
データベースと連携したウェブアプリケーションの多くは、利用者からの入力情報を基にSQL文(データベースへの命令文)を組み立てています。ここで、SQL文の組み立て方法に問題がある場合、攻撃によってデータベースの不正利用をまねく可能性があります。このような問題を「SQLインジェクションの脆弱性」と呼び、問題を悪用した攻撃を、「SQLインジェクション攻撃」と呼びます。
利用者からの入力情報を元にSQLを発行しデータベースにアクセスする機能がある場合に発生する脆弱性の問題のことです。
例えば、名前とパスワードを入力して該当のユーザがいればログインできる機能があるとします。
SELECT * FROM users WHERE name='tanaka' AND password='password'
ここで' OR 'a'='a
といった文字列をパスワードフォームに入力すると以下のSQLが発行されます。
SELECT * FROM users WHERE name='tanaka' AND password='' OR 'a'='a'
WHERE句は常に「真」となり、パスワードを知らなくても該当のデータにアクセスできてしまうのです。
以下は、セミコロンで複数のSQL文を読み込ませている例です。投稿を全て削除しようとしてます。
SELECT * FROM posts WHERE user_id < 1 ; DELETE FROM posts
RailsのSQLインジェクション対策
Railsが提供しているActiveRecordのクエリメソッドではキー/値形式の書き方の場合、'
や"
といったSQL特殊記号をエスケープしてくれます。これによりインジェクション攻撃を防げます。
User.where(name: name, password: password).first
ただし、下記のような形式で書くと、先に述べた' OR 'a'='a
のような攻撃も受けてしまうので注意です。
× User.where("name='#{params[:name]}'")
直接パラメータを渡すのではなく以下のように疑問符を通して渡す(プレースホルダー)ように書けばエスケープしてくれます。
◯ User.where("name = ? AND password = ?", name, password).first
その他のクエリメソッドについて
カラム名やテーブル名の指定して利用する下記メソッドではキー/値形式やプレースホルダーは使えません。そのため、ユーザが入力した値をそのまま引数に渡さないようにしましょう。
Rails SQL Injectionに記載された例を元に説明します。
- select
params[:column] = "* FROM users WHERE admin = 't'-- " User.select(params[:column]) -> SELECT * FROM users WHERE admin = 't'-- FROM "users"
--
によってFROM "users"
をコメントアウトし、adminユーザを取得
- Calculate系(sum, average, count...)
params[:column] = "age) FROM users WHERE name = 'Bob'--" Order.calculate(:sum, params[:column]) -> SELECT SUM(age) FROM users WHERE name = 'Bob'--) FROM "orders"
--
によりFROM "orders"
をコメントアウト、userテーブルからデータを取得
- pluck
params[:column] = "password FROM users--" Order.pluck(params[:column]) -> SELECT password FROM users-- FROM "orders"
--
によってFROM "orders"
をコメントアウト、passwordカラムの情報を取得
- joins(group、from、having)
params[:table] = "--" Order.joins(params[:table]) -> SELECT "orders".* FROM "orders" --
--
でjoinを無効
終わりに
今回はSQLインジェクションについて書きました。他にも、XSS(herf属性のjavascriptスキーマを利用したパターン)やCSRF(Ajax利用時)など注意したい脆弱ポイントはあります。デフォルトでもセキュアだしと油断せず、気をつけてレールに乗っていきたいです。